From 90edf0f1007a3579ee51ad863b7a7f0560360700 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 25 Apr 2023 20:54:47 +0200 Subject: [PATCH 01/31] Start replacing the charts with fl_chart --- lib/widgets/dashboard/widgets.dart | 3 +- lib/widgets/nutrition/charts.dart | 81 ++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 478 ++++++++++++------ pubspec.yaml | 3 +- 5 files changed, 403 insertions(+), 164 deletions(-) diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 441eda169..63daaf993 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -18,7 +18,6 @@ import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; @@ -191,7 +190,7 @@ class _DashboardNutritionWidgetState extends State { Container( padding: const EdgeInsets.all(15), height: 180, - child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues), + child: FlNutritionalPlanPieChartWidget(_plan!.nutritionalValues), ) ], ), diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 00588d2c4..a0ae27d2a 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -18,7 +18,8 @@ import 'package:charts_flutter/flutter.dart' as charts; import 'package:collection/collection.dart'; -import 'package:flutter/widgets.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; @@ -31,6 +32,83 @@ class NutritionData { NutritionData(this.name, this.value); } +class FlNutritionalPlanPieChartWidget extends StatefulWidget { + final NutritionalValues nutritionalValues; + + const FlNutritionalPlanPieChartWidget(this.nutritionalValues); + + @override + State createState() => FlNutritionalPlanPieChartState(); +} + +class FlNutritionalPlanPieChartState extends State { + int touchedIndex = -1; + + @override + Widget build(BuildContext context) { + return PieChart( + PieChartData( + pieTouchData: PieTouchData( + touchCallback: (FlTouchEvent event, pieTouchResponse) { + setState(() { + if (!event.isInterestedForInteractions || + pieTouchResponse == null || + pieTouchResponse.touchedSection == null) { + touchedIndex = -1; + return; + } + touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; + }); + }, + ), + borderData: FlBorderData( + show: false, + ), + sectionsSpace: 0, + centerSpaceRadius: 40, + sections: showingSections(), + ), + ); + } + + List showingSections() { + return List.generate(3, (i) { + final isTouched = i == touchedIndex; + final fontSize = isTouched ? 25.0 : 16.0; + final radius = isTouched ? 60.0 : 50.0; + switch (i) { + case 0: + return PieChartSectionData( + color: Colors.blue, + value: widget.nutritionalValues.fat, + title: AppLocalizations.of(context).fat, + radius: radius, + titleStyle: TextStyle(fontSize: fontSize), + ); + case 1: + return PieChartSectionData( + color: Colors.amber, + value: widget.nutritionalValues.protein, + title: AppLocalizations.of(context).protein, + radius: radius, + titleStyle: TextStyle(fontSize: fontSize), + ); + case 2: + return PieChartSectionData( + color: Colors.purple, + value: widget.nutritionalValues.carbohydrates, + title: AppLocalizations.of(context).carbohydrates, + radius: radius, + titleStyle: TextStyle(fontSize: fontSize), + ); + + default: + throw Error(); + } + }); + } +} + /// Nutritional plan pie chart widget class NutritionalPlanPieChartWidget extends StatelessWidget { final NutritionalValues _nutritionalValues; @@ -336,6 +414,7 @@ class NutritionalPlanHatchBarChartWidget extends StatelessWidget { class EnergyChart extends StatelessWidget { const EnergyChart({Key? key, required this.nutritionalPlan}) : super(key: key); final NutritionalPlan nutritionalPlan; + NutritionalValues nutritionalValuesFromPlanLogsSevenDayAvg() { NutritionalValues sevenDaysAvg = NutritionalValues(); int count = 0; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 44c695d31..5cd93ff50 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,12 +9,10 @@ import package_info_plus import rive_common import shared_preferences_foundation import url_launcher_macos -import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 9f3bfe718..ca4ce5249 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,261 +5,306 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" + url: "https://pub.dev" source: hosted - version: "58.0.0" + version: "59.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 + url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.1" android_metadata: dependency: "direct main" description: name: android_metadata - url: "https://pub.dartlang.org" + sha256: "29d15dfd03ff67f84bf706d43f1ac087e82d529698e173133bb7cbc84a67d11d" + url: "https://pub.dev" source: hosted version: "0.2.1" archive: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + url: "https://pub.dev" source: hosted version: "3.3.2" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" source: hosted version: "2.4.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted version: "2.10.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted version: "2.1.1" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" source: hosted version: "2.3.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + url: "https://pub.dev" source: hosted version: "3.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + url: "https://pub.dev" source: hosted version: "2.2.0" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" source: hosted version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" source: hosted version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" + url: "https://pub.dev" source: hosted version: "8.4.4" carousel_slider: dependency: "direct main" description: name: carousel_slider - url: "https://pub.dartlang.org" + sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" + url: "https://pub.dev" source: hosted version: "4.2.1" change: dependency: transitive description: name: change - url: "https://pub.dartlang.org" + sha256: b4d4aceb94f5081d40a2c08211bd779e55373265ce2304b7b38b5c4c218a9263 + url: "https://pub.dev" source: hosted version: "0.6.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" charts_common: dependency: transitive description: name: charts_common - url: "https://pub.dartlang.org" + sha256: "7b8922f9b0d9b134122756a787dab1c3946ae4f3fc5022ff323ba0014998ea02" + url: "https://pub.dev" source: hosted version: "0.12.0" charts_flutter: dependency: "direct main" description: name: charts_flutter - url: "https://pub.dartlang.org" + sha256: "4172c3f4b85322fdffe1896ffbed79ae4689ae72cb6fe6690dcaaea620a9c558" + url: "https://pub.dev" source: hosted version: "0.12.0" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + url: "https://pub.dev" source: hosted version: "2.0.2" cider: dependency: "direct dev" description: name: cider - url: "https://pub.dartlang.org" + sha256: "714a853bf92701b982496df0bd601c0cf2d882cfc1f2d41ae9aff2b20cee016a" + url: "https://pub.dev" source: hosted version: "0.1.5" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.dartlang.org" + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + url: "https://pub.dev" source: hosted version: "0.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" source: hosted version: "4.4.0" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted version: "1.17.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted version: "3.1.1" cross_file: dependency: transitive description: name: cross_file - url: "https://pub.dartlang.org" + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" source: hosted version: "0.3.3+4" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" source: hosted version: "0.17.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" + url: "https://pub.dev" source: hosted version: "2.3.0" equatable: dependency: "direct main" description: name: equatable - url: "https://pub.dartlang.org" + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" source: hosted version: "2.0.5" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" source: hosted version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + 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 @@ -269,14 +314,16 @@ packages: dependency: "direct main" description: name: flutter_barcode_scanner - url: "https://pub.dartlang.org" + sha256: a4ba37daf9933f451a5e812c753ddd045d6354e4a3280342d895b07fecaab3fa + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_calendar_carousel: dependency: "direct main" description: name: flutter_calendar_carousel - url: "https://pub.dartlang.org" + sha256: da198154b3a7254757ead8d40eee4f2fa53d4500fe8a226cccb951e8763f988b + url: "https://pub.dev" source: hosted version: "2.4.1" flutter_driver: @@ -288,63 +335,72 @@ packages: dependency: "direct main" description: name: flutter_html - url: "https://pub.dartlang.org" + sha256: "342c7908f0a67bcec62b6e0f7cf23e23bafe7f64693665dd35be98d5e783bdfd" + url: "https://pub.dev" source: hosted version: "3.0.0-alpha.6" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" + sha256: "4983655c26ab5b959252ee204c2fffa4afeb4413cd030455194ec0caa3b8e7cb" + url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.1" flutter_keyboard_visibility_linux: dependency: transitive description: name: flutter_keyboard_visibility_linux - url: "https://pub.dartlang.org" + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" source: hosted version: "1.0.0" flutter_keyboard_visibility_macos: dependency: transitive description: name: flutter_keyboard_visibility_macos - url: "https://pub.dartlang.org" + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" source: hosted version: "1.0.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_keyboard_visibility_windows: dependency: transitive description: name: flutter_keyboard_visibility_windows - url: "https://pub.dartlang.org" + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" source: hosted version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - url: "https://pub.dartlang.org" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" source: hosted version: "0.13.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_localizations: @@ -356,21 +412,24 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf + url: "https://pub.dev" source: hosted version: "2.0.9" flutter_staggered_grid_view: dependency: "direct main" description: name: flutter_staggered_grid_view - url: "https://pub.dartlang.org" + sha256: "1312314293acceb65b92754298754801b0e1f26a1845833b740b30415bbbcf07" + url: "https://pub.dev" source: hosted version: "0.6.2" flutter_svg: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f + url: "https://pub.dev" source: hosted version: "2.0.5" flutter_test: @@ -382,7 +441,8 @@ packages: dependency: "direct main" description: name: flutter_typeahead - url: "https://pub.dartlang.org" + sha256: edfc51579ca3756adaa32b6849cf44af03276b93e8a8a68d8247ee243598f1b2 + url: "https://pub.dev" source: hosted version: "4.3.7" flutter_web_plugins: @@ -394,14 +454,16 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - url: "https://pub.dartlang.org" + sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206" + url: "https://pub.dev" source: hosted version: "10.4.0" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" source: hosted version: "3.2.0" fuchsia_remote_debug_protocol: @@ -413,84 +475,96 @@ packages: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" source: hosted version: "2.1.1" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" source: hosted version: "2.2.0" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + sha256: "79d498e6d6761925a34ee5ea8fa6dfef38607781d2fa91e37523474282af55cb" + url: "https://pub.dev" source: hosted version: "0.15.2" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" source: hosted version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted version: "4.0.2" image: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + url: "https://pub.dev" source: hosted version: "4.0.15" image_picker: dependency: "direct main" description: name: image_picker - url: "https://pub.dartlang.org" + sha256: "3da954c3b8906d82ecb50fd5e2b5401758f06d5678904eed6cbc06172283a263" + url: "https://pub.dev" source: hosted - version: "0.8.7+3" + version: "0.8.7+4" image_picker_android: dependency: transitive description: name: image_picker_android - url: "https://pub.dartlang.org" + sha256: "1ea6870350f56af8dab716459bd9d5dc76947e29e07a2ba1d0c172eaaf4f269c" + url: "https://pub.dev" source: hosted version: "0.8.6+7" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - url: "https://pub.dartlang.org" + sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" + url: "https://pub.dev" source: hosted version: "2.1.12" image_picker_ios: dependency: transitive description: name: image_picker_ios - url: "https://pub.dartlang.org" + sha256: a1546ff5861fc15812953d4733b520c3d371cec3d2859a001ff04c46c4d81883 + url: "https://pub.dev" source: hosted version: "0.8.7+3" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - url: "https://pub.dartlang.org" + sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" + url: "https://pub.dev" source: hosted version: "2.6.3" integration_test: @@ -502,336 +576,384 @@ packages: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted version: "1.0.4" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted version: "0.6.5" json_annotation: dependency: "direct main" description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" source: hosted version: "4.8.0" json_serializable: dependency: "direct dev" description: name: json_serializable - url: "https://pub.dartlang.org" + sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a + url: "https://pub.dev" source: hosted version: "6.6.1" klizma: dependency: transitive description: name: klizma - url: "https://pub.dartlang.org" + sha256: "3bb0ed5cc183274e39d48fa42a61484e4dda3dc52970aa902379c7403cd7f0f2" + url: "https://pub.dev" source: hosted version: "0.2.1" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted version: "1.1.1" markdown: dependency: transitive description: name: markdown - url: "https://pub.dartlang.org" + sha256: d95a9d12954aafc97f984ca29baaa7690ed4d9ec4140a23ad40580bcdb6c87f5 + url: "https://pub.dev" source: hosted version: "7.0.2" marker: dependency: transitive description: name: marker - url: "https://pub.dartlang.org" + sha256: "970eb97ea43752bd443b91d992068575f37bcc7f952c01334779673b7db23114" + url: "https://pub.dev" source: hosted version: "0.5.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" source: hosted version: "1.0.4" mockito: dependency: "direct dev" description: name: mockito - url: "https://pub.dartlang.org" + sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 + url: "https://pub.dev" source: hosted version: "5.4.0" multi_select_flutter: dependency: "direct main" description: name: multi_select_flutter - url: "https://pub.dartlang.org" + sha256: "503857b415d390d29159df8a9d92d83c6aac17aaf1c307fb7bcfc77d097d20ed" + url: "https://pub.dev" source: hosted version: "4.1.3" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" network_image_mock: dependency: "direct dev" description: name: network_image_mock - url: "https://pub.dartlang.org" + sha256: "855cdd01d42440e0cffee0d6c2370909fc31b3bcba308a59829f24f64be42db7" + url: "https://pub.dev" source: hosted version: "2.1.1" numerus: dependency: transitive description: name: numerus - url: "https://pub.dartlang.org" + sha256: "436759d84f233b40107d0cc31cfa92d24e0960afeb2e506be70926d4cddffd9e" + url: "https://pub.dev" source: hosted version: "2.0.0" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" + sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d + url: "https://pub.dev" source: hosted version: "3.1.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" source: hosted version: "2.0.1" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" source: hosted version: "1.0.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + url: "https://pub.dev" source: hosted version: "2.1.10" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" source: hosted version: "2.0.6" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 + url: "https://pub.dev" source: hosted version: "2.1.5" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted version: "5.1.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" source: hosted version: "2.1.4" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" source: hosted version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c + url: "https://pub.dev" source: hosted version: "1.2.2" rfc_6901: dependency: transitive description: name: rfc_6901 - url: "https://pub.dartlang.org" + sha256: "8d97680dada633146cf75ab9382f2ce2b7e4bd63ceecd867416cf43b5832b988" + url: "https://pub.dev" source: hosted version: "0.1.1" rive: dependency: "direct main" description: name: rive - url: "https://pub.dartlang.org" + sha256: f7f365ee0e6cf0af99fb239bc3424370ca6ee2b9ad6fc879b1a79ba5e3b40770 + url: "https://pub.dev" source: hosted version: "0.10.4" rive_common: dependency: transitive description: name: rive_common - url: "https://pub.dartlang.org" + sha256: "12ea4a1ca1aa2ddeb2ef212afa20517d6c140a5deb32149c713912a7e6b7e26e" + url: "https://pub.dev" source: hosted version: "0.0.3" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" + url: "https://pub.dev" source: hosted version: "2.1.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: "7fa90471a6875d26ad78c7e4a675874b2043874586891128dc5899662c97db46" + url: "https://pub.dev" source: hosted version: "2.1.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - url: "https://pub.dartlang.org" + sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07" + url: "https://pub.dev" source: hosted version: "2.2.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + url: "https://pub.dev" source: hosted version: "2.2.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + url: "https://pub.dev" source: hosted version: "2.2.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + url: "https://pub.dev" source: hosted version: "2.1.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + url: "https://pub.dev" source: hosted version: "2.2.0" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" source: hosted version: "1.4.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" source: hosted version: "1.0.3" simple_gesture_detector: dependency: transitive description: name: simple_gesture_detector - url: "https://pub.dartlang.org" + sha256: "86d08f85f1f58583b7b4b941d989f48ea6ce08c1724a1d10954a277c2ec36592" + url: "https://pub.dev" source: hosted version: "0.2.0" sky_engine: @@ -843,280 +965,320 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 + url: "https://pub.dev" source: hosted version: "1.2.7" source_helper: dependency: transitive description: name: source_helper - url: "https://pub.dartlang.org" + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" source: hosted version: "1.3.3" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted version: "2.1.1" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted version: "1.2.0" sync_http: dependency: transitive description: name: sync_http - url: "https://pub.dartlang.org" + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" source: hosted version: "0.3.1" table_calendar: dependency: "direct main" description: name: table_calendar - url: "https://pub.dartlang.org" + sha256: "7f1270313c0cdb245b583ed8518982c01d4a7e95869b3c30abcbae3b642c45d0" + url: "https://pub.dev" source: hosted version: "3.0.8" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted version: "0.4.16" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" source: hosted version: "1.0.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + url: "https://pub.dev" source: hosted version: "6.1.10" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: a52628068d282d01a07cd86e6ba99e497aa45ce8c91159015b2416907d78e411 + url: "https://pub.dev" source: hosted version: "6.0.27" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" source: hosted version: "6.1.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc" + url: "https://pub.dev" source: hosted version: "3.0.4" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" source: hosted version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + url: "https://pub.dev" source: hosted version: "2.1.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + url: "https://pub.dev" source: hosted version: "2.0.16" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd + url: "https://pub.dev" source: hosted version: "3.0.5" vector_graphics: dependency: transitive description: name: vector_graphics - url: "https://pub.dartlang.org" + sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + url: "https://pub.dev" source: hosted version: "1.1.5" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - url: "https://pub.dartlang.org" + sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + url: "https://pub.dev" source: hosted version: "1.1.5" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - url: "https://pub.dartlang.org" + sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + url: "https://pub.dev" source: hosted version: "1.1.5" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted version: "2.1.4" version: dependency: "direct main" description: name: version - url: "https://pub.dartlang.org" + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" source: hosted version: "3.0.2" version_manipulation: dependency: transitive description: name: version_manipulation - url: "https://pub.dartlang.org" + sha256: "2fcb0597589e894082ebde9140b5c018c0858215eef39539d87c200e3da82ec0" + url: "https://pub.dev" source: hosted version: "0.1.1" video_player: dependency: "direct main" description: name: video_player - url: "https://pub.dartlang.org" + sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd + url: "https://pub.dev" source: hosted version: "2.6.1" video_player_android: dependency: transitive description: name: video_player_android - url: "https://pub.dartlang.org" + sha256: a592048a711d5739d9cea2255d425779f138d41095b9149bda60ce4bc1af8871 + url: "https://pub.dev" source: hosted version: "2.4.4" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - url: "https://pub.dartlang.org" + sha256: "75c6d68cd479a25f34d635149ba6887bc8f1b2b2921841121fd44ea0c5bc1927" + url: "https://pub.dev" source: hosted version: "2.4.4" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - url: "https://pub.dartlang.org" + sha256: a8c4dcae2a7a6e7cc1d7f9808294d968eca1993af34a98e95b9bdfa959bec684 + url: "https://pub.dev" source: hosted version: "6.1.0" video_player_web: dependency: transitive description: name: video_player_web - url: "https://pub.dartlang.org" + sha256: "44ce41424d104dfb7cf6982cc6b84af2b007a24d126406025bf40de5d481c74c" + url: "https://pub.dev" source: hosted version: "2.0.16" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" source: hosted version: "9.4.0" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted version: "1.0.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" source: hosted version: "2.4.0" webdriver: dependency: transitive description: name: webdriver - url: "https://pub.dartlang.org" + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + url: "https://pub.dev" source: hosted version: "3.0.1" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + url: "https://pub.dev" source: hosted version: "3.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" source: hosted version: "1.0.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" source: hosted version: "6.2.2" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index dc06cc27f..a3daa69cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.5.4+31 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' dependencies: flutter: @@ -58,6 +58,7 @@ dependencies: carousel_slider: ^4.2.1 multi_select_flutter: ^4.1.3 flutter_svg: ^2.0.5 + fl_chart: ^0.62.0 dev_dependencies: flutter_test: From 04bd1c748b3955331d3da749cdba4fe0b2b947bd Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 19 May 2023 19:17:40 +0200 Subject: [PATCH 02/31] Rename some constants so they are the same as in the react app --- lib/helpers/consts.dart | 8 ++++++-- lib/helpers/misc.dart | 2 +- lib/providers/workout_plans.dart | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/helpers/consts.dart b/lib/helpers/consts.dart index 4dcfb7234..84f148b3b 100644 --- a/lib/helpers/consts.dart +++ b/lib/helpers/consts.dart @@ -36,8 +36,12 @@ const DEFAULT_WEIGHT_UNIT = 1; /// Default impression for a workout session (neutral) const DEFAULT_IMPRESSION = 2; -/// Default weight unit is "repetition" -const DEFAULT_REPETITION_UNIT = 1; +// Weight and repetition units for the workout logs +const REP_UNIT_REPETITIONS = 1; +const REP_UNIT_TILL_FAILURE = 2; + +const WEIGHT_UNIT_KG = 1; +const WEIGHT_UNIT_LB = 2; /// Time to locally cache values such as ingredients, etc const DAYS_TO_CACHE = 20; diff --git a/lib/helpers/misc.dart b/lib/helpers/misc.dart index 805ec1ed7..26c34cc28 100644 --- a/lib/helpers/misc.dart +++ b/lib/helpers/misc.dart @@ -42,7 +42,7 @@ String repText( // rather "8 repetitions". If there is weight we want to output "8 x 50kg", // since the repetitions are implied. If other units are used, we always // print them - if (repetitionUnitObj.id != DEFAULT_REPETITION_UNIT || weight == 0 || weight == null) { + if (repetitionUnitObj.id != REP_UNIT_REPETITIONS || weight == 0 || weight == null) { out.add(repetitionUnitObj.name); } } diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index f00048a43..47dcea351 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -85,7 +85,7 @@ class WorkoutPlansProvider with ChangeNotifier { /// Return the default weight unit (reps) RepetitionUnit get defaultRepetitionUnit { - return _repetitionUnit.firstWhere((element) => element.id == DEFAULT_REPETITION_UNIT); + return _repetitionUnit.firstWhere((element) => element.id == REP_UNIT_REPETITIONS); } List getPlans() { From dc56e4295bb64bb16d8b68ffa15455ce80200cb9 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 19 May 2023 19:18:27 +0200 Subject: [PATCH 03/31] Commit updated pubspec.lock --- pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ca4ce5249..c899c7dc5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -441,10 +441,10 @@ packages: dependency: "direct main" description: name: flutter_typeahead - sha256: edfc51579ca3756adaa32b6849cf44af03276b93e8a8a68d8247ee243598f1b2 + sha256: "721610b3d61814efa13fb5f720a6781bc123cd51b7e01f5a45d7c92124376644" url: "https://pub.dev" source: hosted - version: "4.3.7" + version: "4.3.8" flutter_web_plugins: dependency: transitive description: flutter @@ -600,18 +600,18 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a + sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.6.2" klizma: dependency: transitive description: @@ -736,10 +736,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d + sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" package_info_plus_platform_interface: dependency: transitive description: @@ -880,10 +880,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" + sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" shared_preferences_android: dependency: transitive description: @@ -1069,10 +1069,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 url: "https://pub.dev" source: hosted - version: "6.1.10" + version: "6.1.11" url_launcher_android: dependency: transitive description: From 237b4daababf6f183f61bf636fa51b103cc0a0d1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 19 May 2023 20:59:37 +0200 Subject: [PATCH 04/31] Further work on pie chart for the nutritional plan --- lib/helpers/colors.dart | 42 +++++++ lib/widgets/core/charts.dart | 42 +++++++ lib/widgets/dashboard/widgets.dart | 2 +- lib/widgets/nutrition/charts.dart | 174 ++++++++++++++--------------- test/utils/colors.dart | 49 ++++++++ 5 files changed, 218 insertions(+), 91 deletions(-) create mode 100644 lib/helpers/colors.dart create mode 100644 test/utils/colors.dart diff --git a/lib/helpers/colors.dart b/lib/helpers/colors.dart new file mode 100644 index 000000000..b7fd22885 --- /dev/null +++ b/lib/helpers/colors.dart @@ -0,0 +1,42 @@ +import 'dart:ui'; + +const LIST_OF_COLORS8 = [ + Color(0xFF2A4C7D), + Color(0xFF5B5291), + Color(0xFF8E5298), + Color(0xFFBF5092), + Color(0xFFE7537E), + Color(0xFFFF6461), + Color(0xFFFF813D), + Color(0xFFFFA600), +]; + +const LIST_OF_COLORS5 = [ + Color(0xFF2A4C7D), + Color(0xFF825298), + Color(0xFFD45089), + Color(0xFFFF6A59), + Color(0xFFFFA600), +]; + +const LIST_OF_COLORS3 = [ + Color(0xFF2A4C7D), + Color(0xFFD45089), + Color(0xFFFFA600), +]; + +Iterable generateChartColors(int nrOfItems) sync* { + final List colors; + + if (nrOfItems <= 3) { + colors = LIST_OF_COLORS3; + } else if (nrOfItems <= 5) { + colors = LIST_OF_COLORS5; + } else { + colors = LIST_OF_COLORS8; + } + + for (final color in colors) { + yield color; + } +} diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index d1bddd668..6eef78a72 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -57,3 +57,45 @@ class MeasurementChartWidget extends StatelessWidget { ); } } + +class Indicator extends StatelessWidget { + const Indicator({ + super.key, + required this.color, + required this.text, + required this.isSquare, + this.size = 16, + this.textColor, + }); + + final Color color; + final String text; + final bool isSquare; + final double size; + final Color? textColor; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: isSquare ? BoxShape.rectangle : BoxShape.circle, + color: color, + ), + ), + const SizedBox( + width: 4, + ), + Text( + text, + style: TextStyle( + color: textColor, + ), + ) + ], + ); + } +} diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 63daaf993..893cc2d2a 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -190,7 +190,7 @@ class _DashboardNutritionWidgetState extends State { Container( padding: const EdgeInsets.all(15), height: 180, - child: FlNutritionalPlanPieChartWidget(_plan!.nutritionalValues), + child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues), ) ], ), diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index a0ae27d2a..6db07f004 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -21,9 +21,11 @@ import 'package:collection/collection.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:wger/helpers/colors.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; import 'package:wger/theme/theme.dart'; +import 'package:wger/widgets/core/charts.dart'; class NutritionData { final String name; @@ -32,74 +34,119 @@ class NutritionData { NutritionData(this.name, this.value); } -class FlNutritionalPlanPieChartWidget extends StatefulWidget { +class NutritionalPlanPieChartWidget extends StatefulWidget { final NutritionalValues nutritionalValues; - const FlNutritionalPlanPieChartWidget(this.nutritionalValues); + const NutritionalPlanPieChartWidget(this.nutritionalValues); @override State createState() => FlNutritionalPlanPieChartState(); } -class FlNutritionalPlanPieChartState extends State { +class FlNutritionalPlanPieChartState extends State { int touchedIndex = -1; @override Widget build(BuildContext context) { - return PieChart( - PieChartData( - pieTouchData: PieTouchData( - touchCallback: (FlTouchEvent event, pieTouchResponse) { - setState(() { - if (!event.isInterestedForInteractions || - pieTouchResponse == null || - pieTouchResponse.touchedSection == null) { - touchedIndex = -1; - return; - } - touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; - }); - }, + return Row( + children: [ + const SizedBox( + height: 18, ), - borderData: FlBorderData( - show: false, + Expanded( + child: AspectRatio( + aspectRatio: 1, + child: PieChart( + PieChartData( + pieTouchData: PieTouchData( + touchCallback: (FlTouchEvent event, pieTouchResponse) { + setState(() { + if (!event.isInterestedForInteractions || + pieTouchResponse == null || + pieTouchResponse.touchedSection == null) { + touchedIndex = -1; + return; + } + touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; + }); + }, + ), + borderData: FlBorderData( + show: false, + ), + sectionsSpace: 0, + centerSpaceRadius: 0, + sections: showingSections(), + ), + ), + ), ), - sectionsSpace: 0, - centerSpaceRadius: 40, - sections: showingSections(), - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Indicator( + color: LIST_OF_COLORS3[0], + text: AppLocalizations.of(context).fat, + isSquare: true, + ), + const SizedBox( + height: 4, + ), + Indicator( + color: LIST_OF_COLORS3[1], + text: AppLocalizations.of(context).protein, + isSquare: true, + ), + const SizedBox( + height: 4, + ), + Indicator( + color: LIST_OF_COLORS3[2], + text: AppLocalizations.of(context).carbohydrates, + isSquare: true, + ), + ], + ), + const SizedBox( + width: 28, + ), + ], ); } List showingSections() { + final colors = generateChartColors(3).iterator; + return List.generate(3, (i) { final isTouched = i == touchedIndex; - final fontSize = isTouched ? 25.0 : 16.0; - final radius = isTouched ? 60.0 : 50.0; + final radius = isTouched ? 92.0 : 80.0; + colors.moveNext(); + switch (i) { case 0: return PieChartSectionData( - color: Colors.blue, - value: widget.nutritionalValues.fat, - title: AppLocalizations.of(context).fat, - radius: radius, - titleStyle: TextStyle(fontSize: fontSize), - ); + color: colors.current, + value: widget.nutritionalValues.fat, + title: '${widget.nutritionalValues.fat.toStringAsFixed(1)}g', + titlePositionPercentageOffset: 0.5, + radius: radius, + titleStyle: const TextStyle(color: Colors.white70)); case 1: return PieChartSectionData( - color: Colors.amber, + color: colors.current, value: widget.nutritionalValues.protein, - title: AppLocalizations.of(context).protein, + title: '${widget.nutritionalValues.protein.toStringAsFixed(1)}g', + titlePositionPercentageOffset: 0.5, radius: radius, - titleStyle: TextStyle(fontSize: fontSize), ); case 2: return PieChartSectionData( - color: Colors.purple, + color: colors.current, value: widget.nutritionalValues.carbohydrates, - title: AppLocalizations.of(context).carbohydrates, + title: '${widget.nutritionalValues.carbohydrates.toStringAsFixed(1)}g', + titlePositionPercentageOffset: 0.5, radius: radius, - titleStyle: TextStyle(fontSize: fontSize), ); default: @@ -109,59 +156,6 @@ class FlNutritionalPlanPieChartState extends State([ - charts.Series( - id: 'NutritionalValues', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - NutritionData( - AppLocalizations.of(context).protein, - _nutritionalValues.protein, - ), - NutritionData( - AppLocalizations.of(context).fat, - _nutritionalValues.fat, - ), - NutritionData( - AppLocalizations.of(context).carbohydrates, - _nutritionalValues.carbohydrates, - ), - ], - labelAccessorFn: (NutritionData row, _) => - '${row.name}\n${row.value.toStringAsFixed(0)}${AppLocalizations.of(context).g}', - ) - ], - defaultRenderer: charts.ArcRendererConfig(arcWidth: 60, arcRendererDecorators: [ - charts.ArcLabelDecorator( - labelPosition: charts.ArcLabelPosition.outside, - ) - ]) - /* - defaultRenderer: new charts.ArcRendererConfig( - arcWidth: 60, - arcRendererDecorators: [new charts.ArcLabelDecorator()], - ), - - */ - ); - } -} - class NutritionalDiaryChartWidget extends StatelessWidget { const NutritionalDiaryChartWidget({ Key? key, diff --git a/test/utils/colors.dart b/test/utils/colors.dart new file mode 100644 index 000000000..d3e48d7d8 --- /dev/null +++ b/test/utils/colors.dart @@ -0,0 +1,49 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:wger/helpers/colors.dart'; + +void main() { + group('test the color utility', () { + test('3 items or less', () { + final result = generateChartColors(2).iterator; + expect(result.current, equals(const Color(0xFF2A4C7D))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFD45089))); + }); + + test('5 items or less', () { + final result = generateChartColors(5).iterator; + expect(result.current, equals(const Color(0xFF2A4C7D))); + result.moveNext(); + expect(result.current, equals(const Color(0xFF825298))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFD45089))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFFF6A59))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFFFA600))); + }); + + test('8 items or more - last ones undefined', () { + final result = generateChartColors(8).iterator; + expect(result.current, equals(const Color(0xFF2A4C7D))); + result.moveNext(); + expect(result.current, equals(const Color(0xFF5B5291))); + result.moveNext(); + expect(result.current, equals(const Color(0xFF8E5298))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFBF5092))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFE7537E))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFFF6461))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFFF813D))); + result.moveNext(); + expect(result.current, equals(const Color(0xFFFFA600))); + result.moveNext(); + expect(result.current, isNull); + }); + }); +} From cf35cea913fde92c632f1e4ba6a6e8cc4c50af53 Mon Sep 17 00:00:00 2001 From: artchiee Date: Tue, 22 Aug 2023 12:12:41 +0100 Subject: [PATCH 05/31] Unfinished work on Charts replacement --- flatpak/scripts/pubspec.lock | 2 +- lib/widgets/core/charts.dart | 18 + lib/widgets/core/fl_chart_line.dart | 390 ++++++++++++++++++ lib/widgets/dashboard/widgets.dart | 178 ++++---- lib/widgets/measurements/categories_card.dart | 4 +- lib/widgets/measurements/entries.dart | 4 +- pubspec.lock | 2 +- 7 files changed, 519 insertions(+), 79 deletions(-) create mode 100644 lib/widgets/core/fl_chart_line.dart diff --git a/flatpak/scripts/pubspec.lock b/flatpak/scripts/pubspec.lock index 4ab45d80a..d3d4f8469 100644 --- a/flatpak/scripts/pubspec.lock +++ b/flatpak/scripts/pubspec.lock @@ -82,4 +82,4 @@ packages: source: hosted version: "1.3.1" sdks: - dart: ">=2.18.5 <3.0.0" + dart: ">=2.18.5 <4.0.0" diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index 6eef78a72..cebbf0d5f 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -17,6 +17,7 @@ */ import 'package:charts_flutter/flutter.dart' as charts; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:wger/theme/theme.dart'; @@ -45,6 +46,9 @@ class MeasurementChartWidget extends StatelessWidget { id: 'Measurement', colorFn: (_, __) => wgerChartSecondaryColor, domainFn: (MeasurementChartEntry entry, _) => entry.date, + + // expression body => same as above(arrow expression) + //domainFn: (MeasurementChartEntry entry, _){ return entry.date;} , measureFn: (MeasurementChartEntry entry, _) => entry.value, data: _entries, ) @@ -58,6 +62,20 @@ class MeasurementChartWidget extends StatelessWidget { } } +// // #TODO : Del Later /// test state less >> del later +// class GetEntries extends StatelessWidget { +// final List _entries; +// const GetEntries(this._entries); + +// @override +// Widget build(BuildContext context) { +// // return old entries above + +// var entriesal = _entries.toString(); +// return Text(entriesal); +// } +// } + class Indicator extends StatelessWidget { const Indicator({ super.key, diff --git a/lib/widgets/core/fl_chart_line.dart b/lib/widgets/core/fl_chart_line.dart new file mode 100644 index 000000000..1eb4b007b --- /dev/null +++ b/lib/widgets/core/fl_chart_line.dart @@ -0,0 +1,390 @@ +// Fl line chart [SAmple 2 ] for testing +// // #TODO : I may move this content to the core widget file +// + +//import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:wger/helpers/consts.dart'; +import 'package:wger/models/measurements/measurement_entry.dart'; +import 'package:wger/widgets/core/charts.dart'; +//import 'package:intl/intl.dart'; +//import 'package:wger/widgets/core/charts.dart'; + +import 'package:wger/helpers/consts.dart'; + +import 'package:intl/intl.dart'; + +class MeasurementChartEntryflchat { + num value; // this needs to + DateTime date; + + MeasurementChartEntryflchat(this.value, this.date); +} + +class LineChartSample2 extends StatefulWidget { + //#TODO : Substitute below entries to the old entries for data return comapraison !! + final List allentries; + final String unit; + + // entries recieved from original measurement entry (old) + //final List _entries; + + const LineChartSample2(this.allentries, {this.unit = 'kg'}); + + //const LineChartSample2({super.key}); + + @override + State createState() => _LineChartSample2State(); +} + +class AppColors { + static const Color primary = contentColorCyan; + static const Color menuBackground = Color(0xFF090912); + static const Color itemsBackground = Color(0xFF1B2339); + static const Color pageBackground = Color(0xFF282E45); + static const Color mainTextColor1 = Colors.white; + static const Color mainTextColor2 = Colors.white70; + static const Color mainTextColor3 = Colors.white38; + static const Color mainGridLineColor = Colors.white10; + static const Color borderColor = Colors.white54; + static const Color gridLinesColor = Color(0x11FFFFFF); + + static const Color contentColorBlack = Colors.black; + static const Color contentColorWhite = Colors.white; + static const Color contentColorBlue = Color(0xFF2196F3); + static const Color contentColorYellow = Color(0xFFFFC300); + static const Color contentColorOrange = Color(0xFFFF683B); + static const Color contentColorGreen = Color(0xFF3BFF49); + static const Color contentColorPurple = Color(0xFF6E1BFF); + static const Color contentColorPink = Color(0xFFFF3AF2); + static const Color contentColorRed = Color(0xFFE80054); + static const Color contentColorCyan = Color(0xFF50E4FF); +} + +class _LineChartSample2State extends State { + List gradientColors = [ + AppColors.contentColorCyan, + AppColors.contentColorBlue, + ]; + + bool showAvg = false; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AspectRatio( + aspectRatio: 1.70, + child: Padding( + padding: const EdgeInsets.only( + right: 18, + left: 12, + top: 24, + bottom: 12, + ), + child: LineChart( + showAvg ? avgData() : mainData(), + ), + ), + ), + SizedBox( + width: 60, + height: 34, + child: TextButton( + onPressed: () { + setState(() { + showAvg = !showAvg; + }); + }, + child: Text( + 'avg', + style: TextStyle( + fontSize: 12, + color: showAvg ? Colors.white.withOpacity(0.5) : Colors.white, + ), + ), + ), + ), + ], + ); + } + + // #TODO : Automatic dates on (x axis) + + SideTitles _bottomTitles() { + return SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: (value, meta) { + // // #TODO : format dates same as old chart + final getdates = + value.toInt() < widget.allentries.length ? widget.allentries[value.toInt()].date : ""; + + // final datesToStr = getdates.toString(); + + // final tempDate = DateTime.parse(getdates.toString()); + // //debugPrint(('tempDates \n' ) + tempDate.toString() ); + + // final finaldates = DateFormat('MM-dd').format(tempDate); + // debugPrint(('FinaleDates \n') + finaldates); + //final datestoformat = DateFormatLists.format(tempDate); + + // // # used built in above fun to return date format as (yyyy-mm-dd) + // debugPrint(datestoformat); + + // #BUG : if dates are formated -- the chart causes error (invalid dates format) + return SideTitleWidget(axisSide: meta.axisSide, child: Text(getdates.toString())); + }, + ); + } + + // Widget bottomTitleWidgets(double value, TitleMeta meta) { + // const style = TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 16, + // ); + + // List datesgeneric = [ + // 'Sun', + // 'Feb', + // 'Oct', + // 'Nov', + // ]; + + // //MeasurementChartEntry entry ; + // // Widget text; + // // switch (value.toInt()) { + // // case 2: + // // text = const Text('MAR', style: style); + // // break; + // // case 5: + // // text = const Text('JUN', style: style); + // // break; + // // case 8: + // // text = const Text('SEP', style: style); + // // break; + // // default: + // // text = const Text('', style: style); + // // break; + // // } + + // return SideTitleWidget( + // axisSide: meta.axisSide, + // child:Text('$datesgeneric'), + // ); + // } + + + // #TODO : needs to be changed (values for (y) axis) + Widget leftTitleWidgets(double value, TitleMeta meta) { + const style = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + ); + + String text; + switch (value.toInt()) { + case 1: + text = '10K'; + break; + case 3: + text = '30k'; + break; + case 5: + text = '50k'; + break; + default: + return Container(); + } + + return Text(text, style: style, textAlign: TextAlign.left); + } + + LineChartData mainData() { + return LineChartData( + gridData: FlGridData( + show: true, + drawVerticalLine: true, + horizontalInterval: 1, + verticalInterval: 1, + getDrawingHorizontalLine: (value) { + return FlLine( + color: AppColors.mainGridLineColor, + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: AppColors.mainGridLineColor, + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + show: true, + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: _bottomTitles(), + ), + + // #FIXME : Custom bottom titles for dates entry + // bottomTitles: AxisTitles( + // sideTitles: SideTitles( + // showTitles: true, + // reservedSize: 30, + // interval: 1, + // getTitlesWidget: bottomTitleWidgets, + // ), + // ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: leftTitleWidgets, + reservedSize: 42, + ), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all(color: const Color(0xff37434d)), + ), + minX: 0, + maxX: 11, + minY: 0, + maxY: 6, + lineBarsData: [ + LineChartBarData( + // spots: const [ + // FlSpot(0, 3), + // FlSpot(2.6, 2), + // FlSpot(4.9, 5), + // FlSpot(6.8, 3.1), + // FlSpot(8, 4), + // FlSpot(9.5, 3), + // FlSpot(11, 4), + // ], + isCurved: true, + gradient: LinearGradient( + colors: gradientColors, + ), + barWidth: 5, + isStrokeCapRound: true, + dotData: FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(), + ), + ), + ), + ], + ); + } + + LineChartData avgData() { + return LineChartData( + lineTouchData: LineTouchData(enabled: false), + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + verticalInterval: 1, + horizontalInterval: 1, + getDrawingVerticalLine: (value) { + return FlLine( + color: Color(0xff37434d), + strokeWidth: 1, + ); + }, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Color(0xff37434d), + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: _bottomTitles(), + // sideTitles: SideTitles( + // showTitles: true, + // reservedSize: 30, + // getTitlesWidget: _, + // interval: 1, + // ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: leftTitleWidgets, + reservedSize: 42, + interval: 1, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all(color: const Color(0xff37434d)), + ), + minX: 0, + maxX: 11, + minY: 0, + maxY: 6, + lineBarsData: [ + LineChartBarData( + // spots: const [ + // FlSpot(0, 3.44), + // FlSpot(2.6, 3.44), + // FlSpot(4.9, 3.44), + // FlSpot(6.8, 3.44), + // FlSpot(8, 3.44), + // FlSpot(9.5, 3.44), + // FlSpot(11, 3.44), + // ], + isCurved: true, + gradient: LinearGradient( + colors: [ + ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, + ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, + ], + ), + barWidth: 5, + isStrokeCapRound: true, + dotData: FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + ColorTween(begin: gradientColors[0], end: gradientColors[1]) + .lerp(0.2)! + .withOpacity(0.1), + ColorTween(begin: gradientColors[0], end: gradientColors[1]) + .lerp(0.2)! + .withOpacity(0.1), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 893cc2d2a..27ab309c0 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -44,6 +44,9 @@ 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(); @@ -183,6 +186,7 @@ class _DashboardNutritionWidgetState extends State { ), if (_hasContent) Container( + color: Colors.blue, padding: const EdgeInsets.only(left: 10), child: Column( children: [ @@ -267,12 +271,28 @@ 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: MeasurementChartWidget(weightEntriesData.items - .map((e) => MeasurementChartEntry(e.weight, e.date)) - .toList()), + //child: null, + //child: LineChartSample2(), + child: LineChartSample2( + weightEntriesData.items + .map((e) => MeasurementChartEntryflchat(e.weight, e.date)) + .toList(), + ), ), + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -344,81 +364,89 @@ class _DashboardMeasurementWidgetState extends State ); } return Consumer( - builder: (context, workoutProvider, child) => Card( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context).measurements, - style: Theme.of(context).textTheme.headline4, - ), - leading: const FaIcon( - FontAwesomeIcons.weight, - color: Colors.black, - ), - trailing: IconButton( - icon: const Icon(Icons.arrow_forward), - onPressed: () => Navigator.pushNamed( - context, - MeasurementCategoriesScreen.routeName, - ), - ), - ), - 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; - }); - }), + builder: (context, workoutProvider, child) => Card( + child: Container( + color: Colors.amberAccent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context).measurements, + style: Theme.of(context).textTheme.headline4, + ), + leading: const FaIcon( + FontAwesomeIcons.weight, + color: Colors.black, + ), + trailing: IconButton( + icon: const Icon(Icons.arrow_forward), + onPressed: () => Navigator.pushNamed( + context, + MeasurementCategoriesScreen.routeName, + ), + ), ), - 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)), + + //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(), + ), + ), + ]) + else + NothingFound( + AppLocalizations.of(context).noMeasurementEntries, + AppLocalizations.of(context).newEntry, + MeasurementCategoryForm(), ), - ); - }).toList(), + ], ), - ), - ]) - 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 772a02dd9..355944656 100644 --- a/lib/widgets/measurements/categories_card.dart +++ b/lib/widgets/measurements/categories_card.dart @@ -26,8 +26,10 @@ class CategoriesCard extends StatelessWidget { style: Theme.of(context).textTheme.headline6, ), ), + + // #FIXME : Measurements Dashboard (Custom color set ) Container( - color: Colors.white, + color: Color.fromARGB(255, 159, 114, 211), padding: const EdgeInsets.all(10), height: 220, child: MeasurementChartWidget( diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index 416159ed0..d0e264e59 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -36,8 +36,10 @@ class EntriesList extends StatelessWidget { @override Widget build(BuildContext context) { return Column(children: [ + // #TODO ! : Measurements chart > detail page chart (custom color set ) Container( - color: Theme.of(context).cardColor, + //color: Theme.of(context).cardColor, + color: Colors.lightGreenAccent, padding: const EdgeInsets.all(10), height: 220, child: MeasurementChartWidget( diff --git a/pubspec.lock b/pubspec.lock index c899c7dc5..58f4fe3f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1282,5 +1282,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=3.7.0-0" From ef5a7081a00cb1e0c64c4f03eb486f3684c1883b Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 1 Oct 2023 14:27:02 +0200 Subject: [PATCH 06/31] 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)); }); From 779926bc982008d079c784983bb4cf8249d90377 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 1 Oct 2023 20:22:47 +0200 Subject: [PATCH 07/31] Move nutritional plan diary chart to fl charts --- lib/models/nutrition/nutritional_plan.dart | 24 + lib/widgets/dashboard/widgets.dart | 166 +++--- lib/widgets/nutrition/charts.dart | 553 ++++++++---------- .../nutrition/nutritional_diary_detail.dart | 3 +- .../nutrition/nutritional_plan_detail.dart | 16 +- .../measurement_entries_screen_test.dart | 5 +- test/nutrition/nutritional_diary_test.dart | 2 +- test/weight/weight_screen_test.dart | 5 +- 8 files changed, 360 insertions(+), 414 deletions(-) diff --git a/lib/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index 053fecc28..978304c23 100644 --- a/lib/models/nutrition/nutritional_plan.dart +++ b/lib/models/nutrition/nutritional_plan.dart @@ -82,6 +82,30 @@ class NutritionalPlan { return out; } + NutritionalValues get nutritionalValuesToday { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + + return logEntriesValues.containsKey(today) ? logEntriesValues[today]! : NutritionalValues(); + } + + NutritionalValues get nutritionalValues7DayAvg { + final currentDate = DateTime.now(); + final sevenDaysAgo = currentDate.subtract(Duration(days: 7)); + + final entries = logs.where((obj) { + DateTime objDate = obj.datetime; + return objDate.isAfter(sevenDaysAgo) && objDate.isBefore(currentDate); + }).toList(); + + var out = NutritionalValues(); + entries.forEach((log) { + out = out + log.nutritionalValues; + }); + + return out; + } + /// Calculates the percentage each macro nutrient adds to the total energy BaseNutritionalValues energyPercentage(NutritionalValues values) { return BaseNutritionalValues( diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index b0dee91cc..fa11ebfd2 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -182,19 +182,15 @@ class _DashboardNutritionWidgetState extends State { }, ), if (_hasContent) - Container( - color: Colors.blue, - padding: const EdgeInsets.only(left: 10), - child: Column( - children: [ - ...getContent(), - Container( - padding: const EdgeInsets.all(15), - height: 180, - child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues), - ) - ], - ), + Column( + children: [ + ...getContent(), + Container( + padding: const EdgeInsets.all(15), + height: 180, + child: FlNutritionalPlanPieChartWidget(_plan!.nutritionalValues), + ) + ], ) else NothingFound( @@ -345,85 +341,79 @@ class _DashboardMeasurementWidgetState extends State } return Consumer( builder: (context, workoutProvider, child) => Card( - child: Container( - color: Colors.amberAccent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context).measurements, - style: Theme.of(context).textTheme.headline4, - ), - leading: const FaIcon( - FontAwesomeIcons.weight, - color: Colors.black, - ), - trailing: IconButton( - icon: const Icon(Icons.arrow_forward), - onPressed: () => Navigator.pushNamed( - context, - MeasurementCategoriesScreen.routeName, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context).measurements, + style: Theme.of(context).textTheme.headline4, + ), + leading: const FaIcon( + FontAwesomeIcons.weight, + color: Colors.black, + ), + trailing: IconButton( + icon: const Icon(Icons.arrow_forward), + onPressed: () => Navigator.pushNamed( + context, + MeasurementCategoriesScreen.routeName, ), ), - - //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(), - ), + ), + 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(), ), - ]) - 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/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 6db07f004..2de6185ef 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -17,7 +17,6 @@ */ import 'package:charts_flutter/flutter.dart' as charts; -import 'package:collection/collection.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -34,16 +33,16 @@ class NutritionData { NutritionData(this.name, this.value); } -class NutritionalPlanPieChartWidget extends StatefulWidget { +class FlNutritionalPlanPieChartWidget extends StatefulWidget { final NutritionalValues nutritionalValues; - const NutritionalPlanPieChartWidget(this.nutritionalValues); + const FlNutritionalPlanPieChartWidget(this.nutritionalValues); @override State createState() => FlNutritionalPlanPieChartState(); } -class FlNutritionalPlanPieChartState extends State { +class FlNutritionalPlanPieChartState extends State { int touchedIndex = -1; @override @@ -128,7 +127,7 @@ class FlNutritionalPlanPieChartState extends State, DateTime>( - id: 'NutritionDiary', - colorFn: (datum, index) => wgerChartSecondaryColor, - domainFn: (datum, index) => datum[1], - measureFn: (datum, index) => datum[0].energy, - data: _nutritionalPlan.logEntriesValues.keys - .map((e) => [_nutritionalPlan.logEntriesValues[e], e]) - .toList(), - ) - ], - defaultRenderer: charts.BarRendererConfig(), - behaviors: [ - charts.RangeAnnotation([ - charts.LineAnnotationSegment( - _nutritionalPlan.nutritionalValues.energy, - charts.RangeAnnotationAxisType.measure, - strokeWidthPx: 2, - color: charts.MaterialPalette.gray.shade600, - ), - ]), - ], - ); - } + State createState() => NutritionalDiaryChartWidgetFlState(); } -/// Nutritional plan hatch bar chart widget -class NutritionalPlanHatchBarChartWidget extends StatelessWidget { - final NutritionalPlan _nutritionalPlan; - - /// [_nutritionalPlan] is current opened nutrition plan as plan detail. - const NutritionalPlanHatchBarChartWidget(this._nutritionalPlan); - - NutritionalValues nutritionalValuesFromPlanLogsSevenDayAvg() { - NutritionalValues sevenDaysAvg = NutritionalValues(); - int count = 0; - - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - - _nutritionalPlan.logEntriesValues.forEach((key, value) { - if (key.difference(today).inDays >= -7) { - sevenDaysAvg += value; - count++; - } - }); - - if (count != 0) { - sevenDaysAvg.energy = sevenDaysAvg.energy / count; - sevenDaysAvg.protein = sevenDaysAvg.protein / count; - sevenDaysAvg.carbohydrates = sevenDaysAvg.carbohydrates / count; - sevenDaysAvg.carbohydratesSugar = sevenDaysAvg.carbohydratesSugar / count; - sevenDaysAvg.fat = sevenDaysAvg.fat / count; - sevenDaysAvg.fatSaturated = sevenDaysAvg.fatSaturated / count; - sevenDaysAvg.fibres = sevenDaysAvg.fibres / count; - sevenDaysAvg.sodium = sevenDaysAvg.sodium / count; +class NutritionalDiaryChartWidgetFlState extends State { + Widget bottomTitles(double value, TitleMeta meta) { + const style = TextStyle(fontSize: 10); + String text; + switch (value.toInt()) { + case 0: + text = 'Protein'; + break; + case 1: + text = 'Carbohydrates'; + break; + case 2: + text = 'Sugars'; + break; + case 3: + text = 'Fat'; + break; + case 4: + text = 'Saturated fat'; + break; + + default: + text = ''; + break; } - - return sevenDaysAvg; + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text(text, style: style), + ); } - NutritionalValues nutritionalValuesFromPlanLogsToday() { - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - - return _nutritionalPlan.logEntriesValues[_nutritionalPlan.logEntriesValues.keys - .firstWhereOrNull((d) => d.difference(today).inDays == 0)] ?? - NutritionalValues(); + Widget leftTitles(double value, TitleMeta meta) { + if (value == meta.max) { + return Container(); + } + const style = TextStyle( + fontSize: 10, + ); + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + meta.formattedValue, + style: style, + ), + ); } @override Widget build(BuildContext context) { - final NutritionalValues loggedNutritionalValues = nutritionalValuesFromPlanLogsToday(); - final NutritionalValues sevenDayAvg = nutritionalValuesFromPlanLogsSevenDayAvg(); - - if (_nutritionalPlan.nutritionalValues.energy == 0) { - return Container(); - } - - return Column( - children: [ - Container( - padding: const EdgeInsets.all(15), - height: 220, - child: charts.BarChart( - [ - charts.Series( - id: 'Planned', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - NutritionData(AppLocalizations.of(context).energy, - _nutritionalPlan.nutritionalValues.energy), - ], - ), - charts.Series( - id: 'Logged', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch, - data: [ - NutritionData( - AppLocalizations.of(context).energy, loggedNutritionalValues.energy), - ], - ), - charts.Series( - id: 'Avg', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy), - ], - ), - ], - animate: true, - domainAxis: const charts.OrdinalAxisSpec( - ///labelRotation was added to rotate text of X Axis. Without that, - ///titles would overlap each other - renderSpec: charts.SmallTickRendererSpec(labelRotation: 60), - ), - barGroupingType: charts.BarGroupingType.grouped, - defaultRenderer: charts.BarRendererConfig( - groupingType: charts.BarGroupingType.grouped, strokeWidthPx: 0.0, maxBarWidthPx: 8), - primaryMeasureAxis: const charts.NumericAxisSpec( - tickProviderSpec: charts.BasicNumericTickProviderSpec(desiredTickCount: 5), - ), - ), - ), - Container( - padding: const EdgeInsets.all(15), - height: 300, - child: charts.BarChart( - [ - charts.Series( - id: 'Planned', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - // NutritionData( - // AppLocalizations.of(context).energy, - // _nutritionalPlan.nutritionalValues.energy, - // ), - NutritionData( - AppLocalizations.of(context).protein, - _nutritionalPlan.nutritionalValues.protein, + final NutritionalValues planned = widget._nutritionalPlan.nutritionalValues; + final NutritionalValues loggedToday = widget._nutritionalPlan.nutritionalValuesToday; + final NutritionalValues logged7DayAvg = widget._nutritionalPlan.nutritionalValues7DayAvg; + + final colorPlanned = LIST_OF_COLORS3[0]; + final colorLoggedToday = LIST_OF_COLORS3[1]; + final colorLogged7Day = LIST_OF_COLORS3[2]; + + return AspectRatio( + aspectRatio: 1.66, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: LayoutBuilder( + builder: (context, constraints) { + final barsSpace = 4.0 * constraints.maxWidth / 400; + final barsWidth = 8.0 * constraints.maxWidth / 400; + return BarChart( + BarChartData( + alignment: BarChartAlignment.center, + barTouchData: BarTouchData( + enabled: false, + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 48, + getTitlesWidget: bottomTitles, + ), ), - NutritionData( - AppLocalizations.of(context).carbohydrates, - _nutritionalPlan.nutritionalValues.carbohydrates, + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: leftTitles, + ), ), - NutritionData( - AppLocalizations.of(context).sugars, - _nutritionalPlan.nutritionalValues.carbohydratesSugar, + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), ), - NutritionData( - AppLocalizations.of(context).fat, - _nutritionalPlan.nutritionalValues.fat, + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), ), - NutritionData( - AppLocalizations.of(context).saturatedFat, - _nutritionalPlan.nutritionalValues.fatSaturated, + ), + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % 10 == 0, + getDrawingHorizontalLine: (value) => FlLine( + color: Colors.black, + strokeWidth: 1, ), - NutritionData( - AppLocalizations.of(context).fibres, - _nutritionalPlan.nutritionalValues.fibres, + drawVerticalLine: false, + ), + borderData: FlBorderData( + show: false, + ), + groupsSpace: 30, + // groupsSpace: barsSpace, + barGroups: [ + BarChartGroupData( + x: 0, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: planned.protein, + color: colorPlanned, + width: barsWidth, + ), + BarChartRodData( + toY: loggedToday.protein, + color: colorLoggedToday, + width: barsWidth, + ), + BarChartRodData( + toY: logged7DayAvg.protein, + color: colorLogged7Day, + width: barsWidth, + ), + ], ), - NutritionData( - AppLocalizations.of(context).sodium, - _nutritionalPlan.nutritionalValues.sodium, + BarChartGroupData( + x: 1, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: planned.carbohydrates, + color: colorPlanned, + width: barsWidth, + ), + BarChartRodData( + toY: loggedToday.carbohydrates, + color: colorLoggedToday, + width: barsWidth, + ), + BarChartRodData( + toY: logged7DayAvg.carbohydrates, + color: colorLogged7Day, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 2, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: planned.carbohydratesSugar, + color: colorPlanned, + width: barsWidth, + ), + BarChartRodData( + toY: loggedToday.carbohydratesSugar, + color: colorLoggedToday, + width: barsWidth, + ), + BarChartRodData( + toY: logged7DayAvg.carbohydratesSugar, + color: colorLogged7Day, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 3, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: planned.fat, + color: colorPlanned, + width: barsWidth, + ), + BarChartRodData( + toY: loggedToday.fat, + color: colorLoggedToday, + width: barsWidth, + ), + BarChartRodData( + toY: logged7DayAvg.fat, + color: colorLogged7Day, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 4, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: planned.fatSaturated, + color: colorPlanned, + width: barsWidth, + ), + BarChartRodData( + toY: loggedToday.fatSaturated, + color: colorLoggedToday, + width: barsWidth, + ), + BarChartRodData( + toY: logged7DayAvg.fatSaturated, + color: colorLogged7Day, + width: barsWidth, + ), + ], ), ], + // barGroups: getData(barsWidth, barsSpace), ), - charts.Series( - id: 'Logged', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch, - data: [ - // NutritionData( - // AppLocalizations.of(context).energy, - // loggedNutritionalValues.energy - // ), - - NutritionData( - AppLocalizations.of(context).protein, loggedNutritionalValues.protein), - NutritionData(AppLocalizations.of(context).carbohydrates, - loggedNutritionalValues.carbohydrates), - NutritionData(AppLocalizations.of(context).sugars, - loggedNutritionalValues.carbohydratesSugar), - NutritionData(AppLocalizations.of(context).fat, loggedNutritionalValues.fat), - NutritionData(AppLocalizations.of(context).saturatedFat, - loggedNutritionalValues.fatSaturated), - NutritionData( - AppLocalizations.of(context).fibres, loggedNutritionalValues.fibres), - NutritionData( - AppLocalizations.of(context).sodium, loggedNutritionalValues.sodium), - ], - ), - charts.Series( - id: 'Avg', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - // NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy), - NutritionData(AppLocalizations.of(context).protein, sevenDayAvg.protein), - NutritionData( - AppLocalizations.of(context).carbohydrates, sevenDayAvg.carbohydrates), - NutritionData( - AppLocalizations.of(context).sugars, sevenDayAvg.carbohydratesSugar), - NutritionData(AppLocalizations.of(context).fat, sevenDayAvg.fat), - NutritionData( - AppLocalizations.of(context).saturatedFat, sevenDayAvg.fatSaturated), - NutritionData(AppLocalizations.of(context).fibres, sevenDayAvg.fibres), - NutritionData(AppLocalizations.of(context).sodium, sevenDayAvg.sodium), - ], - ), - ], - animate: true, - domainAxis: const charts.OrdinalAxisSpec( - ///labelRotation was added to rotate text of X Axis. Without that, - ///titles would overlap each other - renderSpec: charts.SmallTickRendererSpec(labelRotation: 60), - ), - barGroupingType: charts.BarGroupingType.grouped, - primaryMeasureAxis: const charts.NumericAxisSpec( - tickProviderSpec: charts.BasicNumericTickProviderSpec( - desiredTickCount: 5, - ), - ), - ), + ); + }, ), - ], + ), ); } } -//creating a seperate chart for energy as the energy value and other nutrient's value is not compatable to show in one graph -class EnergyChart extends StatelessWidget { - const EnergyChart({Key? key, required this.nutritionalPlan}) : super(key: key); - final NutritionalPlan nutritionalPlan; - - NutritionalValues nutritionalValuesFromPlanLogsSevenDayAvg() { - NutritionalValues sevenDaysAvg = NutritionalValues(); - int count = 0; - - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - - nutritionalPlan.logEntriesValues.forEach((key, value) { - if (key.difference(today).inDays >= -7) { - sevenDaysAvg += value; - count++; - } - }); - - if (count != 0) { - sevenDaysAvg.energy = sevenDaysAvg.energy / count; - sevenDaysAvg.protein = sevenDaysAvg.protein / count; - sevenDaysAvg.carbohydrates = sevenDaysAvg.carbohydrates / count; - sevenDaysAvg.carbohydratesSugar = sevenDaysAvg.carbohydratesSugar / count; - sevenDaysAvg.fat = sevenDaysAvg.fat / count; - sevenDaysAvg.fatSaturated = sevenDaysAvg.fatSaturated / count; - sevenDaysAvg.fibres = sevenDaysAvg.fibres / count; - sevenDaysAvg.sodium = sevenDaysAvg.sodium / count; - } - - return sevenDaysAvg; - } - - NutritionalValues nutritionalValuesFromPlanLogsToday() { - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); +class NutritionalDiaryChartWidget extends StatelessWidget { + const NutritionalDiaryChartWidget({ + Key? key, + required NutritionalPlan nutritionalPlan, + }) : _nutritionalPlan = nutritionalPlan, + super(key: key); - return nutritionalPlan.logEntriesValues[nutritionalPlan.logEntriesValues.keys - .firstWhereOrNull((d) => d.difference(today).inDays == 0)] ?? - NutritionalValues(); - } + final NutritionalPlan _nutritionalPlan; @override Widget build(BuildContext context) { - final NutritionalValues loggedNutritionalValues = nutritionalValuesFromPlanLogsToday(); - final NutritionalValues sevenDayAvg = nutritionalValuesFromPlanLogsSevenDayAvg(); - - if (nutritionalPlan.nutritionalValues.energy == 0) { - return Container(); - } - - return charts.BarChart( + return charts.TimeSeriesChart( [ - charts.Series( - id: 'Planned', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - NutritionData( - AppLocalizations.of(context).energy, - nutritionalPlan.nutritionalValues.energy, - ), - ], - ), - charts.Series( - id: 'Logged', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - fillPatternFn: (nutritionEntry, index) => charts.FillPatternType.forwardHatch, - data: [ - NutritionData(AppLocalizations.of(context).energy, loggedNutritionalValues.energy), - ], - ), - charts.Series( - id: 'Avg', - domainFn: (nutritionEntry, index) => nutritionEntry.name, - measureFn: (nutritionEntry, index) => nutritionEntry.value, - data: [ - NutritionData(AppLocalizations.of(context).energy, sevenDayAvg.energy), - ], - ), + charts.Series, DateTime>( + id: 'NutritionDiary', + colorFn: (datum, index) => wgerChartSecondaryColor, + domainFn: (datum, index) => datum[1], + measureFn: (datum, index) => datum[0].energy, + data: _nutritionalPlan.logEntriesValues.keys + .map((e) => [_nutritionalPlan.logEntriesValues[e], e]) + .toList(), + ) + ], + defaultRenderer: charts.BarRendererConfig(), + behaviors: [ + charts.RangeAnnotation([ + charts.LineAnnotationSegment( + _nutritionalPlan.nutritionalValues.energy, + charts.RangeAnnotationAxisType.measure, + strokeWidthPx: 2, + color: charts.MaterialPalette.gray.shade600, + ), + ]), ], - animate: true, - domainAxis: const charts.OrdinalAxisSpec( - ///labelRotation was added to rotate text of X Axis. Without that, - ///titles would overlap each other - renderSpec: charts.SmallTickRendererSpec(labelRotation: 60), - ), - barGroupingType: charts.BarGroupingType.grouped, - defaultRenderer: charts.BarRendererConfig( - groupingType: charts.BarGroupingType.grouped, strokeWidthPx: 0.0, maxBarWidthPx: 8), - primaryMeasureAxis: const charts.NumericAxisSpec( - tickProviderSpec: charts.BasicNumericTickProviderSpec(desiredTickCount: 5), - ), ); } } diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index fff02c400..6368fbe78 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -32,6 +32,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { final NutritionalPlan _nutritionalPlan; final DateTime _date; static const double tablePadding = 7; + const NutritionalDiaryDetailWidget(this._nutritionalPlan, this._date); Widget getTable( @@ -223,7 +224,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(15), height: 220, - child: NutritionalPlanPieChartWidget(valuesDate), + child: FlNutritionalPlanPieChartWidget(valuesDate), ), Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 671d0b4fe..ab2026c50 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -32,7 +32,9 @@ import 'package:wger/widgets/nutrition/meal.dart'; class NutritionalPlanDetailWidget extends StatelessWidget { final NutritionalPlan _nutritionalPlan; + const NutritionalPlanDetailWidget(this._nutritionalPlan); + static const double tablePadding = 7; @override @@ -70,7 +72,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(15), height: 220, - child: NutritionalPlanPieChartWidget(nutritionalValues), // chart + child: FlNutritionalPlanPieChartWidget(nutritionalValues), // chart ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10), @@ -210,13 +212,11 @@ class NutritionalPlanDetailWidget extends StatelessWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline6, ), - - NutritionalPlanHatchBarChartWidget(_nutritionalPlan), - // Container( - // padding: const EdgeInsets.all(15), - // height: 300, - // child: NutritionalPlanHatchBarChartWidget(_nutritionalPlan), // chart - // ), + Container( + padding: const EdgeInsets.all(15), + height: 300, + child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan), // chart + ), const Padding(padding: EdgeInsets.all(8.0)), Text( AppLocalizations.of(context).nutritionalDiary, diff --git a/test/measurements/measurement_entries_screen_test.dart b/test/measurements/measurement_entries_screen_test.dart index 58572d614..6182ab373 100644 --- a/test/measurements/measurement_entries_screen_test.dart +++ b/test/measurements/measurement_entries_screen_test.dart @@ -82,8 +82,9 @@ void main() { await tester.tap(find.byType(TextButton)); await tester.pumpAndSettle(); - expect(find.text('8/1/2021'), findsOneWidget); - expect(find.text('8/10/2021'), findsOneWidget); + // From the entries list and from the chart + expect(find.text('8/1/2021'), findsNWidgets(3)); + expect(find.text('8/10/2021'), findsNWidgets(2)); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { diff --git a/test/nutrition/nutritional_diary_test.dart b/test/nutrition/nutritional_diary_test.dart index d65029a76..a1372b7db 100644 --- a/test/nutrition/nutritional_diary_test.dart +++ b/test/nutrition/nutritional_diary_test.dart @@ -41,7 +41,7 @@ void main() { testWidgets('Test the detail view for the nutritional plan', (WidgetTester tester) async { await tester.pumpWidget(getWidget()); - expect(find.byType(NutritionalPlanPieChartWidget), findsOneWidget); + expect(find.byType(FlNutritionalPlanPieChartWidget), findsOneWidget); expect(find.byType(Table), findsOneWidget); expect(find.text('519kcal'), findsOneWidget, reason: 'find total energy'); diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index 881d16b9d..c95c1c049 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -83,8 +83,9 @@ 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); + // One in the entries list, one in the chart + expect(find.text('1/1/2021'), findsNWidgets(2)); + expect(find.text('1/10/2021'), findsNWidgets(2)); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { From 23b8b5e2e0036f961663a27b040488982a66a4d4 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 7 Oct 2023 13:01:55 +0200 Subject: [PATCH 08/31] Translate the macronutrient's names --- lib/widgets/nutrition/charts.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 2de6185ef..4750a602b 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -174,19 +174,19 @@ class NutritionalDiaryChartWidgetFlState extends State Date: Sun, 8 Oct 2023 21:27:55 +0200 Subject: [PATCH 09/31] Move another charts to fl charts These *really* need to get some kind of native time series support.... --- lib/widgets/nutrition/charts.dart | 212 +++++++++++++++--- .../nutrition/nutritional_plan_detail.dart | 2 +- .../nutritional_plan_screen_test.dart | 2 +- 3 files changed, 185 insertions(+), 31 deletions(-) diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 4750a602b..8e1b7399c 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -16,14 +16,13 @@ * 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_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; import 'package:wger/helpers/colors.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; -import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/core/charts.dart'; class NutritionData { @@ -217,9 +216,9 @@ class NutritionalDiaryChartWidgetFlState extends State createState() => FlNutritionalDiaryChartWidgetState(); +} + +class FlNutritionalDiaryChartWidgetState extends State { + final Duration animDuration = const Duration(milliseconds: 250); + + int touchedIndex = -1; @override Widget build(BuildContext context) { - return charts.TimeSeriesChart( - [ - charts.Series, DateTime>( - id: 'NutritionDiary', - colorFn: (datum, index) => wgerChartSecondaryColor, - domainFn: (datum, index) => datum[1], - measureFn: (datum, index) => datum[0].energy, - data: _nutritionalPlan.logEntriesValues.keys - .map((e) => [_nutritionalPlan.logEntriesValues[e], e]) - .toList(), - ) - ], - defaultRenderer: charts.BarRendererConfig(), - behaviors: [ - charts.RangeAnnotation([ - charts.LineAnnotationSegment( - _nutritionalPlan.nutritionalValues.energy, - charts.RangeAnnotationAxisType.measure, - strokeWidthPx: 2, - color: charts.MaterialPalette.gray.shade600, + return AspectRatio( + aspectRatio: 1, + child: BarChart( + mainBarData(), + swapAnimationDuration: animDuration, + ), + ); + } + + List getDatesBetween(DateTime startDate, DateTime endDate) { + final List dateList = []; + DateTime currentDate = startDate; + + while (currentDate.isBefore(endDate) || currentDate.isAtSameMomentAs(endDate)) { + dateList.add(currentDate); + currentDate = currentDate.add(const Duration(days: 1)); + } + + return dateList; + } + + BarChartGroupData makeGroupData( + int x, + double y, { + bool isTouched = false, + Color? barColor, + double width = 1.5, + List showTooltips = const [], + }) { + barColor ??= widget.barColor; + return BarChartGroupData( + x: x, + barRods: [ + BarChartRodData( + toY: isTouched ? y + 1 : y, + color: isTouched ? widget.touchedBarColor : barColor, + width: width, + borderSide: isTouched + ? const BorderSide(color: Colors.black54) + : const BorderSide(color: Colors.white, width: 0), + backDrawRodData: BackgroundBarChartRodData( + show: true, + toY: 20, + color: widget.barBackgroundColor, ), - ]), + ), ], + showingTooltipIndicators: showTooltips, + ); + } + + List showingGroups() { + final logEntries = widget._nutritionalPlan.logEntriesValues; + final List out = []; + final dateList = getDatesBetween(logEntries.keys.last, logEntries.keys.first); + + for (final date in dateList) { + out.add( + makeGroupData( + date.millisecondsSinceEpoch, + logEntries.containsKey(date) ? logEntries[date]!.energy : 0, + isTouched: date.millisecondsSinceEpoch == touchedIndex, + ), + ); + } + + return out; + } + + Widget leftTitles(double value, TitleMeta meta) { + if (value == meta.max) { + return Container(); + } + const style = TextStyle( + fontSize: 10, + ); + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + '${meta.formattedValue} kcal', + style: style, + ), + ); + } + + BarChartData mainBarData() { + return BarChartData( + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.blueGrey, + tooltipHorizontalAlignment: FLHorizontalAlignment.right, + tooltipMargin: -10, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final date = DateTime.fromMillisecondsSinceEpoch(group.x); + + return BarTooltipItem( + '${DateFormat.yMMMd().format(date)}\n', + const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + children: [ + TextSpan( + text: '${(rod.toY - 1).toStringAsFixed(0)} kcal', + style: TextStyle( + color: widget.touchedBarColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + }, + ), + touchCallback: (FlTouchEvent event, barTouchResponse) { + setState(() { + if (!event.isInterestedForInteractions || + barTouchResponse == null || + barTouchResponse.spot == null) { + touchedIndex = -1; + return; + } + touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; + }); + }, + ), + titlesData: FlTitlesData( + show: true, + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 60, + getTitlesWidget: leftTitles, + ), + ), + ), + borderData: FlBorderData( + show: false, + ), + gridData: FlGridData( + show: true, + getDrawingHorizontalLine: (value) => FlLine( + color: Colors.grey, + strokeWidth: 1, + ), + drawVerticalLine: false, + ), + barGroups: showingGroups(), + ); + } + + Future refreshState() async { + setState(() {}); + await Future.delayed( + animDuration + const Duration(milliseconds: 50), ); } } diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index ab2026c50..69d17fe3c 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -226,7 +226,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(15), height: 220, - child: NutritionalDiaryChartWidget(nutritionalPlan: _nutritionalPlan), // chart + child: FlNutritionalDiaryChartWidget(nutritionalPlan: _nutritionalPlan), // chart ), SizedBox( height: 200, diff --git a/test/nutrition/nutritional_plan_screen_test.dart b/test/nutrition/nutritional_plan_screen_test.dart index b2419ddad..af66d0056 100644 --- a/test/nutrition/nutritional_plan_screen_test.dart +++ b/test/nutrition/nutritional_plan_screen_test.dart @@ -81,7 +81,7 @@ void main() { expect(find.text('300g Broccoli cake'), findsOneWidget); expect(find.byType(Dismissible), findsNWidgets(2)); - expect(find.byType(NutritionalDiaryChartWidget), findsNothing); + expect(find.byType(FlNutritionalDiaryChartWidget), findsNothing); }); testWidgets('Tests the localization of times - EN', (WidgetTester tester) async { From 03f6b555afc1e6694c95296898a0e55c8ceb4272 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 8 Oct 2023 21:52:07 +0200 Subject: [PATCH 10/31] Fix order of log entries --- lib/providers/nutrition.dart | 2 +- lib/widgets/nutrition/charts.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index f6f17e8f9..789008a30 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -415,7 +415,7 @@ class NutritionPlansProvider with ChangeNotifier { final data = await baseProvider.fetchPaginated( baseProvider.makeUrl( _nutritionDiaryPath, - query: {'plan': plan.id.toString(), 'limit': '999'}, + query: {'plan': plan.id.toString(), 'limit': '999', 'ordering': 'datetime'}, ), ); diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 8e1b7399c..0a00f6095 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -470,9 +470,9 @@ class FlNutritionalDiaryChartWidgetState extends State showingGroups() { final logEntries = widget._nutritionalPlan.logEntriesValues; final List out = []; - final dateList = getDatesBetween(logEntries.keys.last, logEntries.keys.first); + final dateList = getDatesBetween(logEntries.keys.first, logEntries.keys.last); - for (final date in dateList) { + for (final date in dateList.reversed) { out.add( makeGroupData( date.millisecondsSinceEpoch, From 6bd448f17a531580405f9bd3c43ebd098f45ac08 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 8 Oct 2023 21:56:46 +0200 Subject: [PATCH 11/31] Correctly format the date according to the current locale --- lib/widgets/nutrition/charts.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index 0a00f6095..c38e8f71d 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -512,18 +512,16 @@ class FlNutritionalDiaryChartWidgetState extends State[ TextSpan( text: '${(rod.toY - 1).toStringAsFixed(0)} kcal', - style: TextStyle( - color: widget.touchedBarColor, - fontSize: 16, + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.w500, ), ), From d1af7acb32fa65f0944daa30a6b5ed3df3cb01ad Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 9 Oct 2023 19:08:56 +0200 Subject: [PATCH 12/31] Correctly format the date according to the current locale --- lib/widgets/core/charts.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index 5c4214f92..47d476ed9 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -87,7 +87,9 @@ class _MeasurementChartWidgetFlState extends State { showTitles: true, getTitlesWidget: (value, meta) { final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt() * 1000 * 60); - return Text(DateFormat.yMd().format(date)); + return Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), + ); }, interval: interval, ), From af9b73306067d22decd275aebdf61d5c15a1adef Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 9 Oct 2023 19:10:52 +0200 Subject: [PATCH 13/31] Cleanup --- lib/widgets/core/charts.dart | 21 +-------------------- lib/widgets/measurements/entries.dart | 4 +--- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index 47d476ed9..ceac2cb4d 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -54,8 +54,6 @@ class _MeasurementChartWidgetFlState extends State { LineChartData mainData() { return LineChartData( - // minY: 75, - // maxY: 90, gridData: FlGridData( show: true, drawVerticalLine: true, @@ -97,10 +95,7 @@ class _MeasurementChartWidgetFlState extends State { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - // interval: 1, - //interval: interval, - //getTitlesWidget: leftTitleWidgets, - reservedSize: 42, + reservedSize: 30, ), ), ), @@ -134,20 +129,6 @@ class MeasurementChartEntry { MeasurementChartEntry(this.value, this.date); } -// // #TODO : Del Later /// test state less >> del later -// class GetEntries extends StatelessWidget { -// final List _entries; -// const GetEntries(this._entries); - -// @override -// Widget build(BuildContext context) { -// // return old entries above - -// var entriesal = _entries.toString(); -// return Text(entriesal); -// } -// } - class Indicator extends StatelessWidget { const Indicator({ super.key, diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index c4714ccc1..22ff43ac0 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -36,10 +36,8 @@ class EntriesList extends StatelessWidget { @override Widget build(BuildContext context) { return Column(children: [ - // #TODO ! : Measurements chart > detail page chart (custom color set ) Container( - //color: Theme.of(context).cardColor, - color: Colors.lightGreenAccent, + color: Theme.of(context).cardColor, padding: const EdgeInsets.all(10), height: 220, child: MeasurementChartWidgetFl( From 268d1d945ff26a848cada44a2c9fe08f75fbc37c Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 9 Oct 2023 19:22:19 +0200 Subject: [PATCH 14/31] Render units in the measurements charts Note that the reserved size is probably too small if the unit or the values are longer --- lib/widgets/core/charts.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index ceac2cb4d..4fd223ae2 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -95,7 +95,12 @@ class _MeasurementChartWidgetFlState extends State { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 30, + reservedSize: 65, + getTitlesWidget: (value, meta) { + return Text( + '$value ${widget.unit}', + ); + }, ), ), ), From 70b63b1b2690cd655924da565a338ad53bdcb258 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 9 Oct 2023 20:22:12 +0200 Subject: [PATCH 15/31] Add first version of the repetition chart and finally remove old chart library --- lib/helpers/colors.dart | 16 ++++ lib/providers/workout_plans.dart | 2 + lib/theme/theme.dart | 3 - lib/widgets/workouts/charts.dart | 149 +++++++++++++++++++++---------- lib/widgets/workouts/log.dart | 3 +- pubspec.lock | 16 ---- pubspec.yaml | 1 - 7 files changed, 123 insertions(+), 67 deletions(-) diff --git a/lib/helpers/colors.dart b/lib/helpers/colors.dart index b7fd22885..8fb060dd7 100644 --- a/lib/helpers/colors.dart +++ b/lib/helpers/colors.dart @@ -1,3 +1,4 @@ +import 'dart:math'; import 'dart:ui'; const LIST_OF_COLORS8 = [ @@ -40,3 +41,18 @@ Iterable generateChartColors(int nrOfItems) sync* { yield color; } } + +/// Returns a random color based on the given seed +Color getRandomColor(int nrOfItems, int seed) { + final List colors; + + if (nrOfItems <= 3) { + colors = LIST_OF_COLORS3; + } else if (nrOfItems <= 5) { + colors = LIST_OF_COLORS5; + } else { + colors = LIST_OF_COLORS8; + } + + return colors[Random(seed).nextInt(colors.length)]; +} diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index 47dcea351..7b8d8fa04 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -300,6 +300,8 @@ class WorkoutPlansProvider with ChangeNotifier { query: {'id': base.id.toString()}, ), ); + // log(data.toString()); + return data; } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 89f98fb93..1ba04fac7 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'package:charts_flutter/flutter.dart' as charts; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -30,8 +29,6 @@ const Color wgerTextMuted = Colors.black38; const Color wgerBackground = Color(0xfff4f4f6); // Chart colors -const charts.Color wgerChartPrimaryColor = charts.Color(r: 0x2a, g: 0x4c, b: 0x7d); -const charts.Color wgerChartSecondaryColor = charts.Color(r: 0xe6, g: 0x39, b: 0x46); /// Original sizes for the material text theme /// https://api.flutter.dev/flutter/material/TextTheme-class.html diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index 6cfd628e9..c95dd60a5 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -16,10 +16,10 @@ * 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:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; +import 'package:wger/helpers/colors.dart'; /// Sample time series data type. class TimeSeriesLog { @@ -29,53 +29,112 @@ class TimeSeriesLog { TimeSeriesLog(this.time, this.weight); } -class LogChartWidget extends StatelessWidget { +class LogChartWidgetFl extends StatefulWidget { final Map _data; final DateTime _currentDate; - const LogChartWidget(this._data, this._currentDate); + + const LogChartWidgetFl(this._data, this._currentDate); + + @override + State createState() => _LogChartWidgetFlState(); +} + +class _LogChartWidgetFlState extends State { + final interval = 15 * Duration.millisecondsPerDay / 1000 / 60; @override Widget build(BuildContext context) { - return _data.containsKey('chart_data') && _data['chart_data'].length > 0 - ? charts.TimeSeriesChart( - [ - ..._data['chart_data'].map((e) { - return charts.Series( - id: '${e.first['reps']} ${AppLocalizations.of(context).reps}', - domainFn: (datum, index) => datum.time, - measureFn: (datum, index) => datum.weight, - data: [ - ...e.map( - (entry) => TimeSeriesLog( - DateTime.parse(entry['date']), - double.parse(entry['weight']), - ), - ), - ], - ); - }), - ], - primaryMeasureAxis: const charts.NumericAxisSpec( - tickProviderSpec: charts.BasicNumericTickProviderSpec(zeroBound: false), - ), - behaviors: [ - charts.SeriesLegend( - position: charts.BehaviorPosition.bottom, - desiredMaxColumns: 4, + 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( + 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(Localizations.localeOf(context).languageCode).format(date), + ); + }, + interval: interval, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 50, + getTitlesWidget: (value, meta) { + return Text(value.toString()); + }, + ), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all(color: const Color(0xff37434d)), + ), + lineBarsData: [ + ...widget._data['chart_data'].map( + (e) { + return LineChartBarData( + spots: [ + ...e.map((entry) => FlSpot( + DateTime.parse(entry['date']).millisecondsSinceEpoch / 1000 / 60, + double.parse(entry['weight']), + )) + ], + isCurved: false, + color: getRandomColor(widget._data['chart_data'].length, e.first['reps']), + barWidth: 2, + isStrokeCapRound: true, + dotData: FlDotData( + show: false, ), - charts.RangeAnnotation([ - charts.LineAnnotationSegment( - _currentDate, charts.RangeAnnotationAxisType.domain, - strokeWidthPx: 2, - labelPosition: charts.AnnotationLabelPosition.margin, - color: charts.Color.black, - dashPattern: [0, 1, 1, 1], - //startLabel: DateFormat.yMd(Localizations.localeOf(context).languageCode) - // .format(_currentDate), - ) - ]), - ], - ) - : Container(); + ); + }, + ) + ], + ); } } diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index c0fd30c5b..e58d0d732 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -17,7 +17,6 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:wger/helpers/ui.dart'; @@ -48,7 +47,7 @@ class ExerciseLogChart extends StatelessWidget { height: 150, child: snapshot.connectionState == ConnectionState.waiting ? const Center(child: CircularProgressIndicator()) - : LogChartWidget(snapshot.data!, _currentDate), + : LogChartWidgetFl(snapshot.data!, _currentDate), ), ); } diff --git a/pubspec.lock b/pubspec.lock index df5187e56..9f0f19360 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -201,22 +201,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - charts_common: - dependency: transitive - description: - name: charts_common - sha256: "7b8922f9b0d9b134122756a787dab1c3946ae4f3fc5022ff323ba0014998ea02" - url: "https://pub.dev" - source: hosted - version: "0.12.0" - charts_flutter: - dependency: "direct main" - description: - name: charts_flutter - sha256: "4172c3f4b85322fdffe1896ffbed79ae4689ae72cb6fe6690dcaaea620a9c558" - url: "https://pub.dev" - source: hosted - version: "0.12.0" checked_yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d92665f6f..04d8178dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,6 @@ dependencies: sdk: flutter android_metadata: ^0.2.1 - charts_flutter: ^0.12.0 collection: ^1.17.0 cupertino_icons: ^1.0.5 equatable: ^2.0.5 From bedd65e5b143516b4be3077c16ccc8d306b8318a Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 10 Oct 2023 14:05:53 +0200 Subject: [PATCH 16/31] Mucking around with interval duration --- lib/widgets/workouts/charts.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index c95dd60a5..b7a3f7ec3 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -18,6 +18,7 @@ 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/colors.dart'; @@ -40,8 +41,6 @@ class LogChartWidgetFl extends StatefulWidget { } class _LogChartWidgetFlState extends State { - final interval = 15 * Duration.millisecondsPerDay / 1000 / 60; - @override Widget build(BuildContext context) { return AspectRatio( @@ -61,6 +60,11 @@ class _LogChartWidgetFlState extends State { } LineChartData mainData() { + final dayDiff = DateTime.parse(widget._data['logs'].keys.last) + .difference(DateTime.parse(widget._data['logs'].keys.first)); + + final interval = dayDiff.inDays * 1.3 * Duration.millisecondsPerDay; + return LineChartData( gridData: FlGridData( show: true, @@ -92,7 +96,7 @@ class _LogChartWidgetFlState extends State { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { - final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt() * 1000 * 60); + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); return Text( DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); @@ -103,9 +107,9 @@ class _LogChartWidgetFlState extends State { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 50, + reservedSize: 70, getTitlesWidget: (value, meta) { - return Text(value.toString()); + return Text('$value ${AppLocalizations.of(context).kg}'); }, ), ), @@ -120,7 +124,7 @@ class _LogChartWidgetFlState extends State { return LineChartBarData( spots: [ ...e.map((entry) => FlSpot( - DateTime.parse(entry['date']).millisecondsSinceEpoch / 1000 / 60, + DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), double.parse(entry['weight']), )) ], From 511cde166924556eae158492df1b3d792b279b2a Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 11:07:35 +0100 Subject: [PATCH 17/31] Remove fl_chart_line.dart, not used --- lib/widgets/core/fl_chart_line.dart | 390 ---------------------------- 1 file changed, 390 deletions(-) delete mode 100644 lib/widgets/core/fl_chart_line.dart diff --git a/lib/widgets/core/fl_chart_line.dart b/lib/widgets/core/fl_chart_line.dart deleted file mode 100644 index 1eb4b007b..000000000 --- a/lib/widgets/core/fl_chart_line.dart +++ /dev/null @@ -1,390 +0,0 @@ -// Fl line chart [SAmple 2 ] for testing -// // #TODO : I may move this content to the core widget file -// - -//import 'package:fl_chart_app/presentation/resources/app_resources.dart'; -import 'dart:math'; - -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:wger/helpers/consts.dart'; -import 'package:wger/models/measurements/measurement_entry.dart'; -import 'package:wger/widgets/core/charts.dart'; -//import 'package:intl/intl.dart'; -//import 'package:wger/widgets/core/charts.dart'; - -import 'package:wger/helpers/consts.dart'; - -import 'package:intl/intl.dart'; - -class MeasurementChartEntryflchat { - num value; // this needs to - DateTime date; - - MeasurementChartEntryflchat(this.value, this.date); -} - -class LineChartSample2 extends StatefulWidget { - //#TODO : Substitute below entries to the old entries for data return comapraison !! - final List allentries; - final String unit; - - // entries recieved from original measurement entry (old) - //final List _entries; - - const LineChartSample2(this.allentries, {this.unit = 'kg'}); - - //const LineChartSample2({super.key}); - - @override - State createState() => _LineChartSample2State(); -} - -class AppColors { - static const Color primary = contentColorCyan; - static const Color menuBackground = Color(0xFF090912); - static const Color itemsBackground = Color(0xFF1B2339); - static const Color pageBackground = Color(0xFF282E45); - static const Color mainTextColor1 = Colors.white; - static const Color mainTextColor2 = Colors.white70; - static const Color mainTextColor3 = Colors.white38; - static const Color mainGridLineColor = Colors.white10; - static const Color borderColor = Colors.white54; - static const Color gridLinesColor = Color(0x11FFFFFF); - - static const Color contentColorBlack = Colors.black; - static const Color contentColorWhite = Colors.white; - static const Color contentColorBlue = Color(0xFF2196F3); - static const Color contentColorYellow = Color(0xFFFFC300); - static const Color contentColorOrange = Color(0xFFFF683B); - static const Color contentColorGreen = Color(0xFF3BFF49); - static const Color contentColorPurple = Color(0xFF6E1BFF); - static const Color contentColorPink = Color(0xFFFF3AF2); - static const Color contentColorRed = Color(0xFFE80054); - static const Color contentColorCyan = Color(0xFF50E4FF); -} - -class _LineChartSample2State extends State { - List gradientColors = [ - AppColors.contentColorCyan, - AppColors.contentColorBlue, - ]; - - bool showAvg = false; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - AspectRatio( - aspectRatio: 1.70, - child: Padding( - padding: const EdgeInsets.only( - right: 18, - left: 12, - top: 24, - bottom: 12, - ), - child: LineChart( - showAvg ? avgData() : mainData(), - ), - ), - ), - SizedBox( - width: 60, - height: 34, - child: TextButton( - onPressed: () { - setState(() { - showAvg = !showAvg; - }); - }, - child: Text( - 'avg', - style: TextStyle( - fontSize: 12, - color: showAvg ? Colors.white.withOpacity(0.5) : Colors.white, - ), - ), - ), - ), - ], - ); - } - - // #TODO : Automatic dates on (x axis) - - SideTitles _bottomTitles() { - return SideTitles( - showTitles: true, - interval: 1, - getTitlesWidget: (value, meta) { - // // #TODO : format dates same as old chart - final getdates = - value.toInt() < widget.allentries.length ? widget.allentries[value.toInt()].date : ""; - - // final datesToStr = getdates.toString(); - - // final tempDate = DateTime.parse(getdates.toString()); - // //debugPrint(('tempDates \n' ) + tempDate.toString() ); - - // final finaldates = DateFormat('MM-dd').format(tempDate); - // debugPrint(('FinaleDates \n') + finaldates); - //final datestoformat = DateFormatLists.format(tempDate); - - // // # used built in above fun to return date format as (yyyy-mm-dd) - // debugPrint(datestoformat); - - // #BUG : if dates are formated -- the chart causes error (invalid dates format) - return SideTitleWidget(axisSide: meta.axisSide, child: Text(getdates.toString())); - }, - ); - } - - // Widget bottomTitleWidgets(double value, TitleMeta meta) { - // const style = TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 16, - // ); - - // List datesgeneric = [ - // 'Sun', - // 'Feb', - // 'Oct', - // 'Nov', - // ]; - - // //MeasurementChartEntry entry ; - // // Widget text; - // // switch (value.toInt()) { - // // case 2: - // // text = const Text('MAR', style: style); - // // break; - // // case 5: - // // text = const Text('JUN', style: style); - // // break; - // // case 8: - // // text = const Text('SEP', style: style); - // // break; - // // default: - // // text = const Text('', style: style); - // // break; - // // } - - // return SideTitleWidget( - // axisSide: meta.axisSide, - // child:Text('$datesgeneric'), - // ); - // } - - - // #TODO : needs to be changed (values for (y) axis) - Widget leftTitleWidgets(double value, TitleMeta meta) { - const style = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ); - - String text; - switch (value.toInt()) { - case 1: - text = '10K'; - break; - case 3: - text = '30k'; - break; - case 5: - text = '50k'; - break; - default: - return Container(); - } - - return Text(text, style: style, textAlign: TextAlign.left); - } - - LineChartData mainData() { - return LineChartData( - gridData: FlGridData( - show: true, - drawVerticalLine: true, - horizontalInterval: 1, - verticalInterval: 1, - getDrawingHorizontalLine: (value) { - return FlLine( - color: AppColors.mainGridLineColor, - strokeWidth: 1, - ); - }, - getDrawingVerticalLine: (value) { - return FlLine( - color: AppColors.mainGridLineColor, - strokeWidth: 1, - ); - }, - ), - titlesData: FlTitlesData( - show: true, - rightTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - bottomTitles: AxisTitles( - sideTitles: _bottomTitles(), - ), - - // #FIXME : Custom bottom titles for dates entry - // bottomTitles: AxisTitles( - // sideTitles: SideTitles( - // showTitles: true, - // reservedSize: 30, - // interval: 1, - // getTitlesWidget: bottomTitleWidgets, - // ), - // ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: 1, - getTitlesWidget: leftTitleWidgets, - reservedSize: 42, - ), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: const Color(0xff37434d)), - ), - minX: 0, - maxX: 11, - minY: 0, - maxY: 6, - lineBarsData: [ - LineChartBarData( - // spots: const [ - // FlSpot(0, 3), - // FlSpot(2.6, 2), - // FlSpot(4.9, 5), - // FlSpot(6.8, 3.1), - // FlSpot(8, 4), - // FlSpot(9.5, 3), - // FlSpot(11, 4), - // ], - isCurved: true, - gradient: LinearGradient( - colors: gradientColors, - ), - barWidth: 5, - isStrokeCapRound: true, - dotData: FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(), - ), - ), - ), - ], - ); - } - - LineChartData avgData() { - return LineChartData( - lineTouchData: LineTouchData(enabled: false), - gridData: FlGridData( - show: true, - drawHorizontalLine: true, - verticalInterval: 1, - horizontalInterval: 1, - getDrawingVerticalLine: (value) { - return FlLine( - color: Color(0xff37434d), - strokeWidth: 1, - ); - }, - getDrawingHorizontalLine: (value) { - return FlLine( - color: Color(0xff37434d), - strokeWidth: 1, - ); - }, - ), - titlesData: FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: _bottomTitles(), - // sideTitles: SideTitles( - // showTitles: true, - // reservedSize: 30, - // getTitlesWidget: _, - // interval: 1, - // ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: leftTitleWidgets, - reservedSize: 42, - interval: 1, - ), - ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: const Color(0xff37434d)), - ), - minX: 0, - maxX: 11, - minY: 0, - maxY: 6, - lineBarsData: [ - LineChartBarData( - // spots: const [ - // FlSpot(0, 3.44), - // FlSpot(2.6, 3.44), - // FlSpot(4.9, 3.44), - // FlSpot(6.8, 3.44), - // FlSpot(8, 3.44), - // FlSpot(9.5, 3.44), - // FlSpot(11, 3.44), - // ], - isCurved: true, - gradient: LinearGradient( - colors: [ - ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, - ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, - ], - ), - barWidth: 5, - isStrokeCapRound: true, - dotData: FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - ColorTween(begin: gradientColors[0], end: gradientColors[1]) - .lerp(0.2)! - .withOpacity(0.1), - ColorTween(begin: gradientColors[0], end: gradientColors[1]) - .lerp(0.2)! - .withOpacity(0.1), - ], - ), - ), - ), - ], - ); - } -} From 005f4d3db71ab264438a8cfaadf1e63e8087c206 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 11:56:38 +0100 Subject: [PATCH 18/31] Better intervals While this still doesn't look as nice and centered as I would like to, it is at least an improvement --- lib/helpers/charts.dart | 4 ++++ lib/widgets/core/charts.dart | 14 +++++++++----- lib/widgets/workouts/charts.dart | 19 +++++++++++-------- lib/widgets/workouts/log.dart | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 lib/helpers/charts.dart diff --git a/lib/helpers/charts.dart b/lib/helpers/charts.dart new file mode 100644 index 000000000..522d059dc --- /dev/null +++ b/lib/helpers/charts.dart @@ -0,0 +1,4 @@ +double chartGetInterval(DateTime first, DateTime last, {divider: 3}) { + final dayDiff = last.difference(first); + return dayDiff.inMilliseconds.toDouble() / 3; +} diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index 4fd223ae2..0b62ed2d5 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -19,6 +19,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:wger/helpers/charts.dart'; import 'package:wger/theme/theme.dart'; class MeasurementChartWidgetFl extends StatefulWidget { @@ -32,8 +33,6 @@ class MeasurementChartWidgetFl extends StatefulWidget { } class _MeasurementChartWidgetFlState extends State { - final interval = 15 * Duration.millisecondsPerDay / 1000 / 60; - @override Widget build(BuildContext context) { return AspectRatio( @@ -84,12 +83,17 @@ class _MeasurementChartWidgetFlState extends State { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { - final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt() * 1000 * 60); + // Don't show the first and last entries, otherwise they'll overlap with the + // calculated interval + if (value == meta.min || value == meta.max) { + return const Text(''); + } + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); return Text( DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); }, - interval: interval, + interval: chartGetInterval(widget._entries.last.date, widget._entries.first.date), ), ), leftTitles: AxisTitles( @@ -112,7 +116,7 @@ class _MeasurementChartWidgetFlState extends State { LineChartBarData( spots: [ ...widget._entries - .map((e) => FlSpot(e.date.millisecondsSinceEpoch / 1000 / 60, e.value.toDouble())) + .map((e) => FlSpot(e.date.millisecondsSinceEpoch.toDouble(), e.value.toDouble())) ], isCurved: false, color: wgerSecondaryColor, diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index b7a3f7ec3..082830470 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -20,6 +20,7 @@ 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'; import 'package:wger/helpers/colors.dart'; /// Sample time series data type. @@ -47,8 +48,6 @@ class _LogChartWidgetFlState extends State { aspectRatio: 1.70, child: Padding( padding: const EdgeInsets.only( - right: 18, - left: 12, top: 24, bottom: 12, ), @@ -60,11 +59,6 @@ class _LogChartWidgetFlState extends State { } LineChartData mainData() { - final dayDiff = DateTime.parse(widget._data['logs'].keys.last) - .difference(DateTime.parse(widget._data['logs'].keys.first)); - - final interval = dayDiff.inDays * 1.3 * Duration.millisecondsPerDay; - return LineChartData( gridData: FlGridData( show: true, @@ -96,12 +90,21 @@ class _LogChartWidgetFlState extends State { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { + // Don't show the first and last entries, otherwise they'll overlap with the + // calculated interval + if (value == meta.min || value == meta.max) { + return const Text(''); + } + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); return Text( DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); }, - interval: interval, + interval: chartGetInterval( + DateTime.parse(widget._data['logs'].keys.first), + DateTime.parse(widget._data['logs'].keys.last), + ), ), ), leftTitles: AxisTitles( diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index e58d0d732..2ed27940e 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -44,7 +44,7 @@ class ExerciseLogChart extends StatelessWidget { return FutureBuilder( future: getChartEntries(context), builder: (context, AsyncSnapshot> snapshot) => SizedBox( - height: 150, + height: 190, child: snapshot.connectionState == ConnectionState.waiting ? const Center(child: CircularProgressIndicator()) : LogChartWidgetFl(snapshot.data!, _currentDate), From 67f727867c81497ccc172057ead2780b45e7784e Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 14:52:35 +0100 Subject: [PATCH 19/31] Some improvements to interval handling --- lib/helpers/charts.dart | 5 +++-- lib/widgets/core/charts.dart | 4 +++- test/measurements/measurement_entries_screen_test.dart | 10 +++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/helpers/charts.dart b/lib/helpers/charts.dart index 522d059dc..64a0b6e84 100644 --- a/lib/helpers/charts.dart +++ b/lib/helpers/charts.dart @@ -1,4 +1,5 @@ -double chartGetInterval(DateTime first, DateTime last, {divider: 3}) { +double chartGetInterval(DateTime first, DateTime last, {divider = 3}) { final dayDiff = last.difference(first); - return dayDiff.inMilliseconds.toDouble() / 3; + + return dayDiff.inMilliseconds == 0 ? 1000 : dayDiff.inMilliseconds.abs() / divider; } diff --git a/lib/widgets/core/charts.dart b/lib/widgets/core/charts.dart index 0b62ed2d5..1a33cf98f 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/core/charts.dart @@ -93,7 +93,9 @@ class _MeasurementChartWidgetFlState extends State { DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); }, - interval: chartGetInterval(widget._entries.last.date, widget._entries.first.date), + interval: widget._entries.isNotEmpty + ? chartGetInterval(widget._entries.last.date, widget._entries.first.date) + : 1000, ), ), leftTitles: AxisTitles( diff --git a/test/measurements/measurement_entries_screen_test.dart b/test/measurements/measurement_entries_screen_test.dart index 6182ab373..321a94bf7 100644 --- a/test/measurements/measurement_entries_screen_test.dart +++ b/test/measurements/measurement_entries_screen_test.dart @@ -73,8 +73,8 @@ void main() { expect(find.text('body fat'), findsOneWidget); // Entries - expect(find.text('10.2 %'), findsOneWidget); - expect(find.text('18.1 %'), findsOneWidget); + expect(find.text('10.2 %'), findsNWidgets(2)); + expect(find.text('18.1 %'), findsNWidgets(2)); }); testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async { @@ -83,8 +83,8 @@ void main() { await tester.pumpAndSettle(); // From the entries list and from the chart - expect(find.text('8/1/2021'), findsNWidgets(3)); - expect(find.text('8/10/2021'), findsNWidgets(2)); + expect(find.text('8/1/2021'), findsNWidgets(2)); + expect(find.text('8/10/2021'), findsOneWidget); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { @@ -92,7 +92,7 @@ void main() { await tester.tap(find.byType(TextButton)); await tester.pumpAndSettle(); - expect(find.text('1.8.2021'), findsOneWidget); + expect(find.text('1.8.2021'), findsNWidgets(2)); expect(find.text('10.8.2021'), findsOneWidget); }); } From 6a9ebadb73afac708107fd6f750915eee730bf18 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 15:12:32 +0100 Subject: [PATCH 20/31] Correctly set the colors for the training logs --- lib/widgets/workouts/charts.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index 082830470..621f0fb6d 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -59,12 +59,12 @@ class _LogChartWidgetFlState extends State { } LineChartData mainData() { + final colors = generateChartColors(widget._data['chart_data'].length).iterator; + return LineChartData( gridData: FlGridData( show: true, drawVerticalLine: true, - //horizontalInterval: 1, - //verticalInterval: interval, getDrawingHorizontalLine: (value) { return FlLine( color: Colors.grey, @@ -124,15 +124,18 @@ class _LogChartWidgetFlState extends State { lineBarsData: [ ...widget._data['chart_data'].map( (e) { + colors.moveNext(); return LineChartBarData( spots: [ - ...e.map((entry) => FlSpot( - DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), - double.parse(entry['weight']), - )) + ...e.map( + (entry) => FlSpot( + DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), + double.parse(entry['weight']), + ), + ) ], isCurved: false, - color: getRandomColor(widget._data['chart_data'].length, e.first['reps']), + color: colors.current, barWidth: 2, isStrokeCapRound: true, dotData: FlDotData( From 2ff02c0935a257e3a0b87efd72ef8ce3574867aa Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 15:14:19 +0100 Subject: [PATCH 21/31] Fix test --- test/weight/weight_screen_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index c95c1c049..96b52f87e 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -84,8 +84,8 @@ void main() { await tester.pumpWidget(createWeightScreen()); // One in the entries list, one in the chart - expect(find.text('1/1/2021'), findsNWidgets(2)); - expect(find.text('1/10/2021'), findsNWidgets(2)); + expect(find.text('1/1/2021'), findsOneWidget); + expect(find.text('1/10/2021'), findsOneWidget); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { From 4fcccf2f61afa862c832a3b71788ed7393a374ac Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 5 Nov 2023 15:20:01 +0100 Subject: [PATCH 22/31] Change tester to findsWidgets For some reason this tests behaves differently on github actions vs locally --- test/measurements/measurement_entries_screen_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/measurements/measurement_entries_screen_test.dart b/test/measurements/measurement_entries_screen_test.dart index 321a94bf7..20b9b5dcd 100644 --- a/test/measurements/measurement_entries_screen_test.dart +++ b/test/measurements/measurement_entries_screen_test.dart @@ -83,8 +83,8 @@ void main() { await tester.pumpAndSettle(); // From the entries list and from the chart - expect(find.text('8/1/2021'), findsNWidgets(2)); - expect(find.text('8/10/2021'), findsOneWidget); + expect(find.text('8/1/2021'), findsWidgets); + expect(find.text('8/10/2021'), findsWidgets); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { @@ -92,7 +92,7 @@ void main() { await tester.tap(find.byType(TextButton)); await tester.pumpAndSettle(); - expect(find.text('1.8.2021'), findsNWidgets(2)); - expect(find.text('10.8.2021'), findsOneWidget); + expect(find.text('1.8.2021'), findsWidgets); + expect(find.text('10.8.2021'), findsWidgets); }); } From 3d948dfec43326c2d09bafd75e22b3ca1367d7ee Mon Sep 17 00:00:00 2001 From: Abhishek Saini <78199221+Abhisheksainii@users.noreply.github.com> Date: Sun, 5 Nov 2023 22:23:16 +0530 Subject: [PATCH 23/31] measurement and nutrition chart issues fixes --- .github/pull_request_template.md | 20 +- AUTHORS.md | 1 + README.md | 8 +- android/build.gradle | 2 +- .../metadata/android/hr/full_description.txt | 8 +- lib/helpers/charts.dart | 5 + lib/l10n/app_de.arb | 20 +- lib/l10n/app_el.arb | 20 +- lib/l10n/app_hr.arb | 2 +- lib/l10n/app_pt_BR.arb | 444 +++++++++++++++++- lib/providers/workout_plans.dart | 1 - lib/widgets/core/app_bar.dart | 4 +- lib/widgets/core/fl_chart_line.dart | 5 +- lib/widgets/dashboard/widgets.dart | 2 +- lib/widgets/measurements/categories_card.dart | 2 +- .../{core => measurements}/charts.dart | 28 +- lib/widgets/measurements/entries.dart | 2 +- lib/widgets/nutrition/charts.dart | 2 +- .../nutrition/nutritional_plan_detail.dart | 36 +- lib/widgets/weight/entries_list.dart | 2 +- lib/widgets/workouts/app_bar.dart | 2 +- lib/widgets/workouts/charts.dart | 34 +- lib/widgets/workouts/log.dart | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.lock | 66 +-- pubspec.yaml | 9 +- .../measurement_categories_screen_test.dart | 2 +- .../measurement_entries_screen_test.dart | 12 +- test/weight/weight_screen_test.dart | 6 +- 29 files changed, 657 insertions(+), 92 deletions(-) create mode 100644 lib/helpers/charts.dart rename lib/widgets/{core => measurements}/charts.dart (83%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 28eea257a..4cf8375ad 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,15 +1,25 @@ -# Proposed Changes +## Description (Proposed Changes) +(Please try to mention in bullet points.) -- - - -Related Issues (if applicable) +## Link to the issue : -- +(Add link of the issue you have proposed changes to) + +- Link : + +## Tests + +Please make sure to add tests when implementing new features. + +## Checklist -## Please check that the PR fulfills these requirements +Please check that the PR fulfills all requirements listed below by checking the relevant checkboxes (`[x]`). This will ensure a smooth and quick review process. - [ ] Set a 100 character limit in your editor/IDE to avoid white space diffs in the PR - [ ] Tests for the changes have been added (for bug fixes / features) - [ ] Added yourself to AUTHORS.md +- [ ] Updated/added relevant documentation (doc comments with `///`). +- [ ] Added relevant reviewers. diff --git a/AUTHORS.md b/AUTHORS.md index 4c2d018fc..cbcbda3e7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ - Miroslav Mazel - - artchiee - - Tejas Bir Singh - +- Abhishek Saini - ## Translators diff --git a/README.md b/README.md index dc5e37fb7..0e22ef2db 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,13 @@ height="80">](https://f-droid.org/packages/de.wger.flutter/) Install the [wger server](https://github.com/wger-project/wger), the easiest way is to start the development docker-compose: -Alternatively, you can use one of our test servers, just ask us for access. +Alternatively, you can use the test server (the db is reset every day): + +* URL: `https://wger-master.rge.uber.space` +* username: `user` +* password: `flutteruser` +* API key: `31e2ea0322c07b9df583a9b6d1e794f7139e78d4` + ### 2 diff --git a/android/build.gradle b/android/build.gradle index 58a8c74b1..713d7f6e6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/fastlane/metadata/android/hr/full_description.txt b/fastlane/metadata/android/hr/full_description.txt index 2fe31212a..72d819ac6 100644 --- a/fastlane/metadata/android/hr/full_description.txt +++ b/fastlane/metadata/android/hr/full_description.txt @@ -1,4 +1,4 @@ -Od ljubitelja fitnessa za ljubitelja fitnessa – organiziraj svoje zdravlje s WGER, tvojim upravljaöem treninga! +Od ljubitelja fitnessa za ljubitelje fitnessa – organiziraj svoje zdravlje s WGER, tvojim upravljačem treninga! Već si pronašao/la omiljeni program za fitness i voliš stvarati vlastite sportske rutine? Bez obzira na vrstu sportske zvijeri – svi imamo nešto zajedničko: Volimo pratiti naše zdravstvene podatke <3 @@ -8,9 +8,9 @@ Razvili smo 100 % besplatan program za digitalno praćenje zdravlja i fitnessa, wger je projekt otvorenog koda za: * Tvoje tijelo -* Tvoje treninge +* Tvoji treninzi * Tvoj napredak -* Tvoje podatke +* Tvoji podaci Tvoje tijelo: Nema potrebe za guglanjem sastojaka omiljenih poslastica – odaberi dnevne obroke od više od 78.000 proizvoda i pogledaj nutritivne vrijednosti. Dodaj obroke u plan prehrane i čuvaj pregled svoje prehrane u kalendaru. @@ -22,7 +22,7 @@ Tvoj napredak: Nikada ne gubi iz vida svoje ciljeve. Prati svoju težinu i vodi statistiku. Tvoji podaci: -wger je tvoj personalizirani dnevnik fitnessa – ali ti posjeduješ svoje podatke. Koristi REST API za pristup i obavi s njim nevjerojatnih stvari. +wger je tvoj personalizirani dnevnik fitnessa – ali ti posjeduješ svoje podatke. Koristi REST API za pristup i obavi s njim nevjerojatne stvari. Napomena: Ovaj besplatni program ne temelji se na dodatnim sredstvima i ne tražimo da doniraš novac. Više od toga, ovo je projekt zajednice koji stalno raste. Stoga budi spreman/na za nove funkcije u bilo kojem trenutku! diff --git a/lib/helpers/charts.dart b/lib/helpers/charts.dart new file mode 100644 index 000000000..64a0b6e84 --- /dev/null +++ b/lib/helpers/charts.dart @@ -0,0 +1,5 @@ +double chartGetInterval(DateTime first, DateTime last, {divider = 3}) { + final dayDiff = last.difference(first); + + return dayDiff.inMilliseconds == 0 ? 1000 : dayDiff.inMilliseconds.abs() / divider; +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 43b35d5be..6dc0eb90c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -730,5 +730,23 @@ "swiss_ball": "Gymnastikball", "@swiss_ball": {}, "none__bodyweight_exercise_": "keine (Körpergewichtsübung)", - "@none__bodyweight_exercise_": {} + "@none__bodyweight_exercise_": {}, + "body_weight": "Körpergewicht", + "@body_weight": { + "description": "Generated entry for translation for server strings" + }, + "kg": "kg", + "@kg": { + "description": "Generated entry for translation for server strings" + }, + "kilometers_per_hour": "Kilometer pro Stunde", + "@kilometers_per_hour": { + "description": "Generated entry for translation for server strings" + }, + "miles_per_hour": "Meilen pro Stunde", + "@miles_per_hour": { + "description": "Generated entry for translation for server strings" + }, + "verify": "Verifizieren", + "@verify": {} } diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb index 539e93bf2..afbf83238 100644 --- a/lib/l10n/app_el.arb +++ b/lib/l10n/app_el.arb @@ -136,5 +136,23 @@ "description": "Error message when the user enters an invalid email" }, "confirmPassword": "Επιβεβαίωση κωδικού πρόσβασης", - "@confirmPassword": {} + "@confirmPassword": {}, + "images": "Εικόνες", + "@images": {}, + "save": "Αποθήκευση", + "@save": {}, + "description": "Περιγραφή", + "@description": {}, + "name": "Όνομα", + "@name": { + "description": "Name for a workout or nutritional plan" + }, + "translation": "Μετάφραση", + "@translation": {}, + "language": "Γλώσσα", + "@language": {}, + "category": "Κατηγορία", + "@category": { + "description": "Category for an exercise, ingredient, etc." + } } diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 98cda53b7..29975a6ed 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -91,7 +91,7 @@ "@exercise": { "description": "An exercise for a workout" }, - "logHelpEntriesUnits": "Imaj na umu da su ovdje ucrtani samo unosi s jedinicom težine (kg ili lb) i ponavljanjima, druge kombinacije kao što su vrijeme ili do neuspjeha se ovdje zanemaruju.", + "logHelpEntriesUnits": "Napomena: prikazuju se samo unosi s jedinicom težine (kg ili lb) i ponavljanja, druge kombinacije kao što su vrijeme ili do neuspjeha se ovdje zanemaruju.", "@logHelpEntriesUnits": {}, "description": "Opis", "@description": {}, diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index 986dfb29c..838b0fd13 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -40,5 +40,447 @@ "register": "Inscrever-se", "@register": { "description": "Text for registration button" - } + }, + "useDefaultServer": "Usar servidor padrão", + "@useDefaultServer": { + "description": "Toggle button allowing users to switch between the default and a custom wger server" + }, + "reset": "Redefinir", + "@reset": { + "description": "Button text allowing the user to reset the entered values to the default" + }, + "password": "Senha", + "@password": {}, + "invalidUsername": "Por favor insira um nome de usuário válido", + "@invalidUsername": { + "description": "Error message when the user enters an invalid username" + }, + "passwordTooShort": "A senha é muito curta", + "@passwordTooShort": { + "description": "Error message when the user a password that is too short" + }, + "email": "Endereço de email", + "@email": {}, + "username": "Usuário", + "@username": {}, + "customServerHint": "Digite o endereço do seu próprio servidor, caso contrário o padrão será usado", + "@customServerHint": { + "description": "Hint text for the form where the users can enter their own wger instance" + }, + "useCustomServer": "Usar servidor customizado", + "@useCustomServer": { + "description": "Toggle button allowing users to switch between the default and a custom wger server" + }, + "registerInstead": "Não tem uma conta? Registrar agora", + "@registerInstead": {}, + "usernameValidChars": "Um usuário deve apenas conter letras, digitos e os caracteres @, +, ., -, ou _", + "@usernameValidChars": { + "description": "Error message when the user tries to register a username with forbidden characters" + }, + "invalidUrl": "Por favor, digite uma URL válida", + "@invalidUrl": { + "description": "Error message when the user enters an invalid URL, e.g. in the login form" + }, + "customServerUrl": "URL da instância wger", + "@customServerUrl": { + "description": "Label in the form where the users can enter their own wger instance" + }, + "passwordsDontMatch": "As senhas não coincidem", + "@passwordsDontMatch": { + "description": "Error message when the user enters two different passwords during registration" + }, + "invalidEmail": "Por favor insira um endereço de e-mail válido", + "@invalidEmail": { + "description": "Error message when the user enters an invalid email" + }, + "confirmPassword": "Confirme sua senha", + "@confirmPassword": {}, + "comment": "Comente", + "@comment": { + "description": "Comment, additional information" + }, + "logIngredient": "Salvar no diário nutricional", + "@logIngredient": {}, + "equipment": "Equipamento", + "@equipment": { + "description": "Equipment needed to perform an exercise" + }, + "saturatedFat": "Gordura saturada", + "@saturatedFat": {}, + "mealLogged": "Refeição registrada no diário", + "@mealLogged": {}, + "images": "Imagens", + "@images": {}, + "close": "Fechar", + "@close": { + "description": "Translation for close" + }, + "successfullyDeleted": "Excluído", + "@successfullyDeleted": { + "description": "Message when an item was successfully deleted" + }, + "save": "Salvar", + "@save": {}, + "goToToday": "Vá para hoje", + "@goToToday": { + "description": "Label on button to jump back to 'today' in the calendar widget" + }, + "enterRepetitionsOrWeight": "Por favor preencha as repetições ou o peso para pelo menos uma das séries", + "@enterRepetitionsOrWeight": { + "description": "Error message when the user hasn't filled in the forms for exercise sets" + }, + "set": "Definir", + "@set": { + "description": "A set in a workout plan" + }, + "noMeasurementEntries": "Você não tem entradas de medição", + "@noMeasurementEntries": {}, + "newSet": "Novo conjunto", + "@newSet": { + "description": "Header when adding a new set to a workout day" + }, + "impression": "Impressão", + "@impression": { + "description": "General impression (e.g. for a workout session) such as good, bad, etc." + }, + "plateCalculator": "Pratos", + "@plateCalculator": { + "description": "Label used for the plate calculator in the gym mode" + }, + "newNutritionalPlan": "Novo plano nutricional", + "@newNutritionalPlan": {}, + "setNr": "Definir {nr}", + "@setNr": { + "description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.", + "type": "text", + "placeholders": { + "nr": {} + } + }, + "fat": "Gordura", + "@fat": {}, + "timeStartAhead": "O horário de início não pode ser anterior ao horário de término", + "@timeStartAhead": {}, + "carbohydrates": "Carboidratos", + "@carbohydrates": {}, + "noWorkoutPlans": "Você não tem planos de treino", + "@noWorkoutPlans": { + "description": "Message shown when the user has no workout plans" + }, + "aboutTranslationTitle": "Tradução", + "@aboutTranslationTitle": { + "description": "Title for translation section in the about dialog" + }, + "muscles": "Músculos", + "@muscles": { + "description": "(main) muscles trained by an exercise" + }, + "total": "Total", + "@total": { + "description": "Label used for total sums of e.g. calories or similar" + }, + "dayDescriptionHelp": "Uma descrição do que é feito neste dia (por exemplo, 'dia de puxar') ou quais partes do corpo são treinadas (por exemplo, 'peito e ombros')", + "@dayDescriptionHelp": {}, + "gymMode": "Modo Gym", + "@gymMode": { + "description": "Label when starting the gym mode" + }, + "logged": "Desconectar", + "@logged": { + "description": "Header for the column of 'logged' nutritional values, i.e. what was eaten" + }, + "amount": "Quantia", + "@amount": { + "description": "The amount (e.g. in grams) of an ingredient in a meal" + }, + "loginInstead": "Já tem uma conta ? Entre", + "@loginInstead": {}, + "pause": "Pausar", + "@pause": { + "description": "Noun, not an imperative! Label used for the pause when using the gym mode" + }, + "success": "Sucesso", + "@success": { + "description": "Message when an action completed successfully, usually used as a heading" + }, + "repetitionUnit": "Unidade de repetição", + "@repetitionUnit": {}, + "weightUnit": "Unidade de peso", + "@weightUnit": {}, + "searchIngredient": "Pesquisar Ingrediente", + "@searchIngredient": { + "description": "Label on ingredient search form" + }, + "aboutBugsText": "Entre em contato se algo não se comportou conforme o esperado ou se houver algum recurso que você acha que está faltando.", + "@aboutBugsText": { + "description": "Text for bugs section in the about dialog" + }, + "anErrorOccurred": "Ocorreu um erro!", + "@anErrorOccurred": {}, + "aboutContactUsTitle": "Diga oi!", + "@aboutContactUsTitle": { + "description": "Title for contact us section in the about dialog" + }, + "enterValue": "por favor insira um valor", + "@enterValue": { + "description": "Error message when the user hasn't entered a value on a required field" + }, + "logMeal": "Registrar esta refeição", + "@logMeal": {}, + "newEntry": "Nova entrada", + "@newEntry": { + "description": "Title when adding a new entry such as a weight or log entry" + }, + "addSet": "Adicionar set", + "@addSet": { + "description": "Label for the button that adds a set (to a workout day)" + }, + "newWorkout": "Novo plano de treino", + "@newWorkout": { + "description": "Header when adding a new workout" + }, + "energyShort": "E", + "@energyShort": { + "description": "The first letter or short name of the word 'Energy', used in overviews" + }, + "name": "Nome", + "@name": { + "description": "Name for a workout or nutritional plan" + }, + "percentEnergy": "Porcentagem de energia", + "@percentEnergy": {}, + "searchNamesInEnglish": "Pesquise também por nomes em inglês", + "@searchNamesInEnglish": {}, + "exercise": "Exercício", + "@exercise": { + "description": "An exercise for a workout" + }, + "addIngredient": "Adicionar ingrediente", + "@addIngredient": {}, + "fatShort": "G", + "@fatShort": { + "description": "The first letter or short name of the word 'Fat', used in overviews" + }, + "start": "Começar", + "@start": { + "description": "Label on button to start the gym mode (i.e., an imperative)" + }, + "jumpTo": "Pule para", + "@jumpTo": { + "description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode" + }, + "difference": "Diferença", + "@difference": {}, + "fibres": "Fibra", + "@fibres": {}, + "aboutDescription": "Obrigado por usar o Wger! Wger é um projeto colaborativo de código aberto, feito por entusiastas do fitness de todo o mundo.", + "@aboutDescription": { + "description": "Text in the about dialog" + }, + "timeStart": "Hora de início", + "@timeStart": { + "description": "The starting time of a workout" + }, + "searchExercise": "Exercício de pesquisa para adicionar", + "@searchExercise": { + "description": "Label on set form. Selected exercises are added to the set" + }, + "moreMeasurementEntries": "Adicionar nova medição", + "@moreMeasurementEntries": { + "description": "Message shown when the user wants to add new measurement" + }, + "loadingText": "Carregando...", + "@loadingText": { + "description": "Text to show when entries are being loaded in the background: Loading..." + }, + "selectExercises": "Se quiser fazer um superset você pode procurar vários exercícios, eles estarão agrupados", + "@selectExercises": {}, + "nutritionalDiary": "Diário nutricional", + "@nutritionalDiary": {}, + "protein": "Proteína", + "@protein": {}, + "labelWorkoutPlans": "Planos de treino", + "@labelWorkoutPlans": { + "description": "Title for screen workout plans" + }, + "proteinShort": "P", + "@proteinShort": { + "description": "The first letter or short name of the word 'Protein', used in overviews" + }, + "noWeightEntries": "Você não tem entradas de peso", + "@noWeightEntries": { + "description": "Message shown when the user has no logged weight entries" + }, + "noNutritionalPlans": "Você não tem planos nutricionais", + "@noNutritionalPlans": { + "description": "Message shown when the user has no nutritional plans" + }, + "goToDetailPage": "Ir para a página de detalhes", + "@goToDetailPage": {}, + "labelWorkoutLogs": "Registros de treinos", + "@labelWorkoutLogs": { + "description": "(Workout) logs" + }, + "aboutTranslationText": "Este aplicativo está traduzido no weblate. Se você também quiser ajudar, clique no link e comece a traduzir", + "@aboutTranslationText": { + "description": "Text for translation section in the about dialog" + }, + "ingredient": "Ingrediente", + "@ingredient": {}, + "measurementCategoriesHelpText": "Categoria de medição, como 'bíceps' ou 'gordura corporal'", + "@measurementCategoriesHelpText": {}, + "rirNotUsed": "RiR não usado", + "@rirNotUsed": { + "description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log" + }, + "todaysWorkout": "Seu treino hoje", + "@todaysWorkout": {}, + "aboutSourceText": "Obtenha o código fonte deste aplicativo e seu servidor no github", + "@aboutSourceText": { + "description": "Text for source code section in the about dialog" + }, + "kJ": "kJ", + "@kJ": { + "description": "Energy in a meal in kilo joules, kJ" + }, + "sodium": "Sódio", + "@sodium": {}, + "translation": "Tradução", + "@translation": {}, + "successfullySaved": "Salvo", + "@successfullySaved": { + "description": "Message when an item was successfully saved" + }, + "exerciseList": "Lista de exercícios", + "@exerciseList": {}, + "energy": "Energia", + "@energy": { + "description": "Energy in a meal, ingredient etc. e.g. in kJ" + }, + "newDay": "Novo dia", + "@newDay": {}, + "toggleDetails": "Alterar detalhes", + "@toggleDetails": { + "description": "Switch to toggle detail / overview" + }, + "musclesSecondary": "Músculos secundários", + "@musclesSecondary": { + "description": "secondary muscles trained by an exercise" + }, + "aboutContactUsText": "Se você quiser bater um papo conosco, entre no servidor Discord e entre em contato", + "@aboutContactUsText": { + "description": "Text for contact us section in the about dialog" + }, + "labelDashboard": "Painel", + "@labelDashboard": { + "description": "Title for screen dashboard" + }, + "timeEnd": "Fim do tempo", + "@timeEnd": { + "description": "The end time of a workout" + }, + "planned": "Planejado", + "@planned": { + "description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten" + }, + "logHelpEntries": "Se em um mesmo dia houver mais de uma inscrição com o mesmo número de repetições, mas pesos diferentes, apenas a inscrição com maior peso será mostrada no diagrama.", + "@logHelpEntries": {}, + "exerciseName": "Nome do exercício", + "@exerciseName": { + "description": "Label for the name of a workout exercise" + }, + "labelBottomNavNutrition": "Nutrição", + "@labelBottomNavNutrition": { + "description": "Label used in bottom navigation, use a short word" + }, + "language": "Linguagem", + "@language": {}, + "nutritionalPlans": "Planos nutricionais", + "@nutritionalPlans": {}, + "kcal": "kcal", + "@kcal": { + "description": "Energy in a meal in kilocalories, kcal" + }, + "g": "g", + "@g": { + "description": "Abbreviation for gram" + }, + "addMeal": "Adicionar refeição", + "@addMeal": {}, + "sameRepetitions": "Se você fizer as mesmas repetições e peso para todas as séries, poderá preencher apenas uma linha. Por exemplo, para 4 séries basta inserir 10 para as repetições, isso automaticamente se torna \"4 x 10\".", + "@sameRepetitions": {}, + "measurementEntriesHelpText": "A unidade usada para medir a categoria, como 'cm' ou '%'", + "@measurementEntriesHelpText": {}, + "supersetWith": "superset com", + "@supersetWith": { + "description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'" + }, + "sugars": "Açúcar", + "@sugars": {}, + "aboutBugsTitle": "Tem um problema ou ideia?", + "@aboutBugsTitle": { + "description": "Title for bugs section in the about dialog" + }, + "carbohydratesShort": "C", + "@carbohydratesShort": { + "description": "The first letter or short name of the word 'Carbohydrates', used in overviews" + }, + "measurements": "Medidas", + "@measurements": { + "description": "Categories for the measurements such as biceps size, body fat, etc." + }, + "macronutrients": "Macronutrientes", + "@macronutrients": {}, + "plateCalculatorNotDivisible": "Não é possível atingir o peso com os pratos disponíveis", + "@plateCalculatorNotDivisible": { + "description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)" + }, + "confirmDelete": "Tem certeza de que deseja excluir '{toDelete}'?", + "@confirmDelete": { + "description": "Confirmation text before the user deletes an object", + "type": "text", + "placeholders": { + "toDelete": {} + } + }, + "gPerBodyKg": "g por kg corporal", + "@gPerBodyKg": { + "description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight" + }, + "weekAverage": "Média de 7 dias", + "@weekAverage": { + "description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week" + }, + "labelWorkoutPlan": "Plano de treino", + "@labelWorkoutPlan": { + "description": "Title for screen workout plan" + }, + "category": "Categoria", + "@category": { + "description": "Category for an exercise, ingredient, etc." + }, + "exercises": "Exercícios", + "@exercises": { + "description": "Multiple exercises for a workout" + }, + "time": "Tempo", + "@time": { + "description": "The time of a meal or workout" + }, + "calendar": "Calendário", + "@calendar": {}, + "verify": "Verificar", + "@verify": {}, + "workoutSession": "Sessão de treino", + "@workoutSession": { + "description": "A (logged) workout session" + }, + "logHelpEntriesUnits": "Observe que apenas as entradas com uma unidade de peso (kg ou lb) e repetições são registradas; outras combinações, como tempo ou até a falha, são ignoradas aqui.", + "@logHelpEntriesUnits": {}, + "aboutSourceTitle": "Código fonte", + "@aboutSourceTitle": { + "description": "Title for source code section in the about dialog" + }, + "measurement": "Medição", + "@measurement": {} } diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index 7b8d8fa04..3977fdee0 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -351,7 +351,6 @@ class WorkoutPlansProvider with ChangeNotifier { 'weightUnit': _weightUnits.map((e) => e.toJson()).toList(), }; prefs.setString('workoutUnits', json.encode(exerciseData)); - log(json.encode(exerciseData)); notifyListeners(); } diff --git a/lib/widgets/core/app_bar.dart b/lib/widgets/core/app_bar.dart index 6e420c794..8f618cd33 100644 --- a/lib/widgets/core/app_bar.dart +++ b/lib/widgets/core/app_bar.dart @@ -29,7 +29,7 @@ import 'package:wger/screens/form_screen.dart'; import 'package:wger/widgets/core/about.dart'; import 'package:wger/widgets/user/forms.dart'; -class MainAppBar extends StatelessWidget with PreferredSizeWidget { +class MainAppBar extends StatelessWidget implements PreferredSizeWidget { final String _title; MainAppBar(this._title); @@ -113,7 +113,7 @@ class MainAppBar extends StatelessWidget with PreferredSizeWidget { } /// App bar that only displays a title -class EmptyAppBar extends StatelessWidget with PreferredSizeWidget { +class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget { final String _title; EmptyAppBar(this._title); diff --git a/lib/widgets/core/fl_chart_line.dart b/lib/widgets/core/fl_chart_line.dart index 1eb4b007b..566e1b768 100644 --- a/lib/widgets/core/fl_chart_line.dart +++ b/lib/widgets/core/fl_chart_line.dart @@ -9,7 +9,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/models/measurements/measurement_entry.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; //import 'package:intl/intl.dart'; //import 'package:wger/widgets/core/charts.dart'; @@ -177,14 +177,13 @@ class _LineChartSample2State extends State { // ); // } - // #TODO : needs to be changed (values for (y) axis) Widget leftTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontWeight: FontWeight.bold, fontSize: 15, ); - + String text; switch (value.toInt()) { case 1: diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index fa11ebfd2..c1e02b026 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -35,7 +35,7 @@ import 'package:wger/screens/nutritional_plan_screen.dart'; import 'package:wger/screens/weight_screen.dart'; import 'package:wger/screens/workout_plan_screen.dart'; import 'package:wger/theme/theme.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'package:wger/widgets/core/core.dart'; import 'package:wger/widgets/measurements/categories_card.dart'; import 'package:wger/widgets/measurements/forms.dart'; diff --git a/lib/widgets/measurements/categories_card.dart b/lib/widgets/measurements/categories_card.dart index 60ef224c6..80e746381 100644 --- a/lib/widgets/measurements/categories_card.dart +++ b/lib/widgets/measurements/categories_card.dart @@ -4,7 +4,7 @@ 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 'charts.dart'; import 'forms.dart'; class CategoriesCard extends StatelessWidget { diff --git a/lib/widgets/core/charts.dart b/lib/widgets/measurements/charts.dart similarity index 83% rename from lib/widgets/core/charts.dart rename to lib/widgets/measurements/charts.dart index 4fd223ae2..65cee1526 100644 --- a/lib/widgets/core/charts.dart +++ b/lib/widgets/measurements/charts.dart @@ -19,6 +19,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:wger/helpers/charts.dart'; import 'package:wger/theme/theme.dart'; class MeasurementChartWidgetFl extends StatefulWidget { @@ -32,8 +33,6 @@ class MeasurementChartWidgetFl extends StatefulWidget { } class _MeasurementChartWidgetFlState extends State { - final interval = 15 * Duration.millisecondsPerDay / 1000 / 60; - @override Widget build(BuildContext context) { return AspectRatio( @@ -52,8 +51,20 @@ class _MeasurementChartWidgetFlState extends State { ); } + LineTouchData tooltipData() { + return LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) { + return touchedSpots.map((touchedSpot) { + return LineTooltipItem( + '${touchedSpot.y} kg', + const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), + ); + }).toList(); + })); + } + LineChartData mainData() { return LineChartData( + lineTouchData: tooltipData(), gridData: FlGridData( show: true, drawVerticalLine: true, @@ -84,12 +95,19 @@ class _MeasurementChartWidgetFlState extends State { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { - final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt() * 1000 * 60); + // Don't show the first and last entries, otherwise they'll overlap with the + // calculated interval + if (value == meta.min || value == meta.max) { + return const Text(''); + } + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); return Text( DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); }, - interval: interval, + interval: widget._entries.isNotEmpty + ? chartGetInterval(widget._entries.last.date, widget._entries.first.date) + : 1000, ), ), leftTitles: AxisTitles( @@ -112,7 +130,7 @@ class _MeasurementChartWidgetFlState extends State { LineChartBarData( spots: [ ...widget._entries - .map((e) => FlSpot(e.date.millisecondsSinceEpoch / 1000 / 60, e.value.toDouble())) + .map((e) => FlSpot(e.date.millisecondsSinceEpoch.toDouble(), e.value.toDouble())) ], isCurved: false, color: wgerSecondaryColor, diff --git a/lib/widgets/measurements/entries.dart b/lib/widgets/measurements/entries.dart index 22ff43ac0..55b2023f2 100644 --- a/lib/widgets/measurements/entries.dart +++ b/lib/widgets/measurements/entries.dart @@ -24,7 +24,7 @@ import 'package:wger/models/measurements/measurement_category.dart'; import 'package:wger/providers/measurement.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/theme/theme.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'forms.dart'; diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index c38e8f71d..24e605ad0 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -23,7 +23,7 @@ import 'package:intl/intl.dart'; import 'package:wger/helpers/colors.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; class NutritionData { final String name; diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 69d17fe3c..75fb42ddd 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -20,12 +20,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:wger/helpers/colors.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/nutritional_diary_screen.dart'; import 'package:wger/theme/theme.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/meal.dart'; @@ -212,12 +214,42 @@ class NutritionalPlanDetailWidget extends StatelessWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline6, ), + Container( - padding: const EdgeInsets.all(15), + padding: const EdgeInsets.only(top: 15, left: 15, right: 15), height: 300, child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan), // chart ), - const Padding(padding: EdgeInsets.all(8.0)), + // const Padding(padding: EdgeInsets.all(8.0)), + Padding( + padding: const EdgeInsets.only(bottom: 40, left: 25, right: 25), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Indicator( + color: LIST_OF_COLORS3[0], + text: AppLocalizations.of(context).planned, + isSquare: true, + ), + const SizedBox( + width: 4, + ), + Indicator( + color: LIST_OF_COLORS3[1], + text: AppLocalizations.of(context).logged, + isSquare: true, + ), + const SizedBox( + width: 4, + ), + Indicator( + color: LIST_OF_COLORS3[2], + text: AppLocalizations.of(context).weekAverage, + isSquare: true, + ), + ], + ), + ), Text( AppLocalizations.of(context).nutritionalDiary, textAlign: TextAlign.center, diff --git a/lib/widgets/weight/entries_list.dart b/lib/widgets/weight/entries_list.dart index 10611e1ad..10e51dcca 100644 --- a/lib/widgets/weight/entries_list.dart +++ b/lib/widgets/weight/entries_list.dart @@ -24,7 +24,7 @@ import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/measurement_categories_screen.dart'; import 'package:wger/theme/theme.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'package:wger/widgets/weight/forms.dart'; class WeightEntriesList extends StatelessWidget { diff --git a/lib/widgets/workouts/app_bar.dart b/lib/widgets/workouts/app_bar.dart index ab8bd1480..73ce790d4 100644 --- a/lib/widgets/workouts/app_bar.dart +++ b/lib/widgets/workouts/app_bar.dart @@ -26,7 +26,7 @@ enum _WorkoutAppBarOptions { contribute, } -class WorkoutOverviewAppBar extends StatelessWidget with PreferredSizeWidget { +class WorkoutOverviewAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index b7a3f7ec3..621f0fb6d 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -20,6 +20,7 @@ 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'; import 'package:wger/helpers/colors.dart'; /// Sample time series data type. @@ -47,8 +48,6 @@ class _LogChartWidgetFlState extends State { aspectRatio: 1.70, child: Padding( padding: const EdgeInsets.only( - right: 18, - left: 12, top: 24, bottom: 12, ), @@ -60,17 +59,12 @@ class _LogChartWidgetFlState extends State { } LineChartData mainData() { - final dayDiff = DateTime.parse(widget._data['logs'].keys.last) - .difference(DateTime.parse(widget._data['logs'].keys.first)); - - final interval = dayDiff.inDays * 1.3 * Duration.millisecondsPerDay; + final colors = generateChartColors(widget._data['chart_data'].length).iterator; return LineChartData( gridData: FlGridData( show: true, drawVerticalLine: true, - //horizontalInterval: 1, - //verticalInterval: interval, getDrawingHorizontalLine: (value) { return FlLine( color: Colors.grey, @@ -96,12 +90,21 @@ class _LogChartWidgetFlState extends State { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { + // Don't show the first and last entries, otherwise they'll overlap with the + // calculated interval + if (value == meta.min || value == meta.max) { + return const Text(''); + } + final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); return Text( DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), ); }, - interval: interval, + interval: chartGetInterval( + DateTime.parse(widget._data['logs'].keys.first), + DateTime.parse(widget._data['logs'].keys.last), + ), ), ), leftTitles: AxisTitles( @@ -121,15 +124,18 @@ class _LogChartWidgetFlState extends State { lineBarsData: [ ...widget._data['chart_data'].map( (e) { + colors.moveNext(); return LineChartBarData( spots: [ - ...e.map((entry) => FlSpot( - DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), - double.parse(entry['weight']), - )) + ...e.map( + (entry) => FlSpot( + DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), + double.parse(entry['weight']), + ), + ) ], isCurved: false, - color: getRandomColor(widget._data['chart_data'].length, e.first['reps']), + color: colors.current, barWidth: 2, isStrokeCapRound: true, dotData: FlDotData( diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index e58d0d732..2ed27940e 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -44,7 +44,7 @@ class ExerciseLogChart extends StatelessWidget { return FutureBuilder( future: getChartEntries(context), builder: (context, AsyncSnapshot> snapshot) => SizedBox( - height: 150, + height: 190, child: snapshot.connectionState == ConnectionState.waiting ? const Center(child: CircularProgressIndicator()) : LogChartWidgetFl(snapshot.data!, _currentDate), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index fa46ecee3..3de627737 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,7 +13,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 9f0f19360..f3e45bc4a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" charcode: dependency: transitive description: @@ -253,10 +253,10 @@ packages: dependency: "direct main" description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.2" conventional_commit: dependency: transitive description: @@ -784,18 +784,18 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" melos: dependency: transitive description: @@ -808,10 +808,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mime: dependency: transitive description: @@ -824,10 +824,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 + sha256: "8b46d7eb40abdda92d62edd01546051f0c27365e65608c284de336dccfef88cc" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.1" multi_select_flutter: dependency: "direct main" description: @@ -872,10 +872,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -888,10 +888,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -1056,10 +1056,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_android: dependency: transitive description: @@ -1157,10 +1157,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -1221,10 +1221,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.0" timing: dependency: transitive description: @@ -1405,10 +1405,10 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "11.7.1" watcher: dependency: transitive description: @@ -1417,6 +1417,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1429,10 +1437,10 @@ packages: dependency: transitive description: name: webdriver - sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" win32: dependency: transitive description: @@ -1474,5 +1482,5 @@ packages: source: hosted version: "2.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 04d8178dd..3beb0a2b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.5.6+34 environment: - sdk: '>=2.17.0 <3.0.0' + sdk: ">=2.17.0 <3.0.0" dependencies: flutter: @@ -45,10 +45,10 @@ dependencies: intl: ^0.17.0 json_annotation: ^4.8.1 version: ^3.0.2 - package_info_plus: ^4.1.0 + package_info_plus: ^4.2.0 provider: ^6.0.5 rive: ^0.11.4 - shared_preferences: ^2.2.1 + shared_preferences: ^2.2.2 table_calendar: ^3.0.8 url_launcher: ^6.1.11 flutter_barcode_scanner: ^2.0.0 @@ -60,6 +60,9 @@ dependencies: fl_chart: ^0.62.0 flutter_zxing: ^1.1.2 +dependency_overrides: + intl: any + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/measurements/measurement_categories_screen_test.dart b/test/measurements/measurement_categories_screen_test.dart index 7119108b4..1fa899e4d 100644 --- a/test/measurements/measurement_categories_screen_test.dart +++ b/test/measurements/measurement_categories_screen_test.dart @@ -26,7 +26,7 @@ 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/screens/measurement_categories_screen.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'measurement_categories_screen_test.mocks.dart'; diff --git a/test/measurements/measurement_entries_screen_test.dart b/test/measurements/measurement_entries_screen_test.dart index 6182ab373..20b9b5dcd 100644 --- a/test/measurements/measurement_entries_screen_test.dart +++ b/test/measurements/measurement_entries_screen_test.dart @@ -73,8 +73,8 @@ void main() { expect(find.text('body fat'), findsOneWidget); // Entries - expect(find.text('10.2 %'), findsOneWidget); - expect(find.text('18.1 %'), findsOneWidget); + expect(find.text('10.2 %'), findsNWidgets(2)); + expect(find.text('18.1 %'), findsNWidgets(2)); }); testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async { @@ -83,8 +83,8 @@ void main() { await tester.pumpAndSettle(); // From the entries list and from the chart - expect(find.text('8/1/2021'), findsNWidgets(3)); - expect(find.text('8/10/2021'), findsNWidgets(2)); + expect(find.text('8/1/2021'), findsWidgets); + expect(find.text('8/10/2021'), findsWidgets); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { @@ -92,7 +92,7 @@ void main() { await tester.tap(find.byType(TextButton)); await tester.pumpAndSettle(); - expect(find.text('1.8.2021'), findsOneWidget); - expect(find.text('10.8.2021'), findsOneWidget); + expect(find.text('1.8.2021'), findsWidgets); + expect(find.text('10.8.2021'), findsWidgets); }); } diff --git a/test/weight/weight_screen_test.dart b/test/weight/weight_screen_test.dart index c95c1c049..10bd75bd0 100644 --- a/test/weight/weight_screen_test.dart +++ b/test/weight/weight_screen_test.dart @@ -25,7 +25,7 @@ import 'package:provider/provider.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/weight_screen.dart'; -import 'package:wger/widgets/core/charts.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'package:wger/widgets/weight/forms.dart'; import '../../test_data/body_weight.dart'; @@ -84,8 +84,8 @@ void main() { await tester.pumpWidget(createWeightScreen()); // One in the entries list, one in the chart - expect(find.text('1/1/2021'), findsNWidgets(2)); - expect(find.text('1/10/2021'), findsNWidgets(2)); + expect(find.text('1/1/2021'), findsOneWidget); + expect(find.text('1/10/2021'), findsOneWidget); }); testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { From f2f9582031a0a0bbb682ccf69f596515f5019465 Mon Sep 17 00:00:00 2001 From: Abhishek Saini <78199221+Abhisheksainii@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:27:25 +0530 Subject: [PATCH 24/31] fix: tooltip and legend in workout chart --- lib/l10n/app_en.arb | 12 ++++++++++++ lib/widgets/workouts/charts.dart | 25 ++++++++++++++++++++++++ lib/widgets/workouts/log.dart | 33 +++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7c2e595ca..45e26795e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -155,6 +155,18 @@ "@reps": { "description": "Shorthand for repetitions, used when space constraints are tighter" }, + "eight_reps": "8", + "@reps": { + "description": "Shorthand for 8 repetitions, used when space constraints are tighter" + }, + "ten_reps": "10", + "@reps": { + "description": "Shorthand for 10 repetitions, used when space constraints are tighter" + }, + "twelve_reps": "12", + "@reps": { + "description": "Shorthand for 12 repetitions, used when space constraints are tighter" + }, "rir": "RiR", "@rir": { "description": "Shorthand for Repetitions In Reserve" diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index 621f0fb6d..7bc574660 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -58,10 +58,35 @@ class _LogChartWidgetFlState extends State { ); } + int findRepsForWeight(List data, int weight) { + for (final dataList in data) { + if (dataList != null) { + for (final entry in dataList) { + if (double.parse(entry['weight']).toInt() == weight) { + return entry['reps']; + } + } + } + } + return -1; // Return -1 if the weight is not found + } + + LineTouchData tooltipData() { + return LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) { + return touchedSpots.map((touchedSpot) { + return LineTooltipItem( + '${findRepsForWeight(widget._data["chart_data"], (touchedSpot.y).toInt())} reps: ${touchedSpot.y} kg', + const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ); + }).toList(); + })); + } + LineChartData mainData() { final colors = generateChartColors(widget._data['chart_data'].length).iterator; return LineChartData( + lineTouchData: tooltipData(), gridData: FlGridData( show: true, drawVerticalLine: true, diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index 2ed27940e..dd136408a 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -16,14 +16,17 @@ * along with this program. If not, see . */ +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:wger/helpers/colors.dart'; import 'package:wger/helpers/ui.dart'; import 'package:wger/models/exercises/base.dart'; import 'package:wger/models/workouts/log.dart'; import 'package:wger/models/workouts/session.dart'; import 'package:wger/providers/workout_plans.dart'; +import 'package:wger/widgets/measurements/charts.dart'; import 'package:wger/widgets/workouts/charts.dart'; class ExerciseLogChart extends StatelessWidget { @@ -109,7 +112,35 @@ class _DayLogWidgetState extends State { ) .toList(), ExerciseLogChart(base, widget._date), - const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.all(30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Indicator( + color: LIST_OF_COLORS3[0], + text: AppLocalizations.of(context).eight_reps, + isSquare: false, + ), + const SizedBox( + width: 15, + ), + Indicator( + color: LIST_OF_COLORS3[1], + text: AppLocalizations.of(context).ten_reps, + isSquare: false, + ), + const SizedBox( + width: 15, + ), + Indicator( + color: LIST_OF_COLORS3[2], + text: AppLocalizations.of(context).twelve_reps, + isSquare: false, + ), + ], + ), + ), ], ); }).toList() From 221a1505e7e22b7dd7b4a62cd557aea59879f657 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 8 Nov 2023 19:50:54 +0100 Subject: [PATCH 25/31] Dynamically generate a legend for the training logs Why don't can't you provide this fl_chart, why!!!!! --- lib/l10n/app_en.arb | 12 ----- lib/widgets/measurements/charts.dart | 7 ++- lib/widgets/workouts/log.dart | 77 ++++++++++++++-------------- 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 45e26795e..7c2e595ca 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -155,18 +155,6 @@ "@reps": { "description": "Shorthand for repetitions, used when space constraints are tighter" }, - "eight_reps": "8", - "@reps": { - "description": "Shorthand for 8 repetitions, used when space constraints are tighter" - }, - "ten_reps": "10", - "@reps": { - "description": "Shorthand for 10 repetitions, used when space constraints are tighter" - }, - "twelve_reps": "12", - "@reps": { - "description": "Shorthand for 12 repetitions, used when space constraints are tighter" - }, "rir": "RiR", "@rir": { "description": "Shorthand for Repetitions In Reserve" diff --git a/lib/widgets/measurements/charts.dart b/lib/widgets/measurements/charts.dart index 65cee1526..fdbae75f4 100644 --- a/lib/widgets/measurements/charts.dart +++ b/lib/widgets/measurements/charts.dart @@ -159,6 +159,7 @@ class Indicator extends StatelessWidget { required this.text, required this.isSquare, this.size = 16, + this.marginRight = 15, this.textColor, }); @@ -166,6 +167,7 @@ class Indicator extends StatelessWidget { final String text; final bool isSquare; final double size; + final double marginRight; final Color? textColor; @override @@ -188,7 +190,10 @@ class Indicator extends StatelessWidget { style: TextStyle( color: textColor, ), - ) + ), + SizedBox( + width: marginRight, + ), ], ); } diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index dd136408a..0ca46332b 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -39,20 +38,51 @@ class ExerciseLogChart extends StatelessWidget { Widget build(BuildContext context) { final workoutPlansData = Provider.of(context, listen: false); final workout = workoutPlansData.currentPlan; + var colors = generateChartColors(1).iterator; Future> getChartEntries(BuildContext context) async { return workoutPlansData.fetchLogData(workout!, _base); } return FutureBuilder( - future: getChartEntries(context), - builder: (context, AsyncSnapshot> snapshot) => SizedBox( - height: 190, - child: snapshot.connectionState == ConnectionState.waiting - ? const Center(child: CircularProgressIndicator()) - : LogChartWidgetFl(snapshot.data!, _currentDate), - ), - ); + future: getChartEntries(context), + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + colors = generateChartColors(snapshot.data!['chart_data'].length).iterator; + } + + return SizedBox( + height: 260, + child: snapshot.connectionState == ConnectionState.waiting + ? const Center(child: CircularProgressIndicator()) + : Column( + mainAxisSize: MainAxisSize.max, + children: [ + LogChartWidgetFl(snapshot.data!, _currentDate), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...snapshot.data!['chart_data'].map((e) { + // e is the list of logs with the same reps, so we can just take the + // first entry and read the reps from it. Yes, this is an amazingly ugly hack + final reps = e.first['reps']; + + colors.moveNext(); + return Indicator( + color: colors.current, + text: reps.toString(), + isSquare: false, + ); + }).toList(), + ], + ), + const SizedBox( + height: 15, + ) + ], + ), + ); + }); } } @@ -112,35 +142,6 @@ class _DayLogWidgetState extends State { ) .toList(), ExerciseLogChart(base, widget._date), - Padding( - padding: const EdgeInsets.all(30.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Indicator( - color: LIST_OF_COLORS3[0], - text: AppLocalizations.of(context).eight_reps, - isSquare: false, - ), - const SizedBox( - width: 15, - ), - Indicator( - color: LIST_OF_COLORS3[1], - text: AppLocalizations.of(context).ten_reps, - isSquare: false, - ), - const SizedBox( - width: 15, - ), - Indicator( - color: LIST_OF_COLORS3[2], - text: AppLocalizations.of(context).twelve_reps, - isSquare: false, - ), - ], - ), - ), ], ); }).toList() From 08cc1a30ea2ed54f7f55caaea8ef9f776592fb66 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 8 Nov 2023 19:55:39 +0100 Subject: [PATCH 26/31] Add some padding to the chart --- lib/widgets/workouts/log.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/widgets/workouts/log.dart b/lib/widgets/workouts/log.dart index 0ca46332b..c9feff23f 100644 --- a/lib/widgets/workouts/log.dart +++ b/lib/widgets/workouts/log.dart @@ -141,7 +141,10 @@ class _DayLogWidgetState extends State { ), ) .toList(), - ExerciseLogChart(base, widget._date), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ExerciseLogChart(base, widget._date), + ) ], ); }).toList() From b4686978f2a812c66d912c30b1b280bd6a9f53b5 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 8 Nov 2023 19:59:12 +0100 Subject: [PATCH 27/31] Remove getRandomColor, wasn't being used --- lib/helpers/colors.dart | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/helpers/colors.dart b/lib/helpers/colors.dart index 8fb060dd7..62cb1cf49 100644 --- a/lib/helpers/colors.dart +++ b/lib/helpers/colors.dart @@ -40,19 +40,4 @@ Iterable generateChartColors(int nrOfItems) sync* { for (final color in colors) { yield color; } -} - -/// Returns a random color based on the given seed -Color getRandomColor(int nrOfItems, int seed) { - final List colors; - - if (nrOfItems <= 3) { - colors = LIST_OF_COLORS3; - } else if (nrOfItems <= 5) { - colors = LIST_OF_COLORS5; - } else { - colors = LIST_OF_COLORS8; - } - - return colors[Random(seed).nextInt(colors.length)]; -} +} \ No newline at end of file From b2e7f65d29c84f01021bf2ba2f674f764330c66f Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 8 Nov 2023 20:05:25 +0100 Subject: [PATCH 28/31] Remove debug code and unused files --- lib/providers/workout_plans.dart | 3 - lib/widgets/core/fl_chart_line.dart | 389 ------------------ .../nutrition/nutritional_plan_detail.dart | 11 +- 3 files changed, 3 insertions(+), 400 deletions(-) delete mode 100644 lib/widgets/core/fl_chart_line.dart diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index 3977fdee0..c5d2406aa 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -18,7 +18,6 @@ import 'dart:convert'; import 'dart:developer' as dev; -import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -300,8 +299,6 @@ class WorkoutPlansProvider with ChangeNotifier { query: {'id': base.id.toString()}, ), ); - // log(data.toString()); - return data; } diff --git a/lib/widgets/core/fl_chart_line.dart b/lib/widgets/core/fl_chart_line.dart deleted file mode 100644 index 566e1b768..000000000 --- a/lib/widgets/core/fl_chart_line.dart +++ /dev/null @@ -1,389 +0,0 @@ -// Fl line chart [SAmple 2 ] for testing -// // #TODO : I may move this content to the core widget file -// - -//import 'package:fl_chart_app/presentation/resources/app_resources.dart'; -import 'dart:math'; - -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:wger/helpers/consts.dart'; -import 'package:wger/models/measurements/measurement_entry.dart'; -import 'package:wger/widgets/measurements/charts.dart'; -//import 'package:intl/intl.dart'; -//import 'package:wger/widgets/core/charts.dart'; - -import 'package:wger/helpers/consts.dart'; - -import 'package:intl/intl.dart'; - -class MeasurementChartEntryflchat { - num value; // this needs to - DateTime date; - - MeasurementChartEntryflchat(this.value, this.date); -} - -class LineChartSample2 extends StatefulWidget { - //#TODO : Substitute below entries to the old entries for data return comapraison !! - final List allentries; - final String unit; - - // entries recieved from original measurement entry (old) - //final List _entries; - - const LineChartSample2(this.allentries, {this.unit = 'kg'}); - - //const LineChartSample2({super.key}); - - @override - State createState() => _LineChartSample2State(); -} - -class AppColors { - static const Color primary = contentColorCyan; - static const Color menuBackground = Color(0xFF090912); - static const Color itemsBackground = Color(0xFF1B2339); - static const Color pageBackground = Color(0xFF282E45); - static const Color mainTextColor1 = Colors.white; - static const Color mainTextColor2 = Colors.white70; - static const Color mainTextColor3 = Colors.white38; - static const Color mainGridLineColor = Colors.white10; - static const Color borderColor = Colors.white54; - static const Color gridLinesColor = Color(0x11FFFFFF); - - static const Color contentColorBlack = Colors.black; - static const Color contentColorWhite = Colors.white; - static const Color contentColorBlue = Color(0xFF2196F3); - static const Color contentColorYellow = Color(0xFFFFC300); - static const Color contentColorOrange = Color(0xFFFF683B); - static const Color contentColorGreen = Color(0xFF3BFF49); - static const Color contentColorPurple = Color(0xFF6E1BFF); - static const Color contentColorPink = Color(0xFFFF3AF2); - static const Color contentColorRed = Color(0xFFE80054); - static const Color contentColorCyan = Color(0xFF50E4FF); -} - -class _LineChartSample2State extends State { - List gradientColors = [ - AppColors.contentColorCyan, - AppColors.contentColorBlue, - ]; - - bool showAvg = false; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - AspectRatio( - aspectRatio: 1.70, - child: Padding( - padding: const EdgeInsets.only( - right: 18, - left: 12, - top: 24, - bottom: 12, - ), - child: LineChart( - showAvg ? avgData() : mainData(), - ), - ), - ), - SizedBox( - width: 60, - height: 34, - child: TextButton( - onPressed: () { - setState(() { - showAvg = !showAvg; - }); - }, - child: Text( - 'avg', - style: TextStyle( - fontSize: 12, - color: showAvg ? Colors.white.withOpacity(0.5) : Colors.white, - ), - ), - ), - ), - ], - ); - } - - // #TODO : Automatic dates on (x axis) - - SideTitles _bottomTitles() { - return SideTitles( - showTitles: true, - interval: 1, - getTitlesWidget: (value, meta) { - // // #TODO : format dates same as old chart - final getdates = - value.toInt() < widget.allentries.length ? widget.allentries[value.toInt()].date : ""; - - // final datesToStr = getdates.toString(); - - // final tempDate = DateTime.parse(getdates.toString()); - // //debugPrint(('tempDates \n' ) + tempDate.toString() ); - - // final finaldates = DateFormat('MM-dd').format(tempDate); - // debugPrint(('FinaleDates \n') + finaldates); - //final datestoformat = DateFormatLists.format(tempDate); - - // // # used built in above fun to return date format as (yyyy-mm-dd) - // debugPrint(datestoformat); - - // #BUG : if dates are formated -- the chart causes error (invalid dates format) - return SideTitleWidget(axisSide: meta.axisSide, child: Text(getdates.toString())); - }, - ); - } - - // Widget bottomTitleWidgets(double value, TitleMeta meta) { - // const style = TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 16, - // ); - - // List datesgeneric = [ - // 'Sun', - // 'Feb', - // 'Oct', - // 'Nov', - // ]; - - // //MeasurementChartEntry entry ; - // // Widget text; - // // switch (value.toInt()) { - // // case 2: - // // text = const Text('MAR', style: style); - // // break; - // // case 5: - // // text = const Text('JUN', style: style); - // // break; - // // case 8: - // // text = const Text('SEP', style: style); - // // break; - // // default: - // // text = const Text('', style: style); - // // break; - // // } - - // return SideTitleWidget( - // axisSide: meta.axisSide, - // child:Text('$datesgeneric'), - // ); - // } - - // #TODO : needs to be changed (values for (y) axis) - Widget leftTitleWidgets(double value, TitleMeta meta) { - const style = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ); - - String text; - switch (value.toInt()) { - case 1: - text = '10K'; - break; - case 3: - text = '30k'; - break; - case 5: - text = '50k'; - break; - default: - return Container(); - } - - return Text(text, style: style, textAlign: TextAlign.left); - } - - LineChartData mainData() { - return LineChartData( - gridData: FlGridData( - show: true, - drawVerticalLine: true, - horizontalInterval: 1, - verticalInterval: 1, - getDrawingHorizontalLine: (value) { - return FlLine( - color: AppColors.mainGridLineColor, - strokeWidth: 1, - ); - }, - getDrawingVerticalLine: (value) { - return FlLine( - color: AppColors.mainGridLineColor, - strokeWidth: 1, - ); - }, - ), - titlesData: FlTitlesData( - show: true, - rightTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - bottomTitles: AxisTitles( - sideTitles: _bottomTitles(), - ), - - // #FIXME : Custom bottom titles for dates entry - // bottomTitles: AxisTitles( - // sideTitles: SideTitles( - // showTitles: true, - // reservedSize: 30, - // interval: 1, - // getTitlesWidget: bottomTitleWidgets, - // ), - // ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: 1, - getTitlesWidget: leftTitleWidgets, - reservedSize: 42, - ), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: const Color(0xff37434d)), - ), - minX: 0, - maxX: 11, - minY: 0, - maxY: 6, - lineBarsData: [ - LineChartBarData( - // spots: const [ - // FlSpot(0, 3), - // FlSpot(2.6, 2), - // FlSpot(4.9, 5), - // FlSpot(6.8, 3.1), - // FlSpot(8, 4), - // FlSpot(9.5, 3), - // FlSpot(11, 4), - // ], - isCurved: true, - gradient: LinearGradient( - colors: gradientColors, - ), - barWidth: 5, - isStrokeCapRound: true, - dotData: FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(), - ), - ), - ), - ], - ); - } - - LineChartData avgData() { - return LineChartData( - lineTouchData: LineTouchData(enabled: false), - gridData: FlGridData( - show: true, - drawHorizontalLine: true, - verticalInterval: 1, - horizontalInterval: 1, - getDrawingVerticalLine: (value) { - return FlLine( - color: Color(0xff37434d), - strokeWidth: 1, - ); - }, - getDrawingHorizontalLine: (value) { - return FlLine( - color: Color(0xff37434d), - strokeWidth: 1, - ); - }, - ), - titlesData: FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: _bottomTitles(), - // sideTitles: SideTitles( - // showTitles: true, - // reservedSize: 30, - // getTitlesWidget: _, - // interval: 1, - // ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: leftTitleWidgets, - reservedSize: 42, - interval: 1, - ), - ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: const Color(0xff37434d)), - ), - minX: 0, - maxX: 11, - minY: 0, - maxY: 6, - lineBarsData: [ - LineChartBarData( - // spots: const [ - // FlSpot(0, 3.44), - // FlSpot(2.6, 3.44), - // FlSpot(4.9, 3.44), - // FlSpot(6.8, 3.44), - // FlSpot(8, 3.44), - // FlSpot(9.5, 3.44), - // FlSpot(11, 3.44), - // ], - isCurved: true, - gradient: LinearGradient( - colors: [ - ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, - ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!, - ], - ), - barWidth: 5, - isStrokeCapRound: true, - dotData: FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - ColorTween(begin: gradientColors[0], end: gradientColors[1]) - .lerp(0.2)! - .withOpacity(0.1), - ColorTween(begin: gradientColors[0], end: gradientColors[1]) - .lerp(0.2)! - .withOpacity(0.1), - ], - ), - ), - ), - ], - ); - } -} diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 75fb42ddd..363d3c6c5 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -214,13 +214,11 @@ class NutritionalPlanDetailWidget extends StatelessWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline6, ), - Container( padding: const EdgeInsets.only(top: 15, left: 15, right: 15), height: 300, child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan), // chart ), - // const Padding(padding: EdgeInsets.all(8.0)), Padding( padding: const EdgeInsets.only(bottom: 40, left: 25, right: 25), child: Row( @@ -230,22 +228,19 @@ class NutritionalPlanDetailWidget extends StatelessWidget { color: LIST_OF_COLORS3[0], text: AppLocalizations.of(context).planned, isSquare: true, - ), - const SizedBox( - width: 4, + marginRight: 0, ), Indicator( color: LIST_OF_COLORS3[1], text: AppLocalizations.of(context).logged, isSquare: true, - ), - const SizedBox( - width: 4, + marginRight: 0, ), Indicator( color: LIST_OF_COLORS3[2], text: AppLocalizations.of(context).weekAverage, isSquare: true, + marginRight: 0, ), ], ), From 77249de6bd749a80214730003d897c93fd9b9506 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 8 Nov 2023 21:22:31 +0100 Subject: [PATCH 29/31] Fix bug in tooltips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is not possible to get the reps just from the weight (and the date for that matter), since it's very possible that there are collisions here. Luckily we can access the number of the series so we can get the reps by going through the original data Why so much manual work 😭 --- lib/widgets/workouts/charts.dart | 88 ++++++++++++++------------------ 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/lib/widgets/workouts/charts.dart b/lib/widgets/workouts/charts.dart index 7bc574660..cbf5bbd57 100644 --- a/lib/widgets/workouts/charts.dart +++ b/lib/widgets/workouts/charts.dart @@ -23,14 +23,6 @@ import 'package:intl/intl.dart'; import 'package:wger/helpers/charts.dart'; import 'package:wger/helpers/colors.dart'; -/// Sample time series data type. -class TimeSeriesLog { - final DateTime time; - final double weight; - - TimeSeriesLog(this.time, this.weight); -} - class LogChartWidgetFl extends StatefulWidget { final Map _data; final DateTime _currentDate; @@ -58,28 +50,21 @@ class _LogChartWidgetFlState extends State { ); } - int findRepsForWeight(List data, int weight) { - for (final dataList in data) { - if (dataList != null) { - for (final entry in dataList) { - if (double.parse(entry['weight']).toInt() == weight) { - return entry['reps']; - } - } - } - } - return -1; // Return -1 if the weight is not found - } - LineTouchData tooltipData() { - return LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) { - return touchedSpots.map((touchedSpot) { - return LineTooltipItem( - '${findRepsForWeight(widget._data["chart_data"], (touchedSpot.y).toInt())} reps: ${touchedSpot.y} kg', - const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ); - }).toList(); - })); + return LineTouchData( + touchTooltipData: LineTouchTooltipData( + getTooltipItems: (touchedSpots) { + return touchedSpots.map((touchedSpot) { + final reps = widget._data['chart_data'][touchedSpot.barIndex].first['reps']; + + return LineTooltipItem( + '$reps × ${touchedSpot.y} kg', + const TextStyle(color: Colors.white), + ); + }).toList(); + }, + ), + ); } LineChartData mainData() { @@ -147,28 +132,31 @@ class _LogChartWidgetFlState extends State { border: Border.all(color: const Color(0xff37434d)), ), lineBarsData: [ - ...widget._data['chart_data'].map( - (e) { - colors.moveNext(); - return LineChartBarData( - spots: [ - ...e.map( - (entry) => FlSpot( - DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), - double.parse(entry['weight']), - ), - ) - ], - isCurved: false, - color: colors.current, - barWidth: 2, - isStrokeCapRound: true, - dotData: FlDotData( - show: false, + ...widget._data['chart_data'].map((e) { + colors.moveNext(); + return LineChartBarData( + spots: [ + ...e.map( + (entry) => FlSpot( + DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(), + double.parse(entry['weight']), + ), + ) + ], + isCurved: true, + color: colors.current, + barWidth: 2, + isStrokeCapRound: true, + dotData: FlDotData( + show: true, + getDotPainter: (p0, p1, p2, p3) => FlDotCirclePainter( + radius: 2, + color: Colors.black, + strokeWidth: 0, ), - ); - }, - ) + ), + ); + }) ], ); } From 7c1dc05d81a7ae4e519b435342210838fd1f2489 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Thu, 9 Nov 2023 18:37:34 +0100 Subject: [PATCH 30/31] Only render the FlNutritionalDiaryChartWidget chart if there is data for it --- .../nutrition/nutritional_plan_detail.dart | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 363d3c6c5..993a28630 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -245,45 +245,51 @@ class NutritionalPlanDetailWidget extends StatelessWidget { ], ), ), - Text( - AppLocalizations.of(context).nutritionalDiary, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headline6, - ), - Container( - padding: const EdgeInsets.all(15), - height: 220, - child: FlNutritionalDiaryChartWidget(nutritionalPlan: _nutritionalPlan), // chart - ), - SizedBox( - height: 200, - child: ListView( - scrollDirection: Axis.horizontal, + if (_nutritionalPlan.logEntriesValues.isNotEmpty) + Column( children: [ - Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, + Text( + AppLocalizations.of(context).nutritionalDiary, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline6, + ), + Container( + padding: const EdgeInsets.all(15), + height: 220, + child: FlNutritionalDiaryChartWidget(nutritionalPlan: _nutritionalPlan), // chart + ), + SizedBox( + height: 200, + child: ListView( + scrollDirection: Axis.horizontal, children: [ - TextButton(onPressed: () {}, child: const Text('')), - Text( - '${AppLocalizations.of(context).energyShort} (${AppLocalizations.of(context).kcal})'), - Text( - '${AppLocalizations.of(context).proteinShort} (${AppLocalizations.of(context).g})'), - Text( - '${AppLocalizations.of(context).carbohydratesShort} (${AppLocalizations.of(context).g})'), - Text( - '${AppLocalizations.of(context).fatShort} (${AppLocalizations.of(context).g})'), + Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextButton(onPressed: () {}, child: const Text('')), + Text( + '${AppLocalizations.of(context).energyShort} (${AppLocalizations.of(context).kcal})'), + Text( + '${AppLocalizations.of(context).proteinShort} (${AppLocalizations.of(context).g})'), + Text( + '${AppLocalizations.of(context).carbohydratesShort} (${AppLocalizations.of(context).g})'), + Text( + '${AppLocalizations.of(context).fatShort} (${AppLocalizations.of(context).g})'), + ], + ), + ), + ..._nutritionalPlan.logEntriesValues.entries + .map((entry) => + NutritionDiaryEntry(entry.key, entry.value, _nutritionalPlan)) + .toList() + .reversed, ], ), - ), - ..._nutritionalPlan.logEntriesValues.entries - .map((entry) => NutritionDiaryEntry(entry.key, entry.value, _nutritionalPlan)) - .toList() - .reversed, + ) ], ), - ) ], ), ); From ccc10b04261ce6c819f20ff8923f8713b5efb61b Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Thu, 9 Nov 2023 18:41:13 +0100 Subject: [PATCH 31/31] Don't run the test twice on PRs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b77669497..dcc30a8eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: - '**.dart' - 'pubspec.yaml' pull_request: - branches: [ pull_request, master ] + branches: [ master, ] paths: - '**.dart' - 'pubspec.yaml'