import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz_init; import 'package:posyandu/models/jadwal_model.dart'; import 'dart:io'; import 'package:intl/intl.dart'; class NotificationService { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); // Singleton pattern static final NotificationService _instance = NotificationService._internal(); factory NotificationService() { return _instance; } NotificationService._internal(); Future init() async { // Inisialisasi timezone untuk notifikasi terjadwal tz_init.initializeTimeZones(); // Konfigurasi untuk Android yang ditingkatkan agar tampil di latar belakang final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); // Konfigurasi untuk iOS final DarwinInitializationSettings initializationSettingsDarwin = DarwinInitializationSettings( requestSoundPermission: true, requestBadgePermission: true, requestAlertPermission: true, defaultPresentSound: true, defaultPresentAlert: true, ); final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsDarwin, ); await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse details) { print('onDidReceiveNotificationResponse: ${details.payload}'); // Disini bisa ditambahkan kode untuk navigasi ke halaman tertentu }, ); // Minta izin notifikasi eksplisit (terutama untuk Android 13+) if (Platform.isAndroid) { try { await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.requestNotificationsPermission(); // Setup notification channels untuk Android 8.0+ await _setupNotificationChannels(); } catch (e) { print('Error saat meminta izin notifikasi: $e'); } } else if (Platform.isIOS) { await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions( alert: true, badge: true, sound: true, ); } print('NotificationService initialized'); } // Setup notification channels dengan prioritas tinggi Future _setupNotificationChannels() async { // Channel untuk notifikasi jadwal dengan prioritas tinggi final AndroidNotificationChannelGroup channelGroup = AndroidNotificationChannelGroup( 'posyandu_group', 'Notifikasi Posyandu', description: 'Semua notifikasi Posyandu', ); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannelGroup(channelGroup); // Channel untuk jadwal terdekat (prioritas tertinggi) final jadwalChannel = AndroidNotificationChannel( 'jadwal_reminder_urgent_channel', 'Pengingat Jadwal Penting', description: 'Pengingat untuk jadwal penting yang akan datang', importance: Importance.high, enableVibration: true, playSound: true, enableLights: true, ledColor: Colors.teal, groupId: 'posyandu_group', showBadge: true, sound: RawResourceAndroidNotificationSound('notification'), ); // Channel untuk notifikasi standar final standardChannel = AndroidNotificationChannel( 'jadwal_posyandu_channel', 'Jadwal Posyandu', description: 'Notifikasi untuk jadwal Posyandu', importance: Importance.high, enableVibration: true, playSound: true, groupId: 'posyandu_group', ); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(jadwalChannel); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(standardChannel); } // Notifikasi langsung (instant) dengan prioritas tinggi Future showNotification({ required int id, required String title, required String body, String? payload, }) async { const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'jadwal_posyandu_channel', 'Jadwal Posyandu', channelDescription: 'Notifikasi untuk jadwal Posyandu', importance: Importance.high, priority: Priority.high, fullScreenIntent: true, // Tampilkan sebagai full screen intent visibility: NotificationVisibility.public, // Tampilkan di lock screen category: AndroidNotificationCategory.reminder, // Kategori pengingat color: Colors.teal, ledColor: Colors.teal, ledOnMs: 1000, ledOffMs: 500, enableLights: true, playSound: true, enableVibration: true, ticker: 'Notifikasi Posyandu', timeoutAfter: 300000, // 5 menit timeout autoCancel: true, ongoing: false, ); const NotificationDetails platformDetails = NotificationDetails( android: androidDetails, iOS: DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ); await flutterLocalNotificationsPlugin.show( id, title, body, platformDetails, payload: payload, ); print('Notification shown: ID = $id, Title = $title'); } // Notifikasi terjadwal berdasarkan jadwal Future scheduleNotification({ required JadwalModel jadwal, int reminderHoursBefore = 24, }) async { final int notificationId = jadwal.id?.hashCode ?? DateTime.now().millisecondsSinceEpoch.remainder(100000); // Tentukan waktu notifikasi (1 hari sebelum jadwal) final scheduleDate = jadwal.tanggal.subtract(Duration(hours: reminderHoursBefore)); // Jika jadwalnya sudah lewat, tidak perlu menjadwalkan notifikasi if (scheduleDate.isBefore(DateTime.now())) { print('Jadwal sudah lewat, tidak perlu notifikasi: ${jadwal.nama}'); return; } final formattedDate = DateFormat('dd MMM yyyy').format(jadwal.tanggal); final formattedTime = jadwal.waktu ?? '-'; String jenisIcon = ''; // Tentukan icon berdasarkan jenis jadwal switch (jadwal.jenis.toLowerCase()) { case 'imunisasi': jenisIcon = '💉'; break; case 'vitamin': jenisIcon = '💊'; break; case 'pemeriksaan rutin': jenisIcon = '🏥'; break; default: jenisIcon = '📅'; } final bool isUrgent = reminderHoursBefore <= 2; // Jika 2 jam atau kurang sebelum jadwal, anggap urgent final AndroidNotificationDetails androidDetails = AndroidNotificationDetails( isUrgent ? 'jadwal_reminder_urgent_channel' : 'jadwal_reminder_channel', isUrgent ? 'Pengingat Jadwal Penting' : 'Pengingat Jadwal', channelDescription: isUrgent ? 'Pengingat untuk jadwal penting yang akan datang' : 'Pengingat untuk jadwal yang akan datang', importance: Importance.high, priority: Priority.high, fullScreenIntent: isUrgent, // Full screen intent untuk urgent notifications visibility: NotificationVisibility.public, category: AndroidNotificationCategory.reminder, color: Colors.teal, ledColor: Colors.teal, ledOnMs: 1000, ledOffMs: 500, enableLights: true, playSound: true, enableVibration: true, ticker: 'Notifikasi Posyandu', when: jadwal.tanggal.millisecondsSinceEpoch, // Tampilkan waktu jadwal ); final NotificationDetails platformDetails = NotificationDetails( android: androidDetails, iOS: DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ); final tz.TZDateTime notificationTime = tz.TZDateTime.from(scheduleDate, tz.local); await flutterLocalNotificationsPlugin.zonedSchedule( notificationId, 'Pengingat Jadwal $jenisIcon', '${jadwal.nama} pada $formattedDate jam $formattedTime', notificationTime, platformDetails, payload: 'jadwal_id=${jadwal.id}', androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, matchDateTimeComponents: DateTimeComponents.time, ); print('Jadwal notifikasi diatur untuk: ${jadwal.nama} pada $formattedDate ${jadwal.waktu}, ID: $notificationId'); } // Notifikasi saat jadwal baru tersedia dengan prioritas tinggi Future showNewJadwalNotification(JadwalModel jadwal) async { final int notificationId = jadwal.id?.hashCode ?? DateTime.now().millisecondsSinceEpoch.remainder(100000); final formattedDate = DateFormat('dd MMM yyyy').format(jadwal.tanggal); String jenisIcon = ''; switch (jadwal.jenis.toLowerCase()) { case 'imunisasi': jenisIcon = '💉'; break; case 'vitamin': jenisIcon = '💊'; break; case 'pemeriksaan rutin': jenisIcon = '🏥'; break; default: jenisIcon = '📅'; } // Gunakan prioritas tinggi untuk notifikasi jadwal baru final AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'jadwal_posyandu_channel', 'Jadwal Posyandu', channelDescription: 'Notifikasi untuk jadwal Posyandu', importance: Importance.high, priority: Priority.high, fullScreenIntent: true, // Tampilkan sebagai full screen intent visibility: NotificationVisibility.public, category: AndroidNotificationCategory.message, color: Colors.teal, ledColor: Colors.teal, enableLights: true, playSound: true, enableVibration: true, ticker: 'Jadwal baru tersedia', ); final NotificationDetails platformDetails = NotificationDetails( android: androidDetails, iOS: DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ); await flutterLocalNotificationsPlugin.show( notificationId, 'Jadwal Baru $jenisIcon', '${jadwal.nama} telah dijadwalkan pada $formattedDate', platformDetails, payload: 'jadwal_id=${jadwal.id}', ); print('New schedule notification shown: ID = $notificationId, Title = ${jadwal.nama}'); } // Atur notifikasi untuk semua jadwal yang akan datang Future scheduleAllUpcomingNotifications(List jadwalList) async { print('Menyiapkan notifikasi untuk ${jadwalList.length} jadwal yang akan datang'); // Bersihkan semua notifikasi yang sudah ada await flutterLocalNotificationsPlugin.cancelAll(); // Jadwalkan notifikasi baru untuk setiap jadwal yang akan datang for (final jadwal in jadwalList) { // Jadwalkan notifikasi 1 hari sebelum jadwal await scheduleNotification(jadwal: jadwal, reminderHoursBefore: 24); // Jadwalkan juga notifikasi 2 jam sebelum jadwal untuk pengingat terakhir await scheduleNotification(jadwal: jadwal, reminderHoursBefore: 2); } } // Batalkan semua notifikasi Future cancelAllNotifications() async { await flutterLocalNotificationsPlugin.cancelAll(); print('Semua notifikasi dibatalkan'); } // Batalkan notifikasi berdasarkan ID Future cancelNotification(int id) async { await flutterLocalNotificationsPlugin.cancel(id); print('Notifikasi dengan ID $id dibatalkan'); } }