218 lines
7.3 KiB
Dart
218 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../providers/notification_provider.dart';
|
|
import '../modals/view_activity_modal.dart';
|
|
import '../../screens/dashboard/widgets/notification_dropdown.dart';
|
|
|
|
Color getNotificationColorHelper(String title) {
|
|
final t = title.toLowerCase();
|
|
if (t.contains('student')) return Colors.blue;
|
|
if (t.contains('document')) return const Color(0xFF1C8248);
|
|
if (t.contains('password')) return Colors.orange;
|
|
return Colors.grey;
|
|
}
|
|
|
|
IconData getNotificationIconHelper(String title) {
|
|
final t = title.toLowerCase();
|
|
if (t.contains('student')) return Icons.person_add;
|
|
if (t.contains('document')) return Icons.upload_file;
|
|
if (t.contains('password')) return Icons.lock_reset;
|
|
return Icons.notifications;
|
|
}
|
|
|
|
void showNotificationDropdownMenu(BuildContext context, WidgetRef ref) {
|
|
final notificationsAsync = ref.read(notificationsProvider);
|
|
final list = notificationsAsync.value ?? [];
|
|
|
|
final RenderBox? button = context.findRenderObject() as RenderBox?;
|
|
final RenderBox? overlay =
|
|
Navigator.of(context).overlay?.context.findRenderObject() as RenderBox?;
|
|
if (button == null || overlay == null) return;
|
|
|
|
final position = RelativeRect.fromRect(
|
|
Rect.fromPoints(
|
|
button.localToGlobal(
|
|
Offset(0, button.size.height + 4),
|
|
ancestor: overlay,
|
|
),
|
|
button.localToGlobal(
|
|
button.size.bottomRight(const Offset(0, 4)),
|
|
ancestor: overlay,
|
|
),
|
|
),
|
|
Offset.zero & overlay.size,
|
|
);
|
|
|
|
showMenu(
|
|
context: context,
|
|
position: position,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 8,
|
|
items: [
|
|
// ── Header row ────────────────────────────────────────────────
|
|
PopupMenuItem(
|
|
enabled: false,
|
|
child: Container(
|
|
width: 300,
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(color: Theme.of(context).dividerColor),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Notifications',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
),
|
|
),
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (list.isNotEmpty) ...[
|
|
InkWell(
|
|
mouseCursor: SystemMouseCursors.click,
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
ref
|
|
.read(notificationsProvider.notifier)
|
|
.markAllAsRead();
|
|
},
|
|
child: const Text(
|
|
'Mark all read',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Color(0xFF1C8248),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
InkWell(
|
|
mouseCursor: SystemMouseCursors.click,
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
ref
|
|
.read(notificationsProvider.notifier)
|
|
.clearNotifications();
|
|
},
|
|
child: const Text(
|
|
'Clear',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.redAccent,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
// ── Empty state ───────────────────────────────────────────────
|
|
if (list.isEmpty)
|
|
PopupMenuItem(
|
|
enabled: false,
|
|
child: SizedBox(
|
|
width: 300,
|
|
height: 120,
|
|
child: const Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.notifications_off_rounded,
|
|
size: 48,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(height: 12),
|
|
Text(
|
|
'No new notifications',
|
|
style: TextStyle(color: Colors.grey, fontSize: 13),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)
|
|
// ── Notification items ────────────────────────────────────────
|
|
else
|
|
PopupMenuItem(
|
|
enabled: false,
|
|
padding: EdgeInsets.zero,
|
|
child: NotificationDropdownWidget(
|
|
notifications: list,
|
|
onViewActivity: (ctx, title, desc, date, icon, color) {
|
|
ViewActivityModal.show(
|
|
context: ctx,
|
|
title: title,
|
|
description: desc,
|
|
date: date,
|
|
icon: icon,
|
|
actionColor: color,
|
|
);
|
|
},
|
|
getIcon: getNotificationIconHelper,
|
|
getColor: getNotificationColorHelper,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
class NotificationIconButton extends ConsumerWidget {
|
|
final double iconSize;
|
|
|
|
const NotificationIconButton({super.key, this.iconSize = 24});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final notificationsAsync = ref.watch(notificationsProvider);
|
|
final unreadCount =
|
|
notificationsAsync.value?.where((n) => !n.isRead).length ?? 0;
|
|
|
|
return Builder(
|
|
builder: (ctx) => Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(Icons.notifications, size: iconSize),
|
|
tooltip: 'Notifications',
|
|
onPressed: () => showNotificationDropdownMenu(ctx, ref),
|
|
),
|
|
if (unreadCount > 0)
|
|
Positioned(
|
|
right: 6,
|
|
top: 6,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(3),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.redAccent,
|
|
shape: BoxShape.circle,
|
|
),
|
|
constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
|
|
child: Text(
|
|
unreadCount > 99 ? '99+' : unreadCount.toString(),
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|