import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; import '../local/hive_boxes.dart'; import '../local/models/app_settings.dart'; import '../local/models/daily_worship_log.dart'; import 'notification_inbox_service.dart'; import 'notification_runtime_service.dart'; import 'notification_service.dart'; /// Creates in-app inbox events from runtime/system conditions. class NotificationEventProducerService { NotificationEventProducerService._(); static final NotificationEventProducerService instance = NotificationEventProducerService._(); final NotificationInboxService _inbox = NotificationInboxService.instance; final NotificationRuntimeService _runtime = NotificationRuntimeService.instance; Future emitPermissionWarningsIfNeeded({ required AppSettings settings, required NotificationPermissionStatus permissionStatus, }) async { if (!settings.adhanEnabled.values.any((v) => v)) return; final dateKey = _todayKey(); if (!permissionStatus.notificationsAllowed) { final title = 'Izin notifikasi dinonaktifkan'; final body = 'Aktifkan izin notifikasi agar pengingat adzan dan iqamah dapat muncul.'; if (settings.inboxEnabled) { await _inbox.addItem( title: title, body: body, type: 'system', source: 'local', deeplink: '/settings', dedupeKey: 'system.permission.notifications.$dateKey', expiresAt: DateTime.now().add(const Duration(days: 2)), ); } await _pushSystemIfAllowed( settings: settings, dedupeSeed: 'push.system.permission.notifications.$dateKey', title: title, body: body, ); } if (!permissionStatus.exactAlarmAllowed) { final title = 'Izin alarm presisi belum aktif'; final body = 'Aktifkan alarm presisi agar pengingat adzan tepat waktu di perangkat Android.'; if (settings.inboxEnabled) { await _inbox.addItem( title: title, body: body, type: 'system', source: 'local', deeplink: '/settings', dedupeKey: 'system.permission.exact_alarm.$dateKey', expiresAt: DateTime.now().add(const Duration(days: 2)), ); } await _pushSystemIfAllowed( settings: settings, dedupeSeed: 'push.system.permission.exact_alarm.$dateKey', title: title, body: body, ); } } Future emitScheduleFallback({ required AppSettings settings, required String cityId, required bool locationUnavailable, }) async { final dateKey = _todayKey(); final title = locationUnavailable ? 'Lokasi belum tersedia' : 'Jadwal online terganggu'; final body = locationUnavailable ? 'Lokasi perangkat belum aktif. Aplikasi menggunakan lokasi default sementara.' : 'Aplikasi memakai perhitungan lokal sementara. Pastikan internet aktif untuk jadwal paling akurat.'; final scope = locationUnavailable ? 'loc' : 'net'; final dedupe = 'system.schedule.fallback.$cityId.$dateKey.$scope'; if (settings.inboxEnabled) { await _inbox.addItem( title: title, body: body, type: 'system', source: 'local', deeplink: '/imsakiyah', dedupeKey: dedupe, expiresAt: DateTime.now().add(const Duration(days: 1)), meta: { 'cityId': cityId, 'date': dateKey, 'scope': scope, }, ); } await _pushSystemIfAllowed( settings: settings, dedupeSeed: 'push.$dedupe', title: title, body: body, ); } Future emitNotificationSyncFailed({ required AppSettings settings, required String cityId, }) async { final dateKey = _todayKey(); final title = 'Sinkronisasi alarm adzan gagal'; final body = 'Pengingat adzan belum tersinkron. Coba buka aplikasi lagi atau periksa pengaturan notifikasi.'; final dedupe = 'system.notification.sync_failed.$cityId.$dateKey'; if (settings.inboxEnabled) { await _inbox.addItem( title: title, body: body, type: 'system', source: 'local', deeplink: '/settings', dedupeKey: dedupe, expiresAt: DateTime.now().add(const Duration(days: 1)), meta: { 'cityId': cityId, 'date': dateKey, }, ); } await _pushSystemIfAllowed( settings: settings, dedupeSeed: 'push.$dedupe', title: title, body: body, ); } Future emitStreakRiskIfNeeded({ required AppSettings settings, }) async { if (!settings.inboxEnabled || !settings.streakRiskEnabled) return; final now = DateTime.now(); if (now.hour < 18) return; final dateKey = _todayKey(); final worshipBox = Hive.box(HiveBoxes.worshipLogs); final log = worshipBox.get(dateKey); if (log == null) return; final tilawahRisk = log.tilawahLog != null && !log.tilawahLog!.isCompleted; final dzikirRisk = settings.trackDzikir && log.dzikirLog != null && !log.dzikirLog!.petang; if (tilawahRisk) { final title = 'Streak Tilawah berisiko terputus'; const body = 'Selesaikan target tilawah hari ini untuk menjaga konsistensi.'; final dedupe = 'streak.tilawah.$dateKey'; await _inbox.addItem( title: title, body: body, type: 'streak_risk', source: 'local', deeplink: '/quran', dedupeKey: dedupe, expiresAt: DateTime(now.year, now.month, now.day, 23, 59), ); await _pushHabitIfAllowed( settings: settings, dedupeSeed: 'push.$dedupe', title: title, body: body, ); } if (dzikirRisk) { final title = 'Dzikir petang belum tercatat'; const body = 'Lengkapi dzikir petang untuk menjaga streak amalan harian.'; final dedupe = 'streak.dzikir.$dateKey'; await _inbox.addItem( title: title, body: body, type: 'streak_risk', source: 'local', deeplink: '/tools/dzikir', dedupeKey: dedupe, expiresAt: DateTime(now.year, now.month, now.day, 23, 59), ); await _pushHabitIfAllowed( settings: settings, dedupeSeed: 'push.$dedupe', title: title, body: body, ); } } Future emitWeeklySummaryIfNeeded({ required AppSettings settings, }) async { if (!settings.inboxEnabled || !settings.weeklySummaryEnabled) return; final now = DateTime.now(); if (now.weekday != DateTime.monday || now.hour < 6) return; final monday = now.subtract(Duration(days: now.weekday - 1)); final weekKey = DateFormat('yyyy-MM-dd').format(monday); if (_runtime.lastWeeklySummaryWeekKey() == weekKey) return; final worshipBox = Hive.box(HiveBoxes.worshipLogs); var completionDays = 0; var totalPoints = 0; for (int i = 1; i <= 7; i++) { final date = now.subtract(Duration(days: i)); final key = DateFormat('yyyy-MM-dd').format(date); final log = worshipBox.get(key); if (log == null) continue; if (log.completionPercent >= 70) completionDays++; totalPoints += log.totalPoints; } await _inbox.addItem( title: 'Ringkasan Ibadah Mingguan', body: '7 hari terakhir: $completionDays hari konsisten, total $totalPoints poin. Lihat detail laporan.', type: 'summary', source: 'local', deeplink: '/laporan', dedupeKey: 'summary.weekly.$weekKey', expiresAt: now.add(const Duration(days: 7)), ); await _runtime.setLastWeeklySummaryWeekKey(weekKey); } String _todayKey() => DateFormat('yyyy-MM-dd').format(DateTime.now()); Future _pushSystemIfAllowed({ required AppSettings settings, required String dedupeSeed, required String title, required String body, }) async { await _pushNonPrayer( settings: settings, dedupeSeed: dedupeSeed, title: title, body: body, payloadType: 'system', silent: true, ); } Future _pushHabitIfAllowed({ required AppSettings settings, required String dedupeSeed, required String title, required String body, }) async { await _pushNonPrayer( settings: settings, dedupeSeed: dedupeSeed, title: title, body: body, payloadType: 'streak_risk', silent: false, ); } Future _pushNonPrayer({ required AppSettings settings, required String dedupeSeed, required String title, required String body, required String payloadType, required bool silent, }) async { if (!settings.alertsEnabled) return; final notif = NotificationService.instance; await notif.showNonPrayerAlert( settings: settings, id: notif.nonPrayerNotificationId(dedupeSeed), title: title, body: body, payloadType: payloadType, silent: silent, ); } }