From 78d5b3017c928ad2d8fb6697e7e34fa7460864dd Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 27 Sep 2025 12:06:09 +0200 Subject: [PATCH 1/3] always show/use plans by startDate --- lib/providers/nutrition.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index b3f840631..acb984744 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -107,7 +107,7 @@ class NutritionPlansProvider with ChangeNotifier { for (final planData in data) { final plan = NutritionalPlan.fromJson(planData); _plans.add(plan); - _plans.sort((a, b) => b.creationDate.compareTo(a.creationDate)); + _plans.sort((a, b) => a.startDate.compareTo(b.startDate)); } notifyListeners(); } From 18bb7ca5c81b9288070a4f3b0cc4ca25df3477af Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 27 Sep 2025 12:06:56 +0200 Subject: [PATCH 2/3] nutrition plans list: show weekly delta and total duration, consistent styling --- lib/helpers/date.dart | 45 +++++++++++++++++++ .../nutrition/nutritional_plans_list.dart | 30 ++++++++----- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lib/helpers/date.dart b/lib/helpers/date.dart index 9177b8fc6..10b9b3e00 100644 --- a/lib/helpers/date.dart +++ b/lib/helpers/date.dart @@ -30,6 +30,51 @@ List daysInRange(DateTime first, DateTime last) { ); } +/// Formats a Duration into a human-readable string with years, months, and days +String humanDuration(DateTime startDate, DateTime endDate) { + var years = endDate.year - startDate.year; + var months = endDate.month - startDate.month; + var days = endDate.day - startDate.day; + + if (months < 0) { + months += 12; + years -= 1; + } + +// if we overcounted the days, it's a bit trickier to solve than overcounting months +// e.g. consider a start date february 10 and end date june 8 +// -> days = -2 +// -> months = 4 +// the proper answer can be thought of in 3 ways: +// * count whole months first, then days: from febr 10 to may 10, and then to june 8. that's 3 months + (num_days_in_may - 10 + 8 days) +// * count days first, then whole months: from febr 10 to march 8, and then to june 8. that's (num_days_in_feb - 10 + 8 days) + 3 months +// * technically, one can also use any month to adjust the day: e.g. +// count months from febr 10 to april 10, count days to may 8, and count months again to june 8. +// probably no-one uses the last method. The first approach seems most natural. +// it means we need to know the number of days (aka the last day index) of the month before endDate +// which we can do by setting the day to 0 and asking for the day + if (days < 0) { + days += DateTime(endDate.year, endDate.month, 0).day; + months -= 1; + } + + final parts = []; + if (years > 0) { + parts.add('$years year${years == 1 ? '' : 's'}'); + } + if (months > 0) { + parts.add('$months month${months == 1 ? '' : 's'}'); + } + if (days > 0) { + parts.add('$days day${days == 1 ? '' : 's'}'); + } + + if (parts.isEmpty) { + return 'Duration: 0 days'; + } + return 'Duration: ${parts.join(', ')}'; +} + extension DateTimeExtension on DateTime { bool isSameDayAs(DateTime other) { final thisDay = DateTime(year, month, day); diff --git a/lib/widgets/nutrition/nutritional_plans_list.dart b/lib/widgets/nutrition/nutritional_plans_list.dart index 9f45149d2..c971abbc3 100644 --- a/lib/widgets/nutrition/nutritional_plans_list.dart +++ b/lib/widgets/nutrition/nutritional_plans_list.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:wger/helpers/date.dart'; import 'package:wger/helpers/measurements.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/body_weight.dart'; @@ -48,8 +49,14 @@ class NutritionalPlansList extends StatelessWidget { final lastWeight = entries7dAvg.last; final weightDifference = lastWeight.value - firstWeight.value; + // Calculate the time period in weeks + final timeDifference = lastWeight.date.difference(firstWeight.date); + final weeklyRate = + weightDifference / (timeDifference.inDays == 0 ? 1 : timeDifference.inDays) * 7; + // Format the weight change text and determine color final String weightChangeText; + final String weeklyRateText; final Color weightChangeColor; final profile = context.read().profile; @@ -57,12 +64,15 @@ class NutritionalPlansList extends StatelessWidget { if (weightDifference > 0) { weightChangeText = '+${weightDifference.toStringAsFixed(1)} $unit'; + weeklyRateText = '+${weeklyRate.toStringAsFixed(2)} $unit'; weightChangeColor = Colors.red; } else if (weightDifference < 0) { weightChangeText = '${weightDifference.toStringAsFixed(1)} $unit'; + weeklyRateText = '${weeklyRate.toStringAsFixed(2)} $unit'; weightChangeColor = Colors.green; } else { weightChangeText = '0 $unit'; + weeklyRateText = '0 $unit'; weightChangeColor = Colors.grey; } @@ -71,15 +81,11 @@ class NutritionalPlansList extends StatelessWidget { child: Row( children: [ Text( - '${AppLocalizations.of(context).weight} change: ', - style: Theme.of(context).textTheme.bodySmall, + '${AppLocalizations.of(context).weight}: ', ), Text( - weightChangeText, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - color: weightChangeColor, - ), + '$weightChangeText ($weeklyRateText/week)', + style: TextStyle(color: weightChangeColor), ), ], ), @@ -109,14 +115,18 @@ class NutritionalPlansList extends StatelessWidget { subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + humanDuration( + currentPlan.startDate, currentPlan.endDate ?? DateTime.now()), + ), Text( currentPlan.endDate != null - ? 'from ${DateFormat.yMd( + ? 'From: ${DateFormat.yMd( Localizations.localeOf(context).languageCode, - ).format(currentPlan.startDate)} to ${DateFormat.yMd( + ).format(currentPlan.startDate)} To: ${DateFormat.yMd( Localizations.localeOf(context).languageCode, ).format(currentPlan.endDate!)}' - : 'from ${DateFormat.yMd( + : 'From: ${DateFormat.yMd( Localizations.localeOf(context).languageCode, ).format(currentPlan.startDate)} (open ended)', ), From 91a67034a19ec2ef2e2f3757d845246d364bebeb Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 27 Sep 2025 12:54:05 +0200 Subject: [PATCH 3/3] fix tests --- test/nutrition/nutritional_plans_screen_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/nutrition/nutritional_plans_screen_test.dart b/test/nutrition/nutritional_plans_screen_test.dart index 042efd5b0..cb735c381 100644 --- a/test/nutrition/nutritional_plans_screen_test.dart +++ b/test/nutrition/nutritional_plans_screen_test.dart @@ -152,15 +152,15 @@ void main() { await tester.pumpWidget(createHomeScreen()); // note .. "(open ended)" at the time, depending on localisation strings - expect(find.textContaining('from 1/1/2021 ('), findsOneWidget); - expect(find.textContaining('from 1/10/2021 ('), findsOneWidget); + expect(find.textContaining('From: 1/1/2021 ('), findsOneWidget); + expect(find.textContaining('From: 1/10/2021 ('), findsOneWidget); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { await tester.pumpWidget(createHomeScreen(locale: 'de')); // note .. "(open ended)" at the time, depending on localisation strings - expect(find.textContaining('from 1.1.2021 ('), findsOneWidget); - expect(find.textContaining('from 10.1.2021 ('), findsOneWidget); + expect(find.textContaining('From: 1.1.2021 ('), findsOneWidget); + expect(find.textContaining('From: 10.1.2021 ('), findsOneWidget); }); }