diff --git a/lib/screens/measurement_entries_screen.dart b/lib/screens/measurement_entries_screen.dart index 55d5493b..dcbcae1a 100644 --- a/lib/screens/measurement_entries_screen.dart +++ b/lib/screens/measurement_entries_screen.dart @@ -133,8 +133,10 @@ class MeasurementEntriesScreen extends StatelessWidget { ); }, ), - body: Consumer( - builder: (context, provider, child) => EntriesList(category), + body: SingleChildScrollView( + child: Consumer( + builder: (context, provider, child) => EntriesList(category), + ), ), ); } diff --git a/lib/screens/weight_screen.dart b/lib/screens/weight_screen.dart index 9f53e6d6..5af357be 100644 --- a/lib/screens/weight_screen.dart +++ b/lib/screens/weight_screen.dart @@ -22,8 +22,8 @@ import 'package:provider/provider.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/widgets/core/app_bar.dart'; -import 'package:wger/widgets/weight/weight_overview.dart'; import 'package:wger/widgets/weight/forms.dart'; +import 'package:wger/widgets/weight/weight_overview.dart'; class WeightScreen extends StatelessWidget { const WeightScreen(); diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 9f31b9b2..9fc26761 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -280,6 +280,9 @@ class _DashboardMeasurementWidgetState extends State FontAwesomeIcons.chartLine, color: Theme.of(context).textTheme.headlineSmall!.color, ), + // TODO: this icon feels out of place and inconsistent with all + // other dashboard widgets. + // maybe we should just add a "Go to all" at the bottom of the widget trailing: IconButton( icon: const Icon(Icons.arrow_forward), onPressed: () => Navigator.pushNamed( diff --git a/lib/widgets/measurements/categories_card.dart b/lib/widgets/measurements/categories_card.dart index fdf47dcf..4b39e169 100644 --- a/lib/widgets/measurements/categories_card.dart +++ b/lib/widgets/measurements/categories_card.dart @@ -15,9 +15,12 @@ class CategoriesCard extends StatelessWidget { @override Widget build(BuildContext context) { + final entriesAll = + currentCategory.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(); + final entries7dAvg = moving7dAverage(entriesAll); + return Card( elevation: elevation, - color: Theme.of(context).colorScheme.onInverseSurface, child: Column( children: [ Padding( @@ -31,10 +34,16 @@ class CategoriesCard extends StatelessWidget { padding: const EdgeInsets.all(10), height: 220, child: MeasurementChartWidgetFl( - currentCategory.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(), + entriesAll, currentCategory.unit, + avgs: entries7dAvg, ), ), + MeasurementOverallChangeWidget( + entries7dAvg.first, + entries7dAvg.last, + currentCategory.unit, + ), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/widgets/measurements/charts.dart b/lib/widgets/measurements/charts.dart index 9e516206..37e0a018 100644 --- a/lib/widgets/measurements/charts.dart +++ b/lib/widgets/measurements/charts.dart @@ -30,9 +30,14 @@ class MeasurementOverallChangeWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( - 'overall change ${(_last.value - _first.value).toStringAsFixed(1)} $_unit', - ); + final delta = _last.value - _first.value; + final prefix = delta > 0 + ? '+' + : delta < 0 + ? '-' + : ''; + + return Text('overall change $prefix ${delta.abs().toStringAsFixed(1)} $_unit'); } } diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index 73b1d779..68a3db34 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -22,8 +22,10 @@ import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:wger/models/measurements/measurement_category.dart'; import 'package:wger/providers/measurement.dart'; +import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/widgets/measurements/charts.dart'; +import 'package:wger/widgets/measurements/helpers.dart'; import 'forms.dart'; @@ -34,16 +36,23 @@ class EntriesList extends StatelessWidget { @override Widget build(BuildContext context) { + final plan = Provider.of(context, listen: false).currentPlan; + + final entriesAll = + _category.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(); + final entries7dAvg = moving7dAverage(entriesAll); + return Column(children: [ - Container( - padding: const EdgeInsets.all(10), - height: 220, - child: MeasurementChartWidgetFl( - _category.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(), - _category.unit, - ), + ...getOverviewWidgetsSeries( + _category.name, + entriesAll, + entries7dAvg, + plan, + _category.unit, + context, ), - Expanded( + SizedBox( + height: 300, child: ListView.builder( padding: const EdgeInsets.all(10.0), itemCount: _category.entries.length, diff --git a/lib/widgets/measurements/helpers.dart b/lib/widgets/measurements/helpers.dart new file mode 100644 index 00000000..2fe157ff --- /dev/null +++ b/lib/widgets/measurements/helpers.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:wger/models/nutrition/nutritional_plan.dart'; +import 'package:wger/widgets/measurements/charts.dart'; + +List getOverviewWidgets( + String title, + List raw, + List avg, + String unit, + BuildContext context, +) { + return [ + Text( + title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + Container( + padding: const EdgeInsets.all(15), + height: 220, + child: MeasurementChartWidgetFl(raw, unit, avgs: avg), + ), + if (avg.isNotEmpty) MeasurementOverallChangeWidget(avg.first, avg.last, unit), + const SizedBox(height: 8), + ]; +} + +List getOverviewWidgetsSeries( + String name, + List entriesAll, + List entries7dAvg, + NutritionalPlan? plan, + String unit, + BuildContext context, +) { + final monthAgo = DateTime.now().subtract(const Duration(days: 30)); + return [ + ...getOverviewWidgets( + '$name all-time', + entriesAll, + entries7dAvg, + unit, + context, + ), + if (plan != null) + ...getOverviewWidgets( + '$name during nutritional plan ${plan.description}', + entriesAll.where((e) => e.date.isAfter(plan.creationDate)).toList(), + entries7dAvg.where((e) => e.date.isAfter(plan.creationDate)).toList(), + unit, + context, + ), + // if all time is significantly longer than 30 days (let's say > 75 days) + // and if there is is a plan and it also was > 75 days, + // then let's show a separate chart just focusing on the last 30 days + if (entriesAll.first.date.isBefore(entriesAll.last.date.subtract(const Duration(days: 75))) && + (plan == null || + entriesAll + .firstWhere((e) => e.date.isAfter(plan.creationDate)) + .date + .isBefore(entriesAll.last.date.subtract(const Duration(days: 30))))) + ...getOverviewWidgets( + '$name last 30 days', + entriesAll.where((e) => e.date.isAfter(monthAgo)).toList(), + entries7dAvg.where((e) => e.date.isAfter(monthAgo)).toList(), + unit, + context, + ), + // legend + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Indicator(color: Theme.of(context).colorScheme.primary, text: 'raw', isSquare: true), + Indicator(color: Theme.of(context).colorScheme.tertiary, text: 'avg', isSquare: true), + ], + ), + ]; +} diff --git a/lib/widgets/weight/weight_overview.dart b/lib/widgets/weight/weight_overview.dart index e0684adc..31aca511 100644 --- a/lib/widgets/weight/weight_overview.dart +++ b/lib/widgets/weight/weight_overview.dart @@ -26,6 +26,7 @@ import 'package:wger/providers/user.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/measurement_categories_screen.dart'; import 'package:wger/widgets/measurements/charts.dart'; +import 'package:wger/widgets/measurements/helpers.dart'; import 'package:wger/widgets/weight/forms.dart'; class WeightOverview extends StatelessWidget { @@ -40,78 +41,18 @@ class WeightOverview extends StatelessWidget { weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList(); final entries7dAvg = moving7dAverage(entriesAll); - List getOverviewWidgets(String title, bool isMetric, List raw, - List avg, BuildContext context) { - return [ - Text( - title, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge, - ), - Container( - padding: const EdgeInsets.all(15), - height: 220, - child: MeasurementChartWidgetFl( - raw, - weightUnit(isMetric, context), - avgs: avg, - ), - ), - MeasurementOverallChangeWidget( - avg.first, - avg.last, - weightUnit(isMetric, context), - ), - const SizedBox(height: 8), - ]; - } + final unit = weightUnit(profile!.isMetric, context); return Column( children: [ - ...getOverviewWidgets( - 'Weight all-time', - profile!.isMetric, + ...getOverviewWidgetsSeries( + 'Weight', entriesAll, entries7dAvg, + plan, + unit, context, ), - if (plan != null) - ...getOverviewWidgets( - 'Weight during nutritional plan ${plan.description}', - profile.isMetric, - entriesAll.where((e) => e.date.isAfter(plan.creationDate)).toList(), - entries7dAvg.where((e) => e.date.isAfter(plan.creationDate)).toList(), - context, - ), - // if all time is significantly longer than 30 days (let's say > 75 days) - // and if there is is a plan and it also was > 75 days, - // then let's show a separate chart just focusing on the last 30 days - if (entriesAll.first.date - .isBefore(entriesAll.last.date.subtract(const Duration(days: 75))) && - (plan == null || - entriesAll - .firstWhere((e) => e.date.isAfter(plan.creationDate)) - .date - .isBefore(entriesAll.last.date.subtract(const Duration(days: 30))))) - ...getOverviewWidgets( - 'Weight last 30 days', - profile.isMetric, - entriesAll - .where((e) => e.date.isAfter(DateTime.now().subtract(const Duration(days: 30)))) - .toList(), - entries7dAvg - .where((e) => e.date.isAfter(DateTime.now().subtract(const Duration(days: 30)))) - .toList(), - context, - ), - - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Indicator(color: Theme.of(context).colorScheme.primary, text: 'raw', isSquare: true), - Indicator(color: Theme.of(context).colorScheme.tertiary, text: 'avg', isSquare: true), - ], - ), TextButton( onPressed: () => Navigator.pushNamed( context,