Improve notifications, tilawah flow, and dzikir structure

This commit is contained in:
Dwindi Ramadhana
2026-05-20 19:52:15 +07:00
parent c32b56c00e
commit 5195ba19ad
19 changed files with 1056 additions and 318 deletions

View File

@@ -1,5 +1,14 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import '../local/models/app_settings.dart';
import '../local/hive_boxes.dart';
import 'notification_inbox_service.dart';
import 'notification_service.dart';
import 'package:hive_flutter/hive_flutter.dart';
/// Phase-4 bridge for future FCM/APNs wiring.
///
@@ -10,9 +19,30 @@ class RemotePushService {
static final RemotePushService instance = RemotePushService._();
final NotificationInboxService _inbox = NotificationInboxService.instance;
bool _initialized = false;
Future<void> init() async {
// Reserved for SDK wiring (FCM/APNs token registration, topic subscription).
if (_initialized) return;
try {
await Firebase.initializeApp();
final messaging = FirebaseMessaging.instance;
await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen((message) {
unawaited(_handleMessage(message, isForeground: true));
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
unawaited(_handleMessage(message, isForeground: false));
});
_initialized = true;
} catch (_) {
// Firebase may not be configured in all build variants yet.
}
}
Future<void> ingestPayload(
@@ -44,4 +74,60 @@ class RemotePushService {
meta: <String, dynamic>{'remoteId': id},
);
}
Future<void> _handleMessage(
RemoteMessage message, {
required bool isForeground,
}) async {
final payload = <String, dynamic>{
'id': message.messageId ?? message.data['id'] ?? '',
'title': message.notification?.title ?? message.data['title'] ?? '',
'body': message.notification?.body ?? message.data['body'] ?? '',
'type': message.data['type'] ?? 'content',
'deeplink': message.data['deeplink'] ?? '',
'expiresAt': message.data['expiresAt'] ?? '',
'isPinned': message.data['isPinned'] == 'true',
};
final settings = Hive.box<AppSettings>(HiveBoxes.settings).get('default') ??
AppSettings();
await ingestPayload(payload, settings: settings);
if (isForeground &&
settings.alertsEnabled &&
(payload['title'] as String).trim().isNotEmpty &&
(payload['body'] as String).trim().isNotEmpty) {
await NotificationService.instance.showNonPrayerAlert(
settings: settings,
id: NotificationService.instance
.nonPrayerNotificationId('remote.${payload['id']}'),
title: (payload['title'] as String).trim(),
body: (payload['body'] as String).trim(),
payloadType: 'remote',
bypassDailyCap: true,
);
}
}
}
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await initHive();
try {
await Firebase.initializeApp();
} catch (_) {}
final payload = <String, dynamic>{
'id': message.messageId ?? message.data['id'] ?? '',
'title': message.notification?.title ?? message.data['title'] ?? '',
'body': message.notification?.body ?? message.data['body'] ?? '',
'type': message.data['type'] ?? 'content',
'deeplink': message.data['deeplink'] ?? '',
'expiresAt': message.data['expiresAt'] ?? '',
'isPinned': message.data['isPinned'] == 'true',
};
final settings =
Hive.box<AppSettings>(HiveBoxes.settings).get('default') ?? AppSettings();
await RemotePushService.instance.ingestPayload(payload, settings: settings);
}