// FaiEmptyState — gracious empty / loading / error placeholder. // Replaces "(no data)" strings with one tone, one icon, one tip. // // The blank surface is where an app most easily feels unfinished, // so this state is deliberately *designed*: the Ch∆In delta (∆) // sits as a soft watermark behind the icon badge, the badge // carries a whisper of elevation, and the copy breathes. The // result reads "nothing here yet, on purpose" rather than // "something is broken". import 'package:flutter/material.dart'; import '../theme/tokens.dart'; import 'fai_delta_mark.dart'; class FaiEmptyState extends StatelessWidget { final IconData icon; final String title; final String? hint; final Widget? action; /// Icon tone. Defaults to muted; set to [Theme.colorScheme.error] /// for connection-failure variants. final Color? iconColor; const FaiEmptyState({ super.key, required this.icon, required this.title, this.hint, this.action, this.iconColor, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final color = iconColor ?? theme.colorScheme.onSurfaceVariant; return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: Padding( padding: const EdgeInsets.all(FaiSpace.xxl), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ // Brand hero: an oversized, low-opacity ∆ watermark // anchors the composition while the contextual icon // badge sits crisply on top — so every empty surface // still carries the Ch∆In signature, never a bare icon. SizedBox( width: 112, height: 112, child: Stack( alignment: Alignment.center, children: [ Opacity( opacity: 0.10, child: FaiDeltaMark( color: theme.colorScheme.primary, size: 112, ), ), Container( width: 60, height: 60, decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHigh, shape: BoxShape.circle, border: Border.all( color: color.withValues(alpha: 0.18), ), boxShadow: FaiElevation.low(theme.brightness), ), child: Icon(icon, size: 26, color: color), ), ], ), ), const SizedBox(height: FaiSpace.xl), Text( title, textAlign: TextAlign.center, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), if (hint != null) ...[ const SizedBox(height: FaiSpace.sm), Text( hint!, textAlign: TextAlign.center, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.5, ), ), ], if (action != null) ...[ const SizedBox(height: FaiSpace.xl), action!, ], ], ), ), ), ); } }