142 lines
4.9 KiB
Dart
142 lines
4.9 KiB
Dart
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
import 'package:logger/logger.dart';
|
|
|
|
import 'package:marco/helpers/services/local_notification_service.dart';
|
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
import 'package:marco/helpers/services/auth_service.dart';
|
|
import 'package:marco/helpers/services/notification_action_handler.dart';
|
|
|
|
/// Firebase Notification Service
|
|
class FirebaseNotificationService {
|
|
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
|
final Logger _logger = Logger();
|
|
|
|
/// Initialize FCM (Firebase.initializeApp() should be called once globally)
|
|
Future<void> initialize() async {
|
|
_logger.i('✅ FirebaseMessaging initializing...');
|
|
|
|
await _requestNotificationPermission();
|
|
_registerMessageListeners();
|
|
_registerTokenRefreshListener();
|
|
|
|
// Fetch token on app start (and register with server if JWT available)
|
|
await getFcmToken(registerOnServer: true);
|
|
}
|
|
|
|
/// Request notification permission
|
|
Future<void> _requestNotificationPermission() async {
|
|
final settings = await _firebaseMessaging.requestPermission();
|
|
_logger.i('📩 Permission Status: ${settings.authorizationStatus}');
|
|
}
|
|
|
|
/// Foreground, background, and tap listeners
|
|
void _registerMessageListeners() {
|
|
FirebaseMessaging.onMessage.listen((message) {
|
|
_logger.i('📩 Foreground Notification');
|
|
_logNotificationDetails(message);
|
|
|
|
// Handle custom actions
|
|
NotificationActionHandler.handle(message.data);
|
|
|
|
// Show local notification
|
|
if (message.notification != null) {
|
|
LocalNotificationService.showNotification(
|
|
title: message.notification!.title ?? "No title",
|
|
body: message.notification!.body ?? "No body",
|
|
);
|
|
}
|
|
});
|
|
|
|
FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);
|
|
|
|
// Background messages
|
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
|
}
|
|
|
|
/// Token refresh handler
|
|
void _registerTokenRefreshListener() {
|
|
_firebaseMessaging.onTokenRefresh.listen((newToken) async {
|
|
_logger.i('🔄 Token refreshed: $newToken');
|
|
if (newToken.isEmpty) return;
|
|
|
|
await LocalStorage.setFcmToken(newToken);
|
|
|
|
final jwt = await LocalStorage.getJwtToken();
|
|
if (jwt?.isNotEmpty ?? false) {
|
|
final success = await AuthService.registerDeviceToken(newToken);
|
|
_logger.i(success
|
|
? '✅ Device token updated on server after refresh.'
|
|
: '⚠️ Failed to update device token on server.');
|
|
} else {
|
|
_logger.w('⚠️ JWT not available — will retry after login.');
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Get current token (optionally sync to server if logged in)
|
|
Future<String?> getFcmToken({bool registerOnServer = false}) async {
|
|
try {
|
|
final token = await _firebaseMessaging.getToken();
|
|
_logger.i('🔑 FCM token: $token');
|
|
|
|
if (token?.isNotEmpty ?? false) {
|
|
await LocalStorage.setFcmToken(token!);
|
|
|
|
if (registerOnServer) {
|
|
final jwt = await LocalStorage.getJwtToken();
|
|
if (jwt?.isNotEmpty ?? false) {
|
|
final success = await AuthService.registerDeviceToken(token);
|
|
_logger.i(success
|
|
? '✅ Device token registered on server.'
|
|
: '⚠️ Failed to register device token on server.');
|
|
} else {
|
|
_logger.w('⚠️ JWT not available — skipping server registration.');
|
|
}
|
|
}
|
|
}
|
|
return token;
|
|
} catch (e, s) {
|
|
_logger.e('❌ Failed to get FCM token', error: e, stackTrace: s);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Re-register token with server (useful after login)
|
|
Future<void> registerTokenAfterLogin() async {
|
|
final token = await LocalStorage.getFcmToken();
|
|
if (token?.isNotEmpty ?? false) {
|
|
final success = await AuthService.registerDeviceToken(token!);
|
|
_logger.i(success
|
|
? "✅ FCM token registered after login."
|
|
: "⚠️ Failed to register FCM token after login.");
|
|
}
|
|
}
|
|
|
|
/// Handle tap on notification
|
|
void _handleNotificationTap(RemoteMessage message) {
|
|
_logger.i('📌 Notification tapped: ${message.data}');
|
|
NotificationActionHandler.handle(message.data);
|
|
}
|
|
|
|
/// Log notification details
|
|
void _logNotificationDetails(RemoteMessage message) {
|
|
_logger
|
|
..i('🆔 ID: ${message.messageId}')
|
|
..i('📜 Title: ${message.notification?.title}')
|
|
..i('📜 Body: ${message.notification?.body}')
|
|
..i('📦 Data: ${message.data}');
|
|
}
|
|
}
|
|
|
|
/// 🔹 Background handler (required by Firebase)
|
|
/// Must be a top-level function and annotated for AOT
|
|
@pragma('vm:entry-point')
|
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
final logger = Logger();
|
|
logger
|
|
..i('⚡ Handling background notification...')
|
|
..i('📦 Data: ${message.data}');
|
|
|
|
NotificationActionHandler.handle(message.data);
|
|
}
|