// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; import '../theme/app_dimens.dart'; const double _regularIconSize = 48; const double _compactIconSize = 32; const double _maxContentWidth = 260; const double _regularHorizontal = 24; const double _regularVertical = 28; const double _compactPad = 16; const double _regularGap = 20; const double _compactGap = 12; const double _actionMinHeight = 44; /// 统一空列表占位:一枚图标、标题、可选说明与操作。 class EmptyState extends StatelessWidget { const EmptyState({ super.key, required this.icon, required this.title, this.subtitle, this.actionLabel, this.actionKey, this.onAction, this.compact = false, }); final IconData icon; final String title; final String? subtitle; final String? actionLabel; final Key? actionKey; final VoidCallback? onAction; final bool compact; @override Widget build(BuildContext context) { final hasAction = actionLabel != null && onAction != null; return Center( child: ConstrainedBox( key: const Key('empty-state-content'), constraints: const BoxConstraints(maxWidth: _maxContentWidth), child: Padding( key: const Key('empty-state-padding'), padding: EdgeInsets.symmetric( horizontal: compact ? _compactPad : _regularHorizontal, vertical: compact ? _compactPad : _regularVertical, ), child: Semantics( container: true, label: title, child: Column( mainAxisSize: MainAxisSize.min, children: [ ExcludeSemantics( child: _EmptyMark(icon: icon, compact: compact), ), SizedBox(height: compact ? _compactGap : _regularGap), _EmptyCopy( title: title, subtitle: subtitle, actionLabel: hasAction ? actionLabel : null, actionKey: actionKey, onAction: hasAction ? onAction : null, ), ], ), ), ), ), ); } } /// 空状态只留一枚图标,不加水印底和描边。 class _EmptyMark extends StatelessWidget { const _EmptyMark({required this.icon, required this.compact}); final IconData icon; final bool compact; @override Widget build(BuildContext context) { final dark = Theme.of(context).brightness == Brightness.dark; return Icon( icon, key: const Key('empty-state-icon'), size: compact ? _compactIconSize : _regularIconSize, color: dark ? AppColors.gold : AppColors.blue, ); } } class _EmptyCopy extends StatelessWidget { const _EmptyCopy({ required this.title, required this.subtitle, required this.actionLabel, required this.actionKey, required this.onAction, }); final String title; final String? subtitle; final String? actionLabel; final Key? actionKey; final VoidCallback? onAction; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Column( mainAxisSize: MainAxisSize.min, children: [ ExcludeSemantics( child: Text( title, key: const Key('empty-state-title'), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: TextStyle( color: scheme.onSurface, fontSize: 15, fontWeight: FontWeight.w600, height: 1.35, ), ), ), if (subtitle != null) ...[ const SizedBox(height: 6), ExcludeSemantics( child: Text( subtitle!, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: const TextStyle( color: AppColors.muted, fontSize: AppDimens.emptyStateSubtitle, height: 1.45, ), ), ), ], if (actionLabel != null && onAction != null) ...[ const SizedBox(height: 8), TextButton( key: actionKey ?? const Key('empty-state-action'), onPressed: onAction, style: TextButton.styleFrom( foregroundColor: AppColors.blueSoft, minimumSize: const Size(44, _actionMinHeight), padding: const EdgeInsets.symmetric(horizontal: 14), ), child: Text( actionLabel!, style: const TextStyle(fontSize: AppDimens.emptyStateAction), ), ), ], ], ); } }