From ef5a7081a00cb1e0c64c4f03eb486f3684c1883b Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 1 Oct 2023 14:27:02 +0200 Subject: [PATCH] Make MeasurementChartWidgetFl work This still doesn't look completely nice, but it's a good start --- ios/Podfile.lock | 2 +- lib/exceptions/http_exception.dart | 5 +- lib/widgets/core/charts.dart | 132 +++++++++++++---- lib/widgets/dashboard/widgets.dart | 135 ++++++++---------- lib/widgets/measurements/categories_card.dart | 7 +- lib/widgets/measurements/entries.dart | 2 +- lib/widgets/weight/entries_list.dart | 2 +- pubspec.lock | 8 ++ .../measurement_categories_screen_test.dart | 2 +- test/weight/weight_screen_test.dart | 2 +- 10 files changed, 175 insertions(+), 122 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8169503c4..d8fa7f919 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -61,7 +61,7 @@ SPEC CHECKSUMS: image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - rive_common: 60ae7896ab40f9513974f36f015de33f70d2c5c5 + rive_common: b5b1aa30c63b8f0f00f32cddc9ea394d3d3473b5 shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126 diff --git a/lib/exceptions/http_exception.dart b/lib/exceptions/http_exception.dart index 01be9d444..b442e34a2 100644 --- a/lib/exceptions/http_exception.dart +++ b/lib/exceptions/http_exception.dart @@ -29,12 +29,11 @@ class WgerHttpException implements Exception { errors = {'unknown_error': 'An unknown error occurred, no further information available'}; } else { try { - errors = json.decode(responseBody); + errors = {'unknown_error': json.decode(responseBody)}; } catch (e) { - errors = responseBody; + errors = {'unknown_error': responseBody}; } } - errors = errors; } @override diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index cebbf0d5f..5c4214f92 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -16,50 +16,120 @@ * along with this program. If not, see . */ -import 'package:charts_flutter/flutter.dart' as charts; +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; import 'package:wger/theme/theme.dart'; -class MeasurementChartEntry { - num value; - DateTime date; - - MeasurementChartEntry(this.value, this.date); -} - -/// Weight chart widget -class MeasurementChartWidget extends StatelessWidget { +class MeasurementChartWidgetFl extends StatefulWidget { final List _entries; final String unit; - /// [_entries] is a list of [MeasurementChartEntry] - const MeasurementChartWidget(this._entries, {this.unit = 'kg'}); + const MeasurementChartWidgetFl(this._entries, {this.unit = 'kg'}); @override - Widget build(BuildContext context) { - final unitTickFormatter = charts.BasicNumericTickFormatterSpec((num? value) => '$value $unit'); + State createState() => _MeasurementChartWidgetFlState(); +} - return charts.TimeSeriesChart( - [ - charts.Series( - id: 'Measurement', - colorFn: (_, __) => wgerChartSecondaryColor, - domainFn: (MeasurementChartEntry entry, _) => entry.date, +class _MeasurementChartWidgetFlState extends State { + final interval = 15 * Duration.millisecondsPerDay / 1000 / 60; - // expression body => same as above(arrow expression) - //domainFn: (MeasurementChartEntry entry, _){ return entry.date;} , - measureFn: (MeasurementChartEntry entry, _) => entry.value, - data: _entries, - ) - ], - defaultRenderer: charts.LineRendererConfig(includePoints: true), - primaryMeasureAxis: charts.NumericAxisSpec( - tickProviderSpec: const charts.BasicNumericTickProviderSpec(zeroBound: false), - tickFormatterSpec: unitTickFormatter, + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 1.70, + child: Padding( + padding: const EdgeInsets.only( + right: 18, + left: 12, + top: 24, + bottom: 12, + ), + child: LineChart( + mainData(), + ), ), ); } + + LineChartData mainData() { + return LineChartData( + // minY: 75, + // maxY: 90, + gridData: FlGridData( + show: true, + drawVerticalLine: true, + //horizontalInterval: 1, + //verticalInterval: interval, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey, + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey, + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + show: true, + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt() * 1000 * 60); + return Text(DateFormat.yMd().format(date)); + }, + interval: interval, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + // interval: 1, + //interval: interval, + //getTitlesWidget: leftTitleWidgets, + reservedSize: 42, + ), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all(color: const Color(0xff37434d)), + ), + lineBarsData: [ + LineChartBarData( + spots: [ + ...widget._entries + .map((e) => FlSpot(e.date.millisecondsSinceEpoch / 1000 / 60, e.value.toDouble())) + ], + isCurved: false, + color: wgerSecondaryColor, + barWidth: 2, + isStrokeCapRound: true, + dotData: FlDotData( + show: true, + ), + ), + ], + ); + } +} + +class MeasurementChartEntry { + num value; + DateTime date; + + MeasurementChartEntry(this.value, this.date); } // // #TODO : Del Later /// test state less >> del later diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 27ab309c0..b0dee91cc 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -44,9 +44,6 @@ import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/weight/forms.dart'; import 'package:wger/widgets/workouts/forms.dart'; -// line chart import -import 'package:wger/widgets/core/fl_chart_line.dart'; - class DashboardNutritionWidget extends StatefulWidget { @override _DashboardNutritionWidgetState createState() => _DashboardNutritionWidgetState(); @@ -271,28 +268,11 @@ class _DashboardWeightWidgetState extends State { Column( children: [ Container( - color: Colors.amberAccent, - padding: const EdgeInsets.all(15), - height: 180, - child: MeasurementChartWidget(weightEntriesData.items - .map((e) => MeasurementChartEntry(e.weight, e.date)) - .toList()), - ), - - // Fl _chart line sample 2 for testing - Container( - color: Color.fromARGB(255, 68, 150, 118), - padding: const EdgeInsets.all(15), - height: 180, - //child: null, - //child: LineChartSample2(), - child: LineChartSample2( - weightEntriesData.items - .map((e) => MeasurementChartEntryflchat(e.weight, e.date)) - .toList(), - ), + height: 200, + child: MeasurementChartWidgetFl(weightEntriesData.items + .map((e) => MeasurementChartEntry(e.weight, e.date)) + .toList()), ), - Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -388,62 +368,61 @@ class _DashboardMeasurementWidgetState extends State ), ), - //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; - }); - }), + //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(), ), - 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(), ), - ], - ), - ], + ]) + else + NothingFound( + AppLocalizations.of(context).noMeasurementEntries, + AppLocalizations.of(context).newEntry, + MeasurementCategoryForm(), + ), + ], + ), + ], ), ), )); diff --git a/lib/widgets/measurements/categories_card.dart b/lib/widgets/measurements/categories_card.dart index 355944656..60ef224c6 100644 --- a/lib/widgets/measurements/categories_card.dart +++ b/lib/widgets/measurements/categories_card.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../models/measurements/measurement_category.dart'; import '../../screens/form_screen.dart'; import '../../screens/measurement_entries_screen.dart'; import '../core/charts.dart'; import 'forms.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class CategoriesCard extends StatelessWidget { MeasurementCategory currentCategory; @@ -26,13 +26,10 @@ class CategoriesCard extends StatelessWidget { style: Theme.of(context).textTheme.headline6, ), ), - - // #FIXME : Measurements Dashboard (Custom color set ) Container( - color: Color.fromARGB(255, 159, 114, 211), padding: const EdgeInsets.all(10), height: 220, - child: MeasurementChartWidget( + child: MeasurementChartWidgetFl( currentCategory.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(), unit: currentCategory.unit, ), diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index d0e264e59..c4714ccc1 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -42,7 +42,7 @@ class EntriesList extends StatelessWidget { color: Colors.lightGreenAccent, padding: const EdgeInsets.all(10), height: 220, - child: MeasurementChartWidget( + child: MeasurementChartWidgetFl( _category.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(), unit: _category.unit, ), diff --git a/lib/widgets/weight/entries_list.dart b/lib/widgets/weight/entries_list.dart index 5d55b0317..10611e1ad 100644 --- a/lib/widgets/weight/entries_list.dart +++ b/lib/widgets/weight/entries_list.dart @@ -38,7 +38,7 @@ class WeightEntriesList extends StatelessWidget { color: Theme.of(context).cardColor, padding: const EdgeInsets.all(15), height: 220, - child: MeasurementChartWidget( + child: MeasurementChartWidgetFl( _weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList()), ), TextButton( diff --git a/pubspec.lock b/pubspec.lock index 2d3813d7f..df5187e56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -401,6 +401,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "48a1b69be9544e2b03d9a8e843affd89e43f3194c9248776222efcb4206bb1ec" + url: "https://pub.dev" + source: hosted + version: "0.62.0" flutter: dependency: "direct main" description: flutter diff --git a/test/measurements/measurement_categories_screen_test.dart b/test/measurements/measurement_categories_screen_test.dart index e1d59cd48..7119108b4 100644 --- a/test/measurements/measurement_categories_screen_test.dart +++ b/test/measurements/measurement_categories_screen_test.dart @@ -68,6 +68,6 @@ void main() { expect(find.text('body fat'), findsOneWidget); expect(find.text('biceps'), findsOneWidget); expect(find.byType(Card), findsNWidgets(2)); - expect(find.byType(MeasurementChartWidget), findsNWidgets(2)); + expect(find.byType(MeasurementChartWidgetFl), findsNWidgets(2)); }); } diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index 3ebceef53..881d16b9d 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -57,7 +57,7 @@ void main() { await tester.pumpWidget(createWeightScreen()); expect(find.text('Weight'), findsOneWidget); - expect(find.byType(MeasurementChartWidget), findsOneWidget); + expect(find.byType(MeasurementChartWidgetFl), findsOneWidget); expect(find.byType(Dismissible), findsNWidgets(2)); expect(find.byType(ListTile), findsNWidgets(2)); });