import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_theme.dart'; import '../../models/announcement.dart'; import '../../providers/announcement_provider.dart'; class AnnouncementsScreen extends ConsumerWidget { const AnnouncementsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final activeFilter = ref.watch(announcementTypeFilterProvider); final announcementsAsync = ref.watch(announcementsProvider); return Scaffold( appBar: AppBar( title: const Text('Notice Board'), ), body: Column( children: [ // Filter Chips Bar Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterChip( context: context, ref: ref, label: 'All', filterValue: null, isSelected: activeFilter == null, ), const SizedBox(width: 8), _buildFilterChip( context: context, ref: ref, label: 'Notices', filterValue: 'notice', isSelected: activeFilter == 'notice', ), const SizedBox(width: 8), _buildFilterChip( context: context, ref: ref, label: 'Circulars', filterValue: 'circular', isSelected: activeFilter == 'circular', ), const SizedBox(width: 8), _buildFilterChip( context: context, ref: ref, label: 'Events', filterValue: 'event', isSelected: activeFilter == 'event', ), ], ), ), ), const Divider(height: 1), // Main Content Expanded( child: RefreshIndicator( onRefresh: () async { ref.invalidate(announcementsProvider); await ref.read(announcementsProvider.future); }, child: announcementsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline_rounded, size: 48, color: AppTheme.errorColor), const SizedBox(height: 12), const Text( 'Failed to load announcements', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( err.toString(), textAlign: TextAlign.center, style: const TextStyle(color: AppTheme.textSecondary, fontSize: 13), ), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.refresh(announcementsProvider), child: const Text('Retry'), ), ], ), ), ), data: (pageResult) { final announcements = pageResult.announcements; if (announcements.isEmpty) { return ListView( children: const [ SizedBox(height: 100), Center( child: Padding( padding: EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.campaign_outlined, size: 64, color: AppTheme.textMuted), SizedBox(height: 16), Text( 'No announcements found', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), SizedBox(height: 8), Text( 'There are no active notices or circulars matching your criteria at this time.', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.textSecondary, fontSize: 13), ), ], ), ), ), ], ); } return ListView.separated( padding: const EdgeInsets.all(16), itemCount: announcements.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { final ann = announcements[index]; return _buildAnnouncementCard(context, ann); }, ); }, ), ), ), ], ), ); } Widget _buildFilterChip({ required BuildContext context, required WidgetRef ref, required String label, required String? filterValue, required bool isSelected, }) { return FilterChip( label: Text(label), selected: isSelected, onSelected: (_) { ref.read(announcementTypeFilterProvider.notifier).state = filterValue; }, selectedColor: AppTheme.primaryColor.withOpacity(0.15), checkmarkColor: AppTheme.primaryColor, labelStyle: TextStyle( fontSize: 13, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? AppTheme.primaryColor : AppTheme.textSecondary, ), ); } Widget _buildAnnouncementCard(BuildContext context, AnnouncementItem ann) { return Card( elevation: 1, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () => _showAnnouncementDetail(context, ann), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Top Badges & Date Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildTypeBadge(ann), if (ann.isUrgent || ann.isImportant) ...[ const SizedBox(width: 6), _buildPriorityBadge(ann), ], const Spacer(), if (ann.date.isNotEmpty) Text( ann.date, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary, ), ), ], ), const SizedBox(height: 10), // Title Text( ann.title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), const SizedBox(height: 6), // Summary if (ann.summary.isNotEmpty) Text( ann.summary, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, color: AppTheme.textSecondary, height: 1.3, ), ), // Footer: Author & Attachment if (ann.author != null || ann.hasAttachment) ...[ const SizedBox(height: 10), Row( children: [ if (ann.author != null) Row( children: [ const Icon(Icons.person_outline, size: 14, color: AppTheme.textMuted), const SizedBox(width: 4), Text( ann.author!, style: const TextStyle(fontSize: 11, color: AppTheme.textMuted), ), ], ), const Spacer(), if (ann.hasAttachment) Row( children: [ const Icon(Icons.attach_file, size: 14, color: AppTheme.primaryColor), const SizedBox(width: 2), Text( ann.circularFilename ?? 'Attachment', style: const TextStyle( fontSize: 11, color: AppTheme.primaryColor, fontWeight: FontWeight.w500, ), ), ], ), ], ), ], ], ), ), ), ); } Widget _buildTypeBadge(AnnouncementItem ann) { Color bg; Color fg; switch (ann.type) { case 'circular': bg = Colors.deepPurple.shade50; fg = Colors.deepPurple.shade700; break; case 'event': bg = Colors.teal.shade50; fg = Colors.teal.shade700; break; case 'notice': default: bg = Colors.blue.shade50; fg = Colors.blue.shade700; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(6), ), child: Text( ann.typeLabel, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: fg), ), ); } Widget _buildPriorityBadge(AnnouncementItem ann) { Color bg; Color fg; if (ann.isUrgent) { bg = Colors.red.shade50; fg = Colors.red.shade700; } else { bg = Colors.amber.shade50; fg = Colors.amber.shade900; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(6), ), child: Text( ann.priorityLabel, style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: fg), ), ); } void _showAnnouncementDetail(BuildContext context, AnnouncementItem ann) { showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (context) { return DraggableScrollableSheet( expand: false, initialChildSize: 0.7, minChildSize: 0.4, maxChildSize: 0.95, builder: (context, scrollController) { return SingleChildScrollView( controller: scrollController, padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Handle Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2), ), ), ), // Header Badges Row( children: [ _buildTypeBadge(ann), if (ann.isUrgent || ann.isImportant) ...[ const SizedBox(width: 8), _buildPriorityBadge(ann), ], const Spacer(), if (ann.date.isNotEmpty) Text( 'Published: ${ann.date}', style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), ], ), const SizedBox(height: 16), // Title Text( ann.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), // Expiry Date & Author if (ann.expiryDate != null || ann.author != null) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.surfaceColor, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (ann.author != null) Row( children: [ const Icon(Icons.person_outline, size: 16, color: AppTheme.textSecondary), const SizedBox(width: 6), Text( 'Posted by: ${ann.author}', style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), ], ), if (ann.expiryDate != null) ...[ if (ann.author != null) const SizedBox(height: 6), Row( children: [ const Icon(Icons.event_busy, size: 16, color: AppTheme.textSecondary), const SizedBox(width: 6), Text( 'Expires on: ${ann.expiryDate}', style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), ], ), ], ], ), ), const SizedBox(height: 16), // Content const Text( 'Details', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 8), Text( ann.summary.isNotEmpty ? ann.summary : ann.title, style: const TextStyle( fontSize: 14, color: AppTheme.textPrimary, height: 1.5, ), ), // Attachment info if (ann.hasAttachment) ...[ const SizedBox(height: 20), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all(color: AppTheme.primaryColor.withOpacity(0.3)), borderRadius: BorderRadius.circular(8), color: AppTheme.primaryColor.withOpacity(0.05), ), child: Row( children: [ const Icon(Icons.description_outlined, color: AppTheme.primaryColor), const SizedBox(width: 8), Expanded( child: Text( ann.circularFilename ?? 'Attached Document', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AppTheme.primaryColor, ), ), ), ], ), ), ], const SizedBox(height: 24), SizedBox( width: double.infinity, child: OutlinedButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ), ], ), ); }, ); }, ); } }