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);
 | |
| }
 |