sojorn/sojorn_app/lib/services/notification_service.dart

178 lines
5.3 KiB
Dart

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<void> 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<void> 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<void> _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<String?> _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<void> _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<void> _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),
),
);
}
}