Frontend first draft

This commit is contained in:
Arseni
2025-11-13 15:06:15 +03:00
parent e47f343afb
commit ddb54ddfdc
504 changed files with 25498 additions and 1 deletions

View 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),
],
);
}

View 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 errors 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);
}
}

View 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),
)),
),
);