import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/features/notifications/data/models/notification_model.dart'; import 'package:real_estate_mobile/features/notifications/presentation/providers/notification_provider.dart'; class NotificationsScreen extends ConsumerStatefulWidget { const NotificationsScreen({super.key}); @override ConsumerState createState() => _NotificationsScreenState(); } class _NotificationsScreenState extends ConsumerState { final ScrollController _scrollController = ScrollController(); String _activeFilter = 'all'; @override void initState() { super.initState(); Future.microtask(() { ref.read(notificationListProvider.notifier).loadNotifications(); }); _scrollController.addListener(_onScroll); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { ref.read(notificationListProvider.notifier).loadMore(); } } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final state = ref.watch(notificationListProvider); return Column( children: [ // ── Header bar matching Figma: "Notifications" with bell icon ── Container( height: 55, decoration: BoxDecoration( borderRadius: BorderRadius.circular(7), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.1), width: 0.1, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.notifications, color: AppColors.accentOrange, size: 22, ), const SizedBox(width: 8), const Text( 'Notifications', style: TextStyle( fontFamily: 'Fractul', fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), ], ), ), // ── Filter tabs + Mark all as read ── Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ _buildFilterChip('All', 'all'), const SizedBox(width: 8), Builder(builder: (context) { final unreadCount = ref.watch(unreadCountProvider); final label = unreadCount > 0 ? 'Unread ($unreadCount)' : 'Unread'; return _buildFilterChip(label, 'unread'); }), const Spacer(), GestureDetector( onTap: () { ref .read(notificationListProvider.notifier) .markAllAsRead(); }, child: const Text( 'Mark all as read', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 13, fontWeight: FontWeight.w400, color: AppColors.accentOrange, ), ), ), ], ), ), // ── Notification list ── Expanded( child: _buildContent(state), ), ], ); } Widget _buildFilterChip(String label, String filter) { final isActive = _activeFilter == filter; return GestureDetector( onTap: () { setState(() => _activeFilter = filter); ref .read(notificationListProvider.notifier) .loadNotifications(filter: filter); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: isActive ? AppColors.primaryDark : Colors.transparent, borderRadius: BorderRadius.circular(20), border: Border.all( color: isActive ? AppColors.primaryDark : AppColors.primaryDark.withValues(alpha: 0.2), ), ), child: Text( label, style: TextStyle( fontFamily: 'Fractul', fontSize: 13, fontWeight: FontWeight.w500, color: isActive ? Colors.white : AppColors.primaryDark, ), ), ), ); } Widget _buildContent(NotificationListState state) { if (state.isLoading) { return const Center( child: CircularProgressIndicator( color: AppColors.accentOrange, strokeWidth: 2, ), ); } if (state.error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 48, color: AppColors.accentOrange), const SizedBox(height: 16), Text( 'Failed to load notifications', style: const TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.primaryDark, ), ), const SizedBox(height: 12), TextButton( onPressed: () { ref .read(notificationListProvider.notifier) .loadNotifications(filter: _activeFilter); }, child: const Text('Retry', style: TextStyle(color: AppColors.accentOrange)), ), ], ), ); } if (state.notifications.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.notifications_off_outlined, size: 48, color: AppColors.primaryDark.withValues(alpha: 0.3)), const SizedBox(height: 16), Text( _activeFilter == 'unread' ? 'No unread notifications' : 'No notifications yet', style: TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.primaryDark.withValues(alpha: 0.5), ), ), ], ), ); } return ListView.builder( controller: _scrollController, padding: EdgeInsets.zero, itemCount: state.notifications.length + (state.isLoadingMore ? 1 : 0), itemBuilder: (context, index) { if (index == state.notifications.length) { return const Padding( padding: EdgeInsets.all(16), child: Center( child: CircularProgressIndicator( color: AppColors.accentOrange, strokeWidth: 2, ), ), ); } return _buildNotificationTile(state.notifications[index]); }, ); } Widget _buildNotificationTile(AppNotification notification) { return GestureDetector( onTap: () { if (!notification.read) { ref .read(notificationListProvider.notifier) .markAsRead(notification.id); } }, behavior: HitTestBehavior.opaque, child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Avatar with unread dot ── SizedBox( width: 53, height: 52, child: Stack( children: [ Container( width: 52, height: 52, decoration: BoxDecoration( shape: BoxShape.circle, color: _iconBgColor(notification.type), ), child: Center( child: Icon( _iconForType(notification.type), color: AppColors.primaryDark, size: 24, ), ), ), if (!notification.read) Positioned( right: 0, bottom: 4, child: Container( width: 8, height: 8, decoration: const BoxDecoration( shape: BoxShape.circle, color: AppColors.accentOrange, ), ), ), ], ), ), const SizedBox(width: 8), // ── Title + Description ── Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( notification.title, style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: notification.read ? FontWeight.w500 : FontWeight.w700, color: AppColors.primaryDark, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( notification.description, style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark .withValues(alpha: notification.read ? 0.5 : 0.8), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 8), // ── Time ago ── Padding( padding: const EdgeInsets.only(top: 2), child: Text( _timeAgo(notification.createdAt), style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark.withValues(alpha: 0.6), ), ), ), ], ), ), Divider( height: 0.1, thickness: 0.1, color: AppColors.primaryDark.withValues(alpha: 0.1), ), ], ), ); } IconData _iconForType(String type) { switch (type) { case 'connection': return Icons.person_outline; case 'message': return Icons.chat_bubble_outline; case 'request': return Icons.assignment_outlined; case 'update': return Icons.notifications_outlined; case 'system': return Icons.settings_outlined; default: return Icons.notifications_outlined; } } Color _iconBgColor(String type) { switch (type) { case 'connection': return const Color(0xFFE8F5E9); case 'message': return const Color(0xFFE3F2FD); case 'request': return const Color(0xFFFFF3E0); case 'update': return const Color(0xFFF3E5F5); case 'system': return const Color(0xFFF5F5F5); default: return const Color(0xFFF5F5F5); } } String _timeAgo(DateTime dateTime) { final now = DateTime.now(); final diff = now.difference(dateTime); if (diff.inSeconds < 60) return 'Just now'; if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; if (diff.inHours < 24) return '${diff.inHours}hr ago'; if (diff.inDays < 7) return '${diff.inDays}d ago'; if (diff.inDays < 30) return '${(diff.inDays / 7).floor()}w ago'; return '${(diff.inDays / 30).floor()}mo ago'; } }