Files
2026-05-25 05:10:43 -04:00

175 lines
5.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../providers/dashboard_providers.dart';
import '../../../widgets/error_view.dart';
class NotificationsScreen extends ConsumerWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(notificationsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Notifications'),
actions: [
IconButton(
icon: const Icon(Icons.done_all),
tooltip: 'Mark all read',
onPressed: () async {
await ref.read(notificationServiceProvider).markAllRead();
ref.invalidate(notificationsProvider);
ref.invalidate(unreadCountProvider);
},
),
],
),
body: async.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => ErrorView(
message: 'Failed to load notifications',
onRetry: () => ref.invalidate(notificationsProvider),
),
data: (notifications) {
if (notifications.isEmpty) {
return const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.notifications_none,
size: 64, color: Color(0xFFD1D5DB)),
SizedBox(height: 16),
Text('No notifications',
style: TextStyle(color: Color(0xFF9CA3AF))),
],
),
);
}
return RefreshIndicator(
onRefresh: () async => ref.invalidate(notificationsProvider),
child: ListView.separated(
itemCount: notifications.length,
separatorBuilder: (_, _) =>
const Divider(height: 1, indent: 72),
itemBuilder: (_, i) {
final n = notifications[i];
return _NotificationTile(
notification: n,
onRead: () async {
if (!n.isRead) {
await ref
.read(notificationServiceProvider)
.markRead(n.id);
ref.invalidate(notificationsProvider);
ref.invalidate(unreadCountProvider);
}
},
);
},
),
);
},
),
);
}
}
class _NotificationTile extends StatelessWidget {
final dynamic notification;
final VoidCallback onRead;
const _NotificationTile(
{required this.notification, required this.onRead});
IconData _icon(String type) {
switch (type) {
case 'RESERVATION_CREATED':
case 'RESERVATION_CONFIRMED':
return Icons.event_available;
case 'RESERVATION_CANCELLED':
return Icons.event_busy;
case 'PAYMENT_RECEIVED':
return Icons.payments_outlined;
case 'LICENSE_UPLOADED':
return Icons.badge_outlined;
default:
return Icons.notifications_outlined;
}
}
@override
Widget build(BuildContext context) {
final fmt = DateFormat('MMM d, h:mm a');
final isRead = notification.isRead as bool;
return InkWell(
onTap: onRead,
child: Container(
color: isRead ? null : const Color(0xFFF0F7FF),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isRead
? const Color(0xFFF3F4F6)
: const Color(0xFFE1EFFE),
shape: BoxShape.circle,
),
child: Icon(
_icon(notification.type as String),
size: 20,
color: isRead
? const Color(0xFF6B7280)
: const Color(0xFF1A56DB),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
notification.title as String,
style: TextStyle(
fontWeight:
isRead ? FontWeight.normal : FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(height: 2),
Text(
notification.body as String,
style: const TextStyle(
fontSize: 13, color: Color(0xFF6B7280)),
),
const SizedBox(height: 4),
Text(
fmt.format(notification.createdAt as DateTime),
style: const TextStyle(
fontSize: 11, color: Color(0xFF9CA3AF)),
),
],
),
),
if (!isRead)
Container(
width: 8,
height: 8,
margin: const EdgeInsets.only(top: 6),
decoration: const BoxDecoration(
color: Color(0xFF1A56DB),
shape: BoxShape.circle,
),
),
],
),
),
);
}
}