183 lines
5.9 KiB
Dart
183 lines
5.9 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:get/get.dart';
|
|
import '../models/conversation_model.dart';
|
|
import '../screens/chat_screen.dart';
|
|
import 'whatsapp_service.dart';
|
|
|
|
@pragma('vm:entry-point')
|
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
// We can't access GetX services easily here since it's a separate isolate,
|
|
// but Firebase automatically shows the notification.
|
|
print("Handling a background message: ${message.messageId}");
|
|
}
|
|
|
|
class FirebaseService extends GetxService {
|
|
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
|
final FlutterLocalNotificationsPlugin _localNotifications =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
Future<FirebaseService> init() async {
|
|
// 1. Request Permission
|
|
await _messaging.requestPermission(
|
|
alert: true,
|
|
badge: true,
|
|
sound: true,
|
|
);
|
|
|
|
// 2. Initialize Local Notifications (for foreground)
|
|
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
const iosInit = DarwinInitializationSettings();
|
|
const initSettings =
|
|
InitializationSettings(android: androidInit, iOS: iosInit);
|
|
|
|
await _localNotifications.initialize(
|
|
initSettings,
|
|
onDidReceiveNotificationResponse: _onNotificationTap,
|
|
);
|
|
|
|
// 3. Background Handler
|
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
|
|
|
// 4. Foreground Message Handler
|
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
|
final chatId = message.data['chatId'];
|
|
final activeChatId = Get.find<WhatsAppService>().activeChatId.value;
|
|
|
|
// Smart Notification: Only show if we are NOT currently in this chat
|
|
if (chatId != null && activeChatId == chatId) {
|
|
print('[FCM] Silent notification, user is currently in chat $chatId');
|
|
return; // Silent
|
|
}
|
|
|
|
_showLocalNotification(message);
|
|
});
|
|
|
|
// 5. Handle tapping on a background notification when app opens
|
|
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
|
_handleNotificationClick(message.data);
|
|
});
|
|
|
|
// 6. Check if app was opened from a terminated state via a notification
|
|
final initialMessage = await _messaging.getInitialMessage();
|
|
if (initialMessage != null) {
|
|
Future.delayed(const Duration(seconds: 1), () {
|
|
_handleNotificationClick(initialMessage.data);
|
|
});
|
|
}
|
|
|
|
// 7. Request and register FCM Token on the server
|
|
_setupFcmTokenRegistration();
|
|
|
|
return this;
|
|
}
|
|
|
|
void _setupFcmTokenRegistration() async {
|
|
try {
|
|
// For iOS, wait until the APNS token is successfully initialized by the OS before calling getToken()
|
|
if (Platform.isIOS) {
|
|
String? apnsToken = await _messaging.getAPNSToken();
|
|
int retries = 0;
|
|
while (apnsToken == null && retries < 15) {
|
|
print('[FCM] APNS token not set yet. Waiting 1 second... (Attempt ${retries + 1}/15)');
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
apnsToken = await _messaging.getAPNSToken();
|
|
retries++;
|
|
}
|
|
if (apnsToken != null) {
|
|
print('[FCM] APNS Token successfully obtained: $apnsToken');
|
|
} else {
|
|
print('[FCM WARNING] Failed to obtain APNS token after 15 attempts. FCM registration might fail.');
|
|
}
|
|
}
|
|
|
|
final token = await _messaging.getToken();
|
|
if (token != null) {
|
|
final displayToken = token.length > 15 ? token.substring(0, 15) : token;
|
|
print('[FCM] Token obtained: $displayToken...');
|
|
_registerTokenOnServer(token);
|
|
}
|
|
} catch (e) {
|
|
print('[FCM ERROR] Could not get token: $e');
|
|
}
|
|
|
|
_messaging.onTokenRefresh.listen((newToken) {
|
|
print('[FCM] Token refreshed');
|
|
_registerTokenOnServer(newToken);
|
|
});
|
|
}
|
|
|
|
void _registerTokenOnServer(String token) {
|
|
final wsSvc = Get.find<WhatsAppService>();
|
|
if (wsSvc.status.value == WsStatus.waReady ||
|
|
wsSvc.status.value == WsStatus.connected) {
|
|
print('[FCM] Sending token to server...');
|
|
wsSvc.sendFcmToken(token);
|
|
} else {
|
|
// Listen to status changes and send once connected
|
|
late StreamSubscription sub;
|
|
sub = wsSvc.status.listen((status) {
|
|
if (status == WsStatus.waReady || status == WsStatus.connected) {
|
|
print('[FCM] WebSocket connected, sending token to server...');
|
|
wsSvc.sendFcmToken(token);
|
|
sub.cancel();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void _showLocalNotification(RemoteMessage message) {
|
|
final notification = message.notification;
|
|
if (notification == null) return;
|
|
|
|
const androidDetails = AndroidNotificationDetails(
|
|
'whatsapp_channel',
|
|
'WhatsApp Messages',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
ticker: 'ticker',
|
|
);
|
|
const iosDetails = DarwinNotificationDetails();
|
|
const details =
|
|
NotificationDetails(android: androidDetails, iOS: iosDetails);
|
|
|
|
_localNotifications.show(
|
|
notification.hashCode,
|
|
notification.title,
|
|
notification.body,
|
|
details,
|
|
payload: jsonEncode(message.data),
|
|
);
|
|
}
|
|
|
|
void _onNotificationTap(NotificationResponse response) {
|
|
if (response.payload != null) {
|
|
final data = jsonDecode(response.payload!);
|
|
_handleNotificationClick(data);
|
|
}
|
|
}
|
|
|
|
void _handleNotificationClick(Map<String, dynamic> data) {
|
|
final chatId = data['chatId'];
|
|
final name = data['name'] ?? 'Chat';
|
|
|
|
if (chatId != null) {
|
|
// Mock a conversation model to navigate to ChatScreen
|
|
final dummyChat = ConversationModel(
|
|
id: chatId,
|
|
name: name,
|
|
isGroup: false,
|
|
unreadCount: 0,
|
|
timestamp: 0,
|
|
pinned: false,
|
|
isMuted: false,
|
|
);
|
|
|
|
Get.to(() => ChatScreen(conversation: dummyChat));
|
|
}
|
|
}
|
|
}
|