import 'package:flutter/material.dart'; import 'package:shuffle/core/theme/app_spacing.dart'; enum AppBannerType { info, error } /// Inline message banner (info / error) above forms. /// /// Type is conveyed by icon + [typeLabel] (spoken prefix, e.g. "Error"), /// never by color alone. The message is a live region, so screen readers /// announce it when it appears. The optional action is a separate 48 dp text /// button. All texts come from the caller (l10n). class AppBanner extends StatelessWidget { const AppBanner({ required this.type, required this.message, required this.typeLabel, super.key, this.actionLabel, this.onAction, }) : assert( (actionLabel == null) == (onAction == null), 'actionLabel and onAction belong together', ); final AppBannerType type; final String message; final String typeLabel; final String? actionLabel; final VoidCallback? onAction; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; final isError = type == AppBannerType.error; final background = isError ? scheme.errorContainer : scheme.secondaryContainer; final foreground = isError ? scheme.onErrorContainer : scheme.onSecondaryContainer; return DecoratedBox( decoration: BoxDecoration( color: background, borderRadius: AppRadius.mdAll, border: Border.all(color: foreground.withValues(alpha: 0.5)), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ Semantics( container: true, liveRegion: true, label: '$typeLabel: $message', child: ExcludeSemantics( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( isError ? Icons.error_outline : Icons.info_outline, color: foreground, ), const SizedBox(width: AppSpacing.md), Expanded( child: Text( message, style: theme.textTheme.bodyMedium?.copyWith( color: foreground, ), ), ), ], ), ), ), if (actionLabel != null) Align( alignment: AlignmentDirectional.centerEnd, child: TextButton( onPressed: onAction, style: TextButton.styleFrom(foregroundColor: foreground), child: Text(actionLabel!), ), ), ], ), ), ); } }