// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; const double _regularArtSize = 84; const double _compactArtSize = 60; const double _regularInnerSize = 56; const double _compactInnerSize = 40; const double _regularIconSize = 27; const double _compactIconSize = 21; const double _maxContentWidth = 260; const double _actionMinHeight = 44; const double _outerArtOpacity = 0.055; const double _outerBorderOpacity = 0.11; const double _innerArtOpacity = 0.11; /// 统一空列表占位:柔和图标、标题、可选说明与操作。 class EmptyState extends StatelessWidget { const EmptyState({ super.key, required this.icon, required this.title, this.subtitle, this.actionLabel, this.onAction, this.compact = false, }); final IconData icon; final String title; final String? subtitle; final String? actionLabel; final VoidCallback? onAction; final bool compact; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final dark = Theme.of(context).brightness == Brightness.dark; final iconColor = dark ? AppColors.blueSoft : AppColors.blue; final artSize = compact ? _compactArtSize : _regularArtSize; final innerSize = compact ? _compactInnerSize : _regularInnerSize; final iconSize = compact ? _compactIconSize : _regularIconSize; 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 ? 16 : 24, vertical: compact ? 16 : 28, ), child: Semantics( container: true, label: title, child: Column( mainAxisSize: MainAxisSize.min, children: [ ExcludeSemantics( child: Container( key: const Key('empty-state-art'), width: artSize, height: artSize, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.blue.withValues(alpha: _outerArtOpacity), border: Border.all( color: AppColors.blue.withValues( alpha: _outerBorderOpacity, ), ), ), child: Container( key: const Key('empty-state-art-inner'), width: innerSize, height: innerSize, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.blue.withValues( alpha: _innerArtOpacity, ), ), child: Icon( icon, key: const Key('empty-state-icon'), size: iconSize, color: iconColor, ), ), ), ), SizedBox(height: compact ? 10 : 14), 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: 12.5, height: 1.45, ), ), ), ], if (hasAction) ...[ const SizedBox(height: 8), TextButton( key: const Key('empty-state-action'), onPressed: onAction, style: TextButton.styleFrom( foregroundColor: scheme.primary, minimumSize: const Size(44, _actionMinHeight), padding: const EdgeInsets.symmetric(horizontal: 14), ), child: Text( actionLabel!, style: const TextStyle(fontSize: 13), ), ), ], ], ), ), ), ), ); } }