import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'dart:developer' as dev; import '../../core/app_colors.dart'; import '../../di/service_locator.dart'; import '../../domain/entities/client_notification.dart'; import '../components/custom_app_bar.dart'; import '../event/notifications_event.dart'; import '../state/notifications_state.dart'; import '../viewmodel/notifications_bloc.dart'; enum NotificationFilter { all, auth, payment, order, } class NotificationsScreen extends StatelessWidget { const NotificationsScreen({super.key}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt()..add(NotificationsFetchRequested(page: 1)), child: _NotificationsScreenContent(), ); } } class _NotificationsScreenContent extends StatefulWidget { const _NotificationsScreenContent(); @override State<_NotificationsScreenContent> createState() => _NotificationsScreenContentState(); } class _NotificationsScreenContentState extends State<_NotificationsScreenContent> { NotificationFilter _filter = NotificationFilter.all; final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); } @override void dispose() { _scrollController.dispose(); super.dispose(); } void _setFilter(NotificationFilter filter) { setState(() => _filter = filter); _scrollController.jumpTo(0); context.read().add(NotificationsFetchRequested(page: 1)); } @override Widget build(BuildContext context) { return NotificationsView( filter: _filter, onFilterChanged: _setFilter, scrollController: _scrollController, ); } } class NotificationsView extends StatelessWidget { final NotificationFilter filter; final ValueChanged onFilterChanged; final ScrollController? scrollController; const NotificationsView({ super.key, this.filter = NotificationFilter.all, required this.onFilterChanged, this.scrollController, }); @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg), child: SafeArea( child: Column( children: [ const SizedBox(height: 16), const Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: CustomAppBar(title: 'Уведомления'), ), const SizedBox(height: 16), _buildFilterBar(), const SizedBox(height: 16), Expanded( child: BlocBuilder( builder: (context, state) { if (state.status == NotificationsStatus.loading) { return const Center(child: CircularProgressIndicator(color: Colors.white)); } if (state.status == NotificationsStatus.failure) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Ошибка загрузки уведомлений', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Text( state.errorMessage ?? '', style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 14, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 24), ElevatedButton( onPressed: () { context.read().add(NotificationsFetchRequested(page: 1)); }, child: const Text('Повторить'), ), ], ), ); } final filtered = state.notifications.where((n) { switch (filter) { case NotificationFilter.all: return true; case NotificationFilter.auth: return n.category == NotificationCategory.auth; case NotificationFilter.payment: return n.category == NotificationCategory.payment; case NotificationFilter.order: return n.category == NotificationCategory.scooter; default: return true; } }).toList(); if (filtered.isEmpty) { return const _EmptyState(); } return RefreshIndicator( onRefresh: () async { context.read().add(NotificationsFetchRequested(page: 1)); }, child: NotificationListener( onNotification: (scrollInfo) { if (scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 50 && scrollInfo.metrics.maxScrollExtent > 0 && state.hasMore && !state.isLoadingMore && state.status == NotificationsStatus.success) { context.read().add(NotificationsLoadMoreRequested()); } return false; }, child: ListView.builder( controller: scrollController, padding: const EdgeInsets.symmetric(horizontal: 20), itemCount: filtered.length + (state.hasMore && filtered.length >= 10 ? 1 : 0), itemBuilder: (context, index) { if (index == filtered.length && state.hasMore && filtered.length >= 10) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), ), ), ); } return _NotificationCard(notification: filtered[index]); }, ), ), ); }, ), ), ], ), ), ), ); } Widget _buildFilterBar() { final items = [ {'label': 'Все', 'value': NotificationFilter.all}, {'label': 'Авторизация', 'value': NotificationFilter.auth}, {'label': 'Оплата', 'value': NotificationFilter.payment}, {'label': 'Поездка', 'value': NotificationFilter.order}, ]; return Container( height: 40, padding: const EdgeInsets.symmetric(horizontal: 20), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: items.map((item) { final isActive = item['value'] == filter; return Padding( padding: const EdgeInsets.only(right: 12), child: GestureDetector( onTap: () => onFilterChanged(item['value'] as NotificationFilter), child: Container( height: 32, padding: const EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.center, decoration: BoxDecoration( gradient: isActive ? AppColors.activeButtonGradient : null, color: isActive ? null : Colors.transparent, borderRadius: BorderRadius.circular(20), border: Border.all( color: isActive ? Colors.transparent : Colors.white.withOpacity(0.4), width: 1, ), ), child: Text( item['label'] as String, textAlign: TextAlign.center, style: TextStyle( color: isActive ? AppColors.activeButtonText : Colors.white, fontSize: 14, fontWeight: isActive ? FontWeight.bold : FontWeight.normal, ), ), ), ), ); }).toList(), ), ), ); } } class _EmptyState extends StatelessWidget { const _EmptyState(); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( 'assets/notification_empty.png', width: 280, height: 280, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { return const Icon( Icons.notifications_none_outlined, size: 120, color: Colors.white38, ); }, ), const SizedBox(height: 32), const Padding( padding: EdgeInsets.symmetric(horizontal: 40), child: Text( 'У вас пока нет уведомлений.', textAlign: TextAlign.center, style: TextStyle( color: Colors.white70, fontSize: 16, height: 1.5, ), ), ), ], ); } } class _NotificationCard extends StatelessWidget { final ClientNotification notification; const _NotificationCard({required this.notification}); @override Widget build(BuildContext context) { final date = _formatDate(notification.createdAt); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF141530).withOpacity(0.7), borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( date, style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 12, ), ), const SizedBox(height: 8), Text( notification.content, style: const TextStyle( color: Colors.white, fontSize: 14, ), ), ], ), ); } String _formatDate(DateTime date) { final localDate = date.toLocal(); final now = DateTime.now(); if (localDate.year == now.year && localDate.month == now.month && localDate.day == now.day) { return 'Сегодня, ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}'; } final yesterday = now.subtract(const Duration(days: 1)); if (localDate.year == yesterday.year && localDate.month == yesterday.month && localDate.day == yesterday.day) { return 'Вчера, ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}'; } return '${localDate.day.toString().padLeft(2, '0')}.${localDate.month.toString().padLeft(2, '0')}.${localDate.year}'; } String _getTypeLabel(NotificationType type) { switch (type) { case NotificationType.info: return 'Информация'; case NotificationType.attention: return 'Внимание'; case NotificationType.warning: return 'Предупреждение'; } } String _getCategoryLabel(NotificationCategory category) { switch (category) { case NotificationCategory.auth: return 'Авторизация'; case NotificationCategory.zone: return 'Зоны'; case NotificationCategory.payment: return 'Оплата'; case NotificationCategory.companyInfo: return 'Акции'; case NotificationCategory.adminInfo: return 'Админ'; case NotificationCategory.scooter: return 'Самокат'; } } }