implemented dual Nextcloud push registration with separate general and talk apptypes to ensure reliable Talk notification delivery; introduced stacked MessagingStyle notifications for chat threads with support for circular conversation avatars and disk caching
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:crypton/crypton.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
import '../notification/notification_service.dart';
|
||||
import 'chat_thread_store.dart';
|
||||
import 'nid_store.dart';
|
||||
import 'push_decryptor.dart';
|
||||
import 'push_keypair.dart';
|
||||
@@ -34,6 +35,17 @@ PushKind classifyPush(Map<String, dynamic> data) {
|
||||
return PushKind.unknown;
|
||||
}
|
||||
|
||||
/// FCM background isolate entry point. Must be a TOP-LEVEL function with the
|
||||
/// entry-point pragma: AOT builds cannot invoke static class members from
|
||||
/// native code unless the class itself is annotated too (DartVM error
|
||||
/// "must be annotated"), so a plain function is the reliable form.
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> pushOnBackgroundMessage(RemoteMessage message) async {
|
||||
await NotificationService().initializeNotifications();
|
||||
await PushRenderer.ensureChannels();
|
||||
await PushMessageHandler().handle(message);
|
||||
}
|
||||
|
||||
/// Verifies, decrypts, and renders incoming push messages. Delete-pushes cancel
|
||||
/// the matching tray notification via [NidStore]. Works both in the FCM
|
||||
/// background isolate and the foreground.
|
||||
@@ -42,25 +54,19 @@ class PushMessageHandler {
|
||||
final PushRegistrationStore _registrationStore;
|
||||
final PushRenderer _renderer;
|
||||
final NidStore _nidStore;
|
||||
final ChatThreadStore _threadStore;
|
||||
|
||||
PushMessageHandler({
|
||||
PushKeypair? keypair,
|
||||
PushRegistrationStore? registrationStore,
|
||||
PushRenderer? renderer,
|
||||
NidStore? nidStore,
|
||||
ChatThreadStore? threadStore,
|
||||
}) : _keypair = keypair ?? const PushKeypair(),
|
||||
_registrationStore = registrationStore ?? const PushRegistrationStore(),
|
||||
_renderer = renderer ?? PushRenderer(),
|
||||
_nidStore = nidStore ?? NidStore();
|
||||
|
||||
/// Background isolate entry point registered with
|
||||
/// `FirebaseMessaging.onBackgroundMessage`.
|
||||
@pragma('vm:entry-point')
|
||||
static Future<void> onBackgroundMessage(RemoteMessage message) async {
|
||||
await NotificationService().initializeNotifications();
|
||||
await PushRenderer.ensureChannels();
|
||||
await PushMessageHandler().handle(message);
|
||||
}
|
||||
_nidStore = nidStore ?? NidStore(),
|
||||
_threadStore = threadStore ?? ChatThreadStore();
|
||||
|
||||
/// Processes [message]. In the foreground, pass [foreground] true and
|
||||
/// [openChatToken] so a message for the currently open chat is suppressed
|
||||
@@ -157,6 +163,7 @@ class PushMessageHandler {
|
||||
await _cancel(entry);
|
||||
}
|
||||
await _nidStore.clear();
|
||||
await _threadStore.clearAll();
|
||||
return;
|
||||
}
|
||||
final nids = <int>[
|
||||
@@ -165,7 +172,20 @@ class PushMessageHandler {
|
||||
];
|
||||
for (final nid in nids) {
|
||||
final entry = await _nidStore.get(nid);
|
||||
if (entry != null) await _cancel(entry);
|
||||
final chatToken = entry?.chatToken;
|
||||
if (chatToken != null && chatToken.isNotEmpty) {
|
||||
// Stacked chat notification: drop only this message from the thread.
|
||||
// Remaining messages re-render WITHOUT alerting again; the last one
|
||||
// going away cancels the whole card.
|
||||
final remaining = await _threadStore.removeNid(chatToken, nid);
|
||||
if (remaining.isEmpty) {
|
||||
await _cancel(entry!);
|
||||
} else {
|
||||
await _renderer.renderTalkThread(chatToken, remaining, alert: false);
|
||||
}
|
||||
} else if (entry != null) {
|
||||
await _cancel(entry);
|
||||
}
|
||||
await _nidStore.delete(nid);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user