134 lines
4.6 KiB
Dart
134 lines
4.6 KiB
Dart
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.
|
|
///
|
|
/// This app currently ships without Firebase/APNs SDK setup in source control.
|
|
/// Once push SDK is configured, route incoming payloads to [ingestPayload].
|
|
class RemotePushService {
|
|
RemotePushService._();
|
|
static final RemotePushService instance = RemotePushService._();
|
|
|
|
final NotificationInboxService _inbox = NotificationInboxService.instance;
|
|
bool _initialized = false;
|
|
|
|
Future<void> init() async {
|
|
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(
|
|
Map<String, dynamic> payload, {
|
|
AppSettings? settings,
|
|
}) async {
|
|
if (settings != null && !settings.inboxEnabled) return;
|
|
|
|
final id = (payload['id'] ?? payload['messageId'] ?? '').toString().trim();
|
|
final title = (payload['title'] ?? '').toString().trim();
|
|
final body = (payload['body'] ?? '').toString().trim();
|
|
if (id.isEmpty || title.isEmpty || body.isEmpty) return;
|
|
|
|
final type = (payload['type'] ?? 'content').toString().trim();
|
|
final deeplink = (payload['deeplink'] ?? '').toString().trim();
|
|
final expiresAt =
|
|
DateTime.tryParse((payload['expiresAt'] ?? '').toString().trim());
|
|
final isPinned = payload['isPinned'] == true;
|
|
|
|
await _inbox.addItem(
|
|
title: title,
|
|
body: body,
|
|
type: type.isEmpty ? 'content' : type,
|
|
source: 'remote',
|
|
deeplink: deeplink.isEmpty ? null : deeplink,
|
|
dedupeKey: 'remote.push.$id',
|
|
expiresAt: expiresAt,
|
|
isPinned: isPinned,
|
|
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);
|
|
}
|