|
|
|
@ -4,16 +4,21 @@ import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:hmg_patient_app_new/core/app_assets.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/app_export.dart';
|
|
|
|
|
|
|
|
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
|
|
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
|
|
|
import 'package:hmg_patient_app_new/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/string_extensions.dart';
|
|
|
|
import 'package:hmg_patient_app_new/extensions/widget_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/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/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/water_consumption_progress_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/theme/colors.dart';
|
|
|
|
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/buttons/custom_button.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
|
|
import 'package:shimmer/shimmer.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class WaterConsumptionScreen extends StatefulWidget {
|
|
|
|
class WaterConsumptionScreen extends StatefulWidget {
|
|
|
|
const WaterConsumptionScreen({super.key});
|
|
|
|
const WaterConsumptionScreen({super.key});
|
|
|
|
@ -26,19 +31,16 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
super.initState();
|
|
|
|
// Initialize VM once the widget is mounted
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
final vm = context.read<WaterMonitorViewModel>();
|
|
|
|
final vm = Provider.of<WaterMonitorViewModel>(context, listen: false);
|
|
|
|
await vm.initialize();
|
|
|
|
vm.initialize();
|
|
|
|
log('WaterMonitor initialized: consumed=${vm.totalConsumedMl}, goal=${vm.dailyGoalMl}, progress=${vm.progressPercent}');
|
|
|
|
// optional: log current progress for debugging
|
|
|
|
|
|
|
|
// ignore: avoid_print
|
|
|
|
|
|
|
|
log('WaterMonitor initialized: consumed=${vm.totalConsumedMl}, goal=${vm.dailyGoalMl}, progress=${vm.progressPercent}');
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_buildHydrationTipsWidget() {
|
|
|
|
Widget _buildHydrationTipsWidget() {
|
|
|
|
return Container(
|
|
|
|
return Container(
|
|
|
|
margin: EdgeInsets.symmetric(horizontal: 24.h),
|
|
|
|
margin: EdgeInsets.symmetric(horizontal: 24.w),
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
@ -56,39 +58,32 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
" • ${"Drink before you feel thirsty"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
|
|
|
|
" • ${"Drink before you feel thirsty"}".needTranslation.toText12(
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
|
|
color: AppColors.textColorLight,
|
|
|
|
|
|
|
|
),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
" • ${"Keep a refillable bottle next to you"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
|
|
|
|
" • ${"Keep a refillable bottle next to you"}".needTranslation.toText12(
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
|
|
color: AppColors.textColorLight,
|
|
|
|
|
|
|
|
),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
" • ${"Track your daily intake to stay motivated"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
|
|
|
|
" • ${"Track your daily intake to stay motivated"}".needTranslation.toText12(
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
|
|
color: AppColors.textColorLight,
|
|
|
|
|
|
|
|
),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
" • ${"Choose sparkling water instead of soda"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
|
|
|
|
" • ${"Choose sparkling water instead of soda"}".needTranslation.toText12(
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
|
|
color: AppColors.textColorLight,
|
|
|
|
|
|
|
|
),
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_buildStatsColumn() {
|
|
|
|
Widget _buildWaterIntakeSummaryWidget() {
|
|
|
|
return Column(
|
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
"Next Drink Time".toText18(weight: FontWeight.w600, color: AppColors.textColor),
|
|
|
|
|
|
|
|
"03:00 AM".toText32(weight: FontWeight.w600, color: AppColors.blueColor),
|
|
|
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
|
|
|
_buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "2000ml"),
|
|
|
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
|
|
|
_buildStatusColumn(title: "Remaining".needTranslation, subTitle: "300ml"),
|
|
|
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
|
|
|
_buildStatusColumn(title: "Completed".needTranslation, subTitle: "90%", subTitleColor: AppColors.successColor),
|
|
|
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
|
|
|
_buildStatusColumn(title: "Hydration Status".needTranslation, subTitle: "Hydrated", subTitleColor: AppColors.blueColor),
|
|
|
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_buildWaterIntakeSummaryWidget() {
|
|
|
|
|
|
|
|
return Container(
|
|
|
|
return Container(
|
|
|
|
width: double.infinity,
|
|
|
|
width: double.infinity,
|
|
|
|
padding: EdgeInsets.all(24.w),
|
|
|
|
padding: EdgeInsets.all(24.w),
|
|
|
|
@ -102,44 +97,68 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
child: LayoutBuilder(
|
|
|
|
child: Column(
|
|
|
|
builder: (context, constraints) {
|
|
|
|
children: [
|
|
|
|
// Responsive: stack vertically on small screens
|
|
|
|
Row(
|
|
|
|
final isSmallScreen = constraints.maxWidth < 600;
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isSmallScreen) {
|
|
|
|
final goalMl = vm.dailyGoalMl;
|
|
|
|
return Row(
|
|
|
|
final consumed = vm.totalConsumedMl;
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
final remaining = (goalMl - consumed) > 0 ? (goalMl - consumed) : 0;
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
final completedPercent = "${(vm.progress * 100).clamp(0.0, 100.0).toStringAsFixed(0)}%";
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Expanded(flex: 3, child: _buildStatsColumn()),
|
|
|
|
return Column(
|
|
|
|
SizedBox(width: 16.w),
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
Expanded(flex: 2, child: _buildWaterBottleWidget()),
|
|
|
|
children: [
|
|
|
|
],
|
|
|
|
"Next Drink Time".toText18(weight: FontWeight.w600, color: AppColors.textColor),
|
|
|
|
);
|
|
|
|
vm.nextDrinkTime.toText32(weight: FontWeight.w600, color: AppColors.blueColor),
|
|
|
|
} else {
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
// Larger screens: more space for bottle
|
|
|
|
_buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"),
|
|
|
|
return Row(
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
_buildStatusColumn(title: "Remaining".needTranslation, subTitle: "${remaining}ml"),
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
children: [
|
|
|
|
_buildStatusColumn(title: "Completed".needTranslation, subTitle: completedPercent, subTitleColor: AppColors.successColor),
|
|
|
|
Flexible(flex: 2, child: _buildStatsColumn()),
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
SizedBox(width: 32.w),
|
|
|
|
_buildStatusColumn(
|
|
|
|
Flexible(flex: 1, child: _buildWaterBottleWidget()),
|
|
|
|
title: "Hydration Status".needTranslation,
|
|
|
|
],
|
|
|
|
subTitle: vm.hydrationStatus,
|
|
|
|
);
|
|
|
|
subTitleColor: vm.hydrationStatusColor,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
SizedBox(width: isTablet ? 32 : 16.w),
|
|
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
|
|
flex: isTablet ? 1 : 2,
|
|
|
|
|
|
|
|
child: _buildWaterBottleWidget(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
_buildBottomActionWidgets(),
|
|
|
|
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_buildStatusColumn({required String title, required String subTitle, Color? subTitleColor}) {
|
|
|
|
Widget _buildStatusColumn({required String title, required String subTitle, Color? subTitleColor}) {
|
|
|
|
return Column(
|
|
|
|
return Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
"$title:".toText16(weight: FontWeight.w500, color: AppColors.textColor),
|
|
|
|
"$title: ".toText16(weight: FontWeight.w500, color: AppColors.textColor),
|
|
|
|
subTitle.toText12(fontWeight: FontWeight.w600, color: subTitleColor ?? AppColors.greyTextColor),
|
|
|
|
subTitle.toText12(
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
|
|
color: subTitleColor ?? AppColors.greyTextColor,
|
|
|
|
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -147,86 +166,507 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
Widget _buildWaterBottleWidget() {
|
|
|
|
Widget _buildWaterBottleWidget() {
|
|
|
|
return Consumer<WaterMonitorViewModel>(
|
|
|
|
return Consumer<WaterMonitorViewModel>(
|
|
|
|
builder: (context, vm, _) {
|
|
|
|
builder: (context, vm, _) {
|
|
|
|
// final progressPercent = 100.0;
|
|
|
|
|
|
|
|
final progressPercent = (vm.progress * 100).clamp(0.0, 100.0);
|
|
|
|
final progressPercent = (vm.progress * 100).clamp(0.0, 100.0);
|
|
|
|
log("progressPercent: ${progressPercent}");
|
|
|
|
log("progressPercent: $progressPercent");
|
|
|
|
// SVG original dimensions
|
|
|
|
|
|
|
|
final svgOriginalWidth = 143.w;
|
|
|
|
// SVG aspect ratio
|
|
|
|
final svgOriginalHeight = 315.h;
|
|
|
|
const svgAspectRatio = 315.0 / 143.0; // ~2.2
|
|
|
|
|
|
|
|
|
|
|
|
// Responsive sizing - scale based on screen width but maintain aspect ratio
|
|
|
|
// Responsive bottle sizing with device-specific constraints
|
|
|
|
final bottleWidth = 120.w; // Adjust this for your layout
|
|
|
|
double bottleWidth;
|
|
|
|
final aspectRatio = svgOriginalHeight / svgOriginalWidth; // 2. 2027
|
|
|
|
if (isTablet) {
|
|
|
|
final bottleHeight = bottleWidth * aspectRatio;
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Exact fillable area based on SVG paths (provided by VM calibration)
|
|
|
|
final bottleHeight = bottleWidth * svgAspectRatio;
|
|
|
|
final fillableHeightPercent = vm.fillableHeightPercent;
|
|
|
|
|
|
|
|
final fillableWidthPercent = 0.8; // 80.7% is body width
|
|
|
|
// Fillable area percentages (calibrated to your SVG)
|
|
|
|
final bottomPaddingPercent = 0; // 0% padding from bottom (8px of 315px)
|
|
|
|
final fillableHeightPercent = 0.7;
|
|
|
|
|
|
|
|
const fillableWidthPercent = 0.8;
|
|
|
|
|
|
|
|
|
|
|
|
final fillableHeight = bottleHeight * fillableHeightPercent;
|
|
|
|
final fillableHeight = bottleHeight * fillableHeightPercent;
|
|
|
|
final fillableWidth = bottleWidth * fillableWidthPercent;
|
|
|
|
final fillableWidth = bottleWidth * fillableWidthPercent;
|
|
|
|
final bottomPadding = bottleHeight * bottomPaddingPercent;
|
|
|
|
|
|
|
|
|
|
|
|
// Device-specific positioning offsets
|
|
|
|
|
|
|
|
final double leftOffset = isTablet ? 4.w : 8.w;
|
|
|
|
|
|
|
|
final double bottomOffset = isTablet ? -65.h : -78.h;
|
|
|
|
|
|
|
|
|
|
|
|
return SizedBox(
|
|
|
|
return SizedBox(
|
|
|
|
height: bottleHeight,
|
|
|
|
height: bottleHeight,
|
|
|
|
width: bottleWidth,
|
|
|
|
width: bottleWidth,
|
|
|
|
child: Stack(
|
|
|
|
child: Stack(
|
|
|
|
|
|
|
|
fit: StackFit.expand,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
// Bottle SVG
|
|
|
|
// Bottle SVG outline
|
|
|
|
Center(
|
|
|
|
Center(
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
icon: AppAssets.waterBottle,
|
|
|
|
icon: AppAssets.waterBottle,
|
|
|
|
height: bottleHeight,
|
|
|
|
height: bottleHeight,
|
|
|
|
width: bottleWidth,
|
|
|
|
width: bottleWidth,
|
|
|
|
fit: BoxFit.contain, // Important for scaling
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// Water wave - positioned from bottom
|
|
|
|
// Wave and bubbles clipped to bottle shape
|
|
|
|
Positioned(
|
|
|
|
Positioned.fill(
|
|
|
|
bottom: bottomPadding,
|
|
|
|
left: leftOffset,
|
|
|
|
right: 10.w,
|
|
|
|
bottom: bottomOffset,
|
|
|
|
child: ClipPath(
|
|
|
|
child: Center(
|
|
|
|
clipper: BottleShapeClipper(), // Use custom clipper for exact bottle shape
|
|
|
|
|
|
|
|
child: SizedBox(
|
|
|
|
child: SizedBox(
|
|
|
|
height: fillableHeight,
|
|
|
|
|
|
|
|
width: fillableWidth,
|
|
|
|
width: fillableWidth,
|
|
|
|
child: Align(
|
|
|
|
height: fillableHeight,
|
|
|
|
alignment: Alignment.bottomCenter,
|
|
|
|
child: ClipPath(
|
|
|
|
child: WaterConsumptionProgressWidget(
|
|
|
|
clipper: BottleShapeClipper(),
|
|
|
|
progress: progressPercent,
|
|
|
|
child: Stack(
|
|
|
|
// Pass a square size so the painter's vertical range matches the clip area.
|
|
|
|
alignment: Alignment.bottomCenter,
|
|
|
|
// Use the larger of width/height so the waves render correctly and won't be squashed.
|
|
|
|
children: [
|
|
|
|
size: math.max(fillableWidth, fillableHeight),
|
|
|
|
// Animated wave
|
|
|
|
// Provide exact container dimensions so the painter draws waves using the bottle's rect
|
|
|
|
Positioned(
|
|
|
|
containerWidth: fillableWidth,
|
|
|
|
child: WaterConsumptionProgressWidget(
|
|
|
|
containerHeight: fillableHeight,
|
|
|
|
progress: progressPercent,
|
|
|
|
waveDuration: const Duration(milliseconds: 3000),
|
|
|
|
size: math.min(fillableWidth, fillableHeight),
|
|
|
|
waveColor: AppColors.blueColor,
|
|
|
|
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,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_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,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Bubbles positioned dynamically based on water level
|
|
|
|
_buildActionButton({String? overlayWidget, required String title, required Widget icon, required VoidCallback onTap}) {
|
|
|
|
if (progressPercent > 10)
|
|
|
|
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(
|
|
|
|
Positioned(
|
|
|
|
bottom: 40.h,
|
|
|
|
top: 0,
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
right: 0,
|
|
|
|
icon: AppAssets.waterBottleOuterBubbles,
|
|
|
|
child: Container(
|
|
|
|
height: 60.h,
|
|
|
|
padding: EdgeInsets.all(2.w),
|
|
|
|
width: fillableWidth * 0.7,
|
|
|
|
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,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Column(
|
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
title.toText14(weight: FontWeight.w500, color: AppColors.labelTextColor),
|
|
|
|
|
|
|
|
subTitle.toText18(weight: FontWeight.w600),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.greenTickIcon)
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
).paddingSymmetrical(0, 8.h);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildHistoryGraphOrList() {
|
|
|
|
|
|
|
|
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: Consumer(builder: (BuildContext context, WaterMonitorViewModel viewModel, Widget? child) {
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Row(
|
|
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
"History".needTranslation.toText16(isBold: true),
|
|
|
|
|
|
|
|
Row(
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
InkWell(
|
|
|
|
|
|
|
|
onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
|
|
|
|
|
|
|
|
child: AnimatedSwitcher(
|
|
|
|
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
|
|
|
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
|
|
|
|
|
|
|
return FadeTransition(
|
|
|
|
|
|
|
|
opacity: animation,
|
|
|
|
|
|
|
|
child: ScaleTransition(
|
|
|
|
|
|
|
|
scale: animation,
|
|
|
|
|
|
|
|
child: child,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
|
|
key: ValueKey<bool>(viewModel.isGraphView),
|
|
|
|
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
|
|
|
|
icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
|
|
|
|
|
|
|
|
height: 24.h,
|
|
|
|
|
|
|
|
width: 24.h,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
|
|
|
InkWell(
|
|
|
|
|
|
|
|
onTap: () => _showHistoryDurationBottomsheet(context, viewModel),
|
|
|
|
|
|
|
|
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_calendar_icon, height: 24.h, width: 24.h))
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
if (!viewModel.isGraphView)
|
|
|
|
|
|
|
|
_buildHistoryListView(viewModel)
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
Center(
|
|
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 32.h),
|
|
|
|
|
|
|
|
child: "Graph view coming soon".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Build list items based on duration
|
|
|
|
|
|
|
|
List<Widget> listItems = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedDuration == 'Daily') {
|
|
|
|
|
|
|
|
if (viewModel.todayProgressList.isNotEmpty) {
|
|
|
|
|
|
|
|
final todayData = viewModel.todayProgressList.first;
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
buildHistoryListTile(
|
|
|
|
|
|
|
|
title: "Today's Progress",
|
|
|
|
|
|
|
|
subTitle: "${todayData.quantityConsumed?.toStringAsFixed(0) ?? '0'} ml / ${todayData.quantityLimit?.toStringAsFixed(0) ?? '0'} ml",
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
listItems.add(Divider(height: 1, color: AppColors.dividerColor));
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
buildHistoryListTile(
|
|
|
|
|
|
|
|
title: "Percentage Completed",
|
|
|
|
|
|
|
|
subTitle: "${todayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
Center(
|
|
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
|
|
|
|
|
|
|
child: "No data available for today".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (selectedDuration == 'Weekly') {
|
|
|
|
|
|
|
|
if (viewModel.weekProgressList.isNotEmpty) {
|
|
|
|
|
|
|
|
for (var dayData in viewModel.weekProgressList) {
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
buildHistoryListTile(
|
|
|
|
|
|
|
|
title: dayData.dayName ?? 'Unknown',
|
|
|
|
|
|
|
|
subTitle: "${dayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
if (dayData != viewModel.weekProgressList.last) {
|
|
|
|
|
|
|
|
listItems.add(Divider(height: 1, color: AppColors.dividerColor));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
Center(
|
|
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
|
|
|
|
|
|
|
child: "No data available for this week".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (selectedDuration == 'Monthly') {
|
|
|
|
|
|
|
|
if (viewModel.monthProgressList.isNotEmpty) {
|
|
|
|
|
|
|
|
for (var monthData in viewModel.monthProgressList) {
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
buildHistoryListTile(
|
|
|
|
|
|
|
|
title: monthData.monthName ?? 'Unknown',
|
|
|
|
|
|
|
|
subTitle: "${monthData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
if (monthData != viewModel.monthProgressList.last) {
|
|
|
|
|
|
|
|
listItems.add(Divider(height: 1, color: AppColors.dividerColor));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
listItems.add(
|
|
|
|
|
|
|
|
Center(
|
|
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
|
|
|
|
|
|
|
child: "No data available for this year".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Return scrollable list with min and max height constraints
|
|
|
|
|
|
|
|
return ConstrainedBox(
|
|
|
|
|
|
|
|
constraints: BoxConstraints(
|
|
|
|
|
|
|
|
minHeight: 80.h,
|
|
|
|
|
|
|
|
maxHeight: 270.h,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
child: viewModel.isLoading
|
|
|
|
|
|
|
|
? _buildLoadingShimmer().paddingOnly(top: 16.h)
|
|
|
|
|
|
|
|
: listItems.isEmpty
|
|
|
|
|
|
|
|
? Center(
|
|
|
|
|
|
|
|
child: "No history data available".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
: ListView.separated(
|
|
|
|
|
|
|
|
padding: EdgeInsets.only(top: 16.h),
|
|
|
|
|
|
|
|
shrinkWrap: true,
|
|
|
|
|
|
|
|
itemCount: listItems.length,
|
|
|
|
|
|
|
|
separatorBuilder: (context, index) => SizedBox.shrink(),
|
|
|
|
|
|
|
|
itemBuilder: (context, index) => listItems[index],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reusable method to build selection row widget
|
|
|
|
|
|
|
|
Widget _buildSelectionRow({
|
|
|
|
|
|
|
|
required String value,
|
|
|
|
|
|
|
|
required String groupValue,
|
|
|
|
|
|
|
|
required VoidCallback onTap,
|
|
|
|
|
|
|
|
bool useUpperCase = false,
|
|
|
|
|
|
|
|
}) {
|
|
|
|
|
|
|
|
return SizedBox(
|
|
|
|
|
|
|
|
height: 70.h,
|
|
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
|
|
spacing: 8.h,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Radio<String>(
|
|
|
|
|
|
|
|
value: value,
|
|
|
|
|
|
|
|
groupValue: groupValue,
|
|
|
|
|
|
|
|
activeColor: AppColors.errorColor,
|
|
|
|
|
|
|
|
onChanged: (_) => onTap(),
|
|
|
|
|
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
(useUpperCase ? value.toUpperCase() : value.toCamelCase)
|
|
|
|
|
|
|
|
.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1)
|
|
|
|
|
|
|
|
.expanded,
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
).onPress(onTap),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _showSelectionBottomSheet({
|
|
|
|
|
|
|
|
required BuildContext context,
|
|
|
|
|
|
|
|
required String title,
|
|
|
|
|
|
|
|
required List<String> items,
|
|
|
|
|
|
|
|
required String selectedValue,
|
|
|
|
|
|
|
|
required Function(String) onSelected,
|
|
|
|
|
|
|
|
bool useUpperCase = false,
|
|
|
|
|
|
|
|
}) {
|
|
|
|
|
|
|
|
final dialogService = getIt.get<DialogService>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dialogService.showFamilyBottomSheetWithoutHWithChild(
|
|
|
|
|
|
|
|
label: title.needTranslation,
|
|
|
|
|
|
|
|
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)),
|
|
|
|
|
|
|
|
child: ListView.separated(
|
|
|
|
|
|
|
|
shrinkWrap: true,
|
|
|
|
|
|
|
|
itemCount: items.length,
|
|
|
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
|
|
|
final item = items[index];
|
|
|
|
|
|
|
|
return _buildSelectionRow(
|
|
|
|
|
|
|
|
value: item,
|
|
|
|
|
|
|
|
groupValue: selectedValue,
|
|
|
|
|
|
|
|
useUpperCase: useUpperCase,
|
|
|
|
|
|
|
|
onTap: () {
|
|
|
|
|
|
|
|
onSelected(item);
|
|
|
|
|
|
|
|
Navigator.pop(context);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
onOkPressed: () {},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _showHistoryDurationBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) {
|
|
|
|
|
|
|
|
_showSelectionBottomSheet(
|
|
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
|
|
title: "Select Duration".needTranslation,
|
|
|
|
|
|
|
|
items: viewModel.durationFilters,
|
|
|
|
|
|
|
|
selectedValue: viewModel.selectedDurationFilter,
|
|
|
|
|
|
|
|
onSelected: viewModel.setFilterDuration,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
|
|
|
return Scaffold(
|
|
|
|
@ -245,7 +685,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
text: "Set Reminder".needTranslation,
|
|
|
|
text: "Set Reminder".needTranslation,
|
|
|
|
textColor: AppColors.successColor,
|
|
|
|
textColor: AppColors.successColor,
|
|
|
|
backgroundColor: AppColors.successLightBgColor,
|
|
|
|
backgroundColor: AppColors.successLightBgColor,
|
|
|
|
onPressed: () => {},
|
|
|
|
onPressed: () {},
|
|
|
|
icon: AppAssets.bell,
|
|
|
|
icon: AppAssets.bell,
|
|
|
|
iconColor: AppColors.successColor,
|
|
|
|
iconColor: AppColors.successColor,
|
|
|
|
borderRadius: 12.r,
|
|
|
|
borderRadius: 12.r,
|
|
|
|
@ -259,11 +699,13 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
_buildWaterIntakeSummaryWidget(),
|
|
|
|
_buildWaterIntakeSummaryWidget(),
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
|
|
|
_buildHistoryGraphOrList(),
|
|
|
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
_buildHydrationTipsWidget(),
|
|
|
|
_buildHydrationTipsWidget(),
|
|
|
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
// );
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|