feat(mobile): amélioration UX NotImplementedFailure + SnackbarHelper
- NotImplementedFailure: ajout userFriendlyMessage et icon construction (blue) - ErrorDisplayWidget: support spécial pour NotImplementedFailure (bientôt disponible) - SnackbarHelper: classe centralisée pour messages cohérents (success, error, warning, info, notImplemented) - budgets_list_page: remplace generic snackbar par SnackbarHelper.showNotImplemented - conversations_page: remplace 2 TODOs par SnackbarHelper.showNotImplemented - export_members: met à jour TODO obsolète (endpoint PDF maintenant disponible) - cache_service: fix AppLogger.error calls (error: named param) - cached_datasource_decorator: fix AppLogger.error call Task #64 - Fix Snackbar Placeholders + NotImplementedFailure UX
This commit is contained in:
213
lib/shared/utils/snackbar_helper.dart
Normal file
213
lib/shared/utils/snackbar_helper.dart
Normal file
@@ -0,0 +1,213 @@
|
||||
/// Centralized helper for showing consistent Snackbar messages
|
||||
library snackbar_helper;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
|
||||
/// Helper class for showing consistent Snackbar messages throughout the app
|
||||
class SnackbarHelper {
|
||||
/// Show success message
|
||||
static void showSuccess(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
SnackBarAction? action,
|
||||
}) {
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green[700],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: duration,
|
||||
action: action,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show error message
|
||||
static void showError(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
VoidCallback? onRetry,
|
||||
}) {
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.red[700],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: duration,
|
||||
action: onRetry != null
|
||||
? SnackBarAction(
|
||||
label: 'Réessayer',
|
||||
textColor: Colors.white,
|
||||
onPressed: onRetry,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show warning message
|
||||
static void showWarning(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
}) {
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.warning_amber, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.orange[700],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: duration,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show info message
|
||||
static void showInfo(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
}) {
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.info_outline, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.blue[700],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: duration,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show "not implemented" message with appropriate styling
|
||||
static void showNotImplemented(
|
||||
BuildContext context,
|
||||
String? featureName,
|
||||
) {
|
||||
final message = featureName != null
|
||||
? '$featureName sera bientôt disponible !'
|
||||
: 'Cette fonctionnalité sera bientôt disponible !';
|
||||
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.construction, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.blue[700],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show failure with appropriate styling based on failure type
|
||||
static void showFailure(
|
||||
BuildContext context,
|
||||
Failure failure, {
|
||||
VoidCallback? onRetry,
|
||||
}) {
|
||||
// Handle NotImplementedFailure specially
|
||||
if (failure is NotImplementedFailure) {
|
||||
showNotImplemented(context, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Color backgroundColor;
|
||||
IconData icon;
|
||||
|
||||
if (failure is NetworkFailure) {
|
||||
backgroundColor = Colors.orange[700]!;
|
||||
icon = Icons.wifi_off;
|
||||
} else if (failure is UnauthorizedFailure) {
|
||||
backgroundColor = Colors.red[700]!;
|
||||
icon = Icons.lock_outline;
|
||||
} else if (failure is ForbiddenFailure) {
|
||||
backgroundColor = Colors.deepOrange[700]!;
|
||||
icon = Icons.block;
|
||||
} else if (failure is ValidationFailure) {
|
||||
backgroundColor = Colors.amber[700]!;
|
||||
icon = Icons.error_outline;
|
||||
} else {
|
||||
backgroundColor = Colors.red[700]!;
|
||||
icon = Icons.error_outline;
|
||||
}
|
||||
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(icon, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(failure.getUserMessage())),
|
||||
],
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: Duration(seconds: failure.isRetryable ? 6 : 4),
|
||||
action: failure.isRetryable && onRetry != null
|
||||
? SnackBarAction(
|
||||
label: 'Réessayer',
|
||||
textColor: Colors.white,
|
||||
onPressed: onRetry,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Show loading message (use with caution - prefer progress indicators)
|
||||
static void showLoading(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 2),
|
||||
}) {
|
||||
final snackBar = SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.grey[800],
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: duration,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
/// Dismiss current Snackbar
|
||||
static void dismiss(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user