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 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 _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 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 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 _firebaseMessagingBackgroundHandler(RemoteMessage message) async { final logger = Logger(); logger ..i('⚡ Handling background notification...') ..i('📦 Data: ${message.data}'); NotificationActionHandler.handle(message.data); }