import 'package:flutter/material.dart'; import 'tokens.dart'; enum HeqiStatusTone { neutral, info, success, warning, danger } class HeqiGutter extends StatelessWidget { const HeqiGutter({required this.child, super.key}); final Widget child; @override Widget build(BuildContext context) => Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: HeqiSize.contentMaxWidth), child: Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.sizeOf(context).width >= HeqiBreakpoints.compact ? HeqiSpacing.x7 : HeqiSpacing.x5, ), child: child, ), ), ); } class HeqiSurfaceSection extends StatelessWidget { const HeqiSurfaceSection({ required this.child, this.padding = const EdgeInsets.all(HeqiSpacing.x5), super.key, }); final Widget child; final EdgeInsetsGeometry padding; @override Widget build(BuildContext context) => DecoratedBox( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(HeqiRadius.large), border: Border.all(color: Theme.of(context).colorScheme.outlineVariant), ), child: Padding(padding: padding, child: child), ); } class HeqiSectionHeader extends StatelessWidget { const HeqiSectionHeader({ required this.title, this.description, this.trailing, super.key, }); final String title; final String? description; final Widget? trailing; @override Widget build(BuildContext context) => Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context).textTheme.titleLarge), if (description != null) ...[ const SizedBox(height: HeqiSpacing.x1), Text( description!, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ], ), ), ?trailing, ], ); } class HeqiStatusPill extends StatelessWidget { const HeqiStatusPill({ required this.label, this.tone = HeqiStatusTone.neutral, this.icon, super.key, }); final String label; final HeqiStatusTone tone; final IconData? icon; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final (foreground, background) = switch (tone) { HeqiStatusTone.info => ( scheme.onPrimaryContainer, scheme.primaryContainer, ), HeqiStatusTone.success => ( scheme.onSecondaryContainer, scheme.secondaryContainer, ), HeqiStatusTone.warning => ( scheme.onTertiaryContainer, scheme.tertiaryContainer, ), HeqiStatusTone.danger => (scheme.onErrorContainer, scheme.errorContainer), HeqiStatusTone.neutral => ( scheme.onSurfaceVariant, scheme.surfaceContainerHighest, ), }; return Semantics( label: '状态:$label', child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(HeqiRadius.small), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (icon != null) ...[ Icon(icon, size: HeqiSize.iconSmall, color: foreground), const SizedBox(width: HeqiSpacing.x1), ], Text( label, style: Theme.of( context, ).textTheme.labelSmall?.copyWith(color: foreground), ), ], ), ), ); } } class HeqiMessageState extends StatelessWidget { const HeqiMessageState({ required this.title, required this.description, this.onRetry, this.icon = Icons.info_outline_rounded, super.key, }); final String title; final String description; final VoidCallback? onRetry; final IconData icon; @override Widget build(BuildContext context) => Center( child: Padding( padding: const EdgeInsets.all(HeqiSpacing.x8), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, shape: BoxShape.circle, ), child: Icon( icon, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: HeqiSpacing.x5), Text( title, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center, ), const SizedBox(height: HeqiSpacing.x2), Text( description, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), if (onRetry != null) ...[ const SizedBox(height: HeqiSpacing.x5), OutlinedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh_rounded), label: const Text('重新加载'), ), ], ], ), ), ); }