Frontend first draft
This commit is contained in:
27
frontend/pweb/lib/utils/error/content.dart
Normal file
27
frontend/pweb/lib/utils/error/content.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class ErrorSnackBarContent extends StatelessWidget {
|
||||
final String situation;
|
||||
final String localizedError;
|
||||
|
||||
const ErrorSnackBarContent({
|
||||
super.key,
|
||||
required this.situation,
|
||||
required this.localizedError,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min, // wrap to content
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
situation,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(localizedError),
|
||||
],
|
||||
);
|
||||
}
|
||||
66
frontend/pweb/lib/utils/error/handler.dart
Normal file
66
frontend/pweb/lib/utils/error/handler.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/api/responses/error/connectivity.dart';
|
||||
import 'package:pshared/api/responses/error/server.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class ErrorHandler {
|
||||
/// A mapping of server-side error codes to localized user-friendly messages.
|
||||
/// Update these keys to match the 'ErrorResponse.Error' field in your Go code.
|
||||
static Map<String, String> getErrorMessagesLocs(AppLocalizations locs) {
|
||||
return {
|
||||
'account_not_verified': locs.errorAccountNotVerified,
|
||||
'unauthorized': locs.errorLoginUnauthorized,
|
||||
'verification_token_not_found': locs.errorVerificationTokenNotFound,
|
||||
'internal_error': locs.errorInternalError,
|
||||
|
||||
'data_conflict': locs.errorDataConflict,
|
||||
'access_denied': locs.errorAccessDenied,
|
||||
'broken_payload': locs.errorBrokenPayload,
|
||||
'invalid_argument': locs.errorInvalidArgument,
|
||||
'broken_reference': locs.errorBrokenReference,
|
||||
'invalid_query_parameter': locs.errorInvalidQueryParameter,
|
||||
'not_implemented': locs.errorNotImplemented,
|
||||
'license_required': locs.errorLicenseRequired,
|
||||
'not_found': locs.errorNotFound,
|
||||
'name_missing': locs.errorNameMissing,
|
||||
'email_missing': locs.errorEmailMissing,
|
||||
'password_missing': locs.errorPasswordMissing,
|
||||
'email_not_registered': locs.errorEmailNotRegistered,
|
||||
'duplicate_email': locs.errorDuplicateEmail,
|
||||
};
|
||||
}
|
||||
|
||||
static Map<String, String> getErrorMessages(BuildContext context) {
|
||||
return getErrorMessagesLocs(AppLocalizations.of(context)!);
|
||||
}
|
||||
|
||||
/// Determine which handler to use based on the runtime type of [e].
|
||||
/// If no match is found, just return the error’s string representation.
|
||||
static String handleError(BuildContext context, Object e) {
|
||||
return handleErrorLocs(AppLocalizations.of(context)!, e);
|
||||
}
|
||||
|
||||
static String handleErrorLocs(AppLocalizations locs, Object e) {
|
||||
final errorHandlers = <Type, String Function(Object)>{
|
||||
ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse),
|
||||
ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError),
|
||||
};
|
||||
|
||||
return errorHandlers[e.runtimeType]?.call(e) ?? e.toString();
|
||||
}
|
||||
|
||||
static String _handleErrorResponseLocs(AppLocalizations locs, ErrorResponse e) {
|
||||
final errorMessages = getErrorMessagesLocs(locs);
|
||||
// Return the localized message if we recognize the error key, else use the raw details
|
||||
return errorMessages[e.error] ?? e.details;
|
||||
}
|
||||
|
||||
/// Handler for connectivity issues.
|
||||
static String _handleConnectivityErrorLocs(AppLocalizations locs, ConnectivityError e) {
|
||||
return locs.connectivityError(Constants.serviceUrl);
|
||||
}
|
||||
}
|
||||
132
frontend/pweb/lib/utils/error/snackbar.dart
Normal file
132
frontend/pweb/lib/utils/error/snackbar.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/error/handler.dart';
|
||||
import 'package:pweb/widgets/error/content.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> notifyUserOfErrorX({
|
||||
required ScaffoldMessengerState scaffoldMessenger,
|
||||
required String errorSituation,
|
||||
required Object exception,
|
||||
required AppLocalizations appLocalizations,
|
||||
int delaySeconds = 3,
|
||||
}) {
|
||||
// A. Localized user-friendly error message
|
||||
final String localizedError = ErrorHandler.handleErrorLocs(appLocalizations, exception);
|
||||
|
||||
// B. Technical details for advanced reference
|
||||
final String technicalDetails = exception.toString();
|
||||
|
||||
// C. Build the snack bar
|
||||
final snackBar = _buildMainErrorSnackBar(
|
||||
errorSituation: errorSituation,
|
||||
localizedError: localizedError,
|
||||
technicalDetails: technicalDetails,
|
||||
loc: appLocalizations,
|
||||
scaffoldMessenger: scaffoldMessenger,
|
||||
delaySeconds: delaySeconds,
|
||||
);
|
||||
|
||||
// D. Show it
|
||||
return scaffoldMessenger.showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> notifyUserOfError({
|
||||
required BuildContext context,
|
||||
required String errorSituation,
|
||||
required Object exception,
|
||||
int delaySeconds = 3,
|
||||
}) => notifyUserOfErrorX(
|
||||
scaffoldMessenger: ScaffoldMessenger.of(context),
|
||||
errorSituation: errorSituation,
|
||||
exception: exception,
|
||||
appLocalizations: AppLocalizations.of(context)!,
|
||||
delaySeconds: delaySeconds,
|
||||
);
|
||||
|
||||
Future<T?> executeActionWithNotification<T>({
|
||||
required BuildContext context,
|
||||
required Future<T> Function() action,
|
||||
required String errorMessage,
|
||||
int delaySeconds = 3,
|
||||
}) async {
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
try {
|
||||
return await action();
|
||||
} catch (e) {
|
||||
// Report the error using your existing notifier.
|
||||
notifyUserOfErrorX(
|
||||
scaffoldMessenger: scaffoldMessenger,
|
||||
errorSituation: errorMessage,
|
||||
exception: e,
|
||||
appLocalizations: localizations,
|
||||
delaySeconds: delaySeconds,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> postNotifyUserOfError({
|
||||
required ScaffoldMessengerState scaffoldMessenger,
|
||||
required String errorSituation,
|
||||
required Object exception,
|
||||
required AppLocalizations appLocalizations,
|
||||
int delaySeconds = 3,
|
||||
}) {
|
||||
|
||||
final completer = Completer<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => completer.complete(notifyUserOfErrorX(
|
||||
scaffoldMessenger: scaffoldMessenger,
|
||||
errorSituation: errorSituation,
|
||||
exception: exception,
|
||||
appLocalizations: appLocalizations,
|
||||
delaySeconds: delaySeconds,
|
||||
)),
|
||||
);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> postNotifyUserOfErrorX({
|
||||
required BuildContext context,
|
||||
required String errorSituation,
|
||||
required Object exception,
|
||||
int delaySeconds = 3,
|
||||
}) => postNotifyUserOfError(
|
||||
scaffoldMessenger: ScaffoldMessenger.of(context),
|
||||
errorSituation: errorSituation,
|
||||
exception: exception,
|
||||
appLocalizations: AppLocalizations.of(context)!,
|
||||
delaySeconds: delaySeconds,
|
||||
);
|
||||
|
||||
|
||||
/// 2) A helper function that returns the main SnackBar widget
|
||||
SnackBar _buildMainErrorSnackBar({
|
||||
required String errorSituation,
|
||||
required String localizedError,
|
||||
required String technicalDetails,
|
||||
required AppLocalizations loc,
|
||||
required ScaffoldMessengerState scaffoldMessenger,
|
||||
int delaySeconds = 3,
|
||||
}) => SnackBar(
|
||||
duration: Duration(seconds: delaySeconds),
|
||||
content: ErrorSnackBarContent(
|
||||
situation: errorSituation,
|
||||
localizedError: localizedError,
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: loc.showDetailsAction,
|
||||
onPressed: () => scaffoldMessenger.showSnackBar(SnackBar(
|
||||
content: Text(technicalDetails),
|
||||
duration: const Duration(seconds: 6),
|
||||
)),
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user