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

541 lines
14 KiB
Dart
Raw Permalink 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';
/// 空状态组件库 - 提供多种样式的空状态页面展示
/// 包括无数据、无结果、错误等不同场景的空状态显示
/// 基础空状态组件 - 通用的空状态显示组件
/// [icon] 空状态图标使用Material Icons
/// [title] 空状态标题文本
/// [message] 空状态描述文本
/// [actionText] 操作按钮文本null时不显示按钮
/// [onAction] 操作按钮点击回调null时按钮不可点击
/// [iconSize] 图标大小null时使用默认大小
/// [iconColor] 图标颜色null时使用主题色彩
class EmptyState extends StatelessWidget {
/// 空状态图标使用Material Icons
final IconData icon;
/// 空状态标题文本
final String title;
/// 空状态描述文本
final String? message;
/// 操作按钮文本null时不显示按钮
final String? actionText;
/// 操作按钮点击回调null时按钮不可点击
final VoidCallback? onAction;
/// 图标大小null时使用默认大小
final double? iconSize;
/// 图标颜色null时使用主题色彩
final Color? iconColor;
/// 自定义图标组件优先级高于icon参数
final Widget? customIcon;
/// 自定义操作组件优先级高于actionText参数
final Widget? customAction;
/// 主要内容垂直间距
final double spacing;
/// 主要内容内边距
final EdgeInsetsGeometry? padding;
/// 是否显示边框装饰
final bool showBorder;
/// 边框圆角半径
final double borderRadius;
/// 背景颜色null时使用透明背景
final Color? backgroundColor;
const EmptyState({
super.key,
required this.icon,
required this.title,
this.message,
this.actionText,
this.onAction,
this.iconSize,
this.iconColor,
this.customIcon,
this.customAction,
this.spacing = 16,
this.padding,
this.showBorder = false,
this.borderRadius = 12,
this.backgroundColor,
});
/// 创建无数据空状态 - 用于列表、网格等无数据场景
factory EmptyState.noData({
String? title,
String? message,
String? actionText,
VoidCallback? onAction,
IconData? icon,
}) {
return EmptyState(
icon: icon ?? Icons.inbox_outlined,
title: title ?? '暂无数据',
message: message ?? '当前没有可显示的数据',
actionText: actionText,
onAction: onAction,
iconColor: Colors.grey,
);
}
/// 创建无搜索结果空状态 - 用于搜索无结果场景
factory EmptyState.noSearchResults({
String? searchQuery,
VoidCallback? onClearSearch,
}) {
return EmptyState(
icon: Icons.search_off_outlined,
title: '未找到相关结果',
message: searchQuery != null
? '未找到与"$searchQuery"相关的内容'
: '未找到相关内容,请尝试其他关键词',
actionText: onClearSearch != null ? '清除搜索' : null,
onAction: onClearSearch,
iconColor: Colors.grey,
);
}
/// 创建网络错误空状态 - 用于网络连接失败场景
factory EmptyState.networkError({
VoidCallback? onRetry,
}) {
return EmptyState(
icon: Icons.wifi_off_outlined,
title: '网络连接失败',
message: '请检查网络连接后重试',
actionText: '重新加载',
onAction: onRetry,
iconColor: Colors.orange,
);
}
/// 创建服务器错误空状态 - 用于服务器异常场景
factory EmptyState.serverError({
VoidCallback? onRetry,
}) {
return EmptyState(
icon: Icons.cloud_off_outlined,
title: '服务器异常',
message: '服务器暂时无法访问,请稍后重试',
actionText: '重新加载',
onAction: onRetry,
iconColor: Colors.red,
);
}
/// 创建权限不足空状态 - 用于无权限访问场景
factory EmptyState.permissionDenied({
VoidCallback? onRequestPermission,
}) {
return EmptyState(
icon: Icons.lock_outline,
title: '权限不足',
message: '您没有访问此内容的权限',
actionText: onRequestPermission != null ? '申请权限' : null,
onAction: onRequestPermission,
iconColor: Colors.orange,
);
}
/// 创建收藏为空状态 - 用于收藏列表为空场景
factory EmptyState.emptyFavorites({
VoidCallback? onBrowse,
}) {
return EmptyState(
icon: Icons.favorite_border_outlined,
title: '暂无收藏',
message: '您还没有收藏任何内容',
actionText: onBrowse != null ? '去浏览' : null,
onAction: onBrowse,
iconColor: Colors.pink,
);
}
/// 创建购物车为空状态 - 用于购物车为空场景
factory EmptyState.emptyCart({
VoidCallback? onShop,
}) {
return EmptyState(
icon: Icons.shopping_cart_outlined,
title: '购物车是空的',
message: '您还没有添加任何商品',
actionText: onShop != null ? '去购物' : null,
onAction: onShop,
iconColor: Colors.blue,
);
}
/// 创建历史记录为空状态 - 用于历史记录为空场景
factory EmptyState.emptyHistory({
VoidCallback? onBrowse,
}) {
return EmptyState(
icon: Icons.history_outlined,
title: '暂无历史记录',
message: '您还没有任何操作记录',
actionText: onBrowse != null ? '去浏览' : null,
onAction: onBrowse,
iconColor: Colors.grey,
);
}
/// 创建文件为空状态 - 用于文件列表为空场景
factory EmptyState.emptyFiles({
VoidCallback? onUpload,
}) {
return EmptyState(
icon: Icons.folder_open_outlined,
title: '文件夹是空的',
message: '此文件夹中还没有任何文件',
actionText: onUpload != null ? '上传文件' : null,
onAction: onUpload,
iconColor: Colors.blue,
);
}
/// 创建图片为空状态 - 用于图片列表为空场景
factory EmptyState.emptyImages({
VoidCallback? onAddImages,
}) {
return EmptyState(
icon: Icons.photo_library_outlined,
title: '暂无图片',
message: '您还没有添加任何图片',
actionText: onAddImages != null ? '添加图片' : null,
onAction: onAddImages,
iconColor: Colors.purple,
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Widget content = Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 图标部分
if (customIcon != null) ...[
customIcon!,
SizedBox(height: spacing),
] else ...[
Icon(
icon,
size: iconSize ?? 64,
color: iconColor ?? theme.colorScheme.onSurface.withOpacity(0.4),
),
SizedBox(height: spacing),
],
// 标题部分
Text(
title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: theme.colorScheme.onSurface.withOpacity(0.8),
),
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 (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(
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 ?? theme.colorScheme.surface.withOpacity(0.5),
border: Border.all(
color: theme.colorScheme.outline.withOpacity(0.2),
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;
}
}
/// 空状态页面 - 全屏空状态显示,包含图标、文本和操作按钮
/// [icon] 空状态图标
/// [title] 空状态标题
/// [message] 空状态描述文本
/// [actionText] 操作按钮文本
/// [onAction] 操作按钮点击回调
/// [showAppBar] 是否显示应用栏
/// [appBarTitle] 应用栏标题
class EmptyStatePage extends StatelessWidget {
/// 空状态图标
final IconData icon;
/// 空状态标题
final String title;
/// 空状态描述文本
final String? message;
/// 操作按钮文本
final String? actionText;
/// 操作按钮点击回调
final VoidCallback? onAction;
/// 是否显示应用栏
final bool showAppBar;
/// 应用栏标题
final String? appBarTitle;
/// 自定义空状态组件,优先级高于其他参数
final Widget? customEmptyState;
/// 背景颜色null时使用主题背景色
final Color? backgroundColor;
const EmptyStatePage({
super.key,
required this.icon,
required this.title,
this.message,
this.actionText,
this.onAction,
this.showAppBar = false,
this.appBarTitle,
this.customEmptyState,
this.backgroundColor,
});
/// 创建无数据空状态页面
factory EmptyStatePage.noData({
String? title,
String? message,
String? actionText,
VoidCallback? onAction,
bool showAppBar = false,
String? appBarTitle,
}) {
return EmptyStatePage(
icon: Icons.inbox_outlined,
title: title ?? '暂无数据',
message: message ?? '当前没有可显示的数据',
actionText: actionText,
onAction: onAction,
showAppBar: showAppBar,
appBarTitle: appBarTitle ?? '无数据',
);
}
/// 创建无搜索结果空状态页面
factory EmptyStatePage.noSearchResults({
String? searchQuery,
VoidCallback? onClearSearch,
bool showAppBar = false,
}) {
return EmptyStatePage(
icon: Icons.search_off_outlined,
title: '未找到相关结果',
message: searchQuery != null
? '未找到与"$searchQuery"相关的内容'
: '未找到相关内容,请尝试其他关键词',
actionText: onClearSearch != null ? '清除搜索' : null,
onAction: onClearSearch,
showAppBar: showAppBar,
appBarTitle: '搜索结果',
);
}
/// 创建网络错误空状态页面
factory EmptyStatePage.networkError({
VoidCallback? onRetry,
bool showAppBar = false,
}) {
return EmptyStatePage(
icon: Icons.wifi_off_outlined,
title: '网络连接失败',
message: '请检查网络连接后重试',
actionText: '重新加载',
onAction: onRetry,
showAppBar: showAppBar,
appBarTitle: '网络错误',
);
}
@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: customEmptyState ?? EmptyState(
icon: icon,
title: title,
message: message,
actionText: actionText,
onAction: onAction,
showBorder: false,
),
),
),
);
}
}
/// 空状态构建器 - 根据条件显示空状态或正常内容
/// [isEmpty] 是否为空状态
/// [emptyState] 空状态组件
/// [child] 正常内容组件
class EmptyStateBuilder extends StatelessWidget {
/// 是否为空状态
final bool isEmpty;
/// 空状态组件
final Widget emptyState;
/// 正常内容组件
final Widget child;
const EmptyStateBuilder({
super.key,
required this.isEmpty,
required this.emptyState,
required this.child,
});
@override
Widget build(BuildContext context) {
if (isEmpty) {
return emptyState;
}
return child;
}
}
/// 空状态包装器 - 简化版本,自动构建空状态
/// [isEmpty] 是否为空状态
/// [icon] 空状态图标
/// [title] 空状态标题
/// [message] 空状态描述
/// [actionText] 操作按钮文本
/// [onAction] 操作按钮点击回调
/// [child] 正常内容组件
class EmptyStateWrapper extends StatelessWidget {
/// 是否为空状态
final bool isEmpty;
/// 空状态图标
final IconData icon;
/// 空状态标题
final String title;
/// 空状态描述
final String? message;
/// 操作按钮文本
final String? actionText;
/// 操作按钮点击回调
final VoidCallback? onAction;
/// 正常内容组件
final Widget child;
/// 自定义空状态组件,优先级高于其他参数
final Widget? customEmptyState;
const EmptyStateWrapper({
super.key,
required this.isEmpty,
required this.icon,
required this.title,
required this.child,
this.message,
this.actionText,
this.onAction,
this.customEmptyState,
});
@override
Widget build(BuildContext context) {
if (isEmpty) {
return customEmptyState ?? EmptyState(
icon: icon,
title: title,
message: message,
actionText: actionText,
onAction: onAction,
);
}
return child;
}
}