Files
ai_english/client/lib/shared/widgets/error_handler.dart

243 lines
5.8 KiB
Dart
Raw Permalink Normal View History

2025-11-17 13:39:05 +08:00
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/error_provider.dart';
/// 全局错误处理组件
class GlobalErrorHandler extends ConsumerWidget {
final Widget child;
const GlobalErrorHandler({super.key, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
final errorState = ref.watch(errorProvider);
// 监听错误状态变化
ref.listen<ErrorState>(errorProvider, (previous, next) {
if (next.currentError != null) {
_showErrorDialog(context, next.currentError!, ref);
}
});
return child;
}
void _showErrorDialog(BuildContext context, AppError error, WidgetRef ref) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => ErrorDialog(
error: error,
onDismiss: () {
Navigator.of(context).pop();
ref.read(errorProvider.notifier).removeError(error.id);
},
onRetry: null,
),
);
}
}
/// 错误对话框
class ErrorDialog extends StatelessWidget {
final AppError error;
final VoidCallback onDismiss;
final VoidCallback? onRetry;
const ErrorDialog({
super.key,
required this.error,
required this.onDismiss,
this.onRetry,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Row(
children: [
Icon(
_getErrorIcon(),
color: _getErrorColor(),
),
const SizedBox(width: 8),
Text(_getErrorTitle()),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(error.message),
if (error.details != null) ...[
const SizedBox(height: 8),
Text(
error.details!,
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
actions: [
if (onRetry != null)
TextButton(
onPressed: () {
onDismiss();
onRetry!();
},
child: const Text('重试'),
),
TextButton(
onPressed: onDismiss,
child: const Text('确定'),
),
],
);
}
IconData _getErrorIcon() {
switch (error.type) {
case ErrorType.network:
return Icons.wifi_off;
case ErrorType.authentication:
return Icons.lock;
case ErrorType.validation:
return Icons.warning;
case ErrorType.server:
return Icons.error;
case ErrorType.unknown:
default:
return Icons.help_outline;
}
}
Color _getErrorColor() {
switch (error.severity) {
case ErrorSeverity.critical:
return Colors.red;
case ErrorSeverity.error:
return Colors.orange;
case ErrorSeverity.warning:
return Colors.yellow[700]!;
case ErrorSeverity.info:
return Colors.blue;
}
}
String _getErrorTitle() {
switch (error.type) {
case ErrorType.network:
return '网络错误';
case ErrorType.authentication:
return '认证错误';
case ErrorType.validation:
return '验证错误';
case ErrorType.server:
return '服务器错误';
case ErrorType.unknown:
default:
return '未知错误';
}
}
}
/// 错误横幅组件
class ErrorBanner extends ConsumerWidget {
const ErrorBanner({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final errorState = ref.watch(errorProvider);
if (!errorState.hasErrors) {
return const SizedBox.shrink();
}
final lowSeverityErrors = errorState.errors
.where((error) => error.severity == ErrorSeverity.info)
.toList();
if (lowSeverityErrors.isEmpty) {
return const SizedBox.shrink();
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
color: Colors.blue[50],
child: Row(
children: [
Icon(
Icons.info_outline,
color: Colors.blue[700],
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
lowSeverityErrors.first.message,
style: TextStyle(
color: Colors.blue[700],
fontSize: 14,
),
),
),
IconButton(
onPressed: () {
ref.read(errorProvider.notifier).removeError(lowSeverityErrors.first.id);
},
icon: Icon(
Icons.close,
color: Colors.blue[700],
size: 20,
),
),
],
),
);
}
}
/// 错误重试组件
class ErrorRetryWidget extends StatelessWidget {
final String message;
final VoidCallback onRetry;
final IconData? icon;
const ErrorRetryWidget({
super.key,
required this.message,
required this.onRetry,
this.icon,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon ?? Icons.error_outline,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
message,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: const Text('重试'),
),
],
),
);
}
}