mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 23:42:00 +01:00
Move nutritional plan diary chart to fl charts
This commit is contained in:
@@ -82,6 +82,30 @@ class NutritionalPlan {
|
||||
return out;
|
||||
}
|
||||
|
||||
NutritionalValues get nutritionalValuesToday {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
return logEntriesValues.containsKey(today) ? logEntriesValues[today]! : NutritionalValues();
|
||||
}
|
||||
|
||||
NutritionalValues get nutritionalValues7DayAvg {
|
||||
final currentDate = DateTime.now();
|
||||
final sevenDaysAgo = currentDate.subtract(Duration(days: 7));
|
||||
|
||||
final entries = logs.where((obj) {
|
||||
DateTime objDate = obj.datetime;
|
||||
return objDate.isAfter(sevenDaysAgo) && objDate.isBefore(currentDate);
|
||||
}).toList();
|
||||
|
||||
var out = NutritionalValues();
|
||||
entries.forEach((log) {
|
||||
out = out + log.nutritionalValues;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Calculates the percentage each macro nutrient adds to the total energy
|
||||
BaseNutritionalValues energyPercentage(NutritionalValues values) {
|
||||
return BaseNutritionalValues(
|
||||
|
||||
@@ -182,19 +182,15 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
},
|
||||
),
|
||||
if (_hasContent)
|
||||
Container(
|
||||
color: Colors.blue,
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
...getContent(),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 180,
|
||||
child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
...getContent(),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 180,
|
||||
child: FlNutritionalPlanPieChartWidget(_plan!.nutritionalValues),
|
||||
)
|
||||
],
|
||||
)
|
||||
else
|
||||
NothingFound(
|
||||
@@ -345,85 +341,79 @@ class _DashboardMeasurementWidgetState extends State<DashboardMeasurementWidget>
|
||||
}
|
||||
return Consumer<MeasurementProvider>(
|
||||
builder: (context, workoutProvider, child) => Card(
|
||||
child: Container(
|
||||
color: Colors.amberAccent,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).measurements,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
leading: const FaIcon(
|
||||
FontAwesomeIcons.weight,
|
||||
color: Colors.black,
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context,
|
||||
MeasurementCategoriesScreen.routeName,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).measurements,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
leading: const FaIcon(
|
||||
FontAwesomeIcons.weight,
|
||||
color: Colors.black,
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context,
|
||||
MeasurementCategoriesScreen.routeName,
|
||||
),
|
||||
),
|
||||
|
||||
//color: Colors.lightBlue,
|
||||
Column(
|
||||
children: [
|
||||
if (items.isNotEmpty)
|
||||
Column(children: [
|
||||
CarouselSlider(
|
||||
items: items,
|
||||
carouselController: _controller,
|
||||
options: CarouselOptions(
|
||||
autoPlay: false,
|
||||
enlargeCenterPage: false,
|
||||
viewportFraction: 1,
|
||||
enableInfiniteScroll: false,
|
||||
aspectRatio: 1.1,
|
||||
onPageChanged: (index, reason) {
|
||||
setState(() {
|
||||
_current = index;
|
||||
});
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: items.asMap().entries.map(
|
||||
(entry) {
|
||||
return GestureDetector(
|
||||
onTap: () => _controller.animateToPage(entry.key),
|
||||
child: Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
margin:
|
||||
EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: (Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: wgerPrimaryColor)
|
||||
.withOpacity(_current == entry.key ? 0.9 : 0.4)),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
])
|
||||
else
|
||||
NothingFound(
|
||||
AppLocalizations.of(context).noMeasurementEntries,
|
||||
AppLocalizations.of(context).newEntry,
|
||||
MeasurementCategoryForm(),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
if (items.isNotEmpty)
|
||||
Column(children: [
|
||||
CarouselSlider(
|
||||
items: items,
|
||||
carouselController: _controller,
|
||||
options: CarouselOptions(
|
||||
autoPlay: false,
|
||||
enlargeCenterPage: false,
|
||||
viewportFraction: 1,
|
||||
enableInfiniteScroll: false,
|
||||
aspectRatio: 1.1,
|
||||
onPageChanged: (index, reason) {
|
||||
setState(() {
|
||||
_current = index;
|
||||
});
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: items.asMap().entries.map(
|
||||
(entry) {
|
||||
return GestureDetector(
|
||||
onTap: () => _controller.animateToPage(entry.key),
|
||||
child: Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: (Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: wgerPrimaryColor)
|
||||
.withOpacity(_current == entry.key ? 0.9 : 0.4)),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
])
|
||||
else
|
||||
NothingFound(
|
||||
AppLocalizations.of(context).noMeasurementEntries,
|
||||
AppLocalizations.of(context).newEntry,
|
||||
MeasurementCategoryForm(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@@ -34,16 +33,16 @@ class NutritionData {
|
||||
NutritionData(this.name, this.value);
|
||||
}
|
||||
|
||||
class NutritionalPlanPieChartWidget extends StatefulWidget {
|
||||
class FlNutritionalPlanPieChartWidget extends StatefulWidget {
|
||||
final NutritionalValues nutritionalValues;
|
||||
|
||||
const NutritionalPlanPieChartWidget(this.nutritionalValues);
|
||||
const FlNutritionalPlanPieChartWidget(this.nutritionalValues);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => FlNutritionalPlanPieChartState();
|
||||
}
|
||||
|
||||
class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget> {
|
||||
class FlNutritionalPlanPieChartState extends State<FlNutritionalPlanPieChartWidget> {
|
||||
int touchedIndex = -1;
|
||||
|
||||
@override
|
||||
@@ -128,7 +127,7 @@ class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget
|
||||
return PieChartSectionData(
|
||||
color: colors.current,
|
||||
value: widget.nutritionalValues.fat,
|
||||
title: '${widget.nutritionalValues.fat.toStringAsFixed(1)}g',
|
||||
title: '${widget.nutritionalValues.fat.toStringAsFixed(0)}g',
|
||||
titlePositionPercentageOffset: 0.5,
|
||||
radius: radius,
|
||||
titleStyle: const TextStyle(color: Colors.white70));
|
||||
@@ -136,7 +135,7 @@ class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget
|
||||
return PieChartSectionData(
|
||||
color: colors.current,
|
||||
value: widget.nutritionalValues.protein,
|
||||
title: '${widget.nutritionalValues.protein.toStringAsFixed(1)}g',
|
||||
title: '${widget.nutritionalValues.protein.toStringAsFixed(0)}g',
|
||||
titlePositionPercentageOffset: 0.5,
|
||||
radius: radius,
|
||||
);
|
||||
@@ -144,7 +143,7 @@ class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget
|
||||
return PieChartSectionData(
|
||||
color: colors.current,
|
||||
value: widget.nutritionalValues.carbohydrates,
|
||||
title: '${widget.nutritionalValues.carbohydrates.toStringAsFixed(1)}g',
|
||||
title: '${widget.nutritionalValues.carbohydrates.toStringAsFixed(0)}g',
|
||||
titlePositionPercentageOffset: 0.5,
|
||||
radius: radius,
|
||||
);
|
||||
@@ -156,6 +155,244 @@ class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget
|
||||
}
|
||||
}
|
||||
|
||||
class NutritionalDiaryChartWidgetFl extends StatefulWidget {
|
||||
const NutritionalDiaryChartWidgetFl({
|
||||
Key? key,
|
||||
required NutritionalPlan nutritionalPlan,
|
||||
}) : _nutritionalPlan = nutritionalPlan,
|
||||
super(key: key);
|
||||
|
||||
final NutritionalPlan _nutritionalPlan;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => NutritionalDiaryChartWidgetFlState();
|
||||
}
|
||||
|
||||
class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidgetFl> {
|
||||
Widget bottomTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(fontSize: 10);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Protein';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Carbohydrates';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Sugars';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Fat';
|
||||
break;
|
||||
case 4:
|
||||
text = 'Saturated fat';
|
||||
break;
|
||||
|
||||
default:
|
||||
text = '';
|
||||
break;
|
||||
}
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitles(double value, TitleMeta meta) {
|
||||
if (value == meta.max) {
|
||||
return Container();
|
||||
}
|
||||
const style = TextStyle(
|
||||
fontSize: 10,
|
||||
);
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
child: Text(
|
||||
meta.formattedValue,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final NutritionalValues planned = widget._nutritionalPlan.nutritionalValues;
|
||||
final NutritionalValues loggedToday = widget._nutritionalPlan.nutritionalValuesToday;
|
||||
final NutritionalValues logged7DayAvg = widget._nutritionalPlan.nutritionalValues7DayAvg;
|
||||
|
||||
final colorPlanned = LIST_OF_COLORS3[0];
|
||||
final colorLoggedToday = LIST_OF_COLORS3[1];
|
||||
final colorLogged7Day = LIST_OF_COLORS3[2];
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.66,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final barsSpace = 4.0 * constraints.maxWidth / 400;
|
||||
final barsWidth = 8.0 * constraints.maxWidth / 400;
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.center,
|
||||
barTouchData: BarTouchData(
|
||||
enabled: false,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 48,
|
||||
getTitlesWidget: bottomTitles,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: leftTitles,
|
||||
),
|
||||
),
|
||||
topTitles: AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
checkToShowHorizontalLine: (value) => value % 10 == 0,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
color: Colors.black,
|
||||
strokeWidth: 1,
|
||||
),
|
||||
drawVerticalLine: false,
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
groupsSpace: 30,
|
||||
// groupsSpace: barsSpace,
|
||||
barGroups: [
|
||||
BarChartGroupData(
|
||||
x: 0,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: planned.protein,
|
||||
color: colorPlanned,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: loggedToday.protein,
|
||||
color: colorLoggedToday,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: logged7DayAvg.protein,
|
||||
color: colorLogged7Day,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 1,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: planned.carbohydrates,
|
||||
color: colorPlanned,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: loggedToday.carbohydrates,
|
||||
color: colorLoggedToday,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: logged7DayAvg.carbohydrates,
|
||||
color: colorLogged7Day,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 2,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: planned.carbohydratesSugar,
|
||||
color: colorPlanned,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: loggedToday.carbohydratesSugar,
|
||||
color: colorLoggedToday,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: logged7DayAvg.carbohydratesSugar,
|
||||
color: colorLogged7Day,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 3,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: planned.fat,
|
||||
color: colorPlanned,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: loggedToday.fat,
|
||||
color: colorLoggedToday,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: logged7DayAvg.fat,
|
||||
color: colorLogged7Day,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 4,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: planned.fatSaturated,
|
||||
color: colorPlanned,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: loggedToday.fatSaturated,
|
||||
color: colorLoggedToday,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: logged7DayAvg.fatSaturated,
|
||||
color: colorLogged7Day,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
// barGroups: getData(barsWidth, barsSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NutritionalDiaryChartWidget extends StatelessWidget {
|
||||
const NutritionalDiaryChartWidget({
|
||||
Key? key,
|
||||
@@ -193,311 +430,3 @@ class NutritionalDiaryChartWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Nutritional plan hatch bar chart widget
|
||||
class NutritionalPlanHatchBarChartWidget extends StatelessWidget {
|
||||
final NutritionalPlan _nutritionalPlan;
|
||||
|
||||
/// [_nutritionalPlan] is current opened nutrition plan as plan detail.
|
||||
const NutritionalPlanHatchBarChartWidget(this._nutritionalPlan);
|
||||
|
||||
NutritionalValues nutritionalValuesFromPlanLogsSevenDayAvg() {
|
||||
NutritionalValues sevenDaysAvg = NutritionalValues();
|
||||
int count = 0;
|
||||
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
_nutritionalPlan.logEntriesValues.forEach((key, value) {
|
||||
if (key.difference(today).inDays >= -7) {
|
||||
sevenDaysAvg += value;
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
if (count != 0) {
|
||||
sevenDaysAvg.energy = sevenDaysAvg.energy / count;
|
||||
sevenDaysAvg.protein = sevenDaysAvg.protein / count;
|
||||
sevenDaysAvg.carbohydrates = sevenDaysAvg.carbohydrates / count;
|
||||
sevenDaysAvg.carbohydratesSugar = sevenDaysAvg.carbohydratesSugar / count;
|
||||
sevenDaysAvg.fat = sevenDaysAvg.fat / count;
|
||||
sevenDaysAvg.fatSaturated = sevenDaysAvg.fatSaturated / count;
|
||||
sevenDaysAvg.fibres = sevenDaysAvg.fibres / count;
|
||||
sevenDaysAvg.sodium = sevenDaysAvg.sodium / count;
|
||||
}
|
||||
|
||||
return sevenDaysAvg;
|
||||
}
|
||||
|
||||
NutritionalValues nutritionalValuesFromPlanLogsToday() {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
return _nutritionalPlan.logEntriesValues[_nutritionalPlan.logEntriesValues.keys
|
||||
.firstWhereOrNull((d) => d.difference(today).inDays == 0)] ??
|
||||
NutritionalValues();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final NutritionalValues loggedNutritionalValues = nutritionalValuesFromPlanLogsToday();
|
||||
final NutritionalValues sevenDayAvg = nutritionalValuesFromPlanLogsSevenDayAvg();
|
||||
|
||||
if (_nutritionalPlan.nutritionalValues.energy == 0) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: charts.BarChart(
|
||||
[
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Planned',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
NutritionData(AppLocalizations.of(context).energy,
|
||||
_nutritionalPlan.nutritionalValues.energy),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Logged',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch,
|
||||
data: [
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).energy, loggedNutritionalValues.energy),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Avg',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy),
|
||||
],
|
||||
),
|
||||
],
|
||||
animate: true,
|
||||
domainAxis: const charts.OrdinalAxisSpec(
|
||||
///labelRotation was added to rotate text of X Axis. Without that,
|
||||
///titles would overlap each other
|
||||
renderSpec: charts.SmallTickRendererSpec(labelRotation: 60),
|
||||
),
|
||||
barGroupingType: charts.BarGroupingType.grouped,
|
||||
defaultRenderer: charts.BarRendererConfig(
|
||||
groupingType: charts.BarGroupingType.grouped, strokeWidthPx: 0.0, maxBarWidthPx: 8),
|
||||
primaryMeasureAxis: const charts.NumericAxisSpec(
|
||||
tickProviderSpec: charts.BasicNumericTickProviderSpec(desiredTickCount: 5),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 300,
|
||||
child: charts.BarChart(
|
||||
[
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Planned',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
// NutritionData(
|
||||
// AppLocalizations.of(context).energy,
|
||||
// _nutritionalPlan.nutritionalValues.energy,
|
||||
// ),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).protein,
|
||||
_nutritionalPlan.nutritionalValues.protein,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).carbohydrates,
|
||||
_nutritionalPlan.nutritionalValues.carbohydrates,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).sugars,
|
||||
_nutritionalPlan.nutritionalValues.carbohydratesSugar,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).fat,
|
||||
_nutritionalPlan.nutritionalValues.fat,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).saturatedFat,
|
||||
_nutritionalPlan.nutritionalValues.fatSaturated,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).fibres,
|
||||
_nutritionalPlan.nutritionalValues.fibres,
|
||||
),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).sodium,
|
||||
_nutritionalPlan.nutritionalValues.sodium,
|
||||
),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Logged',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch,
|
||||
data: [
|
||||
// NutritionData(
|
||||
// AppLocalizations.of(context).energy,
|
||||
// loggedNutritionalValues.energy
|
||||
// ),
|
||||
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).protein, loggedNutritionalValues.protein),
|
||||
NutritionData(AppLocalizations.of(context).carbohydrates,
|
||||
loggedNutritionalValues.carbohydrates),
|
||||
NutritionData(AppLocalizations.of(context).sugars,
|
||||
loggedNutritionalValues.carbohydratesSugar),
|
||||
NutritionData(AppLocalizations.of(context).fat, loggedNutritionalValues.fat),
|
||||
NutritionData(AppLocalizations.of(context).saturatedFat,
|
||||
loggedNutritionalValues.fatSaturated),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).fibres, loggedNutritionalValues.fibres),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).sodium, loggedNutritionalValues.sodium),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Avg',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
// NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy),
|
||||
NutritionData(AppLocalizations.of(context).protein, sevenDayAvg.protein),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).carbohydrates, sevenDayAvg.carbohydrates),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).sugars, sevenDayAvg.carbohydratesSugar),
|
||||
NutritionData(AppLocalizations.of(context).fat, sevenDayAvg.fat),
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).saturatedFat, sevenDayAvg.fatSaturated),
|
||||
NutritionData(AppLocalizations.of(context).fibres, sevenDayAvg.fibres),
|
||||
NutritionData(AppLocalizations.of(context).sodium, sevenDayAvg.sodium),
|
||||
],
|
||||
),
|
||||
],
|
||||
animate: true,
|
||||
domainAxis: const charts.OrdinalAxisSpec(
|
||||
///labelRotation was added to rotate text of X Axis. Without that,
|
||||
///titles would overlap each other
|
||||
renderSpec: charts.SmallTickRendererSpec(labelRotation: 60),
|
||||
),
|
||||
barGroupingType: charts.BarGroupingType.grouped,
|
||||
primaryMeasureAxis: const charts.NumericAxisSpec(
|
||||
tickProviderSpec: charts.BasicNumericTickProviderSpec(
|
||||
desiredTickCount: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//creating a seperate chart for energy as the energy value and other nutrient's value is not compatable to show in one graph
|
||||
class EnergyChart extends StatelessWidget {
|
||||
const EnergyChart({Key? key, required this.nutritionalPlan}) : super(key: key);
|
||||
final NutritionalPlan nutritionalPlan;
|
||||
|
||||
NutritionalValues nutritionalValuesFromPlanLogsSevenDayAvg() {
|
||||
NutritionalValues sevenDaysAvg = NutritionalValues();
|
||||
int count = 0;
|
||||
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
nutritionalPlan.logEntriesValues.forEach((key, value) {
|
||||
if (key.difference(today).inDays >= -7) {
|
||||
sevenDaysAvg += value;
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
if (count != 0) {
|
||||
sevenDaysAvg.energy = sevenDaysAvg.energy / count;
|
||||
sevenDaysAvg.protein = sevenDaysAvg.protein / count;
|
||||
sevenDaysAvg.carbohydrates = sevenDaysAvg.carbohydrates / count;
|
||||
sevenDaysAvg.carbohydratesSugar = sevenDaysAvg.carbohydratesSugar / count;
|
||||
sevenDaysAvg.fat = sevenDaysAvg.fat / count;
|
||||
sevenDaysAvg.fatSaturated = sevenDaysAvg.fatSaturated / count;
|
||||
sevenDaysAvg.fibres = sevenDaysAvg.fibres / count;
|
||||
sevenDaysAvg.sodium = sevenDaysAvg.sodium / count;
|
||||
}
|
||||
|
||||
return sevenDaysAvg;
|
||||
}
|
||||
|
||||
NutritionalValues nutritionalValuesFromPlanLogsToday() {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
return nutritionalPlan.logEntriesValues[nutritionalPlan.logEntriesValues.keys
|
||||
.firstWhereOrNull((d) => d.difference(today).inDays == 0)] ??
|
||||
NutritionalValues();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final NutritionalValues loggedNutritionalValues = nutritionalValuesFromPlanLogsToday();
|
||||
final NutritionalValues sevenDayAvg = nutritionalValuesFromPlanLogsSevenDayAvg();
|
||||
|
||||
if (nutritionalPlan.nutritionalValues.energy == 0) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return charts.BarChart(
|
||||
[
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Planned',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
NutritionData(
|
||||
AppLocalizations.of(context).energy,
|
||||
nutritionalPlan.nutritionalValues.energy,
|
||||
),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Logged',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch,
|
||||
data: [
|
||||
NutritionData(AppLocalizations.of(context).energy, loggedNutritionalValues.energy),
|
||||
],
|
||||
),
|
||||
charts.Series<NutritionData, String>(
|
||||
id: 'Avg',
|
||||
domainFn: (nutritionEntry, index) => nutritionEntry.name,
|
||||
measureFn: (nutritionEntry, index) => nutritionEntry.value,
|
||||
data: [
|
||||
NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy),
|
||||
],
|
||||
),
|
||||
],
|
||||
animate: true,
|
||||
domainAxis: const charts.OrdinalAxisSpec(
|
||||
///labelRotation was added to rotate text of X Axis. Without that,
|
||||
///titles would overlap each other
|
||||
renderSpec: charts.SmallTickRendererSpec(labelRotation: 60),
|
||||
),
|
||||
barGroupingType: charts.BarGroupingType.grouped,
|
||||
defaultRenderer: charts.BarRendererConfig(
|
||||
groupingType: charts.BarGroupingType.grouped, strokeWidthPx: 0.0, maxBarWidthPx: 8),
|
||||
primaryMeasureAxis: const charts.NumericAxisSpec(
|
||||
tickProviderSpec: charts.BasicNumericTickProviderSpec(desiredTickCount: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
|
||||
final NutritionalPlan _nutritionalPlan;
|
||||
final DateTime _date;
|
||||
static const double tablePadding = 7;
|
||||
|
||||
const NutritionalDiaryDetailWidget(this._nutritionalPlan, this._date);
|
||||
|
||||
Widget getTable(
|
||||
@@ -223,7 +224,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: NutritionalPlanPieChartWidget(valuesDate),
|
||||
child: FlNutritionalPlanPieChartWidget(valuesDate),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
||||
@@ -32,7 +32,9 @@ import 'package:wger/widgets/nutrition/meal.dart';
|
||||
|
||||
class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
final NutritionalPlan _nutritionalPlan;
|
||||
|
||||
const NutritionalPlanDetailWidget(this._nutritionalPlan);
|
||||
|
||||
static const double tablePadding = 7;
|
||||
|
||||
@override
|
||||
@@ -70,7 +72,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: NutritionalPlanPieChartWidget(nutritionalValues), // chart
|
||||
child: FlNutritionalPlanPieChartWidget(nutritionalValues), // chart
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
@@ -210,13 +212,11 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
|
||||
NutritionalPlanHatchBarChartWidget(_nutritionalPlan),
|
||||
// Container(
|
||||
// padding: const EdgeInsets.all(15),
|
||||
// height: 300,
|
||||
// child: NutritionalPlanHatchBarChartWidget(_nutritionalPlan), // chart
|
||||
// ),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 300,
|
||||
child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan), // chart
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8.0)),
|
||||
Text(
|
||||
AppLocalizations.of(context).nutritionalDiary,
|
||||
|
||||
@@ -82,8 +82,9 @@ void main() {
|
||||
await tester.tap(find.byType(TextButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('8/1/2021'), findsOneWidget);
|
||||
expect(find.text('8/10/2021'), findsOneWidget);
|
||||
// From the entries list and from the chart
|
||||
expect(find.text('8/1/2021'), findsNWidgets(3));
|
||||
expect(find.text('8/10/2021'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async {
|
||||
|
||||
@@ -41,7 +41,7 @@ void main() {
|
||||
testWidgets('Test the detail view for the nutritional plan', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(getWidget());
|
||||
|
||||
expect(find.byType(NutritionalPlanPieChartWidget), findsOneWidget);
|
||||
expect(find.byType(FlNutritionalPlanPieChartWidget), findsOneWidget);
|
||||
expect(find.byType(Table), findsOneWidget);
|
||||
|
||||
expect(find.text('519kcal'), findsOneWidget, reason: 'find total energy');
|
||||
|
||||
@@ -83,8 +83,9 @@ void main() {
|
||||
testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createWeightScreen());
|
||||
|
||||
expect(find.text('1/1/2021'), findsOneWidget);
|
||||
expect(find.text('1/10/2021'), findsOneWidget);
|
||||
// One in the entries list, one in the chart
|
||||
expect(find.text('1/1/2021'), findsNWidgets(2));
|
||||
expect(find.text('1/10/2021'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async {
|
||||
|
||||
Reference in New Issue
Block a user