import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/announcements_provider.dart'; /// A persistent, globally visible banner that appears at the top of every /// authenticated screen when one or more [Announcement]s have an active banner. /// /// Mirrors the pattern used by [PassSlipCountdownBanner] and /// [ShiftCountdownBanner]. Registered in [_ShellBackground] so it wraps all /// shell-route screens. /// /// Tapping the banner navigates to `/announcements`. The X button dismisses /// the leading announcement for the current session; if more remain they /// continue to be shown. class AnnouncementBanner extends ConsumerStatefulWidget { const AnnouncementBanner({required this.child, super.key}); final Widget child; @override ConsumerState createState() => _AnnouncementBannerState(); } class _AnnouncementBannerState extends ConsumerState { /// IDs of banners the user has dismissed for this session. final Set _sessionDismissed = {}; @override Widget build(BuildContext context) { final visible = ref .watch(activeBannerAnnouncementsProvider) .where((a) => !_sessionDismissed.contains(a.id)) .toList(); if (visible.isEmpty) return widget.child; final first = visible.first; final extra = visible.length - 1; final cs = Theme.of(context).colorScheme; final tt = Theme.of(context).textTheme; return Column( children: [ Material( child: InkWell( onTap: () => context.go('/announcements'), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration(color: cs.primaryContainer), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.campaign_rounded, size: 22, color: cs.onPrimaryContainer, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( extra > 0 ? 'Announcement ยท +$extra more' : 'Announcement', style: tt.labelSmall?.copyWith( color: cs.onPrimaryContainer.withValues(alpha: 0.75), letterSpacing: 0.3, ), ), const SizedBox(height: 1), Text( first.title, style: tt.bodyMedium?.copyWith( color: cs.onPrimaryContainer, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), Text( 'Tap to view', style: tt.bodySmall?.copyWith( color: cs.onPrimaryContainer.withValues(alpha: 0.7), ), ), Icon( Icons.chevron_right, size: 18, color: cs.onPrimaryContainer.withValues(alpha: 0.7), ), const SizedBox(width: 4), // Dismiss for session InkWell( borderRadius: BorderRadius.circular(16), onTap: () => setState(() => _sessionDismissed.add(first.id)), child: Padding( padding: const EdgeInsets.all(4), child: Icon( Icons.close, size: 18, color: cs.onPrimaryContainer, ), ), ), ], ), ), ), ), Expanded(child: widget.child), ], ); } }