From 1dc3776e7e111f83da999fd3ee9346e06d4c6a26 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 2 Sep 2025 14:49:28 +0200 Subject: [PATCH 01/26] Improve video error handling Users now see a smaller error message in the exercise description, instead of the big general popup. --- lib/helpers/errors.dart | 58 ++++++++++++++++++++++++++++--- lib/models/exercises/video.dart | 2 ++ lib/widgets/exercises/videos.dart | 52 +++++++++++++++++++-------- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/lib/helpers/errors.dart b/lib/helpers/errors.dart index 91a4a75e..e1132694 100644 --- a/lib/helpers/errors.dart +++ b/lib/helpers/errors.dart @@ -49,7 +49,7 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? co return; } - final errorList = formatErrors(extractErrors(exception.errors)); + final errorList = formatApiErrors(extractErrors(exception.errors)); showDialog( context: dialogContext, @@ -104,6 +104,15 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext } else if (error is SocketException) { isNetworkError = true; } + /* + else if (error is PlatformException) { + errorTitle = 'Problem with media'; + errorMessage = + 'There was a problem loading the media. This can be a e.g. problem with the codec that' + 'is not supported by your device. Original error message: ${error.message}'; + } + + */ final String fullStackTrace = stackTrace?.toString() ?? 'No stack trace available.'; @@ -115,13 +124,13 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext builder: (BuildContext context) { return AlertDialog( title: Row( + spacing: 8, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( isNetworkError ? Icons.signal_wifi_connected_no_internet_4_outlined : Icons.error, color: Theme.of(context).colorScheme.error, ), - const SizedBox(width: 8), Expanded( child: Text( isNetworkError ? i18n.errorCouldNotConnectToServer : i18n.anErrorOccurred, @@ -309,7 +318,7 @@ List extractErrors(Map errors) { } /// Processes the error messages from the server and returns a list of widgets -List formatErrors(List errors, {Color? color}) { +List formatApiErrors(List errors, {Color? color}) { final textColor = color ?? Colors.black; final List errorList = []; @@ -328,6 +337,26 @@ List formatErrors(List errors, {Color? color}) { return errorList; } +/// Processes the error messages from the server and returns a list of widgets +List formatTextErrors(List errors, {String? title, Color? color}) { + final textColor = color ?? Colors.black; + + final List errorList = []; + + if (title != null) { + errorList.add( + Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: textColor)), + ); + } + + for (final message in errors) { + errorList.add(Text(message, style: TextStyle(color: textColor))); + } + errorList.add(const SizedBox(height: 8)); + + return errorList; +} + class FormHttpErrorsWidget extends StatelessWidget { final WgerHttpException exception; @@ -338,7 +367,7 @@ class FormHttpErrorsWidget extends StatelessWidget { return Column( children: [ Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), - ...formatErrors( + ...formatApiErrors( extractErrors(exception.errors), color: Theme.of(context).colorScheme.error, ), @@ -346,3 +375,24 @@ class FormHttpErrorsWidget extends StatelessWidget { ); } } + +class GeneralErrorsWidget extends StatelessWidget { + final String? title; + final List widgets; + + const GeneralErrorsWidget(this.widgets, {this.title, super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), + ...formatTextErrors( + widgets, + title: title, + color: Theme.of(context).colorScheme.error, + ), + ], + ); + } +} diff --git a/lib/models/exercises/video.dart b/lib/models/exercises/video.dart index c499664d..a6948baa 100644 --- a/lib/models/exercises/video.dart +++ b/lib/models/exercises/video.dart @@ -32,6 +32,8 @@ class Video { @JsonKey(name: 'video', required: true) final String url; + Uri get uri => Uri.parse(url); + @JsonKey(name: 'exercise', required: true) final int exerciseId; diff --git a/lib/widgets/exercises/videos.dart b/lib/widgets/exercises/videos.dart index aad1ebd9..0a5496bf 100644 --- a/lib/widgets/exercises/videos.dart +++ b/lib/widgets/exercises/videos.dart @@ -17,7 +17,10 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; import 'package:video_player/video_player.dart'; +import 'package:wger/helpers/errors.dart'; import 'package:wger/models/exercises/video.dart'; class ExerciseVideoWidget extends StatefulWidget { @@ -31,35 +34,56 @@ class ExerciseVideoWidget extends StatefulWidget { class _ExerciseVideoWidgetState extends State { late VideoPlayerController _controller; + bool hasError = false; + final logger = Logger('ExerciseVideoWidgetState'); @override void initState() { super.initState(); - _controller = VideoPlayerController.network(widget.video.url); - _controller.addListener(() { + _controller = VideoPlayerController.networkUrl(widget.video.uri); + _initializeVideo(); + } + + Future _initializeVideo() async { + try { + await _controller.initialize(); setState(() {}); - }); - _controller.initialize().then((_) => setState(() {})); + } on PlatformException catch (e) { + if (mounted) { + setState(() => hasError = true); + } + + logger.warning('PlatformException while initializing video: ${e.message}'); + } } @override void dispose() { - super.dispose(); _controller.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { - return _controller.value.isInitialized - ? AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: Stack(alignment: Alignment.bottomCenter, children: [ - VideoPlayer(_controller), - _ControlsOverlay(controller: _controller), - VideoProgressIndicator(_controller, allowScrubbing: true), - ]), + return hasError + ? const GeneralErrorsWidget( + [ + 'An error happened while loading the video. If you can, please check the application logs.' + ], ) - : Container(); + : _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller, allowScrubbing: true), + ], + ), + ) + : Container(); } } From e896a2eeab3daccc290273ace783ed432ea98eeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 00:08:28 +0000 Subject: [PATCH 02/26] Bump json_serializable from 6.10.0 to 6.11.0 Bumps [json_serializable](https://github.com/google/json_serializable.dart) from 6.10.0 to 6.11.0. - [Release notes](https://github.com/google/json_serializable.dart/releases) - [Commits](https://github.com/google/json_serializable.dart/compare/json_serializable-v6.10.0...json_serializable-v6.11.0) --- updated-dependencies: - dependency-name: json_serializable dependency-version: 6.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 28 ++++++++++++++-------------- pubspec.yaml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 1458fcc9..1fc385f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -736,34 +736,34 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27 + sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd" url: "https://pub.dev" source: hosted - version: "6.10.0" + version: "6.11.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1189,10 +1189,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134 + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" source_helper: dependency: transitive description: @@ -1301,10 +1301,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timing: dependency: transitive description: @@ -1413,10 +1413,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" version: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9d7486e1..41d92ae7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,7 +79,7 @@ dev_dependencies: drift_dev: ^2.28.1 flutter_lints: ^6.0.0 freezed: ^3.2.0 - json_serializable: ^6.9.5 + json_serializable: ^6.11.0 mockito: ^5.4.6 network_image_mock: ^2.1.1 shared_preferences_platform_interface: ^2.0.0 From 0f44e0b25fd187c7cf3931dc90c1dcf6bdec9447 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Thu, 4 Sep 2025 14:34:52 +0200 Subject: [PATCH 03/26] Refactor the general error popup This should make it clearer where the messages and titles come from --- lib/helpers/errors.dart | 73 ++++++++++++++++++++--------------------- pubspec.lock | 20 +++++------ 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/lib/helpers/errors.dart b/lib/helpers/errors.dart index e1132694..4a4a22de 100644 --- a/lib/helpers/errors.dart +++ b/lib/helpers/errors.dart @@ -87,37 +87,35 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext return; } + final i18n = AppLocalizations.of(dialogContext); + // If possible, determine the error title and message based on the error type. - bool isNetworkError = false; - String errorTitle = 'An error occurred'; - String errorMessage = error.toString(); + // (Note that issue titles and error messages are not localized) + bool allowReportIssue = true; + String issueTitle = 'An error occurred'; + String issueErrorMessage = error.toString(); + String errorTitle = i18n.anErrorOccurred; + String errorDescription = i18n.errorInfoDescription; + var icon = Icons.error; if (error is TimeoutException) { - errorTitle = 'Network Timeout'; - errorMessage = - 'The connection to the server timed out. Please check your internet connection and try again.'; + issueTitle = 'Network Timeout'; + issueErrorMessage = 'The connection to the server timed out. Please check your ' + 'internet connection and try again.'; } else if (error is FlutterErrorDetails) { - errorTitle = 'Application Error'; - errorMessage = error.exceptionAsString(); + issueTitle = 'Application Error'; + issueErrorMessage = error.exceptionAsString(); } else if (error is MissingRequiredKeysException) { - errorTitle = 'Missing Required Key'; + issueTitle = 'Missing Required Key'; } else if (error is SocketException) { - isNetworkError = true; + allowReportIssue = false; + icon = Icons.signal_wifi_connected_no_internet_4_outlined; + errorTitle = i18n.errorCouldNotConnectToServer; + errorDescription = i18n.errorCouldNotConnectToServerDetails; } - /* - else if (error is PlatformException) { - errorTitle = 'Problem with media'; - errorMessage = - 'There was a problem loading the media. This can be a e.g. problem with the codec that' - 'is not supported by your device. Original error message: ${error.message}'; - } - - */ final String fullStackTrace = stackTrace?.toString() ?? 'No stack trace available.'; - final i18n = AppLocalizations.of(dialogContext); - showDialog( context: dialogContext, barrierDismissible: false, @@ -128,12 +126,12 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( - isNetworkError ? Icons.signal_wifi_connected_no_internet_4_outlined : Icons.error, + icon, color: Theme.of(context).colorScheme.error, ), Expanded( child: Text( - isNetworkError ? i18n.errorCouldNotConnectToServer : i18n.anErrorOccurred, + errorTitle, style: TextStyle(color: Theme.of(context).colorScheme.error), ), ), @@ -142,11 +140,7 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext content: SingleChildScrollView( child: ListBody( children: [ - Text( - isNetworkError - ? i18n.errorCouldNotConnectToServerDetails - : i18n.errorInfoDescription, - ), + Text(errorDescription), const SizedBox(height: 8), Text(i18n.errorInfoDescription2), const SizedBox(height: 10), @@ -155,7 +149,7 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext title: Text(i18n.errorViewDetails), children: [ Text( - errorMessage, + issueErrorMessage, style: const TextStyle(fontWeight: FontWeight.bold), ), Container( @@ -178,14 +172,17 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: () { - final String clipboardText = - 'Error Title: $errorTitle\nError Message: $errorMessage\n\nStack Trace:\n$fullStackTrace'; + final String clipboardText = 'Error Title: $issueTitle\n' + 'Error Message: $issueErrorMessage\n\n' + 'Stack Trace:\n$fullStackTrace'; Clipboard.setData(ClipboardData(text: clipboardText)).then((_) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Error details copied to clipboard!')), ); }).catchError((copyError) { - if (kDebugMode) logger.fine('Error copying to clipboard: $copyError'); + if (kDebugMode) { + logger.fine('Error copying to clipboard: $copyError'); + } ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Could not copy details.')), ); @@ -198,7 +195,7 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext ), ), actions: [ - if (!isNetworkError) + if (allowReportIssue) TextButton( child: const Text('Report issue'), onPressed: () async { @@ -206,20 +203,22 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext '## Description\n\n' '[Please describe what you were doing when the error occurred.]\n\n' '## Error details\n\n' - 'Error title: $errorTitle\n' - 'Error message: $errorMessage\n' + 'Error title: $issueTitle\n' + 'Error message: $issueErrorMessage\n' 'Stack trace:\n' '```\n$stackTrace\n```', ); final githubIssueUrl = '$GITHUB_ISSUES_BUG_URL' - '&title=$errorTitle' + '&title=$issueTitle' '&description=$description'; final Uri reportUri = Uri.parse(githubIssueUrl); try { await launchUrl(reportUri, mode: LaunchMode.externalApplication); } catch (e) { - if (kDebugMode) logger.warning('Error launching URL: $e'); + if (kDebugMode) { + logger.warning('Error launching URL: $e'); + } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error opening issue tracker: $e')), ); diff --git a/pubspec.lock b/pubspec.lock index 1fc385f8..d82f1cbb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -744,26 +744,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -1301,10 +1301,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" timing: dependency: transitive description: @@ -1413,10 +1413,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" version: dependency: "direct main" description: From d447f4be1ae6d118c0517acd01f41bb3573fa1a4 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Thu, 4 Sep 2025 15:19:29 +0200 Subject: [PATCH 04/26] Bump flutter version to 3.35 --- .github/actions/flutter-common/action.yml | 2 +- pubspec.lock | 26 +++++++++++------------ pubspec.yaml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/flutter-common/action.yml b/.github/actions/flutter-common/action.yml index 2f3c19bc..f3e4a73f 100644 --- a/.github/actions/flutter-common/action.yml +++ b/.github/actions/flutter-common/action.yml @@ -9,7 +9,7 @@ runs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.8 + flutter-version: 3.35.2 cache: true - name: Install Flutter dependencies diff --git a/pubspec.lock b/pubspec.lock index d82f1cbb..cd3c1eba 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: flex_color_scheme - sha256: "3344f8f6536c6ce0473b98e9f084ef80ca89024ad3b454f9c32cf840206f4387" + sha256: "034d5720747e6af39b2ad090d82dd92d33fde68e7964f1814b714c9d49ddbd64" url: "https://pub.dev" source: hosted - version: "8.2.0" + version: "8.3.0" flex_seed_scheme: dependency: "direct main" description: @@ -744,26 +744,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1301,10 +1301,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timing: dependency: transitive description: @@ -1413,10 +1413,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" version: dependency: "direct main" description: @@ -1555,4 +1555,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 41d92ae7..e79c8fa3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: drift: ^2.28.1 equatable: ^2.0.7 fl_chart: ^1.0.0 - flex_color_scheme: ^8.1.1 + flex_color_scheme: ^8.3.0 flex_seed_scheme: ^3.5.1 flutter_html: ^3.0.0 flutter_staggered_grid_view: ^0.7.0 From 29cbd21460c6717a0e497f47b1c7b10f4314478c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:27:44 +0000 Subject: [PATCH 05/26] Bump fl_chart from 1.0.0 to 1.1.0 Bumps [fl_chart](https://github.com/imaNNeo/fl_chart) from 1.0.0 to 1.1.0. - [Release notes](https://github.com/imaNNeo/fl_chart/releases) - [Changelog](https://github.com/imaNNeo/fl_chart/blob/main/CHANGELOG.md) - [Commits](https://github.com/imaNNeo/fl_chart/compare/1.0.0...1.1.0) --- updated-dependencies: - dependency-name: fl_chart dependency-version: 1.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index cd3c1eba..623206b8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -373,10 +373,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" + sha256: d3f82f4a38e33ba23d05a08ff304d7d8b22d2a59a5503f20bd802966e915db89 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" flex_color_scheme: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e79c8fa3..6bfde1d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: cupertino_icons: ^1.0.8 drift: ^2.28.1 equatable: ^2.0.7 - fl_chart: ^1.0.0 + fl_chart: ^1.1.0 flex_color_scheme: ^8.3.0 flex_seed_scheme: ^3.5.1 flutter_html: ^3.0.0 From 5c80fe2a9a7453520ef3d89edffe87b28cd13c0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:28:31 +0000 Subject: [PATCH 06/26] Bump font_awesome_flutter from 10.9.0 to 10.10.0 Bumps [font_awesome_flutter](https://github.com/fluttercommunity/font_awesome_flutter) from 10.9.0 to 10.10.0. - [Release notes](https://github.com/fluttercommunity/font_awesome_flutter/releases) - [Changelog](https://github.com/fluttercommunity/font_awesome_flutter/blob/master/CHANGELOG.md) - [Commits](https://github.com/fluttercommunity/font_awesome_flutter/commits/10.10.0) --- updated-dependencies: - dependency-name: font_awesome_flutter dependency-version: 10.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 623206b8..f3612baa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -542,10 +542,10 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: f50ce90dbe26d977415b9540400d6778bef00894aced6358ae578abd92b14b10 + sha256: "27af5982e6c510dec1ba038eff634fa284676ee84e3fd807225c80c4ad869177" url: "https://pub.dev" source: hosted - version: "10.9.0" + version: "10.10.0" freezed: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6bfde1d4..966095a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: flutter_svg_icons: ^0.0.1 flutter_typeahead: ^5.2.0 flutter_zxing: ^2.2.1 - font_awesome_flutter: ^10.9.0 + font_awesome_flutter: ^10.10.0 freezed_annotation: ^3.0.0 get_it: ^8.2.0 http: ^1.5.0 From 244468fbca0a2a51e70c73b9f8f6944f66f870db Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 5 Sep 2025 15:25:41 +0200 Subject: [PATCH 07/26] Update deployment target for ios --- ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 8c6e5614..d57061dd 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 74e7ac47..e2a08f6f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -105,7 +105,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93 image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9d2e3de6..650ff389 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -344,7 +344,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -432,7 +432,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -481,7 +481,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; From 4330e51f8667247e548d9276993a03b1db92794c Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 5 Sep 2025 22:35:34 +0200 Subject: [PATCH 08/26] Retrieve the current routine dynamically This behaviour is now the same as the nutritional plans. --- integration_test/1_dashboard.dart | 3 +-- lib/providers/routines.dart | 24 +++++++------------ lib/screens/home_tabs_screen.dart | 4 ++-- lib/widgets/dashboard/widgets/routines.dart | 2 +- lib/widgets/routines/routines_list.dart | 2 -- pubspec.lock | 2 +- test/routine/day_form_test.mocks.dart | 9 ------- .../forms/session_form_test.mocks.dart | 9 ------- .../gym_mode_session_screen_test.mocks.dart | 9 ------- ...epetition_unit_form_widget_test.mocks.dart | 9 ------- .../routine_edit_screen_test.mocks.dart | 9 ------- test/routine/routine_edit_test.mocks.dart | 9 ------- test/routine/routine_form_test.mocks.dart | 9 ------- .../routine_logs_screen_test.mocks.dart | 9 ------- test/routine/slot_entry_form_test.mocks.dart | 9 ------- .../weight_unit_form_widget_test.mocks.dart | 9 ------- 16 files changed, 14 insertions(+), 113 deletions(-) diff --git a/integration_test/1_dashboard.dart b/integration_test/1_dashboard.dart index e408830c..e369c0ed 100644 --- a/integration_test/1_dashboard.dart +++ b/integration_test/1_dashboard.dart @@ -24,8 +24,7 @@ import '../test_data/routines.dart'; Widget createDashboardScreen({locale = 'en'}) { final mockWorkoutProvider = MockRoutinesProvider(); - when(mockWorkoutProvider.activeRoutine) - .thenReturn(getTestRoutine(exercises: getScreenshotExercises())); + when(mockWorkoutProvider.items).thenReturn([getTestRoutine(exercises: getScreenshotExercises())]); when(mockWorkoutProvider.fetchSessionData()).thenAnswer((a) => Future.value([ WorkoutSession( diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index a2d9a192..ee22eae9 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -83,6 +83,15 @@ class RoutinesProvider with ChangeNotifier { _repetitionUnits = repetitionUnits ?? []; } + /// Returns the current active nutritional plan. At the moment this is just + /// the latest, but this might change in the future. + Routine? get currentRoutine { + if (_routines.isNotEmpty) { + return _routines.first; + } + return null; + } + List get items { return [..._routines]; } @@ -97,7 +106,6 @@ class RoutinesProvider with ChangeNotifier { /// Clears all lists void clear() { - activeRoutine = null; _routines = []; _weightUnits = []; _repetitionUnits = []; @@ -138,16 +146,6 @@ class RoutinesProvider with ChangeNotifier { return _routines.indexWhere((routine) => routine.id == id); } - /// Sets the current active routine. At the moment this is just the latest, - /// but this might change in the future. - void setActiveRoutine() { - if (_routines.isNotEmpty) { - activeRoutine = _routines.first; - } else { - activeRoutine = null; - } - } - /* * Routines */ @@ -165,7 +163,6 @@ class RoutinesProvider with ChangeNotifier { await fetchAndSetRoutineFull(entry['id']); } - setActiveRoutine(); notifyListeners(); } @@ -181,7 +178,6 @@ class RoutinesProvider with ChangeNotifier { _routines.add(plan); } - setActiveRoutine(); notifyListeners(); } @@ -220,7 +216,6 @@ class RoutinesProvider with ChangeNotifier { _routines.add(routine); _routines.sort((a, b) => b.created.compareTo(a.created)); - setActiveRoutine(); notifyListeners(); return routine; } @@ -338,7 +333,6 @@ class RoutinesProvider with ChangeNotifier { _routines.add(routine); } - setActiveRoutine(); notifyListeners(); return routine; } diff --git a/lib/screens/home_tabs_screen.dart b/lib/screens/home_tabs_screen.dart index 3fea4cee..299aeada 100644 --- a/lib/screens/home_tabs_screen.dart +++ b/lib/screens/home_tabs_screen.dart @@ -116,8 +116,8 @@ class _HomeTabsScreenState extends State with SingleTickerProvid // Current routine widget._logger.info('Loading current routine'); - if (routinesProvider.activeRoutine != null) { - final planId = routinesProvider.activeRoutine!.id!; + if (routinesProvider.currentRoutine != null) { + final planId = routinesProvider.currentRoutine!.id!; await routinesProvider.fetchAndSetRoutineFull(planId); } } diff --git a/lib/widgets/dashboard/widgets/routines.dart b/lib/widgets/dashboard/widgets/routines.dart index ae4c06e2..66392c68 100644 --- a/lib/widgets/dashboard/widgets/routines.dart +++ b/lib/widgets/dashboard/widgets/routines.dart @@ -44,7 +44,7 @@ class _DashboardRoutineWidgetState extends State { @override Widget build(BuildContext context) { - final routine = context.watch().activeRoutine; + final routine = context.watch().currentRoutine; _hasContent = routine != null; final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode); diff --git a/lib/widgets/routines/routines_list.dart b/lib/widgets/routines/routines_list.dart index 2932e285..618f9a85 100644 --- a/lib/widgets/routines/routines_list.dart +++ b/lib/widgets/routines/routines_list.dart @@ -53,8 +53,6 @@ class _RoutinesListState extends State { return Card( child: ListTile( onTap: () async { - widget._routineProvider.activeRoutine = currentRoutine; - setState(() { _loadingRoutine = currentRoutine.id; }); diff --git a/pubspec.lock b/pubspec.lock index f3612baa..551d71e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1554,5 +1554,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.35.0" diff --git a/test/routine/day_form_test.mocks.dart b/test/routine/day_form_test.mocks.dart index 45d67cb1..533407f7 100644 --- a/test/routine/day_form_test.mocks.dart +++ b/test/routine/day_form_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/forms/session_form_test.mocks.dart b/test/routine/forms/session_form_test.mocks.dart index 68bc92d3..141e88f8 100644 --- a/test/routine/forms/session_form_test.mocks.dart +++ b/test/routine/forms/session_form_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/gym_mode_session_screen_test.mocks.dart b/test/routine/gym_mode_session_screen_test.mocks.dart index d45aabfb..5bb488b7 100644 --- a/test/routine/gym_mode_session_screen_test.mocks.dart +++ b/test/routine/gym_mode_session_screen_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/repetition_unit_form_widget_test.mocks.dart b/test/routine/repetition_unit_form_widget_test.mocks.dart index 539db43d..ae386b9e 100644 --- a/test/routine/repetition_unit_form_widget_test.mocks.dart +++ b/test/routine/repetition_unit_form_widget_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/routine_edit_screen_test.mocks.dart b/test/routine/routine_edit_screen_test.mocks.dart index ce8ce0d6..ee06b9cb 100644 --- a/test/routine/routine_edit_screen_test.mocks.dart +++ b/test/routine/routine_edit_screen_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/routine_edit_test.mocks.dart b/test/routine/routine_edit_test.mocks.dart index 78ce146e..e607298d 100644 --- a/test/routine/routine_edit_test.mocks.dart +++ b/test/routine/routine_edit_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/routine_form_test.mocks.dart b/test/routine/routine_form_test.mocks.dart index 48438fa8..ab5629f7 100644 --- a/test/routine/routine_form_test.mocks.dart +++ b/test/routine/routine_form_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/routine_logs_screen_test.mocks.dart b/test/routine/routine_logs_screen_test.mocks.dart index 23e1a83a..e1df625d 100644 --- a/test/routine/routine_logs_screen_test.mocks.dart +++ b/test/routine/routine_logs_screen_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/slot_entry_form_test.mocks.dart b/test/routine/slot_entry_form_test.mocks.dart index 5a00cd20..3ed76010 100644 --- a/test/routine/slot_entry_form_test.mocks.dart +++ b/test/routine/slot_entry_form_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/weight_unit_form_widget_test.mocks.dart b/test/routine/weight_unit_form_widget_test.mocks.dart index daf91fde..b76ec9ab 100644 --- a/test/routine/weight_unit_form_widget_test.mocks.dart +++ b/test/routine/weight_unit_form_widget_test.mocks.dart @@ -294,15 +294,6 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: 0, ) as int); - @override - void setActiveRoutine() => super.noSuchMethod( - Invocation.method( - #setActiveRoutine, - [], - ), - returnValueForMissingStub: null, - ); - @override _i13.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( Invocation.method( From c89ccb630c7aed862dc6f5ef41c437ef64d41155 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 6 Sep 2025 00:50:00 +0200 Subject: [PATCH 09/26] Use max pagination size when loading data This is a workaround for #867, but not a real solution --- lib/helpers/consts.dart | 2 ++ lib/providers/body_weight.dart | 3 ++- lib/providers/exercises.dart | 2 +- lib/providers/measurement.dart | 16 +++++++----- lib/providers/nutrition.dart | 9 ++++--- lib/providers/routines.dart | 9 ++++--- .../measurement_provider_test.dart | 25 +++++++++++-------- test/routine/routines_provider_test.dart | 5 ++-- 8 files changed, 44 insertions(+), 27 deletions(-) diff --git a/lib/helpers/consts.dart b/lib/helpers/consts.dart index ab58fb67..7d538e02 100644 --- a/lib/helpers/consts.dart +++ b/lib/helpers/consts.dart @@ -135,3 +135,5 @@ enum WeightUnitEnum { kg, lb } /// TextInputType for decimal numbers const textInputTypeDecimal = TextInputType.numberWithOptions(decimal: true); + +const String API_MAX_PAGE_SIZE = '999'; diff --git a/lib/providers/body_weight.dart b/lib/providers/body_weight.dart index 7e2b2986..9aa52c27 100644 --- a/lib/providers/body_weight.dart +++ b/lib/providers/body_weight.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:wger/exceptions/http_exception.dart'; +import 'package:wger/helpers/consts.dart'; import 'package:wger/models/body_weight/weight_entry.dart'; import 'package:wger/providers/base_provider.dart'; @@ -69,7 +70,7 @@ class BodyWeightProvider with ChangeNotifier { // Process the response final data = await baseProvider.fetchPaginated(baseProvider.makeUrl( BODY_WEIGHT_URL, - query: {'ordering': '-date', 'limit': '100'}, + query: {'ordering': '-date', 'limit': API_MAX_PAGE_SIZE}, )); _entries = []; for (final entry in data) { diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index 63af8d91..6a4d90bf 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -460,7 +460,7 @@ class ExercisesProvider with ChangeNotifier { /// Updates the exercise database with *all* the exercises from the server Future updateExerciseCache(ExerciseDatabase database) async { final data = await baseProvider.fetchPaginated( - baseProvider.makeUrl(exerciseInfoUrlPath, query: {'limit': '999'}), + baseProvider.makeUrl(exerciseInfoUrlPath, query: {'limit': API_MAX_PAGE_SIZE}), ); exercises = data.map((e) => Exercise.fromApiDataJson(e, _languages)).toList(); diff --git a/lib/providers/measurement.dart b/lib/providers/measurement.dart index 77b888bc..faa07699 100644 --- a/lib/providers/measurement.dart +++ b/lib/providers/measurement.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:wger/exceptions/http_exception.dart'; import 'package:wger/exceptions/no_such_entry_exception.dart'; +import 'package:wger/helpers/consts.dart'; import 'package:wger/models/measurements/measurement_category.dart'; import 'package:wger/models/measurements/measurement_entry.dart'; import 'package:wger/providers/base_provider.dart'; @@ -54,10 +55,10 @@ class MeasurementProvider with ChangeNotifier { /// Fetches and sets the categories from the server (no entries) Future fetchAndSetCategories() async { // Process the response - final requestUrl = baseProvider.makeUrl(_categoryUrl); - final data = await baseProvider.fetch(requestUrl); + final requestUrl = baseProvider.makeUrl(_categoryUrl, query: {'limit': API_MAX_PAGE_SIZE}); + final data = await baseProvider.fetchPaginated(requestUrl); final List loadedEntries = []; - for (final entry in data['results']) { + for (final entry in data) { loadedEntries.add(MeasurementCategory.fromJson(entry)); } @@ -71,10 +72,13 @@ class MeasurementProvider with ChangeNotifier { final categoryIndex = _categories.indexOf(category); // Process the response - final requestUrl = baseProvider.makeUrl(_entryUrl, query: {'category': category.id.toString()}); - final data = await baseProvider.fetch(requestUrl); + final requestUrl = baseProvider.makeUrl( + _entryUrl, + query: {'category': category.id.toString(), 'limit': API_MAX_PAGE_SIZE}, + ); + final data = await baseProvider.fetchPaginated(requestUrl); final List loadedEntries = []; - for (final entry in data['results']) { + for (final entry in data) { loadedEntries.add(MeasurementEntry.fromJson(entry)); } final MeasurementCategory editedCategory = category.copyWith(entries: loadedEntries); diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 384890c9..09c864af 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -96,7 +96,7 @@ class NutritionPlansProvider with ChangeNotifier { /// object itself and no child attributes Future fetchAndSetAllPlansSparse() async { final data = await baseProvider.fetchPaginated( - baseProvider.makeUrl(_nutritionalPlansPath, query: {'limit': '1000'}), + baseProvider.makeUrl(_nutritionalPlansPath, query: {'limit': API_MAX_PAGE_SIZE}), ); _plans = []; for (final planData in data) { @@ -109,7 +109,10 @@ class NutritionPlansProvider with ChangeNotifier { /// Fetches and sets all plans fully, i.e. with all corresponding child objects Future fetchAndSetAllPlansFull() async { - final data = await baseProvider.fetchPaginated(baseProvider.makeUrl(_nutritionalPlansPath)); + final data = await baseProvider.fetchPaginated(baseProvider.makeUrl( + _nutritionalPlansPath, + query: {'limit': API_MAX_PAGE_SIZE}, + )); await Future.wait(data.map((e) => fetchAndSetPlanFull(e['id'])).toList()); } @@ -448,7 +451,7 @@ class NutritionPlansProvider with ChangeNotifier { _nutritionDiaryPath, query: { 'plan': plan.id?.toString(), - 'limit': '999', + 'limit': API_MAX_PAGE_SIZE, 'ordering': 'datetime', }, ), diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index ee22eae9..e270f7d3 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -156,7 +156,7 @@ class RoutinesProvider with ChangeNotifier { final data = await baseProvider.fetch( baseProvider.makeUrl( _routinesUrlPath, - query: {'ordering': '-creation_date', 'limit': '1000', 'is_template': 'false'}, + query: {'ordering': '-creation_date', 'limit': API_MAX_PAGE_SIZE, 'is_template': 'false'}, ), ); for (final entry in data['results']) { @@ -170,7 +170,10 @@ class RoutinesProvider with ChangeNotifier { /// and no child attributes Future fetchAndSetAllRoutinesSparse() async { final data = await baseProvider.fetch( - baseProvider.makeUrl(_routinesUrlPath, query: {'limit': '1000', 'is_template': 'false'}), + baseProvider.makeUrl( + _routinesUrlPath, + query: {'limit': API_MAX_PAGE_SIZE, 'is_template': 'false'}, + ), ); _routines = []; for (final workoutPlanData in data['results']) { @@ -210,7 +213,7 @@ class RoutinesProvider with ChangeNotifier { /// and no child attributes Future fetchAndSetRoutineSparse(int planId) async { final fullPlanData = await baseProvider.fetch( - baseProvider.makeUrl(_routinesUrlPath, id: planId), + baseProvider.makeUrl(_routinesUrlPath, id: planId, query: {'limit': API_MAX_PAGE_SIZE}), ); final routine = Routine.fromJson(fullPlanData); _routines.add(routine); diff --git a/test/measurements/measurement_provider_test.dart b/test/measurements/measurement_provider_test.dart index 90f60bc1..0b50dd1c 100644 --- a/test/measurements/measurement_provider_test.dart +++ b/test/measurements/measurement_provider_test.dart @@ -6,6 +6,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:wger/exceptions/http_exception.dart'; import 'package:wger/exceptions/no_such_entry_exception.dart'; +import 'package:wger/helpers/consts.dart'; import 'package:wger/models/measurements/measurement_category.dart'; import 'package:wger/models/measurements/measurement_entry.dart'; import 'package:wger/providers/base_provider.dart'; @@ -14,8 +15,6 @@ import 'package:wger/providers/measurement.dart'; import '../fixtures/fixture_reader.dart'; import 'measurement_provider_test.mocks.dart'; -// class MockWgerBaseProvider extends Mock implements WgerBaseProvider {} - @GenerateMocks([WgerBaseProvider]) void main() { late MeasurementProvider measurementProvider; @@ -48,16 +47,17 @@ void main() { measurementProvider = MeasurementProvider(mockWgerBaseProvider); when(mockWgerBaseProvider.makeUrl(any)).thenReturn(tCategoryUri); - when(mockWgerBaseProvider.makeUrl(any, id: anyNamed('id'))).thenReturn(tCategoryUri); - when(mockWgerBaseProvider.fetch(any)) - .thenAnswer((realInvocation) => Future.value(tMeasurementCategoriesMap)); + when(mockWgerBaseProvider.makeUrl(any, id: anyNamed('id'), query: anyNamed('query'))) + .thenReturn(tCategoryUri); + when(mockWgerBaseProvider.fetchPaginated(any)) + .thenAnswer((realInvocation) => Future.value(tMeasurementCategoriesMap['results'])); when(mockWgerBaseProvider.makeUrl(entryUrl, query: anyNamed('query'))) .thenReturn(tCategoryEntriesUri); when(mockWgerBaseProvider.makeUrl(entryUrl, id: anyNamed('id'), query: anyNamed('query'))) .thenReturn(tCategoryEntriesUri); - when(mockWgerBaseProvider.fetch(tCategoryEntriesUri)) - .thenAnswer((realInvocation) => Future.value(tMeasurementCategoryMap)); + when(mockWgerBaseProvider.fetchPaginated(tCategoryEntriesUri)) + .thenAnswer((realInvocation) => Future.value(tMeasurementCategoryMap['results'])); }); group('clear()', () { @@ -100,7 +100,7 @@ void main() { await measurementProvider.fetchAndSetCategories(); // assert - verify(mockWgerBaseProvider.makeUrl(categoryUrl)); + verify(mockWgerBaseProvider.makeUrl(categoryUrl, query: {'limit': API_MAX_PAGE_SIZE})); }); test('should fetch data from api', () async { @@ -108,7 +108,7 @@ void main() { await measurementProvider.fetchAndSetCategories(); // assert - verify(mockWgerBaseProvider.fetch(tCategoryUri)); + verify(mockWgerBaseProvider.fetchPaginated(tCategoryUri)); }); test('should set categories', () async { @@ -130,7 +130,10 @@ void main() { await measurementProvider.fetchAndSetCategoryEntries(tCategoryId); // assert - verify(mockWgerBaseProvider.makeUrl(entryUrl, query: {'category': tCategoryId.toString()})); + verify(mockWgerBaseProvider.makeUrl( + entryUrl, + query: {'category': tCategoryId.toString(), 'limit': API_MAX_PAGE_SIZE}, + )); }); test('should fetch categories entries for id', () async { @@ -138,7 +141,7 @@ void main() { await measurementProvider.fetchAndSetCategoryEntries(tCategoryId); // assert - verify(mockWgerBaseProvider.fetch(tCategoryEntriesUri)); + verify(mockWgerBaseProvider.fetchPaginated(tCategoryEntriesUri)); }); test('should add entries to category in list', () async { diff --git a/test/routine/routines_provider_test.dart b/test/routine/routines_provider_test.dart index 2569b4fc..6250415d 100644 --- a/test/routine/routines_provider_test.dart +++ b/test/routine/routines_provider_test.dart @@ -54,11 +54,12 @@ void main() { }); group('test the workout routine provider', () { - test('Test fetching and setting a plan', () async { + test('Test fetching and setting a routine', () async { final exercisesProvider = ExercisesProvider(mockBaseProvider); final uri = Uri.https('localhost', 'api/v2/routine/325397/'); - when(mockBaseProvider.makeUrl('routine', id: 325397)).thenReturn(uri); + when(mockBaseProvider.makeUrl('routine', id: 325397, query: {'limit': API_MAX_PAGE_SIZE})) + .thenReturn(uri); when(mockBaseProvider.fetch(uri)).thenAnswer( (_) async => Future.value({ 'id': 325397, From d85ee13ed9bf15f4af2f4bb1cbae2cf22fbf3032 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 6 Sep 2025 00:04:45 +0200 Subject: [PATCH 10/26] Give users more control over the exercise cache This allows users to manually refresh the cache and load all exercises from the server. --- lib/providers/exercises.dart | 14 +++ lib/providers/routines.dart | 6 +- lib/widgets/core/settings.dart | 28 ++---- lib/widgets/core/settings/exercise_cache.dart | 89 +++++++++++++++++++ test/core/settings_test.dart | 14 ++- test/core/settings_test.mocks.dart | 10 +++ .../contribute_exercise_test.mocks.dart | 10 +++ .../exercises_detail_widget_test.mocks.dart | 10 +++ test/routine/gym_mode_screen_test.mocks.dart | 10 +++ .../routine/routines_provider_test.mocks.dart | 10 +++ 10 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 lib/widgets/core/settings/exercise_cache.dart diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index 6a4d90bf..1040df25 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -47,6 +47,7 @@ class ExercisesProvider with ChangeNotifier { static const EXERCISE_CACHE_DAYS = 7; static const CACHE_VERSION = 4; + static const exerciseUrlPath = 'exercise'; static const exerciseInfoUrlPath = 'exerciseinfo'; static const exerciseSearchPath = 'exercise/search'; @@ -274,6 +275,18 @@ class ExercisesProvider with ChangeNotifier { } } + Future fetchAndSetAllExercises() async { + _logger.info('Loading all exercises from API'); + final exerciseData = await baseProvider.fetchPaginated( + baseProvider.makeUrl(exerciseUrlPath, query: {'limit': API_MAX_PAGE_SIZE}), + ); + final exerciseIds = exerciseData.map((e) => e['id'] as int).toSet(); + + for (final exerciseId in exerciseIds) { + await handleUpdateExerciseFromApi(database, exerciseId); + } + } + /// Returns the exercise with the given ID /// /// If the exercise is not known locally, it is fetched from the server. @@ -291,6 +304,7 @@ class ExercisesProvider with ChangeNotifier { return exercise; } on NoSuchEntryException { + // _logger.finer('Exercise not found locally, fetching from the API'); return handleUpdateExerciseFromApi(database, exerciseId); } } diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index e270f7d3..946c9fa3 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -153,13 +153,14 @@ class RoutinesProvider with ChangeNotifier { /// Fetches and sets all workout plans fully, i.e. with all corresponding child /// attributes Future fetchAndSetAllRoutinesFull() async { - final data = await baseProvider.fetch( + _logger.fine('Fetching all routines fully'); + final data = await baseProvider.fetchPaginated( baseProvider.makeUrl( _routinesUrlPath, query: {'ordering': '-creation_date', 'limit': API_MAX_PAGE_SIZE, 'is_template': 'false'}, ), ); - for (final entry in data['results']) { + for (final entry in data) { await fetchAndSetRoutineFull(entry['id']); } @@ -169,6 +170,7 @@ class RoutinesProvider with ChangeNotifier { /// Fetches all routines sparsely, i.e. only with the data on the object itself /// and no child attributes Future fetchAndSetAllRoutinesSparse() async { + _logger.fine('Fetching all routines sparsely'); final data = await baseProvider.fetch( baseProvider.makeUrl( _routinesUrlPath, diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index 19802532..66e7fce8 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -20,10 +20,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; -import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/providers/user.dart'; import 'package:wger/screens/configure_plates_screen.dart'; +import 'package:wger/widgets/core/settings/exercise_cache.dart'; class SettingsPage extends StatelessWidget { static String routeName = '/SettingsPage'; @@ -33,7 +33,6 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { final i18n = AppLocalizations.of(context); - final exerciseProvider = Provider.of(context, listen: false); final nutritionProvider = Provider.of(context, listen: false); final userProvider = Provider.of(context); @@ -47,24 +46,7 @@ class SettingsPage extends StatelessWidget { style: Theme.of(context).textTheme.headlineSmall, ), ), - ListTile( - title: Text(i18n.settingsExerciseCacheDescription), - trailing: IconButton( - key: const ValueKey('cacheIconExercises'), - icon: const Icon(Icons.delete), - onPressed: () async { - await exerciseProvider.clearAllCachesAndPrefs(); - - if (context.mounted) { - final snackBar = SnackBar( - content: Text(i18n.settingsCacheDeletedSnackbar), - ); - - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } - }, - ), - ), + const SettingsExerciseCache(), ListTile( title: Text(i18n.settingsIngredientCacheDescription), trailing: IconButton( @@ -83,6 +65,12 @@ class SettingsPage extends StatelessWidget { }, ), ), + ListTile( + title: Text( + i18n.others, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), ListTile( title: Text(i18n.themeMode), trailing: DropdownButton( diff --git a/lib/widgets/core/settings/exercise_cache.dart b/lib/widgets/core/settings/exercise_cache.dart new file mode 100644 index 00000000..1eef754f --- /dev/null +++ b/lib/widgets/core/settings/exercise_cache.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:wger/l10n/generated/app_localizations.dart'; +import 'package:wger/providers/exercises.dart'; + +class SettingsExerciseCache extends StatefulWidget { + const SettingsExerciseCache({super.key}); + + @override + State createState() => _SettingsExerciseCacheState(); +} + +class _SettingsExerciseCacheState extends State { + bool _isRefreshLoading = false; + String _subtitle = ''; + + @override + Widget build(BuildContext context) { + final exerciseProvider = Provider.of(context, listen: false); + final i18n = AppLocalizations.of(context); + + return ListTile( + enabled: !_isRefreshLoading, + title: Text(i18n.settingsExerciseCacheDescription), + subtitle: _subtitle.isNotEmpty ? Text(_subtitle) : null, + trailing: Row(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + key: const ValueKey('cacheIconExercisesRefresh'), + icon: _isRefreshLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.refresh), + onPressed: _isRefreshLoading + ? null + : () async { + setState(() => _isRefreshLoading = true); + + // Note: status messages are currently left in English on purpose + try { + setState(() => _subtitle = 'Clearing cache...'); + await exerciseProvider.clearAllCachesAndPrefs(); + + if (mounted) { + setState(() => _subtitle = 'Loading languages and units...'); + } + await exerciseProvider.fetchAndSetInitialData(); + + if (mounted) { + setState(() => _subtitle = 'Loading all exercises from server...'); + } + await exerciseProvider.fetchAndSetAllExercises(); + + if (mounted) { + setState(() => _subtitle = ''); + } + } finally { + if (mounted) { + setState(() => _isRefreshLoading = false); + } + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(i18n.success)), + ); + } + } + }, + ), + IconButton( + key: const ValueKey('cacheIconExercisesDelete'), + icon: const Icon(Icons.delete), + onPressed: () async { + await exerciseProvider.clearAllCachesAndPrefs(); + + if (context.mounted) { + final snackBar = SnackBar( + content: Text(i18n.settingsCacheDeletedSnackbar), + ); + + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + }, + ) + ]), + ); + } +} diff --git a/test/core/settings_test.dart b/test/core/settings_test.dart index 8c88fc43..7fe2d7ed 100644 --- a/test/core/settings_test.dart +++ b/test/core/settings_test.dart @@ -39,7 +39,7 @@ import 'settings_test.mocks.dart'; WgerBaseProvider, SharedPreferencesAsync, ]) -void main() async { +void main() { final mockExerciseProvider = MockExercisesProvider(); final mockNutritionProvider = MockNutritionPlansProvider(); final mockSharedPreferences = MockSharedPreferencesAsync(); @@ -68,12 +68,22 @@ void main() async { group('Cache', () { testWidgets('Test resetting the exercise cache', (WidgetTester tester) async { await tester.pumpWidget(createSettingsScreen()); - await tester.tap(find.byKey(const ValueKey('cacheIconExercises'))); + await tester.tap(find.byKey(const ValueKey('cacheIconExercisesDelete'))); await tester.pumpAndSettle(); verify(mockExerciseProvider.clearAllCachesAndPrefs()); }); + testWidgets('Test refreshing the exercise cache', (WidgetTester tester) async { + await tester.pumpWidget(createSettingsScreen()); + await tester.tap(find.byKey(const ValueKey('cacheIconExercisesRefresh'))); + await tester.pumpAndSettle(); + + verify(mockExerciseProvider.clearAllCachesAndPrefs()); + verify(mockExerciseProvider.fetchAndSetInitialData()); + verify(mockExerciseProvider.fetchAndSetAllExercises()); + }); + testWidgets('Test resetting the ingredient cache', (WidgetTester tester) async { await tester.pumpWidget(createSettingsScreen()); await tester.tap(find.byKey(const ValueKey('cacheIconIngredients'))); diff --git a/test/core/settings_test.mocks.dart b/test/core/settings_test.mocks.dart index e5cc01bd..39764e01 100644 --- a/test/core/settings_test.mocks.dart +++ b/test/core/settings_test.mocks.dart @@ -490,6 +490,16 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider { returnValueForMissingStub: _i18.Future.value(), ) as _i18.Future); + @override + _i18.Future fetchAndSetAllExercises() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllExercises, + [], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override _i18.Future<_i4.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( diff --git a/test/exercises/contribute_exercise_test.mocks.dart b/test/exercises/contribute_exercise_test.mocks.dart index 41cf8eff..45db393b 100644 --- a/test/exercises/contribute_exercise_test.mocks.dart +++ b/test/exercises/contribute_exercise_test.mocks.dart @@ -945,6 +945,16 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider { returnValueForMissingStub: _i15.Future.value(), ) as _i15.Future); + @override + _i15.Future fetchAndSetAllExercises() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllExercises, + [], + ), + returnValue: _i15.Future.value(), + returnValueForMissingStub: _i15.Future.value(), + ) as _i15.Future); + @override _i15.Future<_i3.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( diff --git a/test/exercises/exercises_detail_widget_test.mocks.dart b/test/exercises/exercises_detail_widget_test.mocks.dart index 62742657..70c259b8 100644 --- a/test/exercises/exercises_detail_widget_test.mocks.dart +++ b/test/exercises/exercises_detail_widget_test.mocks.dart @@ -377,6 +377,16 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider { returnValueForMissingStub: _i10.Future.value(), ) as _i10.Future); + @override + _i10.Future fetchAndSetAllExercises() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllExercises, + [], + ), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); + @override _i10.Future<_i4.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/gym_mode_screen_test.mocks.dart b/test/routine/gym_mode_screen_test.mocks.dart index 704e421b..6be6a132 100644 --- a/test/routine/gym_mode_screen_test.mocks.dart +++ b/test/routine/gym_mode_screen_test.mocks.dart @@ -580,6 +580,16 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { returnValueForMissingStub: _i11.Future.value(), ) as _i11.Future); + @override + _i11.Future fetchAndSetAllExercises() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllExercises, + [], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override _i11.Future<_i6.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( diff --git a/test/routine/routines_provider_test.mocks.dart b/test/routine/routines_provider_test.mocks.dart index 3512d8a9..76f22596 100644 --- a/test/routine/routines_provider_test.mocks.dart +++ b/test/routine/routines_provider_test.mocks.dart @@ -580,6 +580,16 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { returnValueForMissingStub: _i11.Future.value(), ) as _i11.Future); + @override + _i11.Future fetchAndSetAllExercises() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllExercises, + [], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override _i11.Future<_i6.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( From 47d782fc436ea834a1b4cba6590283e1fbbb4610 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 6 Sep 2025 15:06:09 +0200 Subject: [PATCH 11/26] Re-fetch unit data if necessary --- lib/screens/home_tabs_screen.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/screens/home_tabs_screen.dart b/lib/screens/home_tabs_screen.dart index 299aeada..b05d9a3a 100644 --- a/lib/screens/home_tabs_screen.dart +++ b/lib/screens/home_tabs_screen.dart @@ -86,6 +86,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid final measurementProvider = context.read(); final userProvider = context.read(); + // // Base data widget._logger.info('Loading base data'); await Future.wait([ @@ -95,7 +96,18 @@ class _HomeTabsScreenState extends State with SingleTickerProvid nutritionPlansProvider.fetchIngredientsFromCache(), exercisesProvider.fetchAndSetInitialData(), ]); + exercisesProvider.fetchAndSetAllExercises(); + // Workaround for https://github.com/wger-project/flutter/issues/901 + // It seems that it can happen that sometimes the units were not loaded properly + // so now we check and try again if necessary. We might need a better general + // solution since this could potentially happen with other data as well. + if (routinesProvider.repetitionUnits.isEmpty || routinesProvider.weightUnits.isEmpty) { + widget._logger.info('Routine units are empty, fetching again'); + await routinesProvider.fetchAndSetUnits(); + } + + // // Plans, weight and gallery widget._logger.info('Loading routines, weight, measurements and gallery'); await Future.wait([ @@ -107,6 +119,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid measurementProvider.fetchAndSetAllCategoriesAndEntries(), ]); + // // Current nutritional plan widget._logger.info('Loading current nutritional plan'); if (nutritionPlansProvider.currentPlan != null) { @@ -114,6 +127,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid await nutritionPlansProvider.fetchAndSetPlanFull(plan.id!); } + // // Current routine widget._logger.info('Loading current routine'); if (routinesProvider.currentRoutine != null) { From af482ab9e369bb24f927756d872b49f52ba022e1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 6 Sep 2025 22:46:13 +0200 Subject: [PATCH 12/26] Set a debounce time for TypeAhead-fields (exercises and ingredients) --- lib/widgets/exercises/autocompleter.dart | 1 + lib/widgets/nutrition/widgets.dart | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/widgets/exercises/autocompleter.dart b/lib/widgets/exercises/autocompleter.dart index f0b9d513..133deb03 100644 --- a/lib/widgets/exercises/autocompleter.dart +++ b/lib/widgets/exercises/autocompleter.dart @@ -30,6 +30,7 @@ class _ExerciseAutocompleterState extends State { children: [ TypeAheadField( key: const Key('field-typeahead'), + debounceDuration: const Duration(milliseconds: 500), decorationBuilder: (context, child) { return Material( type: MaterialType.card, diff --git a/lib/widgets/nutrition/widgets.dart b/lib/widgets/nutrition/widgets.dart index 730c9787..74910639 100644 --- a/lib/widgets/nutrition/widgets.dart +++ b/lib/widgets/nutrition/widgets.dart @@ -113,6 +113,7 @@ class _IngredientTypeaheadState extends State { children: [ TypeAheadField( controller: widget._ingredientController, + debounceDuration: const Duration(milliseconds: 500), builder: (context, controller, focusNode) { return TextFormField( controller: controller, @@ -124,11 +125,6 @@ class _IngredientTypeaheadState extends State { } return null; }, - onChanged: (value) { - widget.updateSearchQuery(value); - // unselect to start a new search - widget.unSelectIngredient(); - }, decoration: InputDecoration( prefixIcon: const Icon(Icons.search), labelText: AppLocalizations.of(context).searchIngredient, @@ -142,6 +138,10 @@ class _IngredientTypeaheadState extends State { return null; } + widget.updateSearchQuery(pattern); + // unselect to start a new search + widget.unSelectIngredient(); + return Provider.of(context, listen: false).searchIngredient( pattern, languageCode: Localizations.localeOf(context).languageCode, From d56c3b2e45a577ad629b5e7bbd1f4742a023c8da Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 6 Sep 2025 23:44:54 +0200 Subject: [PATCH 13/26] Handle corner cases with null values While this should not happen, it seems there are situations when some people do run into the "Null check operator used on a null value" error here. Closes #878 --- lib/widgets/dashboard/calendar.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/widgets/dashboard/calendar.dart b/lib/widgets/dashboard/calendar.dart index 5042191c..d3c1bf93 100644 --- a/lib/widgets/dashboard/calendar.dart +++ b/lib/widgets/dashboard/calendar.dart @@ -87,6 +87,7 @@ class _DashboardCalendarWidgetState extends State void loadEvents() async { final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString()); + final i18n = AppLocalizations.of(context); // Process weight entries final weightProvider = context.read(); @@ -98,7 +99,7 @@ class _DashboardCalendarWidgetState extends State } // Add events to lists - _events[date]!.add(Event(EventType.weight, '${numberFormat.format(entry.weight)} kg')); + _events[date]?.add(Event(EventType.weight, '${numberFormat.format(entry.weight)} kg')); } // Process measurements @@ -111,7 +112,7 @@ class _DashboardCalendarWidgetState extends State _events[date] = []; } - _events[date]!.add(Event( + _events[date]?.add(Event( EventType.measurement, '${category.name}: ${numberFormat.format(entry.value)} ${category.unit}', )); @@ -130,9 +131,9 @@ class _DashboardCalendarWidgetState extends State time = '(${timeToString(session.timeStart)} - ${timeToString(session.timeEnd)})'; // Add events to lists - _events[date]!.add(Event( + _events[date]?.add(Event( EventType.session, - '${AppLocalizations.of(context).impression}: ${session.impressionAsString} $time', + '${i18n.impression}: ${session.impressionAsString} $time', )); } }); @@ -148,15 +149,15 @@ class _DashboardCalendarWidgetState extends State } // Add events to lists - _events[date]!.add(Event( + _events[date]?.add(Event( EventType.caloriesDiary, - AppLocalizations.of(context).kcalValue(entry.value.energy.toStringAsFixed(0)), + i18n.kcalValue(entry.value.energy.toStringAsFixed(0)), )); } } // Add initial selected day to events list - _selectedEvents.value = _getEventsForDay(_selectedDay!); + _selectedEvents.value = _selectedDay != null ? _getEventsForDay(_selectedDay!) : []; } @override From 8574bcad19ae7a9bd4403bf8644f3601989792bb Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 7 Sep 2025 15:25:50 +0200 Subject: [PATCH 14/26] Allow the error widget to scroll for longer error messages --- lib/helpers/errors.dart | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/helpers/errors.dart b/lib/helpers/errors.dart index 4a4a22de..97192162 100644 --- a/lib/helpers/errors.dart +++ b/lib/helpers/errors.dart @@ -363,14 +363,19 @@ class FormHttpErrorsWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), - ...formatApiErrors( - extractErrors(exception.errors), - color: Theme.of(context).colorScheme.error, + return Container( + constraints: const BoxConstraints(maxHeight: 250), + child: SingleChildScrollView( + child: Column( + children: [ + Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), + ...formatApiErrors( + extractErrors(exception.errors), + color: Theme.of(context).colorScheme.error, + ), + ], ), - ], + ), ); } } @@ -383,15 +388,20 @@ class GeneralErrorsWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), - ...formatTextErrors( - widgets, - title: title, - color: Theme.of(context).colorScheme.error, + return Container( + constraints: const BoxConstraints(maxHeight: 250), + child: SingleChildScrollView( + child: Column( + children: [ + Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), + ...formatTextErrors( + widgets, + title: title, + color: Theme.of(context).colorScheme.error, + ), + ], ), - ], + ), ); } } From 48d314f2a05ae722a1e687051c5956bba7c651c1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 7 Sep 2025 15:26:08 +0200 Subject: [PATCH 15/26] Use bigger pagination sizes when loading workout sessions --- lib/providers/routines.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index 946c9fa3..ff021931 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -619,7 +619,7 @@ class RoutinesProvider with ChangeNotifier { */ Future> fetchSessionData() async { final data = await baseProvider.fetchPaginated( - baseProvider.makeUrl(_sessionUrlPath), + baseProvider.makeUrl(_sessionUrlPath, query: {'limit': API_MAX_PAGE_SIZE}), ); final sessions = data.map((entry) => WorkoutSession.fromJson(entry)).toList(); From 68e8721cdb02c5f8622504b18e57e1cb6eb934c0 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 7 Sep 2025 15:32:19 +0200 Subject: [PATCH 16/26] Refresh routine data on load when starting the gym mode This prevents errors when the user created or deleted a workout session for the current routine over the web interface See #876 --- lib/widgets/routines/forms/session.dart | 10 ++++++++++ lib/widgets/routines/gym_mode/gym_mode.dart | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/widgets/routines/forms/session.dart b/lib/widgets/routines/forms/session.dart index 4bb6d6cd..c48c8621 100644 --- a/lib/widgets/routines/forms/session.dart +++ b/lib/widgets/routines/forms/session.dart @@ -18,6 +18,7 @@ import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:wger/exceptions/http_exception.dart'; import 'package:wger/helpers/consts.dart'; @@ -28,6 +29,7 @@ import 'package:wger/models/workouts/session.dart'; import 'package:wger/providers/routines.dart'; class SessionForm extends StatefulWidget { + final _logger = Logger('SessionForm'); final WorkoutSession _session; final int _routineId; final Function()? _onSaved; @@ -215,11 +217,18 @@ class _SessionFormState extends State { } _form.currentState!.save(); + // Reset any previous error message + setState(() { + errorMessage = const SizedBox.shrink(); + }); + // Save the entry on the server try { if (widget._session.id == null) { + widget._logger.fine('Adding new session'); await routinesProvider.addSession(widget._session, widget._routineId); } else { + widget._logger.fine('Editing existing session with id ${widget._session.id}'); await routinesProvider.editSession(widget._session); } @@ -231,6 +240,7 @@ class _SessionFormState extends State { widget._onSaved!(); } } on WgerHttpException catch (error) { + widget._logger.warning('Could not save session: $error'); if (context.mounted) { setState(() { errorMessage = FormHttpErrorsWidget(error); diff --git a/lib/widgets/routines/gym_mode/gym_mode.dart b/lib/widgets/routines/gym_mode/gym_mode.dart index 3a82895f..ca53e163 100644 --- a/lib/widgets/routines/gym_mode/gym_mode.dart +++ b/lib/widgets/routines/gym_mode/gym_mode.dart @@ -72,6 +72,14 @@ class _GymModeState extends ConsumerState { } Future _loadGymState() async { + // Re-fetch the current routine data to ensure we have the latest session + // data since it is possible that the user created or deleted it from the + // web interface. + await context + .read() + .fetchAndSetRoutineFull(widget._dayDataGym.day!.routineId); + widget._logger.fine('Refreshed routine data'); + final validUntil = ref.read(gymStateProvider).validUntil; final currentPage = ref.read(gymStateProvider).currentPage; final savedDayId = ref.read(gymStateProvider).dayId; From b298aaee19a171c2e8e2ccf6eabde384bf88b9f9 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 8 Sep 2025 13:32:48 +0200 Subject: [PATCH 17/26] The argument "synthetic-package" no longer has any effect --- l10n.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/l10n.yaml b/l10n.yaml index 45d910ee..56bfbe12 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,5 +1,3 @@ -synthetic-package: false # see https://docs.flutter.dev/release/breaking-changes/flutter-generate-i10n-source - arb-dir: lib/l10n output-dir: lib/l10n/generated template-arb-file: app_en.arb From 407db9cd61688738b4b923d56d65047061950b18 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 8 Sep 2025 13:39:20 +0200 Subject: [PATCH 18/26] Bump kotlin, SDK, NDK and gradle versions --- android/app/build.gradle | 8 +++----- android/gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ce9cc658..da8fd1dc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,8 +23,8 @@ if (keystorePropertiesFile.exists()) { android { namespace = "de.wger.flutter" - compileSdkVersion 35 - ndkVersion "27.0.12077973" + compileSdkVersion 36 + ndkVersion "28.2.13676358" compileOptions { sourceCompatibility JavaVersion.VERSION_11 @@ -39,9 +39,7 @@ android { defaultConfig { // Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "de.wger.flutter" - // NOTE: manually setting the minSdk to 23 instead of "flutter.minSdkVersion" - // because flutter_zxing requires a higher minSdkVersion. - minSdk = 23 + minSdkVersion = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 9c5194d3..d5ce57cb 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index b5e1b3f5..ed68c2f2 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.3.2" apply false - id "org.jetbrains.kotlin.android" version "2.0.20" apply false + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.20" apply false } include ":app" From 5a4357d27d6a3e6d4b3da22b46808f94a5b566b7 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 8 Sep 2025 16:00:05 +0200 Subject: [PATCH 19/26] Fix gym_mode_screen_test.dart, set correct mocks --- test/routine/gym_mode_screen_test.dart | 21 +- test/routine/gym_mode_screen_test.mocks.dart | 977 +++++++++++++++++-- 2 files changed, 895 insertions(+), 103 deletions(-) diff --git a/test/routine/gym_mode_screen_test.dart b/test/routine/gym_mode_screen_test.dart index 56ff274c..e13a4188 100644 --- a/test/routine/gym_mode_screen_test.dart +++ b/test/routine/gym_mode_screen_test.dart @@ -44,28 +44,31 @@ import '../../test_data/exercises.dart'; import '../../test_data/routines.dart'; import 'gym_mode_screen_test.mocks.dart'; -@GenerateMocks([WgerBaseProvider, ExercisesProvider]) +@GenerateMocks([WgerBaseProvider, ExercisesProvider, RoutinesProvider]) void main() { - final mockBaseProvider = MockWgerBaseProvider(); final key = GlobalKey(); + final mockRoutinesProvider = MockRoutinesProvider(); final mockExerciseProvider = MockExercisesProvider(); final testRoutine = getTestRoutine(); final testExercises = getTestExercises(); setUp(() { + when(mockRoutinesProvider.findById(any)).thenReturn(testRoutine); + when(mockRoutinesProvider.items).thenReturn([testRoutine]); + when(mockRoutinesProvider.repetitionUnits).thenReturn(testRepetitionUnits); + when(mockRoutinesProvider.findRepetitionUnitById(1)).thenReturn(testRepetitionUnit1); + when(mockRoutinesProvider.weightUnits).thenReturn(testWeightUnits); + when(mockRoutinesProvider.findWeightUnitById(1)).thenReturn(testWeightUnit1); + when(mockRoutinesProvider.fetchAndSetRoutineFull(any)) + .thenAnswer((_) => Future.value(testRoutine)); + SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); }); Widget renderGymMode({locale = 'en'}) { return ChangeNotifierProvider( - create: (context) => RoutinesProvider( - mockBaseProvider, - mockExerciseProvider, - [testRoutine], - repetitionUnits: testRepetitionUnits, - weightUnits: testWeightUnits, - ), + create: (context) => mockRoutinesProvider, child: ChangeNotifierProvider( create: (context) => mockExerciseProvider, child: riverpod.ProviderScope( diff --git a/test/routine/gym_mode_screen_test.mocks.dart b/test/routine/gym_mode_screen_test.mocks.dart index 6be6a132..995903e5 100644 --- a/test/routine/gym_mode_screen_test.mocks.dart +++ b/test/routine/gym_mode_screen_test.mocks.dart @@ -3,20 +3,32 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i11; -import 'dart:ui' as _i13; +import 'dart:async' as _i20; +import 'dart:ui' as _i22; import 'package:http/http.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i25; import 'package:wger/database/exercises/exercise_database.dart' as _i5; import 'package:wger/models/exercises/category.dart' as _i7; import 'package:wger/models/exercises/equipment.dart' as _i8; import 'package:wger/models/exercises/exercise.dart' as _i6; import 'package:wger/models/exercises/language.dart' as _i10; import 'package:wger/models/exercises/muscle.dart' as _i9; +import 'package:wger/models/workouts/base_config.dart' as _i17; +import 'package:wger/models/workouts/day.dart' as _i14; +import 'package:wger/models/workouts/day_data.dart' as _i24; +import 'package:wger/models/workouts/log.dart' as _i19; +import 'package:wger/models/workouts/repetition_unit.dart' as _i12; +import 'package:wger/models/workouts/routine.dart' as _i13; +import 'package:wger/models/workouts/session.dart' as _i18; +import 'package:wger/models/workouts/slot.dart' as _i15; +import 'package:wger/models/workouts/slot_entry.dart' as _i16; +import 'package:wger/models/workouts/weight_unit.dart' as _i11; import 'package:wger/providers/auth.dart' as _i2; import 'package:wger/providers/base_provider.dart' as _i4; -import 'package:wger/providers/exercises.dart' as _i12; +import 'package:wger/providers/exercises.dart' as _i21; +import 'package:wger/providers/routines.dart' as _i23; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -142,6 +154,96 @@ class _FakeLanguage_10 extends _i1.SmartFake implements _i10.Language { ); } +class _FakeWeightUnit_11 extends _i1.SmartFake implements _i11.WeightUnit { + _FakeWeightUnit_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRepetitionUnit_12 extends _i1.SmartFake implements _i12.RepetitionUnit { + _FakeRepetitionUnit_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRoutine_13 extends _i1.SmartFake implements _i13.Routine { + _FakeRoutine_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDay_14 extends _i1.SmartFake implements _i14.Day { + _FakeDay_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSlot_15 extends _i1.SmartFake implements _i15.Slot { + _FakeSlot_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSlotEntry_16 extends _i1.SmartFake implements _i16.SlotEntry { + _FakeSlotEntry_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBaseConfig_17 extends _i1.SmartFake implements _i17.BaseConfig { + _FakeBaseConfig_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWorkoutSession_18 extends _i1.SmartFake implements _i18.WorkoutSession { + _FakeWorkoutSession_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLog_19 extends _i1.SmartFake implements _i19.Log { + _FakeLog_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [WgerBaseProvider]. /// /// See the documentation for Mockito's code generation for more information. @@ -228,25 +330,25 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { ) as Uri); @override - _i11.Future fetch(Uri? uri) => (super.noSuchMethod( + _i20.Future fetch(Uri? uri) => (super.noSuchMethod( Invocation.method( #fetch, [uri], ), - returnValue: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future> fetchPaginated(Uri? uri) => (super.noSuchMethod( + _i20.Future> fetchPaginated(Uri? uri) => (super.noSuchMethod( Invocation.method( #fetchPaginated, [uri], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i20.Future>.value([]), + ) as _i20.Future>); @override - _i11.Future> post( + _i20.Future> post( Map? data, Uri? uri, ) => @@ -258,11 +360,11 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { uri, ], ), - returnValue: _i11.Future>.value({}), - ) as _i11.Future>); + returnValue: _i20.Future>.value({}), + ) as _i20.Future>); @override - _i11.Future> patch( + _i20.Future> patch( Map? data, Uri? uri, ) => @@ -274,11 +376,11 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { uri, ], ), - returnValue: _i11.Future>.value({}), - ) as _i11.Future>); + returnValue: _i20.Future>.value({}), + ) as _i20.Future>); @override - _i11.Future<_i3.Response> deleteRequest( + _i20.Future<_i3.Response> deleteRequest( String? url, int? id, ) => @@ -290,7 +392,7 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { id, ], ), - returnValue: _i11.Future<_i3.Response>.value(_FakeResponse_3( + returnValue: _i20.Future<_i3.Response>.value(_FakeResponse_3( this, Invocation.method( #deleteRequest, @@ -300,13 +402,13 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { ], ), )), - ) as _i11.Future<_i3.Response>); + ) as _i20.Future<_i3.Response>); } /// A class which mocks [ExercisesProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { +class MockExercisesProvider extends _i1.Mock implements _i21.ExercisesProvider { MockExercisesProvider() { _i1.throwOnMissingStub(this); } @@ -414,14 +516,14 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { ) as bool); @override - _i11.Future setFilters(_i12.Filters? newFilters) => (super.noSuchMethod( + _i20.Future setFilters(_i21.Filters? newFilters) => (super.noSuchMethod( Invocation.method( #setFilters, [newFilters], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override void initFilters() => super.noSuchMethod( @@ -433,14 +535,14 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { ); @override - _i11.Future findByFilters() => (super.noSuchMethod( + _i20.Future findByFilters() => (super.noSuchMethod( Invocation.method( #findByFilters, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override void clear() => super.noSuchMethod( @@ -541,66 +643,66 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { ) as _i10.Language); @override - _i11.Future fetchAndSetCategoriesFromApi() => (super.noSuchMethod( + _i20.Future fetchAndSetCategoriesFromApi() => (super.noSuchMethod( Invocation.method( #fetchAndSetCategoriesFromApi, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetMusclesFromApi() => (super.noSuchMethod( + _i20.Future fetchAndSetMusclesFromApi() => (super.noSuchMethod( Invocation.method( #fetchAndSetMusclesFromApi, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetEquipmentsFromApi() => (super.noSuchMethod( + _i20.Future fetchAndSetEquipmentsFromApi() => (super.noSuchMethod( Invocation.method( #fetchAndSetEquipmentsFromApi, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetLanguagesFromApi() => (super.noSuchMethod( + _i20.Future fetchAndSetLanguagesFromApi() => (super.noSuchMethod( Invocation.method( #fetchAndSetLanguagesFromApi, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetAllExercises() => (super.noSuchMethod( + _i20.Future fetchAndSetAllExercises() => (super.noSuchMethod( Invocation.method( #fetchAndSetAllExercises, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future<_i6.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( + _i20.Future<_i6.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( Invocation.method( #fetchAndSetExercise, [exerciseId], ), - returnValue: _i11.Future<_i6.Exercise?>.value(), - ) as _i11.Future<_i6.Exercise?>); + returnValue: _i20.Future<_i6.Exercise?>.value(), + ) as _i20.Future<_i6.Exercise?>); @override - _i11.Future<_i6.Exercise> handleUpdateExerciseFromApi( + _i20.Future<_i6.Exercise> handleUpdateExerciseFromApi( _i5.ExerciseDatabase? database, int? exerciseId, ) => @@ -612,7 +714,7 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { exerciseId, ], ), - returnValue: _i11.Future<_i6.Exercise>.value(_FakeExercise_6( + returnValue: _i20.Future<_i6.Exercise>.value(_FakeExercise_6( this, Invocation.method( #handleUpdateExerciseFromApi, @@ -622,41 +724,41 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { ], ), )), - ) as _i11.Future<_i6.Exercise>); + ) as _i20.Future<_i6.Exercise>); @override - _i11.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod( + _i20.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod( Invocation.method( #initCacheTimesLocalPrefs, [], {#forceInit: forceInit}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future clearAllCachesAndPrefs() => (super.noSuchMethod( + _i20.Future clearAllCachesAndPrefs() => (super.noSuchMethod( Invocation.method( #clearAllCachesAndPrefs, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetInitialData() => (super.noSuchMethod( + _i20.Future fetchAndSetInitialData() => (super.noSuchMethod( Invocation.method( #fetchAndSetInitialData, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future setExercisesFromDatabase( + _i20.Future setExercisesFromDatabase( _i5.ExerciseDatabase? database, { bool? forceDeleteCache = false, }) => @@ -666,62 +768,62 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { [database], {#forceDeleteCache: forceDeleteCache}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future updateExerciseCache(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + _i20.Future updateExerciseCache(_i5.ExerciseDatabase? database) => (super.noSuchMethod( Invocation.method( #updateExerciseCache, [database], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetMuscles(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + _i20.Future fetchAndSetMuscles(_i5.ExerciseDatabase? database) => (super.noSuchMethod( Invocation.method( #fetchAndSetMuscles, [database], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetCategories(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + _i20.Future fetchAndSetCategories(_i5.ExerciseDatabase? database) => (super.noSuchMethod( Invocation.method( #fetchAndSetCategories, [database], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetLanguages(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + _i20.Future fetchAndSetLanguages(_i5.ExerciseDatabase? database) => (super.noSuchMethod( Invocation.method( #fetchAndSetLanguages, [database], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future fetchAndSetEquipments(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + _i20.Future fetchAndSetEquipments(_i5.ExerciseDatabase? database) => (super.noSuchMethod( Invocation.method( #fetchAndSetEquipments, [database], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); @override - _i11.Future> searchExercise( + _i20.Future> searchExercise( String? name, { String? languageCode = 'en', bool? searchEnglish = false, @@ -735,11 +837,11 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { #searchEnglish: searchEnglish, }, ), - returnValue: _i11.Future>.value(<_i6.Exercise>[]), - ) as _i11.Future>); + returnValue: _i20.Future>.value(<_i6.Exercise>[]), + ) as _i20.Future>); @override - void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i22.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -748,7 +850,694 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { ); @override - void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i22.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #removeListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method( + #notifyListeners, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [RoutinesProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRoutinesProvider extends _i1.Mock implements _i23.RoutinesProvider { + MockRoutinesProvider() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.WgerBaseProvider get baseProvider => (super.noSuchMethod( + Invocation.getter(#baseProvider), + returnValue: _FakeWgerBaseProvider_4( + this, + Invocation.getter(#baseProvider), + ), + ) as _i4.WgerBaseProvider); + + @override + List<_i13.Routine> get items => (super.noSuchMethod( + Invocation.getter(#items), + returnValue: <_i13.Routine>[], + ) as List<_i13.Routine>); + + @override + List<_i11.WeightUnit> get weightUnits => (super.noSuchMethod( + Invocation.getter(#weightUnits), + returnValue: <_i11.WeightUnit>[], + ) as List<_i11.WeightUnit>); + + @override + _i11.WeightUnit get defaultWeightUnit => (super.noSuchMethod( + Invocation.getter(#defaultWeightUnit), + returnValue: _FakeWeightUnit_11( + this, + Invocation.getter(#defaultWeightUnit), + ), + ) as _i11.WeightUnit); + + @override + List<_i12.RepetitionUnit> get repetitionUnits => (super.noSuchMethod( + Invocation.getter(#repetitionUnits), + returnValue: <_i12.RepetitionUnit>[], + ) as List<_i12.RepetitionUnit>); + + @override + _i12.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( + Invocation.getter(#defaultRepetitionUnit), + returnValue: _FakeRepetitionUnit_12( + this, + Invocation.getter(#defaultRepetitionUnit), + ), + ) as _i12.RepetitionUnit); + + @override + set activeRoutine(_i13.Routine? _activeRoutine) => super.noSuchMethod( + Invocation.setter( + #activeRoutine, + _activeRoutine, + ), + returnValueForMissingStub: null, + ); + + @override + set weightUnits(List<_i11.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter( + #weightUnits, + weightUnits, + ), + returnValueForMissingStub: null, + ); + + @override + set repetitionUnits(List<_i12.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter( + #repetitionUnits, + repetitionUnits, + ), + returnValueForMissingStub: null, + ); + + @override + bool get hasListeners => (super.noSuchMethod( + Invocation.getter(#hasListeners), + returnValue: false, + ) as bool); + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i11.WeightUnit findWeightUnitById(int? id) => (super.noSuchMethod( + Invocation.method( + #findWeightUnitById, + [id], + ), + returnValue: _FakeWeightUnit_11( + this, + Invocation.method( + #findWeightUnitById, + [id], + ), + ), + ) as _i11.WeightUnit); + + @override + _i12.RepetitionUnit findRepetitionUnitById(int? id) => (super.noSuchMethod( + Invocation.method( + #findRepetitionUnitById, + [id], + ), + returnValue: _FakeRepetitionUnit_12( + this, + Invocation.method( + #findRepetitionUnitById, + [id], + ), + ), + ) as _i12.RepetitionUnit); + + @override + List<_i13.Routine> getPlans() => (super.noSuchMethod( + Invocation.method( + #getPlans, + [], + ), + returnValue: <_i13.Routine>[], + ) as List<_i13.Routine>); + + @override + _i13.Routine findById(int? id) => (super.noSuchMethod( + Invocation.method( + #findById, + [id], + ), + returnValue: _FakeRoutine_13( + this, + Invocation.method( + #findById, + [id], + ), + ), + ) as _i13.Routine); + + @override + int findIndexById(int? id) => (super.noSuchMethod( + Invocation.method( + #findIndexById, + [id], + ), + returnValue: 0, + ) as int); + + @override + _i20.Future fetchAndSetAllRoutinesFull() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllRoutinesFull, + [], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future fetchAndSetAllRoutinesSparse() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetAllRoutinesSparse, + [], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future setExercisesAndUnits( + List<_i24.DayData>? entries, { + Map? exercises, + }) => + (super.noSuchMethod( + Invocation.method( + #setExercisesAndUnits, + [entries], + {#exercises: exercises}, + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future<_i13.Routine> fetchAndSetRoutineSparse(int? planId) => (super.noSuchMethod( + Invocation.method( + #fetchAndSetRoutineSparse, + [planId], + ), + returnValue: _i20.Future<_i13.Routine>.value(_FakeRoutine_13( + this, + Invocation.method( + #fetchAndSetRoutineSparse, + [planId], + ), + )), + ) as _i20.Future<_i13.Routine>); + + @override + _i20.Future<_i13.Routine> fetchAndSetRoutineFull(int? routineId) => (super.noSuchMethod( + Invocation.method( + #fetchAndSetRoutineFull, + [routineId], + ), + returnValue: _i20.Future<_i13.Routine>.value(_FakeRoutine_13( + this, + Invocation.method( + #fetchAndSetRoutineFull, + [routineId], + ), + )), + ) as _i20.Future<_i13.Routine>); + + @override + _i20.Future<_i13.Routine> addRoutine(_i13.Routine? routine) => (super.noSuchMethod( + Invocation.method( + #addRoutine, + [routine], + ), + returnValue: _i20.Future<_i13.Routine>.value(_FakeRoutine_13( + this, + Invocation.method( + #addRoutine, + [routine], + ), + )), + ) as _i20.Future<_i13.Routine>); + + @override + _i20.Future editRoutine(_i13.Routine? routine) => (super.noSuchMethod( + Invocation.method( + #editRoutine, + [routine], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future deleteRoutine(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteRoutine, + [id], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future fetchAndSetRepetitionUnits() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetRepetitionUnits, + [], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future fetchAndSetWeightUnits() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetWeightUnits, + [], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future fetchAndSetUnits() => (super.noSuchMethod( + Invocation.method( + #fetchAndSetUnits, + [], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future<_i14.Day> addDay(_i14.Day? day) => (super.noSuchMethod( + Invocation.method( + #addDay, + [day], + ), + returnValue: _i20.Future<_i14.Day>.value(_FakeDay_14( + this, + Invocation.method( + #addDay, + [day], + ), + )), + ) as _i20.Future<_i14.Day>); + + @override + _i20.Future editDay(_i14.Day? day) => (super.noSuchMethod( + Invocation.method( + #editDay, + [day], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future editDays(List<_i14.Day>? days) => (super.noSuchMethod( + Invocation.method( + #editDays, + [days], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future deleteDay(int? dayId) => (super.noSuchMethod( + Invocation.method( + #deleteDay, + [dayId], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future<_i15.Slot> addSlot( + _i15.Slot? slot, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #addSlot, + [ + slot, + routineId, + ], + ), + returnValue: _i20.Future<_i15.Slot>.value(_FakeSlot_15( + this, + Invocation.method( + #addSlot, + [ + slot, + routineId, + ], + ), + )), + ) as _i20.Future<_i15.Slot>); + + @override + _i20.Future deleteSlot( + int? slotId, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteSlot, + [ + slotId, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future editSlot( + _i15.Slot? slot, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #editSlot, + [ + slot, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future editSlots( + List<_i15.Slot>? slots, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #editSlots, + [ + slots, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future<_i16.SlotEntry> addSlotEntry( + _i16.SlotEntry? entry, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #addSlotEntry, + [ + entry, + routineId, + ], + ), + returnValue: _i20.Future<_i16.SlotEntry>.value(_FakeSlotEntry_16( + this, + Invocation.method( + #addSlotEntry, + [ + entry, + routineId, + ], + ), + )), + ) as _i20.Future<_i16.SlotEntry>); + + @override + _i20.Future deleteSlotEntry( + int? id, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteSlotEntry, + [ + id, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future editSlotEntry( + _i16.SlotEntry? entry, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #editSlotEntry, + [ + entry, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + String getConfigUrl(_i16.ConfigType? type) => (super.noSuchMethod( + Invocation.method( + #getConfigUrl, + [type], + ), + returnValue: _i25.dummyValue( + this, + Invocation.method( + #getConfigUrl, + [type], + ), + ), + ) as String); + + @override + _i20.Future<_i17.BaseConfig> editConfig( + _i17.BaseConfig? config, + _i16.ConfigType? type, + ) => + (super.noSuchMethod( + Invocation.method( + #editConfig, + [ + config, + type, + ], + ), + returnValue: _i20.Future<_i17.BaseConfig>.value(_FakeBaseConfig_17( + this, + Invocation.method( + #editConfig, + [ + config, + type, + ], + ), + )), + ) as _i20.Future<_i17.BaseConfig>); + + @override + _i20.Future<_i17.BaseConfig> addConfig( + _i17.BaseConfig? config, + _i16.ConfigType? type, + ) => + (super.noSuchMethod( + Invocation.method( + #addConfig, + [ + config, + type, + ], + ), + returnValue: _i20.Future<_i17.BaseConfig>.value(_FakeBaseConfig_17( + this, + Invocation.method( + #addConfig, + [ + config, + type, + ], + ), + )), + ) as _i20.Future<_i17.BaseConfig>); + + @override + _i20.Future deleteConfig( + int? id, + _i16.ConfigType? type, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteConfig, + [ + id, + type, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future handleConfig( + _i16.SlotEntry? entry, + num? value, + _i16.ConfigType? type, + ) => + (super.noSuchMethod( + Invocation.method( + #handleConfig, + [ + entry, + value, + type, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + _i20.Future> fetchSessionData() => (super.noSuchMethod( + Invocation.method( + #fetchSessionData, + [], + ), + returnValue: _i20.Future>.value(<_i18.WorkoutSession>[]), + ) as _i20.Future>); + + @override + _i20.Future<_i18.WorkoutSession> addSession( + _i18.WorkoutSession? session, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #addSession, + [ + session, + routineId, + ], + ), + returnValue: _i20.Future<_i18.WorkoutSession>.value(_FakeWorkoutSession_18( + this, + Invocation.method( + #addSession, + [ + session, + routineId, + ], + ), + )), + ) as _i20.Future<_i18.WorkoutSession>); + + @override + _i20.Future<_i18.WorkoutSession> editSession(_i18.WorkoutSession? session) => (super.noSuchMethod( + Invocation.method( + #editSession, + [session], + ), + returnValue: _i20.Future<_i18.WorkoutSession>.value(_FakeWorkoutSession_18( + this, + Invocation.method( + #editSession, + [session], + ), + )), + ) as _i20.Future<_i18.WorkoutSession>); + + @override + _i20.Future<_i19.Log> addLog(_i19.Log? log) => (super.noSuchMethod( + Invocation.method( + #addLog, + [log], + ), + returnValue: _i20.Future<_i19.Log>.value(_FakeLog_19( + this, + Invocation.method( + #addLog, + [log], + ), + )), + ) as _i20.Future<_i19.Log>); + + @override + _i20.Future deleteLog( + int? logId, + int? routineId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteLog, + [ + logId, + routineId, + ], + ), + returnValue: _i20.Future.value(), + returnValueForMissingStub: _i20.Future.value(), + ) as _i20.Future); + + @override + void addListener(_i22.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeListener(_i22.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], From 1f30b90ddb6d2c06b7c9a0f27128b2e7edc44f22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:07:39 +0000 Subject: [PATCH 20/26] Bump build_runner from 2.7.0 to 2.7.1 Bumps [build_runner](https://github.com/dart-lang/build) from 2.7.0 to 2.7.1. - [Release notes](https://github.com/dart-lang/build/releases) - [Commits](https://github.com/dart-lang/build/compare/build_runner-v2.7.0...build_runner-v2.7.1) --- updated-dependencies: - dependency-name: build_runner dependency-version: 2.7.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 551d71e4..e9dcfa28 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build - sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac" + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" build_config: dependency: transitive description: @@ -77,26 +77,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712" + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7 + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.3.1" built_collection: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 966095a1..036db356 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,7 +74,7 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - build_runner: ^2.7.0 + build_runner: ^2.7.1 cider: ^0.2.7 drift_dev: ^2.28.1 flutter_lints: ^6.0.0 From 55a805077cc27fa85c4ab5fe64a3dd7c1c5b1a38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:07:37 +0000 Subject: [PATCH 21/26] Bump json_serializable from 6.11.0 to 6.11.1 Bumps [json_serializable](https://github.com/google/json_serializable.dart) from 6.11.0 to 6.11.1. - [Release notes](https://github.com/google/json_serializable.dart/releases) - [Commits](https://github.com/google/json_serializable.dart/compare/json_serializable-v6.11.0...json_serializable-v6.11.1) --- updated-dependencies: - dependency-name: json_serializable dependency-version: 6.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index e9dcfa28..f68e1e82 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -736,10 +736,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd" + sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.11.1" leak_tracker: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 036db356..ec570739 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,7 +79,7 @@ dev_dependencies: drift_dev: ^2.28.1 flutter_lints: ^6.0.0 freezed: ^3.2.0 - json_serializable: ^6.11.0 + json_serializable: ^6.11.1 mockito: ^5.4.6 network_image_mock: ^2.1.1 shared_preferences_platform_interface: ^2.0.0 From 992da42de493a04c41877db9f16fd538338356ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:08:15 +0000 Subject: [PATCH 22/26] Bump flutter_svg from 2.2.0 to 2.2.1 Bumps [flutter_svg](https://github.com/flutter/packages/tree/main/third_party/packages) from 2.2.0 to 2.2.1. - [Commits](https://github.com/flutter/packages/commits/flutter_svg-v2.2.1/third_party/packages) --- updated-dependencies: - dependency-name: flutter_svg dependency-version: 2.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index f68e1e82..68f2413a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -500,10 +500,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" flutter_svg_icons: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ec570739..a750e6dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: flex_seed_scheme: ^3.5.1 flutter_html: ^3.0.0 flutter_staggered_grid_view: ^0.7.0 - flutter_svg: ^2.2.0 + flutter_svg: ^2.2.1 flutter_svg_icons: ^0.0.1 flutter_typeahead: ^5.2.0 flutter_zxing: ^2.2.1 From 67241ed04cebfda59b4516516c4c2153654864eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:40:02 +0000 Subject: [PATCH 23/26] Bump drift_dev from 2.28.1 to 2.28.2 Bumps [drift_dev](https://github.com/simolus3/drift) from 2.28.1 to 2.28.2. - [Release notes](https://github.com/simolus3/drift/releases) - [Commits](https://github.com/simolus3/drift/compare/drift_dev-2.28.1...drift_dev-2.28.2) --- updated-dependencies: - dependency-name: drift_dev dependency-version: 2.28.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 8 ++++---- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 68f2413a..4acfa56d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -293,10 +293,10 @@ packages: dependency: "direct dev" description: name: drift_dev - sha256: "2fc05ad458a7c562755bf0cae11178dfc58387a416829b78d4da5155a61465fd" + sha256: d6646ee608b9f359b023ac329321bc9c63b098217291de079b8b2334a48abf39 url: "https://pub.dev" source: hosted - version: "2.28.1" + version: "2.28.2" equatable: dependency: "direct main" description: @@ -1229,10 +1229,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: "7c859c803cf7e9a84d6db918bac824545045692bbe94a6386bd3a45132235d09" + sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67" url: "https://pub.dev" source: hosted - version: "0.41.1" + version: "0.41.2" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a750e6dc..bb4ad2fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,7 +76,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.7.1 cider: ^0.2.7 - drift_dev: ^2.28.1 + drift_dev: ^2.28.2 flutter_lints: ^6.0.0 freezed: ^3.2.0 json_serializable: ^6.11.1 From 823b5015eb72dcc354e888c287418b56d6b940fc Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 12 Sep 2025 14:01:12 +0200 Subject: [PATCH 24/26] WorkoutLogs can be a StatelessWidget --- lib/widgets/routines/workout_logs.dart | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/widgets/routines/workout_logs.dart b/lib/widgets/routines/workout_logs.dart index 55f3ce70..66ab75aa 100644 --- a/lib/widgets/routines/workout_logs.dart +++ b/lib/widgets/routines/workout_logs.dart @@ -25,24 +25,11 @@ import 'package:wger/models/workouts/routine.dart'; import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/routines/log.dart'; -class WorkoutLogs extends StatefulWidget { +class WorkoutLogs extends StatelessWidget { final Routine _routine; const WorkoutLogs(this._routine); - @override - _WorkoutLogsState createState() => _WorkoutLogsState(); -} - -class _WorkoutLogsState extends State { - final dayController = TextEditingController(); - - @override - void dispose() { - dayController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return ListView( @@ -62,7 +49,7 @@ class _WorkoutLogsState extends State { ), SizedBox( width: double.infinity, - child: WorkoutLogCalendar(widget._routine), + child: WorkoutLogCalendar(_routine), ), ], ); From 1c969ebc105220f7791048185a773c288c6422c6 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 12 Sep 2025 14:01:54 +0200 Subject: [PATCH 25/26] Add logging to IngredientTypeahead --- lib/widgets/nutrition/widgets.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/widgets/nutrition/widgets.dart b/lib/widgets/nutrition/widgets.dart index 74910639..b860aec5 100644 --- a/lib/widgets/nutrition/widgets.dart +++ b/lib/widgets/nutrition/widgets.dart @@ -21,6 +21,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/helpers/misc.dart'; @@ -54,18 +55,20 @@ class ScanReader extends StatelessWidget { } class IngredientTypeahead extends StatefulWidget { + final _logger = Logger('IngredientTypeahead'); + final TextEditingController _ingredientController; final TextEditingController _ingredientIdController; final String barcode; - final bool? test; + final bool test; final bool showScanner; final Function(int id, String name, num? amount) selectIngredient; final Function() unSelectIngredient; final Function(String query) updateSearchQuery; - const IngredientTypeahead( + IngredientTypeahead( this._ingredientIdController, this._ingredientController, { this.showScanner = true, @@ -90,19 +93,21 @@ class _IngredientTypeaheadState extends State { barcode = widget.barcode; // for unit tests } - Future readerscan(BuildContext context) async { + Future openBarcodeScan(BuildContext context) async { try { final code = await Navigator.of(context) .push(MaterialPageRoute(builder: (context) => const ScanReader())); + if (code == null) { return ''; } - if (code.compareTo('-1') == 0) { + if (code == '-1') { return ''; } return code; - } on PlatformException { + } on PlatformException catch (e) { + widget._logger.warning('PlatformException during barcode scan: $e'); return ''; } } @@ -202,8 +207,8 @@ class _IngredientTypeaheadState extends State { key: const Key('scan-button'), icon: const FaIcon(FontAwesomeIcons.barcode), onPressed: () async { - if (!widget.test!) { - barcode = await readerscan(context); + if (!widget.test) { + barcode = await openBarcodeScan(context); } if (!mounted) { From f6f81318fcf9e7fb63288344ff6eeef74280445c Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 12 Sep 2025 14:03:11 +0200 Subject: [PATCH 26/26] Add logging to the Exercise model --- lib/models/exercises/exercise.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/models/exercises/exercise.dart b/lib/models/exercises/exercise.dart index 74e269f2..aae54832 100644 --- a/lib/models/exercises/exercise.dart +++ b/lib/models/exercises/exercise.dart @@ -20,6 +20,7 @@ import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:logging/logging.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/models/exercises/category.dart'; import 'package:wger/models/exercises/equipment.dart'; @@ -34,6 +35,8 @@ part 'exercise.g.dart'; @JsonSerializable(explicitToJson: true) class Exercise extends Equatable { + final _logger = Logger('ExerciseModel'); + @JsonKey(required: true) late final int? id; @@ -198,7 +201,13 @@ class Exercise extends Equatable { (e) => e.languageObj.shortName == languageCode, orElse: () => translations.firstWhere( (e) => e.languageObj.shortName == LANGUAGE_SHORT_ENGLISH, - orElse: () => translations.first, + orElse: () { + _logger.info( + 'Could not find fallback english translation for exercise-ID ${id}, returning ' + 'first language (${translations.first.languageObj.shortName}) instead.', + ); + return translations.first; + }, ), ); }