Files
be_happy_public/lib/presentation/screens/notifications_screen.dart

350 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 _NotificationsScreenContent();
}
}
class _NotificationsScreenContent extends StatefulWidget {
const _NotificationsScreenContent();
@override
State<_NotificationsScreenContent> createState() => _NotificationsScreenContentState();
}
class _NotificationsScreenContentState extends State<_NotificationsScreenContent> {
NotificationFilter _filter = NotificationFilter.all;
void _setFilter(NotificationFilter filter) {
setState(() {
_filter = filter;
});
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<NotificationsBloc>()..add(NotificationsFetchRequested()),
child: NotificationsView(
filter: _filter,
onFilterChanged: _setFilter,
),
);
}
}
class NotificationsView extends StatelessWidget {
final NotificationFilter filter;
final ValueChanged<NotificationFilter> onFilterChanged;
const NotificationsView({
super.key,
this.filter = NotificationFilter.all,
required this.onFilterChanged,
});
@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<NotificationsBloc, NotificationsState>(
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<NotificationsBloc>().add(NotificationsFetchRequested());
},
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;
// || n.category == NotificationCategory.adminInfo
// || n.category == NotificationCategory.companyInfo;
default:
return true;
}
}).toList();
if (filtered.isEmpty) {
return const _EmptyState();
}
return RefreshIndicator(
onRefresh: () async {
context.read<NotificationsBloc>().add(NotificationsFetchRequested());
},
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: filtered.length,
itemBuilder: (context, index) {
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 now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays == 0) {
return 'Сегодня, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
} else if (difference.inDays == 1) {
return 'Вчера, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
} else {
return '${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.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 'Самокат';
}
}
}