tasq/lib/services/notification_bridge.dart
Marc Rejohn Castillano 5979a04254 * Push Notification Setup and attempt
* Office Ordering
* Allow editing of Task and Ticket Details after creation
2026-02-24 21:06:46 +08:00

104 lines
2.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import '../models/notification_item.dart';
import '../providers/notifications_provider.dart';
/// Wraps the app and installs both a Supabase realtime listener and the
/// FCM handlers described in the frontend design.
///
/// The navigator key is required so that snackbars and navigation can be
/// triggered from outside the widget tree (e.g. from realtime callbacks).
class NotificationBridge extends ConsumerStatefulWidget {
const NotificationBridge({
required this.navigatorKey,
required this.child,
super.key,
});
final GlobalKey<NavigatorState> navigatorKey;
final Widget child;
@override
ConsumerState<NotificationBridge> createState() => _NotificationBridgeState();
}
class _NotificationBridgeState extends ConsumerState<NotificationBridge> {
// store previous notifications to diff
List<NotificationItem> _prevList = [];
@override
void initState() {
super.initState();
_setupFcmHandlers();
}
@override
void dispose() {
super.dispose();
}
void _showBanner(String type, NotificationItem item) {
final ctx = widget.navigatorKey.currentState?.overlay?.context;
if (ctx == null) return;
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Text('New $type received!'),
action: SnackBarAction(
label: 'View',
onPressed: () => _navigateToNotification(item),
),
),
);
}
void _navigateToNotification(NotificationItem item) {
widget.navigatorKey.currentState?.pushNamed(
'/notification-detail',
arguments: item,
);
}
void _setupFcmHandlers() {
// ignore foreground messages; realtime websocket will surface them
FirebaseMessaging.onMessage.listen((_) {});
// handle taps when the app is backgrounded
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageTap);
// handle a tap that launched the app from a terminated state
FirebaseMessaging.instance.getInitialMessage().then((msg) {
if (msg != null) _handleMessageTap(msg);
});
}
void _handleMessageTap(RemoteMessage message) {
final data = message.data.cast<String, dynamic>();
final item = NotificationItem.fromMap(data);
_navigateToNotification(item);
}
@override
Widget build(BuildContext context) {
// listen inside build; safe with ConsumerState
ref.listen<AsyncValue<List<NotificationItem>>>(notificationsProvider, (
previous,
next,
) {
final prevList = _prevList;
final nextList = next.maybeWhen(
data: (d) => d,
orElse: () => <NotificationItem>[],
);
if (nextList.length > prevList.length) {
final newItem = nextList.last;
_showBanner(newItem.type, newItem);
}
_prevList = nextList;
});
return widget.child;
}
}