2026-05-07 18:03:41 +07:00

197 lines
6.1 KiB
Dart

import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
/// Background message handler — harus top-level function
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
log('[FCM] Background message: ${message.messageId}');
}
@lazySingleton
class FcmService {
FcmService(this._messaging, this._localNotifications);
final FirebaseMessaging _messaging;
final FlutterLocalNotificationsPlugin _localNotifications;
/// Inisialisasi FCM: minta permission, set handler, ambil token
Future<void> initialize() async {
// Setup local notifications
await _setupLocalNotifications();
// Register background handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Minta permission notifikasi (iOS & Android 13+)
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
log('[FCM] Permission status: ${settings.authorizationStatus}');
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
settings.authorizationStatus == AuthorizationStatus.provisional) {
await _setupTokenHandling();
_setupForegroundHandler();
_setupOpenedAppHandler();
}
}
Future<void> _setupLocalNotifications() async {
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const iosSettings = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
const initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await _localNotifications.initialize(
initSettings,
onDidReceiveNotificationResponse: _onNotificationTapped,
);
// Android notification channel
const channel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.high,
playSound: true,
);
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
void _onNotificationTapped(NotificationResponse response) {
log('[FCM] Notification tapped: ${response.payload}');
// TODO: handle navigation berdasarkan payload
}
Future<void> _setupTokenHandling() async {
final token = await getToken();
log('========================================');
log('[FCM] TOKEN: $token');
log('========================================');
// TODO: kirim token ke server jika diperlukan
_messaging.onTokenRefresh.listen((newToken) {
log('========================================');
log('[FCM] TOKEN REFRESHED: $newToken');
log('========================================');
// TODO: kirim token baru ke server jika diperlukan
});
}
void _setupForegroundHandler() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
log('[FCM] Foreground message: ${message.messageId}');
log('[FCM] Title: ${message.notification?.title}');
log('[FCM] Body: ${message.notification?.body}');
log('[FCM] Data: ${message.data}');
// Tampilkan notifikasi lokal saat app di foreground
final notification = message.notification;
if (notification != null) {
_showLocalNotification(
id: message.hashCode,
title: notification.title ?? '',
body: notification.body ?? '',
payload: message.data.toString(),
);
}
});
}
void _setupOpenedAppHandler() {
// App dibuka dari notifikasi saat terminated
_messaging.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
log('[FCM] App opened from terminated via: ${message.messageId}');
_handleMessageNavigation(message);
}
});
// App dibuka dari notifikasi saat background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
log('[FCM] App opened from background via: ${message.messageId}');
_handleMessageNavigation(message);
});
}
void _handleMessageNavigation(RemoteMessage message) {
// TODO: tambahkan logika navigasi berdasarkan message.data
log('[FCM] Handle navigation for: ${message.data}');
}
Future<void> _showLocalNotification({
required int id,
required String title,
required String body,
String? payload,
}) async {
const androidDetails = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.max,
priority: Priority.max,
showWhen: true,
playSound: true,
enableVibration: true,
enableLights: true,
fullScreenIntent: true,
icon: 'ic_notification',
);
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _localNotifications.show(id, title, body, details, payload: payload);
log('[FCM] Local notification shown: $title');
}
/// Ambil FCM token perangkat
Future<String?> getToken() async {
try {
return await _messaging.getToken();
} catch (e) {
log('[FCM] Failed to get token: $e');
return null;
}
}
/// Subscribe ke topic tertentu
Future<void> subscribeToTopic(String topic) async {
await _messaging.subscribeToTopic(topic);
log('[FCM] Subscribed to topic: $topic');
}
/// Unsubscribe dari topic tertentu
Future<void> unsubscribeFromTopic(String topic) async {
await _messaging.unsubscribeFromTopic(topic);
log('[FCM] Unsubscribed from topic: $topic');
}
}