import 'package:flutter/foundation.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import '../config/firebase_web_config.dart'; import '../routes/app_routes.dart'; import '../services/secure_chat_service.dart'; import '../screens/secure_chat/secure_chat_screen.dart'; import 'api_service.dart'; class NotificationService { NotificationService._internal(); static final NotificationService instance = NotificationService._internal(); final FirebaseMessaging _messaging = FirebaseMessaging.instance; bool _initialized = false; String? _currentToken; String? _cachedVapidKey; Future init() async { if (_initialized) return; _initialized = true; try { debugPrint('[FCM] Initializing for platform: ${_resolveDeviceType()}'); final settings = await _messaging.requestPermission( alert: true, badge: true, sound: true, provisional: false, ); debugPrint('[FCM] Permission status: ${settings.authorizationStatus}'); if (settings.authorizationStatus == AuthorizationStatus.denied) { debugPrint('[FCM] Push notification permission denied'); return; } final vapidKey = kIsWeb ? await _resolveVapidKey() : null; if (kIsWeb && (vapidKey == null || vapidKey.isEmpty)) { debugPrint('[FCM] Web push is missing FIREBASE_WEB_VAPID_KEY'); } debugPrint('[FCM] Requesting token...'); final token = await _messaging.getToken( vapidKey: vapidKey, ); if (token != null) { _currentToken = token; debugPrint('[FCM] Token registered (${_resolveDeviceType()}): $token'); await _upsertToken(token); } else { debugPrint('[FCM] WARNING: Token is null after getToken()'); } _messaging.onTokenRefresh.listen((newToken) { debugPrint('[FCM] Token refreshed: $newToken'); _currentToken = newToken; _upsertToken(newToken); }); FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageOpen); FirebaseMessaging.onMessage.listen((message) { debugPrint('[FCM] Foreground message received: ${message.messageId}'); debugPrint('[FCM] Message data: ${message.data}'); debugPrint('[FCM] Notification: ${message.notification?.title}'); }); final initialMessage = await _messaging.getInitialMessage(); if (initialMessage != null) { debugPrint('[FCM] App opened from notification: ${initialMessage.messageId}'); await _handleMessageOpen(initialMessage); } debugPrint('[FCM] Initialization complete'); } catch (e, stackTrace) { debugPrint('[FCM] Failed to initialize notifications: $e'); debugPrint('[FCM] Stack trace: $stackTrace'); } } /// Remove the current device's FCM token (call on logout) Future removeToken() async { if (_currentToken == null) return; try { debugPrint('[FCM] Revoking token...'); await ApiService.instance.callGoApi( '/notifications/device', method: 'DELETE', body: { 'fcm_token': _currentToken, }, ); debugPrint('[FCM] Token revoked successfully'); } catch (e) { debugPrint('[FCM] Failed to revoke token: $e'); } finally { _currentToken = null; } } Future _upsertToken(String token) async { try { debugPrint('[FCM] Syncing token with backend...'); await ApiService.instance.callGoApi( '/notifications/device', method: 'POST', body: { 'fcm_token': token, 'platform': _resolveDeviceType() } ); debugPrint('[FCM] Token synced with Go Backend successfully'); } catch (e, stackTrace) { debugPrint('[FCM] Sync failed: $e'); debugPrint('[FCM] Stack trace: $stackTrace'); } } String _resolveDeviceType() { if (kIsWeb) return 'web'; switch (defaultTargetPlatform) { case TargetPlatform.iOS: return 'ios'; case TargetPlatform.android: return 'android'; case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: return 'desktop'; } } Future _resolveVapidKey() async { if (_cachedVapidKey != null && _cachedVapidKey!.isNotEmpty) { return _cachedVapidKey; } final envKey = FirebaseWebConfig.vapidKey; if (envKey != null && envKey.isNotEmpty) { _cachedVapidKey = envKey; return envKey; } return null; } Future _handleMessageOpen(RemoteMessage message) async { final data = message.data; if (data['type'] != 'chat' && data['type'] != 'new_message') return; final conversationId = data['conversation_id']; if (conversationId == null) return; await _openConversation(conversationId.toString()); } Future _openConversation(String conversationId) async { final conversation = await SecureChatService.instance.getConversationById(conversationId); if (conversation == null) return; final navigator = AppRoutes.rootNavigatorKey.currentState; if (navigator == null) return; navigator.push( MaterialPageRoute( builder: (_) => SecureChatScreen(conversation: conversation), ), ); } }