snap_wish/lib/presentation/widgets/error_widgets.dart
2025-09-17 11:42:38 +08:00

638 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
/// 错误提示组件库 - 提供多种样式的错误状态显示
/// 包括网络错误、服务器错误、权限错误等不同场景的错误提示
/// 基础错误状态组件 - 通用的错误状态显示组件
/// [errorType] 错误类型,决定图标和颜色
/// [title] 错误标题文本
/// [message] 错误描述文本
/// [actionText] 操作按钮文本null时不显示按钮
/// [onAction] 操作按钮点击回调null时按钮不可点击
/// [iconSize] 图标大小null时使用默认大小
/// [showDetails] 是否显示详细错误信息
/// [details] 详细错误信息,用于调试
class ErrorState extends StatelessWidget {
/// 错误类型,决定图标和颜色
final ErrorType errorType;
/// 错误标题文本
final String title;
/// 错误描述文本
final String? message;
/// 操作按钮文本null时不显示按钮
final String? actionText;
/// 操作按钮点击回调null时按钮不可点击
final VoidCallback? onAction;
/// 图标大小null时使用默认大小
final double? iconSize;
/// 自定义图标组件,优先级高于内置图标
final Widget? customIcon;
/// 自定义操作组件优先级高于actionText参数
final Widget? customAction;
/// 主要内容垂直间距
final double spacing;
/// 主要内容内边距
final EdgeInsetsGeometry? padding;
/// 是否显示边框装饰
final bool showBorder;
/// 边框圆角半径
final double borderRadius;
/// 背景颜色null时使用透明背景
final Color? backgroundColor;
/// 是否显示详细错误信息
final bool showDetails;
/// 详细错误信息,用于调试
final String? details;
const ErrorState({
super.key,
this.errorType = ErrorType.general,
required this.title,
this.message,
this.actionText,
this.onAction,
this.iconSize,
this.customIcon,
this.customAction,
this.spacing = 16,
this.padding,
this.showBorder = false,
this.borderRadius = 12,
this.backgroundColor,
this.showDetails = false,
this.details,
});
/// 创建网络错误状态 - 用于网络连接失败场景
factory ErrorState.networkError({
VoidCallback? onRetry,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.network,
title: '网络连接失败',
message: '请检查网络连接后重试',
actionText: '重新加载',
onAction: onRetry,
showDetails: showDetails,
details: details,
);
}
/// 创建服务器错误状态 - 用于服务器异常场景
factory ErrorState.serverError({
VoidCallback? onRetry,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.server,
title: '服务器异常',
message: '服务器暂时无法访问,请稍后重试',
actionText: '重新加载',
onAction: onRetry,
showDetails: showDetails,
details: details,
);
}
/// 创建权限错误状态 - 用于无权限访问场景
factory ErrorState.permissionError({
VoidCallback? onRequestPermission,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.permission,
title: '权限不足',
message: '您没有访问此内容的权限',
actionText: onRequestPermission != null ? '申请权限' : null,
onAction: onRequestPermission,
showDetails: showDetails,
details: details,
);
}
/// 创建数据错误状态 - 用于数据解析失败场景
factory ErrorState.dataError({
VoidCallback? onRetry,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.data,
title: '数据解析失败',
message: '数据格式错误,无法正确解析',
actionText: '重新加载',
onAction: onRetry,
showDetails: showDetails,
details: details,
);
}
/// 创建超时错误状态 - 用于请求超时场景
factory ErrorState.timeoutError({
VoidCallback? onRetry,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.timeout,
title: '请求超时',
message: '请求时间过长,请重试',
actionText: '重新加载',
onAction: onRetry,
showDetails: showDetails,
details: details,
);
}
/// 创建未找到错误状态 - 用于资源未找到场景
factory ErrorState.notFoundError({
VoidCallback? onGoBack,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.notFound,
title: '页面不存在',
message: '您访问的内容可能已被删除或移动',
actionText: onGoBack != null ? '返回' : null,
onAction: onGoBack,
showDetails: showDetails,
details: details,
);
}
/// 创建未知错误状态 - 用于未分类的错误场景
factory ErrorState.unknownError({
VoidCallback? onRetry,
String? details,
bool showDetails = false,
}) {
return ErrorState(
errorType: ErrorType.unknown,
title: '发生未知错误',
message: '抱歉,发生了一些意外情况',
actionText: '重新加载',
onAction: onRetry,
showDetails: showDetails,
details: details,
);
}
/// 获取错误类型对应的图标
IconData _getErrorIcon() {
switch (errorType) {
case ErrorType.network:
return Icons.wifi_off_outlined;
case ErrorType.server:
return Icons.cloud_off_outlined;
case ErrorType.permission:
return Icons.lock_outline;
case ErrorType.data:
return Icons.data_array_outlined;
case ErrorType.timeout:
return Icons.timer_off_outlined;
case ErrorType.notFound:
return Icons.search_off_outlined;
case ErrorType.unknown:
default:
return Icons.error_outline;
}
}
/// 获取错误类型对应的颜色
Color _getErrorColor(BuildContext context) {
final theme = Theme.of(context);
switch (errorType) {
case ErrorType.network:
return Colors.orange;
case ErrorType.server:
return Colors.red;
case ErrorType.permission:
return Colors.orange;
case ErrorType.data:
return Colors.red;
case ErrorType.timeout:
return Colors.orange;
case ErrorType.notFound:
return Colors.grey;
case ErrorType.unknown:
default:
return theme.colorScheme.error;
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final errorColor = _getErrorColor(context);
final errorIcon = _getErrorIcon();
Widget content = Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 图标部分
if (customIcon != null) ...[
customIcon!,
SizedBox(height: spacing),
] else ...[
Icon(
errorIcon,
size: iconSize ?? 64,
color: errorColor,
),
SizedBox(height: spacing),
],
// 标题部分
Text(
title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: errorColor,
),
textAlign: TextAlign.center,
),
// 描述文本部分
if (message != null) ...[
SizedBox(height: spacing * 0.5),
Text(
message!,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
textAlign: TextAlign.center,
),
],
// 详细错误信息部分
if (showDetails && details != null) ...[
SizedBox(height: spacing * 0.5),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: errorColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: errorColor.withOpacity(0.3),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'详细信息:',
style: theme.textTheme.bodySmall?.copyWith(
color: errorColor,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
details!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
],
// 操作按钮部分
if (customAction != null) ...[
SizedBox(height: spacing * 1.5),
customAction!,
] else if (actionText != null) ...[
SizedBox(height: spacing * 1.5),
ElevatedButton.icon(
onPressed: onAction,
icon: const Icon(Icons.refresh, size: 18),
label: Text(actionText!),
style: ElevatedButton.styleFrom(
backgroundColor: errorColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
],
);
// 添加内边距
content = Padding(
padding: padding ?? const EdgeInsets.all(24),
child: content,
);
// 添加边框装饰
if (showBorder) {
content = Container(
decoration: BoxDecoration(
color: backgroundColor ?? errorColor.withOpacity(0.05),
border: Border.all(
color: errorColor.withOpacity(0.3),
width: 1,
),
borderRadius: BorderRadius.circular(borderRadius),
),
child: content,
);
} else if (backgroundColor != null) {
content = Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(borderRadius),
),
child: content,
);
}
return content;
}
}
/// 错误状态枚举 - 定义不同类型的错误
enum ErrorType {
/// 一般错误 - 通用错误类型
general,
/// 网络错误 - 网络连接问题
network,
/// 服务器错误 - 服务端异常
server,
/// 权限错误 - 权限不足
permission,
/// 数据错误 - 数据解析或格式错误
data,
/// 超时错误 - 请求超时
timeout,
/// 未找到错误 - 资源不存在
notFound,
/// 未知错误 - 未分类的错误
unknown,
}
/// 错误状态页面 - 全屏错误状态显示,包含图标、文本和操作按钮
/// [errorType] 错误类型
/// [title] 错误标题
/// [message] 错误描述
/// [actionText] 操作按钮文本
/// [onAction] 操作按钮点击回调
/// [showAppBar] 是否显示应用栏
/// [appBarTitle] 应用栏标题
/// [showDetails] 是否显示详细错误信息
/// [details] 详细错误信息
class ErrorStatePage extends StatelessWidget {
/// 错误类型
final ErrorType errorType;
/// 错误标题
final String title;
/// 错误描述
final String? message;
/// 操作按钮文本
final String? actionText;
/// 操作按钮点击回调
final VoidCallback? onAction;
/// 是否显示应用栏
final bool showAppBar;
/// 应用栏标题
final String? appBarTitle;
/// 自定义错误状态组件,优先级高于其他参数
final Widget? customErrorState;
/// 背景颜色null时使用主题背景色
final Color? backgroundColor;
/// 是否显示详细错误信息
final bool showDetails;
/// 详细错误信息,用于调试
final String? details;
const ErrorStatePage({
super.key,
this.errorType = ErrorType.general,
required this.title,
this.message,
this.actionText,
this.onAction,
this.showAppBar = false,
this.appBarTitle,
this.customErrorState,
this.backgroundColor,
this.showDetails = false,
this.details,
});
/// 创建网络错误状态页面
factory ErrorStatePage.networkError({
VoidCallback? onRetry,
bool showAppBar = false,
String? appBarTitle,
String? details,
bool showDetails = false,
}) {
return ErrorStatePage(
errorType: ErrorType.network,
title: '网络连接失败',
message: '请检查网络连接后重试',
actionText: '重新加载',
onAction: onRetry,
showAppBar: showAppBar,
appBarTitle: appBarTitle ?? '网络错误',
showDetails: showDetails,
details: details,
);
}
/// 创建服务器错误状态页面
factory ErrorStatePage.serverError({
VoidCallback? onRetry,
bool showAppBar = false,
String? appBarTitle,
String? details,
bool showDetails = false,
}) {
return ErrorStatePage(
errorType: ErrorType.server,
title: '服务器异常',
message: '服务器暂时无法访问,请稍后重试',
actionText: '重新加载',
onAction: onRetry,
showAppBar: showAppBar,
appBarTitle: appBarTitle ?? '服务器错误',
showDetails: showDetails,
details: details,
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
backgroundColor: backgroundColor ?? theme.colorScheme.background,
appBar: showAppBar
? AppBar(
title: Text(appBarTitle ?? title),
centerTitle: true,
)
: null,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: customErrorState ?? ErrorState(
errorType: errorType,
title: title,
message: message,
actionText: actionText,
onAction: onAction,
showBorder: false,
showDetails: showDetails,
details: details,
),
),
),
);
}
}
/// 错误状态构建器 - 根据错误状态显示不同内容
/// [hasError] 是否有错误
/// [errorState] 错误状态组件
/// [child] 正常内容组件
class ErrorStateBuilder extends StatelessWidget {
/// 是否有错误
final bool hasError;
/// 错误状态组件
final Widget errorState;
/// 正常内容组件
final Widget child;
const ErrorStateBuilder({
super.key,
required this.hasError,
required this.errorState,
required this.child,
});
@override
Widget build(BuildContext context) {
if (hasError) {
return errorState;
}
return child;
}
}
/// 错误状态包装器 - 简化版本,自动构建错误状态
/// [hasError] 是否有错误
/// [errorType] 错误类型
/// [title] 错误标题
/// [message] 错误描述
/// [actionText] 操作按钮文本
/// [onAction] 操作按钮点击回调
/// [child] 正常内容组件
/// [showDetails] 是否显示详细错误信息
/// [details] 详细错误信息
class ErrorStateWrapper extends StatelessWidget {
/// 是否有错误
final bool hasError;
/// 错误类型
final ErrorType errorType;
/// 错误标题
final String title;
/// 错误描述
final String? message;
/// 操作按钮文本
final String? actionText;
/// 操作按钮点击回调
final VoidCallback? onAction;
/// 正常内容组件
final Widget child;
/// 自定义错误状态组件,优先级高于其他参数
final Widget? customErrorState;
/// 是否显示详细错误信息
final bool showDetails;
/// 详细错误信息
final String? details;
const ErrorStateWrapper({
super.key,
required this.hasError,
this.errorType = ErrorType.general,
required this.title,
required this.child,
this.message,
this.actionText,
this.onAction,
this.customErrorState,
this.showDetails = false,
this.details,
});
@override
Widget build(BuildContext context) {
if (hasError) {
return customErrorState ?? ErrorState(
errorType: errorType,
title: title,
message: message,
actionText: actionText,
onAction: onAction,
showDetails: showDetails,
details: details,
);
}
return child;
}
}