Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,38 @@
}
}
},
"chartLastYearTitle": "{name} last year",
"@chartLastYearTitle": {
"description": "last year chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartLast3MonthsTitle": "{name} last 3 months",
"@chartLast3MonthsTitle": {
"description": "last 3 months chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartRangeAll": "All",
"@chartRangeAll": {
"description": "Label for the chart time-range selector option showing all data"
},
"chartRangeLastYear": "Last year",
"@chartRangeLastYear": {
"description": "Label for the chart time-range selector option showing the last year"
},
"chartRangeLast3Months": "Last 3 months",
"@chartRangeLast3Months": {
"description": "Label for the chart time-range selector option showing the last 3 months"
},
"measurement": "Measurement",
"@measurement": {},
"measurements": "Measurements",
Expand Down
7 changes: 4 additions & 3 deletions lib/widgets/measurements/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ List<Widget> getOverviewWidgetsSeries(
List<MeasurementChartEntry> entries7dAvg,
List<NutritionalPlan> plans,
String unit,
BuildContext context,
) {
BuildContext context, {
String? mainChartTitle,
}) {
final monthAgo = DateTime.now().subtract(const Duration(days: 30));
return [
...getOverviewWidgets(
AppLocalizations.of(context).chartAllTimeTitle(name),
mainChartTitle ?? AppLocalizations.of(context).chartAllTimeTitle(name),
entriesAll,
entries7dAvg,
unit,
Expand Down
68 changes: 63 additions & 5 deletions lib/widgets/weight/weight_overview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
import 'package:intl/intl.dart';
import 'package:wger/helpers/measurements.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/body_weight/weight_entry.dart';
import 'package:wger/providers/body_weight_notifier.dart';
Expand All @@ -31,11 +32,22 @@ import 'package:wger/widgets/measurements/charts.dart';
import 'package:wger/widgets/measurements/helpers.dart';
import 'package:wger/widgets/weight/forms.dart';

class WeightOverview extends riverpod.ConsumerWidget {
/// Time range the user can pick to limit how far back the weight chart goes.
enum WeightChartRange { all, lastYear, last3Months }

class WeightOverview extends riverpod.ConsumerStatefulWidget {
const WeightOverview();

@override
Widget build(BuildContext context, riverpod.WidgetRef ref) {
riverpod.ConsumerState<WeightOverview> createState() => _WeightOverviewState();
}

class _WeightOverviewState extends riverpod.ConsumerState<WeightOverview> {
WeightChartRange _range = WeightChartRange.all;

@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context);
final profileAsync = ref.watch(userProfileProvider);
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
final plans = ref.watch(nutritionProvider).value?.plans ?? const [];
Expand All @@ -54,17 +66,63 @@ class WeightOverview extends riverpod.ConsumerWidget {
final entriesAll = entriesList.map((e) => MeasurementChartEntry(e.weight, e.date)).toList();
final entries7dAvg = moving7dAverage(entriesAll);

// Restrict the data to the selected range. The average is computed over
// the full history first and only filtered afterwards, matching how the
// per-plan and 30-day sub-charts are built.
final (DateTime? cutoff, String? mainChartTitle) = switch (_range) {
WeightChartRange.all => (null, null),
WeightChartRange.lastYear => (
DateTime.now().subtract(const Duration(days: 365)),
i18n.chartLastYearTitle(i18n.weight),
),
WeightChartRange.last3Months => (
DateTime.now().subtract(const Duration(days: 90)),
i18n.chartLast3MonthsTitle(i18n.weight),
),
};
final entriesRange = cutoff == null ? entriesAll : entriesAll.whereDate(cutoff, null);
final entries7dAvgRange =
cutoff == null ? entries7dAvg : entries7dAvg.whereDate(cutoff, null);

final unit = weightUnit(profile.isMetric, context);

return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SegmentedButton<WeightChartRange>(
key: const ValueKey('weightChartRangeSelector'),
showSelectedIcon: false,
segments: [
ButtonSegment(
value: WeightChartRange.all,
label: Text(i18n.chartRangeAll),
),
ButtonSegment(
value: WeightChartRange.lastYear,
label: Text(i18n.chartRangeLastYear),
),
ButtonSegment(
value: WeightChartRange.last3Months,
label: Text(i18n.chartRangeLast3Months),
),
],
selected: {_range},
onSelectionChanged: (selection) =>
setState(() => _range = selection.first),
),
),
),
...getOverviewWidgetsSeries(
AppLocalizations.of(context).weight,
entriesAll,
entries7dAvg,
i18n.weight,
entriesRange,
entries7dAvgRange,
plans,
unit,
context,
mainChartTitle: mainChartTitle,
),
TextButton(
onPressed: () => Navigator.pushNamed(
Expand Down
27 changes: 27 additions & 0 deletions test/weight/weight_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,33 @@ void main() {
expect(find.byType(ListTile), findsNWidgets(2));
});

testWidgets('Weight chart range selector filters the data', (WidgetTester tester) async {
await tester.pumpWidget(createWeightScreen());
await tester.pumpAndSettle();

// The range selector is shown with the three options
expect(find.byKey(const ValueKey('weightChartRangeSelector')), findsOneWidget);
expect(find.text('All'), findsOneWidget);
expect(find.text('Last year'), findsOneWidget);
expect(find.text('Last 3 months'), findsOneWidget);

// By default (all time) the chart is shown for the seeded entries
expect(find.byType(MeasurementChartWidgetFl), findsOneWidget);

// Restricting to the last 3 months excludes the (old) seeded data
await tester.ensureVisible(find.text('Last 3 months'));
await tester.tap(find.text('Last 3 months'));
await tester.pumpAndSettle();
expect(find.byType(MeasurementChartWidgetFl), findsNothing);
expect(find.text('No data available'), findsOneWidget);

// Switching back to all time restores the chart
await tester.ensureVisible(find.text('All'));
await tester.tap(find.text('All'));
await tester.pumpAndSettle();
expect(find.byType(MeasurementChartWidgetFl), findsOneWidget);
});

testWidgets('Test deleting an item using the Delete button', (WidgetTester tester) async {
// Arrange
await tester.pumpWidget(createWeightScreen());
Expand Down