diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index c397c5b5..d4d12c05 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -41,6 +41,7 @@ class _DashboardScreenState extends State { DashboardWorkoutWidget(), DashboardNutritionWidget(), DashboardWeightWidget(), + DashboardMeasurementsWidget(), const DashboardCalendarWidget(), ], ), diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 8979821e..ba9a6415 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +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'; @@ -35,14 +36,20 @@ 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/core/core.dart'; +import 'package:wger/widgets/measurements/categories_card.dart'; import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/weight/forms.dart'; import 'package:wger/widgets/workouts/forms.dart'; +import '../../providers/measurement.dart'; +import '../../screens/measurement_entries_screen.dart'; +import '../measurements/forms.dart'; + class DashboardNutritionWidget extends StatefulWidget { @override - _DashboardNutritionWidgetState createState() => _DashboardNutritionWidgetState(); + _DashboardNutritionWidgetState createState() => + _DashboardNutritionWidgetState(); } class _DashboardNutritionWidgetState extends State { @@ -53,7 +60,8 @@ class _DashboardNutritionWidgetState extends State { @override void initState() { super.initState(); - _plan = Provider.of(context, listen: false).currentPlan; + _plan = + Provider.of(context, listen: false).currentPlan; _hasContent = _plan != null; } @@ -96,7 +104,8 @@ class _DashboardNutritionWidgetState extends State { icon: const Icon(Icons.history_edu), color: wgerPrimaryButtonColor, onPressed: () { - Provider.of(context, listen: false).logMealToDiary(meal); + Provider.of(context, listen: false) + .logMealToDiary(meal); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -127,7 +136,8 @@ class _DashboardNutritionWidgetState extends State { ), ), const SizedBox(width: 5), - Text('${item.amount.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'), + Text( + '${item.amount.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'), ], ), ], @@ -147,7 +157,9 @@ class _DashboardNutritionWidgetState extends State { return const Text(''); } - return _showDetail ? const Icon(Icons.expand_less) : const Icon(Icons.expand_more); + return _showDetail + ? const Icon(Icons.expand_less) + : const Icon(Icons.expand_more); } @override @@ -157,7 +169,9 @@ class _DashboardNutritionWidgetState extends State { children: [ ListTile( title: Text( - _hasContent ? _plan!.description : AppLocalizations.of(context).nutritionalPlan, + _hasContent + ? _plan!.description + : AppLocalizations.of(context).nutritionalPlan, style: Theme.of(context).textTheme.headline4, ), subtitle: Text( @@ -186,7 +200,8 @@ class _DashboardNutritionWidgetState extends State { Container( padding: const EdgeInsets.all(15), height: 180, - child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues), + child: + NutritionalPlanPieChartWidget(_plan!.nutritionalValues), ) ], ), @@ -218,8 +233,9 @@ class _DashboardNutritionWidgetState extends State { TextButton( child: Text(AppLocalizations.of(context).goToDetailPage), onPressed: () { - Navigator.of(context) - .pushNamed(NutritionalPlanScreen.routeName, arguments: _plan); + Navigator.of(context).pushNamed( + NutritionalPlanScreen.routeName, + arguments: _plan); }, ), ], @@ -253,7 +269,7 @@ class _DashboardWeightWidgetState extends State { style: Theme.of(context).textTheme.headline4, ), leading: const FaIcon( - FontAwesomeIcons.weightScale, + FontAwesomeIcons.weight, color: Colors.black, ), trailing: IconButton( @@ -286,9 +302,11 @@ class _DashboardWeightWidgetState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - child: Text(AppLocalizations.of(context).goToDetailPage), + child: Text( + AppLocalizations.of(context).goToDetailPage), onPressed: () { - Navigator.of(context).pushNamed(WeightScreen.routeName); + Navigator.of(context) + .pushNamed(WeightScreen.routeName); }), ], ), @@ -309,6 +327,101 @@ class _DashboardWeightWidgetState extends State { } } +class DashboardMeasurementsWidget extends StatefulWidget { + @override + _DashboardMeasurementsWidgetState createState() => + _DashboardMeasurementsWidgetState(); +} + +class _DashboardMeasurementsWidgetState + extends State { + int _current = 0; + final CarouselController _controller = CarouselController(); + + @override + Widget build(BuildContext context) { + final _provider = Provider.of(context, listen: false); + + var items = _provider.categories + .map( + (item) => CategoriesCard(item), + ) + .toList(); + 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, + ), + ), + 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).noWeightEntries, + AppLocalizations.of(context).newEntry, + WeightForm(), + ), + ], + ), + ], + ), + ), + ); + } +} + class DashboardWorkoutWidget extends StatefulWidget { @override _DashboardWorkoutWidgetState createState() => _DashboardWorkoutWidgetState(); @@ -332,7 +445,9 @@ class _DashboardWorkoutWidgetState extends State { return const Text(''); } - return _showDetail ? const Icon(Icons.expand_less) : const Icon(Icons.expand_more); + return _showDetail + ? const Icon(Icons.expand_less) + : const Icon(Icons.expand_more); } List getContent() { @@ -364,7 +479,8 @@ class _DashboardWorkoutWidgetState extends State { icon: const Icon(Icons.play_arrow), color: wgerPrimaryButtonColor, onPressed: () { - Navigator.of(context).pushNamed(GymModeScreen.routeName, arguments: day); + Navigator.of(context) + .pushNamed(GymModeScreen.routeName, arguments: day); }, ), ], @@ -386,7 +502,8 @@ class _DashboardWorkoutWidgetState extends State { children: [ Text(s.exerciseObj.name), const SizedBox(width: 10), - MutedText(set.getSmartRepr(s.exerciseObj).join('\n')), + MutedText( + set.getSmartRepr(s.exerciseObj).join('\n')), ], ), const SizedBox(height: 10), @@ -411,7 +528,9 @@ class _DashboardWorkoutWidgetState extends State { children: [ ListTile( title: Text( - _hasContent ? _workoutPlan!.name : AppLocalizations.of(context).labelWorkoutPlan, + _hasContent + ? _workoutPlan!.name + : AppLocalizations.of(context).labelWorkoutPlan, style: Theme.of(context).textTheme.headline4, ), subtitle: Text( @@ -453,8 +572,8 @@ class _DashboardWorkoutWidgetState extends State { TextButton( child: Text(AppLocalizations.of(context).goToDetailPage), onPressed: () { - Navigator.of(context) - .pushNamed(WorkoutPlanScreen.routeName, arguments: _workoutPlan); + Navigator.of(context).pushNamed(WorkoutPlanScreen.routeName, + arguments: _workoutPlan); }, ), ], diff --git a/lib/widgets/measurements/categories.dart b/lib/widgets/measurements/categories.dart index fd44426a..49ba73e3 100644 --- a/lib/widgets/measurements/categories.dart +++ b/lib/widgets/measurements/categories.dart @@ -24,6 +24,7 @@ import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/measurement_entries_screen.dart'; import 'package:wger/widgets/core/charts.dart'; +import 'categories_card.dart'; import 'forms.dart'; class CategoriesList extends StatelessWidget { @@ -37,58 +38,7 @@ class CategoriesList extends StatelessWidget { itemBuilder: (context, index) { final currentCategory = _provider.categories[index]; - return Card( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 5), - child: Text( - currentCategory.name, - style: Theme.of(context).textTheme.headline6, - ), - ), - Container( - color: Colors.white, - padding: const EdgeInsets.all(10), - height: 220, - child: MeasurementChartWidget( - currentCategory.entries - .map((e) => MeasurementChartEntry(e.value, e.date)) - .toList(), - unit: currentCategory.unit, - ), - ), - const Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - child: Text(AppLocalizations.of(context).goToDetailPage), - onPressed: () { - Navigator.pushNamed( - context, - MeasurementEntriesScreen.routeName, - arguments: currentCategory.id, - ); - }), - IconButton( - onPressed: () async { - await Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).newEntry, - MeasurementEntryForm(currentCategory.id!), - ), - ); - }, - icon: const Icon(Icons.add), - ), - ], - ), - ], - ), - ); + return CategoriesCard(currentCategory); }, ); } diff --git a/lib/widgets/measurements/categories_card.dart b/lib/widgets/measurements/categories_card.dart new file mode 100644 index 00000000..b2605774 --- /dev/null +++ b/lib/widgets/measurements/categories_card.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.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; + + CategoriesCard(this.currentCategory); + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 5), + child: Text( + currentCategory.name, + style: Theme.of(context).textTheme.headline6, + ), + ), + Container( + color: Colors.white, + padding: const EdgeInsets.all(10), + height: 220, + child: MeasurementChartWidget( + currentCategory.entries + .map((e) => MeasurementChartEntry(e.value, e.date)) + .toList(), + unit: currentCategory.unit, + ), + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + child: Text(AppLocalizations.of(context).goToDetailPage), + onPressed: () { + Navigator.pushNamed( + context, + MeasurementEntriesScreen.routeName, + arguments: currentCategory.id, + ); + }), + IconButton( + onPressed: () async { + await Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).newEntry, + MeasurementEntryForm(currentCategory.id!), + ), + ); + }, + icon: const Icon(Icons.add), + ), + ], + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 07c1a066..de8e7a93 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -141,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" change: dependency: transitive description: @@ -154,7 +161,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -217,7 +224,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -287,7 +294,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -552,7 +559,7 @@ packages: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "6.4.1" + version: "6.4.0" klizma: dependency: transitive description: @@ -594,21 +601,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -664,7 +671,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -879,7 +886,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -907,7 +914,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" table_calendar: dependency: "direct main" description: @@ -921,14 +928,14 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" timing: dependency: transitive description: @@ -956,7 +963,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.6" + version: "6.1.5" url_launcher_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b1ddc0cc..5dc17fb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,9 +51,10 @@ dependencies: rive: ^0.9.1 shared_preferences: ^2.0.15 table_calendar: ^3.0.7 - url_launcher: ^6.1.6 + url_launcher: ^6.1.5 flutter_barcode_scanner: ^2.0.0 video_player: ^2.4.7 + carousel_slider: ^4.1.1 dev_dependencies: flutter_test: @@ -62,7 +63,7 @@ dev_dependencies: # sdk: flutter build_runner: ^2.2.1 flutter_launcher_icons: ^0.10.0 - json_serializable: ^6.4.1 + json_serializable: ^6.3.2 mockito: ^5.3.2 network_image_mock: ^2.1.1 flutter_lints: ^2.0.1