mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Merge pull request #1045 from wger-project/feature/html-error-handling
Handle HTML errors in WgerHttpException
This commit is contained in:
85
lib/core/exceptions/http_exception.dart
Normal file
85
lib/core/exceptions/http_exception.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
enum ErrorType {
|
||||
json,
|
||||
html,
|
||||
text,
|
||||
}
|
||||
|
||||
const HTML_ERROR_KEY = 'html_error';
|
||||
|
||||
class WgerHttpException implements Exception {
|
||||
Map<String, dynamic> errors = {};
|
||||
|
||||
/// The exception type. While the majority will be json, it is possible that
|
||||
/// the server will return HTML, e.g. if there has been an internal server error
|
||||
/// or similar.
|
||||
late ErrorType type;
|
||||
|
||||
/// Custom http exception
|
||||
WgerHttpException(Response response) {
|
||||
type = ErrorType.json;
|
||||
final dynamic responseBody = response.body;
|
||||
|
||||
final contentType = response.headers[HttpHeaders.contentTypeHeader];
|
||||
if (contentType != null && contentType.contains('text/html')) {
|
||||
type = ErrorType.html;
|
||||
}
|
||||
|
||||
if (responseBody == null) {
|
||||
errors = {'unknown_error': 'An unknown error occurred, no further information available'};
|
||||
} else {
|
||||
try {
|
||||
if (type == ErrorType.json) {
|
||||
final response = json.decode(responseBody);
|
||||
errors = (response is Map ? response : {'unknown_error': response})
|
||||
.cast<String, dynamic>();
|
||||
} else if (type == ErrorType.html) {
|
||||
errors = {HTML_ERROR_KEY: responseBody.toString()};
|
||||
} else {
|
||||
errors = {'text_error': responseBody.toString()};
|
||||
}
|
||||
} catch (e) {
|
||||
errors = {'unknown_error': responseBody};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WgerHttpException.fromMap(Map<String, dynamic> map) : type = ErrorType.json {
|
||||
errors = map;
|
||||
}
|
||||
|
||||
String get htmlError {
|
||||
if (type != ErrorType.html) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return errors[HTML_ERROR_KEY] ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WgerHttpException ($type): $errors';
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
class WgerHttpException implements Exception {
|
||||
Map<String, dynamic> errors = {};
|
||||
|
||||
/// Custom http exception.
|
||||
/// Expects the response body of the REST call and will try to parse it to
|
||||
/// JSON. Will use the response as-is if it fails.
|
||||
WgerHttpException(dynamic responseBody) {
|
||||
if (responseBody == null) {
|
||||
errors = {'unknown_error': 'An unknown error occurred, no further information available'};
|
||||
} else {
|
||||
try {
|
||||
final response = json.decode(responseBody);
|
||||
errors = (response is Map ? response : {'unknown_error': response}).cast<String, dynamic>();
|
||||
} catch (e) {
|
||||
errors = {'unknown_error': responseBody};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WgerHttpException.fromMap(Map<String, dynamic> map) {
|
||||
errors = map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return errors.values.toList().join(', ');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -22,11 +22,12 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/main.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
@@ -50,16 +51,23 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? co
|
||||
return;
|
||||
}
|
||||
|
||||
final errorList = formatApiErrors(extractErrors(exception.errors));
|
||||
final theme = Theme.of(dialogContext);
|
||||
|
||||
showDialog(
|
||||
context: dialogContext,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(ctx).anErrorOccurred),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [...errorList],
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (exception.type == ErrorType.html)
|
||||
ServerHtmlError(data: exception.htmlError)
|
||||
else
|
||||
...formatApiErrors(extractErrors(exception.errors)),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -145,7 +153,10 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext
|
||||
tilePadding: EdgeInsets.zero,
|
||||
title: Text(i18n.errorViewDetails),
|
||||
children: [
|
||||
Text(issueErrorMessage, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
issueErrorMessage,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
@@ -237,6 +248,31 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext
|
||||
);
|
||||
}
|
||||
|
||||
/// A widget to render HTML errors returned by the server
|
||||
///
|
||||
/// This is a simple wrapper around the `Html` Widget, with some light changes
|
||||
/// to the style.
|
||||
class ServerHtmlError extends StatelessWidget {
|
||||
final logger = Logger('ServerHtml');
|
||||
final String data;
|
||||
|
||||
ServerHtmlError({required this.data, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Html(
|
||||
data: data,
|
||||
style: {
|
||||
'h1': Style(fontSize: FontSize(theme.textTheme.bodyLarge?.fontSize ?? 15)),
|
||||
'h2': Style(fontSize: FontSize(theme.textTheme.bodyMedium?.fontSize ?? 15)),
|
||||
},
|
||||
doNotRenderTheseTags: const {'a'},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CopyToClipboardButton extends StatelessWidget {
|
||||
final logger = Logger('CopyToClipboardButton');
|
||||
final String text;
|
||||
@@ -423,38 +459,26 @@ class FormHttpErrorsWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 250),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: theme.colorScheme.error, width: 1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralErrorsWidget extends StatelessWidget {
|
||||
final String? title;
|
||||
final List<String> widgets;
|
||||
|
||||
const GeneralErrorsWidget(this.widgets, {this.title, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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),
|
||||
Icon(Icons.error_outline, color: theme.colorScheme.error),
|
||||
if (exception.type == ErrorType.html)
|
||||
ServerHtmlError(data: exception.htmlError)
|
||||
else
|
||||
...formatApiErrors(
|
||||
extractErrors(exception.errors),
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -21,8 +21,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/locator.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/models/measurements/measurement_entry.dart';
|
||||
|
||||
part 'measurement_category.g.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -27,7 +27,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
|
||||
@@ -132,7 +132,7 @@ class AuthProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
return login(username, password, serverUrl, null);
|
||||
@@ -158,8 +158,8 @@ class AuthProvider with ChangeNotifier {
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw WgerHttpException(response.body);
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
token = apiToken;
|
||||
@@ -169,17 +169,18 @@ class AuthProvider with ChangeNotifier {
|
||||
final response = await client.post(
|
||||
makeUri(serverUrl, LOGIN_URL),
|
||||
headers: {
|
||||
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
|
||||
HttpHeaders.contentTypeHeader: 'application/json; charset=utf-8',
|
||||
HttpHeaders.userAgentHeader: getAppNameHeader(),
|
||||
},
|
||||
body: json.encode({'username': username, 'password': password}),
|
||||
);
|
||||
final responseData = json.decode(response.body);
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
final responseData = json.decode(response.body);
|
||||
|
||||
token = responseData['token'];
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/providers/auth.dart';
|
||||
import 'package:wger/providers/helpers.dart';
|
||||
|
||||
@@ -66,7 +66,7 @@ class WgerBaseProvider {
|
||||
|
||||
// Something wrong with our request
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
// Process the response
|
||||
@@ -104,7 +104,7 @@ class WgerBaseProvider {
|
||||
|
||||
// Something wrong with our request
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
return json.decode(response.body);
|
||||
@@ -120,7 +120,7 @@ class WgerBaseProvider {
|
||||
|
||||
// Something wrong with our request
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
|
||||
return json.decode(response.body);
|
||||
@@ -137,7 +137,7 @@ class WgerBaseProvider {
|
||||
|
||||
// Something wrong with our request
|
||||
if (response.statusCode >= 400) {
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/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';
|
||||
@@ -115,7 +115,7 @@ class BodyWeightProvider with ChangeNotifier {
|
||||
if (response.statusCode >= 400) {
|
||||
_entries.insert(existingEntryIndex, existingWeightEntry);
|
||||
notifyListeners();
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ import 'dart:convert';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/core/locator.dart';
|
||||
import 'package:wger/database/exercises/exercise_database.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
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/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/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';
|
||||
|
||||
@@ -21,10 +21,10 @@ import 'dart:convert';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/core/locator.dart';
|
||||
import 'package:wger/database/ingredients/ingredients_database.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/nutrition/ingredient.dart';
|
||||
import 'package:wger/models/nutrition/ingredient_image.dart';
|
||||
@@ -220,7 +220,7 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
if (response.statusCode >= 400) {
|
||||
_plans.insert(existingPlanIndex, existingPlan);
|
||||
notifyListeners();
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
//existingPlan = null;
|
||||
}
|
||||
@@ -263,7 +263,7 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
if (response.statusCode >= 400) {
|
||||
plan.meals.insert(mealIndex, existingMeal);
|
||||
notifyListeners();
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
if (response.statusCode >= 400) {
|
||||
meal.mealItems.insert(mealItemIndex, existingMealItem);
|
||||
notifyListeners();
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
@@ -374,7 +374,7 @@ class RoutinesProvider with ChangeNotifier {
|
||||
if (response.statusCode >= 400) {
|
||||
_routines.insert(routineIndex, routine);
|
||||
notifyListeners();
|
||||
throw WgerHttpException(response.body);
|
||||
throw WgerHttpException(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/wide_screen_wrapper.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/core/wide_screen_wrapper.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/measurements/measurement_category.dart';
|
||||
import 'package:wger/providers/measurement.dart';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
@@ -20,6 +20,7 @@ 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/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/models/exercises/video.dart';
|
||||
|
||||
@@ -33,9 +34,10 @@ class ExerciseVideoWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
|
||||
final logger = Logger('ExerciseVideoWidgetState');
|
||||
|
||||
late VideoPlayerController _controller;
|
||||
bool hasError = false;
|
||||
final logger = Logger('ExerciseVideoWidgetState');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,10 +68,14 @@ class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return hasError
|
||||
? const GeneralErrorsWidget(
|
||||
[
|
||||
'An error happened while loading the video. If you can, please check the application logs.',
|
||||
],
|
||||
? FormHttpErrorsWidget(
|
||||
WgerHttpException.fromMap(
|
||||
const {
|
||||
'error':
|
||||
'An error happened while loading the video. If you can, '
|
||||
'please check the application logs.',
|
||||
},
|
||||
),
|
||||
)
|
||||
: _controller.value.isInitialized
|
||||
? AspectRatio(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -20,7 +20,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/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/day.dart';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
|
||||
77
test/core/http_exception_test.dart
Normal file
77
test/core/http_exception_test.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
|
||||
void main() {
|
||||
group('WgerHttpException', () {
|
||||
test('parses valid JSON response', () {
|
||||
final resp = http.Response(
|
||||
'{"foo":"bar"}',
|
||||
400,
|
||||
headers: {HttpHeaders.contentTypeHeader: 'application/json'},
|
||||
);
|
||||
|
||||
final ex = WgerHttpException(resp);
|
||||
|
||||
expect(ex.type, ErrorType.json);
|
||||
expect(ex.errors['foo'], 'bar');
|
||||
expect(ex.toString(), contains('WgerHttpException'));
|
||||
});
|
||||
|
||||
test('falls back on malformed JSON', () {
|
||||
const body = '{"foo":';
|
||||
final resp = http.Response(
|
||||
body,
|
||||
500,
|
||||
headers: {HttpHeaders.contentTypeHeader: 'application/json'},
|
||||
);
|
||||
|
||||
final ex = WgerHttpException(resp);
|
||||
|
||||
expect(ex.type, ErrorType.json);
|
||||
expect(ex.errors['unknown_error'], body);
|
||||
});
|
||||
|
||||
test('detects HTML response', () {
|
||||
const body = '<html lang="en"><body>Error</body></html>';
|
||||
final resp = http.Response(
|
||||
body,
|
||||
500,
|
||||
headers: {HttpHeaders.contentTypeHeader: 'text/html; charset=utf-8'},
|
||||
);
|
||||
|
||||
final ex = WgerHttpException(resp);
|
||||
|
||||
expect(ex.type, ErrorType.html);
|
||||
expect(ex.htmlError, body);
|
||||
});
|
||||
|
||||
test('fromMap sets errors and type', () {
|
||||
final map = <String, dynamic>{'field': 'value'};
|
||||
final ex = WgerHttpException.fromMap(map);
|
||||
|
||||
expect(ex.type, ErrorType.json);
|
||||
expect(ex.errors, map);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
@@ -422,7 +422,7 @@ void main() {
|
||||
testWidgets('Failed submission displays error message', (WidgetTester tester) async {
|
||||
// Setup: Create verified user and mock failed submission
|
||||
setupFullVerifiedUserContext();
|
||||
final httpException = WgerHttpException({
|
||||
final httpException = WgerHttpException.fromMap({
|
||||
'name': ['This field is required'],
|
||||
});
|
||||
when(mockAddExerciseProvider.postExerciseToServer()).thenThrow(httpException);
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:mockito/mockito.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/database/exercises/exercise_database.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/models/measurements/measurement_category.dart';
|
||||
import 'package:wger/models/measurements/measurement_entry.dart';
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart';
|
||||
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/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/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';
|
||||
@@ -271,7 +271,7 @@ void main() {
|
||||
'should re-add the "removed" MeasurementCategory and relay the exception on WgerHttpException',
|
||||
() {
|
||||
// arrange
|
||||
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException('{}'));
|
||||
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException.fromMap({}));
|
||||
|
||||
// act & assert
|
||||
expect(
|
||||
@@ -330,7 +330,7 @@ void main() {
|
||||
|
||||
test('should keep categories list as is on WgerHttpException', () {
|
||||
// arrange
|
||||
when(mockWgerBaseProvider.patch(any, any)).thenThrow(WgerHttpException('{}'));
|
||||
when(mockWgerBaseProvider.patch(any, any)).thenThrow(WgerHttpException.fromMap({}));
|
||||
|
||||
// act & assert
|
||||
expect(
|
||||
@@ -550,7 +550,7 @@ void main() {
|
||||
),
|
||||
const MeasurementCategory(id: 2, name: 'Biceps', unit: 'cm'),
|
||||
];
|
||||
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException('{}'));
|
||||
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException.fromMap({}));
|
||||
|
||||
// act & assert
|
||||
expect(
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/core/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
|
||||
Reference in New Issue
Block a user