349 lines
12 KiB
Dart
349 lines
12 KiB
Dart
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<void> 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<void> _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<void> 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<void> 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<void> 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<void> scheduleAllUpcomingNotifications(List<JadwalModel> 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<void> cancelAllNotifications() async {
|
|
await flutterLocalNotificationsPlugin.cancelAll();
|
|
print('Semua notifikasi dibatalkan');
|
|
}
|
|
|
|
// Batalkan notifikasi berdasarkan ID
|
|
Future<void> cancelNotification(int id) async {
|
|
await flutterLocalNotificationsPlugin.cancel(id);
|
|
print('Notifikasi dengan ID $id dibatalkan');
|
|
}
|
|
} |