mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Merge pull request #634 from wger-project/chart-weight-since-plan
better weight and measurements visualisation
This commit is contained in:
@@ -133,8 +133,10 @@ class MeasurementEntriesScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
body: Consumer<MeasurementProvider>(
|
||||
builder: (context, provider, child) => EntriesList(category),
|
||||
body: SingleChildScrollView(
|
||||
child: Consumer<MeasurementProvider>(
|
||||
builder: (context, provider, child) => EntriesList(category),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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/entries_list.dart';
|
||||
import 'package:wger/widgets/weight/forms.dart';
|
||||
import 'package:wger/widgets/weight/weight_overview.dart';
|
||||
|
||||
class WeightScreen extends StatelessWidget {
|
||||
const WeightScreen();
|
||||
@@ -48,8 +48,10 @@ class WeightScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
body: Consumer<BodyWeightProvider>(
|
||||
builder: (context, workoutProvider, child) => const WeightEntriesList(),
|
||||
body: SingleChildScrollView(
|
||||
child: Consumer<BodyWeightProvider>(
|
||||
builder: (context, workoutProvider, child) => const WeightOverview(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,6 +159,10 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
|
||||
final profile = context.read<UserProvider>().profile;
|
||||
final weightProvider = context.read<BodyWeightProvider>();
|
||||
|
||||
final entriesAll =
|
||||
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList();
|
||||
final entries7dAvg = moving7dAverage(entriesAll);
|
||||
|
||||
return Consumer<BodyWeightProvider>(
|
||||
builder: (context, workoutProvider, child) => Card(
|
||||
child: Column(
|
||||
@@ -182,14 +186,16 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: MeasurementChartWidgetFl(
|
||||
weightProvider.items
|
||||
.map((e) => MeasurementChartEntry(e.weight, e.date))
|
||||
.toList(),
|
||||
unit: profile!.isMetric
|
||||
? AppLocalizations.of(context).kg
|
||||
: AppLocalizations.of(context).lb,
|
||||
entriesAll,
|
||||
weightUnit(profile!.isMetric, context),
|
||||
avgs: entries7dAvg,
|
||||
),
|
||||
),
|
||||
MeasurementOverallChangeWidget(
|
||||
entries7dAvg.first,
|
||||
entries7dAvg.last,
|
||||
weightUnit(profile!.isMetric, context),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -274,6 +280,9 @@ class _DashboardMeasurementWidgetState extends State<DashboardMeasurementWidget>
|
||||
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(
|
||||
|
||||
@@ -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(),
|
||||
unit: currentCategory.unit,
|
||||
entriesAll,
|
||||
currentCategory.unit,
|
||||
avgs: entries7dAvg,
|
||||
),
|
||||
),
|
||||
MeasurementOverallChangeWidget(
|
||||
entries7dAvg.first,
|
||||
entries7dAvg.last,
|
||||
currentCategory.unit,
|
||||
),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -18,14 +18,39 @@
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:wger/helpers/charts.dart';
|
||||
|
||||
class MeasurementOverallChangeWidget extends StatelessWidget {
|
||||
final MeasurementChartEntry _first;
|
||||
final MeasurementChartEntry _last;
|
||||
final String _unit;
|
||||
const MeasurementOverallChangeWidget(this._first, this._last, this._unit);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final delta = _last.value - _first.value;
|
||||
final prefix = delta > 0
|
||||
? '+'
|
||||
: delta < 0
|
||||
? '-'
|
||||
: '';
|
||||
|
||||
return Text('overall change $prefix ${delta.abs().toStringAsFixed(1)} $_unit');
|
||||
}
|
||||
}
|
||||
|
||||
String weightUnit(bool isMetric, BuildContext context) {
|
||||
return isMetric ? AppLocalizations.of(context).kg : AppLocalizations.of(context).lb;
|
||||
}
|
||||
|
||||
class MeasurementChartWidgetFl extends StatefulWidget {
|
||||
final List<MeasurementChartEntry> _entries;
|
||||
final String unit;
|
||||
final List<MeasurementChartEntry>? avgs;
|
||||
final String _unit;
|
||||
|
||||
const MeasurementChartWidgetFl(this._entries, {this.unit = 'kg'});
|
||||
const MeasurementChartWidgetFl(this._entries, this._unit, {this.avgs});
|
||||
|
||||
@override
|
||||
State<MeasurementChartWidgetFl> createState() => _MeasurementChartWidgetFlState();
|
||||
@@ -37,12 +62,7 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.70,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 18,
|
||||
left: 12,
|
||||
top: 24,
|
||||
bottom: 12,
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: LineChart(mainData()),
|
||||
),
|
||||
);
|
||||
@@ -53,8 +73,8 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((touchedSpot) {
|
||||
return LineTooltipItem(
|
||||
'${touchedSpot.y} kg',
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
'${touchedSpot.y.toStringAsFixed(1)} ${widget._unit}',
|
||||
TextStyle(color: touchedSpot.bar.color, fontWeight: FontWeight.bold),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
@@ -67,13 +87,13 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: true,
|
||||
//horizontalInterval: 1,
|
||||
//verticalInterval: interval,
|
||||
// horizontalInterval: 1,
|
||||
// verticalInterval: 1,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return const FlLine(color: Colors.grey, strokeWidth: 1);
|
||||
return FlLine(color: Theme.of(context).colorScheme.primaryContainer, strokeWidth: 1);
|
||||
},
|
||||
getDrawingVerticalLine: (value) {
|
||||
return const FlLine(color: Colors.grey, strokeWidth: 1);
|
||||
return FlLine(color: Theme.of(context).colorScheme.primaryContainer, strokeWidth: 1);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
@@ -88,14 +108,22 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) {
|
||||
// Don't show the first and last entries, otherwise they'll overlap with the
|
||||
// calculated interval
|
||||
// Don't show the first and last entries, to avoid overlap
|
||||
// see https://stackoverflow.com/questions/73355777/flutter-fl-chart-how-can-we-avoid-the-overlap-of-the-ordinate
|
||||
// this is needlessly aggressive if the titles are "sparse", but we should optimize for more busy data
|
||||
if (value == meta.min || value == meta.max) {
|
||||
return const Text('');
|
||||
}
|
||||
final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt());
|
||||
// if we go across years, show years in the ticks. otherwise leave them out
|
||||
if (DateTime.fromMillisecondsSinceEpoch(meta.min.toInt()).year !=
|
||||
DateTime.fromMillisecondsSinceEpoch(meta.max.toInt()).year) {
|
||||
return Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date),
|
||||
);
|
||||
}
|
||||
return Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date),
|
||||
DateFormat.Md(Localizations.localeOf(context).languageCode).format(date),
|
||||
);
|
||||
},
|
||||
interval: widget._entries.isNotEmpty
|
||||
@@ -111,29 +139,49 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
showTitles: true,
|
||||
reservedSize: 65,
|
||||
getTitlesWidget: (value, meta) {
|
||||
return Text('$value ${widget.unit}');
|
||||
// Don't show the first and last entries, to avoid overlap
|
||||
// see https://stackoverflow.com/questions/73355777/flutter-fl-chart-how-can-we-avoid-the-overlap-of-the-ordinate
|
||||
// this is needlessly aggressive if the titles are "sparse", but we should optimize for more busy data
|
||||
if (value == meta.min || value == meta.max) {
|
||||
return const Text('');
|
||||
}
|
||||
|
||||
return Text('$value ${widget._unit}');
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(color: const Color(0xff37434d)),
|
||||
border: Border.all(color: Theme.of(context).colorScheme.primaryContainer),
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: [
|
||||
...widget._entries.map((e) => FlSpot(
|
||||
e.date.millisecondsSinceEpoch.toDouble(),
|
||||
e.value.toDouble(),
|
||||
)),
|
||||
],
|
||||
spots: widget._entries
|
||||
.map((e) => FlSpot(
|
||||
e.date.millisecondsSinceEpoch.toDouble(),
|
||||
e.value.toDouble(),
|
||||
))
|
||||
.toList(),
|
||||
isCurved: false,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
barWidth: 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
barWidth: 0,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: true),
|
||||
),
|
||||
if (widget.avgs != null)
|
||||
LineChartBarData(
|
||||
spots: widget.avgs!
|
||||
.map((e) => FlSpot(
|
||||
e.date.millisecondsSinceEpoch.toDouble(),
|
||||
e.value.toDouble(),
|
||||
))
|
||||
.toList(),
|
||||
isCurved: false,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
barWidth: 1,
|
||||
dotData: const FlDotData(show: false),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -146,6 +194,34 @@ class MeasurementChartEntry {
|
||||
MeasurementChartEntry(this.value, this.date);
|
||||
}
|
||||
|
||||
// for each point, return the average of all the points in the 7 days preceeding it
|
||||
List<MeasurementChartEntry> moving7dAverage(List<MeasurementChartEntry> vals) {
|
||||
var start = 0;
|
||||
var end = 0;
|
||||
final List<MeasurementChartEntry> out = <MeasurementChartEntry>[];
|
||||
|
||||
// first make sure our list is in ascending order
|
||||
vals.sort((a, b) => a.date.compareTo(b.date));
|
||||
|
||||
while (end < vals.length) {
|
||||
// since users can log measurements several days, or minutes apart,
|
||||
// we can't make assumptions. We have to manually advance 'start'
|
||||
// such that it is always the first point within our desired range.
|
||||
// posibly start == end (when there is only one point in the range)
|
||||
final intervalStart = vals[end].date.subtract(const Duration(days: 7));
|
||||
while (start < end && vals[start].date.isBefore(intervalStart)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
final sub = vals.sublist(start, end + 1).map((e) => e.value);
|
||||
final sum = sub.reduce((val, el) => val + el);
|
||||
out.add(MeasurementChartEntry(sum / sub.length, vals[end].date));
|
||||
|
||||
end++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
class Indicator extends StatelessWidget {
|
||||
const Indicator({
|
||||
super.key,
|
||||
|
||||
@@ -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<NutritionPlansProvider>(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(),
|
||||
unit: _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,
|
||||
|
||||
78
lib/widgets/measurements/helpers.dart
Normal file
78
lib/widgets/measurements/helpers.dart
Normal file
@@ -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<Widget> getOverviewWidgets(
|
||||
String title,
|
||||
List<MeasurementChartEntry> raw,
|
||||
List<MeasurementChartEntry> 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<Widget> getOverviewWidgetsSeries(
|
||||
String name,
|
||||
List<MeasurementChartEntry> entriesAll,
|
||||
List<MeasurementChartEntry> 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),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -21,30 +21,37 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
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 WeightEntriesList extends StatelessWidget {
|
||||
const WeightEntriesList();
|
||||
class WeightOverview extends StatelessWidget {
|
||||
const WeightOverview();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profile = context.read<UserProvider>().profile;
|
||||
final weightProvider = Provider.of<BodyWeightProvider>(context, listen: false);
|
||||
final plan = Provider.of<NutritionPlansProvider>(context, listen: false).currentPlan;
|
||||
|
||||
final entriesAll =
|
||||
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList();
|
||||
final entries7dAvg = moving7dAverage(entriesAll);
|
||||
|
||||
final unit = weightUnit(profile!.isMetric, context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: MeasurementChartWidgetFl(
|
||||
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList(),
|
||||
unit: profile!.isMetric
|
||||
? AppLocalizations.of(context).kg
|
||||
: AppLocalizations.of(context).lb,
|
||||
),
|
||||
...getOverviewWidgetsSeries(
|
||||
'Weight',
|
||||
entriesAll,
|
||||
entries7dAvg,
|
||||
plan,
|
||||
unit,
|
||||
context,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
@@ -59,7 +66,8 @@ class WeightEntriesList extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
SizedBox(
|
||||
height: 300,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => weightProvider.fetchAndSetEntries(),
|
||||
child: ListView.builder(
|
||||
@@ -69,7 +77,7 @@ class WeightEntriesList extends StatelessWidget {
|
||||
final currentEntry = weightProvider.items[index];
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text('${currentEntry.weight} kg'),
|
||||
title: Text('${currentEntry.weight} ${weightUnit(profile.isMetric, context)}'),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
@@ -24,12 +24,15 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wger/models/measurements/measurement_category.dart';
|
||||
import 'package:wger/models/measurements/measurement_entry.dart';
|
||||
import 'package:wger/providers/measurement.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
import 'package:wger/screens/measurement_entries_screen.dart';
|
||||
|
||||
import '../nutrition/nutritional_plan_form_test.mocks.dart';
|
||||
import 'measurement_categories_screen_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late MockMeasurementProvider mockMeasurementProvider;
|
||||
late MockNutritionPlansProvider mockNutritionPlansProvider;
|
||||
|
||||
setUp(() {
|
||||
mockMeasurementProvider = MockMeasurementProvider();
|
||||
@@ -39,26 +42,32 @@ void main() {
|
||||
MeasurementEntry(id: 1, category: 1, date: DateTime(2021, 8, 10), value: 18.1, notes: 'a'),
|
||||
]),
|
||||
);
|
||||
|
||||
mockNutritionPlansProvider = MockNutritionPlansProvider();
|
||||
when(mockNutritionPlansProvider.currentPlan).thenReturn(null);
|
||||
});
|
||||
|
||||
Widget createHomeScreen({locale = 'en'}) {
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
|
||||
return ChangeNotifierProvider<MeasurementProvider>(
|
||||
create: (context) => mockMeasurementProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: const RouteSettings(arguments: 1),
|
||||
builder: (_) => const MeasurementEntriesScreen(),
|
||||
return ChangeNotifierProvider<NutritionPlansProvider>(
|
||||
create: (context) => mockNutritionPlansProvider,
|
||||
child: ChangeNotifierProvider<MeasurementProvider>(
|
||||
create: (context) => mockMeasurementProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: const RouteSettings(arguments: 1),
|
||||
builder: (_) => const MeasurementEntriesScreen(),
|
||||
),
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -73,8 +82,7 @@ void main() {
|
||||
expect(find.text('body fat'), findsOneWidget);
|
||||
|
||||
// Entries
|
||||
expect(find.text('10.2 %'), findsNWidgets(2));
|
||||
expect(find.text('18.1 %'), findsNWidgets(2));
|
||||
expect(find.text('15.0 %'), findsNWidgets(1));
|
||||
});
|
||||
|
||||
testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async {
|
||||
@@ -91,7 +99,6 @@ void main() {
|
||||
await tester.pumpWidget(createHomeScreen(locale: 'de'));
|
||||
await tester.tap(find.byType(TextButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1.8.2021'), findsWidgets);
|
||||
expect(find.text('10.8.2021'), findsWidgets);
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/screens/form_screen.dart';
|
||||
import 'package:wger/screens/weight_screen.dart';
|
||||
@@ -31,12 +32,14 @@ import 'package:wger/widgets/weight/forms.dart';
|
||||
|
||||
import '../../test_data/body_weight.dart';
|
||||
import '../../test_data/profile.dart';
|
||||
import '../nutrition/nutritional_plan_form_test.mocks.dart';
|
||||
import 'weight_screen_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([BodyWeightProvider, UserProvider])
|
||||
void main() {
|
||||
late MockBodyWeightProvider mockWeightProvider;
|
||||
late MockUserProvider mockUserProvider;
|
||||
late MockNutritionPlansProvider mockNutritionPlansProvider;
|
||||
|
||||
setUp(() {
|
||||
mockWeightProvider = MockBodyWeightProvider();
|
||||
@@ -45,21 +48,27 @@ void main() {
|
||||
|
||||
mockUserProvider = MockUserProvider();
|
||||
when(mockUserProvider.profile).thenReturn(tProfile1);
|
||||
|
||||
mockNutritionPlansProvider = MockNutritionPlansProvider();
|
||||
when(mockNutritionPlansProvider.currentPlan).thenReturn(null);
|
||||
});
|
||||
|
||||
Widget createWeightScreen({locale = 'en'}) {
|
||||
return ChangeNotifierProvider<UserProvider>(
|
||||
create: (context) => mockUserProvider,
|
||||
child: ChangeNotifierProvider<BodyWeightProvider>(
|
||||
create: (context) => mockWeightProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const WeightScreen(),
|
||||
routes: {
|
||||
FormScreen.routeName: (_) => const FormScreen(),
|
||||
},
|
||||
return ChangeNotifierProvider<NutritionPlansProvider>(
|
||||
create: (context) => mockNutritionPlansProvider,
|
||||
child: ChangeNotifierProvider<UserProvider>(
|
||||
create: (context) => mockUserProvider,
|
||||
child: ChangeNotifierProvider<BodyWeightProvider>(
|
||||
create: (context) => mockWeightProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const WeightScreen(),
|
||||
routes: {
|
||||
FormScreen.routeName: (_) => const FormScreen(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -100,15 +109,15 @@ 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);
|
||||
// these don't work because we only have 2 points, and to prevent overlaps we don't display their titles
|
||||
// expect(find.text('1/1'), findsOneWidget);
|
||||
// expect(find.text('1/10'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createWeightScreen(locale: 'de'));
|
||||
|
||||
expect(find.text('1.1.2021'), findsOneWidget);
|
||||
expect(find.text('10.1.2021'), findsOneWidget);
|
||||
// these don't work because we only have 2 points, and to prevent overlaps we don't display their titles
|
||||
// expect(find.text('1.1.'), findsOneWidget);
|
||||
// expect(find.text('10.1.'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user