Files
unionflow-mobile-apps/lib/shared/widgets/error_display_widget.dart
dahoud d094d6db9c Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
2026-03-15 16:30:08 +00:00

287 lines
8.1 KiB
Dart

/// Widget for displaying user-friendly error messages with retry capability
library error_display_widget;
import 'package:flutter/material.dart';
import '../../core/error/failures.dart';
/// Error display widget that shows failures in a user-friendly way
class ErrorDisplayWidget extends StatelessWidget {
final Failure failure;
final VoidCallback? onRetry;
final bool showRetryButton;
const ErrorDisplayWidget({
super.key,
required this.failure,
this.onRetry,
this.showRetryButton = true,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error icon
Icon(
_getErrorIcon(),
size: 64,
color: _getErrorColor(context),
),
const SizedBox(height: 24),
// Error title
Text(
_getErrorTitle(),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: _getErrorColor(context),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
// Error message
Text(
failure.getUserMessage(),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
// Retry button (if retryable and callback provided)
if (showRetryButton && failure.isRetryable && onRetry != null) ...[
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: const Text('Réessayer'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
),
],
],
),
),
);
}
/// Get appropriate icon for error type
IconData _getErrorIcon() {
if (failure is NetworkFailure) {
return Icons.wifi_off;
} else if (failure is UnauthorizedFailure) {
return Icons.lock_outline;
} else if (failure is ForbiddenFailure) {
return Icons.block;
} else if (failure is NotFoundFailure) {
return Icons.search_off;
} else if (failure is ValidationFailure) {
return Icons.error_outline;
} else if (failure is ServerFailure) {
return Icons.cloud_off;
} else {
return Icons.warning_amber;
}
}
/// Get appropriate color for error type
Color _getErrorColor(BuildContext context) {
if (failure is NetworkFailure) {
return Colors.orange;
} else if (failure is UnauthorizedFailure) {
return Colors.red;
} else if (failure is ForbiddenFailure) {
return Colors.deepOrange;
} else if (failure is ValidationFailure) {
return Colors.amber;
} else {
return Theme.of(context).colorScheme.error;
}
}
/// Get appropriate title for error type
String _getErrorTitle() {
if (failure is NetworkFailure) {
return 'Problème de connexion';
} else if (failure is UnauthorizedFailure) {
return 'Session expirée';
} else if (failure is ForbiddenFailure) {
return 'Accès refusé';
} else if (failure is NotFoundFailure) {
return 'Non trouvé';
} else if (failure is ValidationFailure) {
return 'Données invalides';
} else if (failure is ServerFailure) {
return 'Erreur serveur';
} else {
return 'Une erreur est survenue';
}
}
}
/// Compact error banner for inline display
class ErrorBanner extends StatelessWidget {
final Failure failure;
final VoidCallback? onRetry;
final VoidCallback? onDismiss;
const ErrorBanner({
super.key,
required this.failure,
this.onRetry,
this.onDismiss,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _getErrorColor(context).withOpacity(0.1),
border: Border.all(
color: _getErrorColor(context),
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
_getErrorIcon(),
color: _getErrorColor(context),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_getErrorTitle(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: _getErrorColor(context),
),
),
const SizedBox(height: 4),
Text(
failure.getUserMessage(),
style: TextStyle(
fontSize: 13,
color: Colors.grey[700],
),
),
],
),
),
if (failure.isRetryable && onRetry != null)
TextButton(
onPressed: onRetry,
child: const Text('Réessayer'),
),
if (onDismiss != null)
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: onDismiss,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
);
}
IconData _getErrorIcon() {
if (failure is NetworkFailure) {
return Icons.wifi_off;
} else if (failure is UnauthorizedFailure) {
return Icons.lock_outline;
} else if (failure is ForbiddenFailure) {
return Icons.block;
} else if (failure is NotFoundFailure) {
return Icons.search_off;
} else if (failure is ValidationFailure) {
return Icons.error_outline;
} else if (failure is ServerFailure) {
return Icons.cloud_off;
} else {
return Icons.warning_amber;
}
}
Color _getErrorColor(BuildContext context) {
if (failure is NetworkFailure) {
return Colors.orange;
} else if (failure is UnauthorizedFailure) {
return Colors.red;
} else if (failure is ForbiddenFailure) {
return Colors.deepOrange;
} else if (failure is ValidationFailure) {
return Colors.amber;
} else {
return Theme.of(context).colorScheme.error;
}
}
String _getErrorTitle() {
if (failure is NetworkFailure) {
return 'Problème de connexion';
} else if (failure is UnauthorizedFailure) {
return 'Session expirée';
} else if (failure is ForbiddenFailure) {
return 'Accès refusé';
} else if (failure is NotFoundFailure) {
return 'Non trouvé';
} else if (failure is ValidationFailure) {
return 'Données invalides';
} else if (failure is ServerFailure) {
return 'Erreur serveur';
} else {
return 'Erreur';
}
}
}
/// Show error as a SnackBar
void showErrorSnackBar(
BuildContext context,
Failure failure, {
VoidCallback? onRetry,
}) {
final snackBar = SnackBar(
content: Row(
children: [
Icon(
failure is NetworkFailure ? Icons.wifi_off : Icons.error_outline,
color: Colors.white,
),
const SizedBox(width: 12),
Expanded(
child: Text(failure.getUserMessage()),
),
],
),
backgroundColor: failure is NetworkFailure ? Colors.orange : Colors.red,
behavior: SnackBarBehavior.floating,
action: failure.isRetryable && onRetry != null
? SnackBarAction(
label: 'Réessayer',
textColor: Colors.white,
onPressed: onRetry,
)
: null,
duration: Duration(seconds: failure.isRetryable ? 6 : 4),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}