Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

refactor and improve notification handling

This commit is contained in:
Henry Hiles 2026-10-19 21:04:28 -04:00
commit 765e96cce4
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
2 changed files with 91 additions and 63 deletions

View file

@ -10,8 +10,16 @@ import "package:nexus/main.dart";
import "package:flutter_local_notifications/flutter_local_notifications.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/notification_request.dart"; import "package:nexus/controllers/notification_request.dart";
import "package:nexus/models/push_notification.dart";
import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; import "package:xdg_desktop_portal/xdg_desktop_portal.dart";
typedef NotificationMessage = ({
NotificationUser sender,
NotificationUser self,
File? senderAvatar,
bool isDm,
});
class NotificationController class NotificationController
extends AsyncNotifier<FlutterLocalNotificationsPlugin> { extends AsyncNotifier<FlutterLocalNotificationsPlugin> {
@override @override
@ -140,24 +148,24 @@ class NotificationController
return true; return true;
} }
/// Passing [message] shows it as a chat message in the conversation for
/// [threadId], naming the sender in group chats.
Future<void> send({ Future<void> send({
required int id, required int id,
required String title, required String title,
String? body, required String body,
File? icon,
String? payload, String? payload,
String? senderName,
File? senderAvatar,
String? selfName,
bool isGroup = false,
bool sound = true,
DateTime? timestamp,
String? threadId, String? threadId,
DateTime? timestamp,
File? icon,
bool sound = true,
NotificationMessage? message,
}) async { }) async {
debugPrint("Sending notification for $id"); debugPrint("Sending notification for $id");
// In DMs the title is already the sender, so only prefix in groups // In DMs the title is already the sender
final prefix = isGroup ? senderName : null; final sender = message == null || message.isDm ? null : message.sender;
final prefixedBody = sender == null ? body : "${sender.name}: $body";
if (Platform.isLinux) { if (Platform.isLinux) {
final portal = await ref.read(PortalController.provider.future); final portal = await ref.read(PortalController.provider.future);
@ -166,14 +174,14 @@ class NotificationController
await portal.notification.addNotification( await portal.notification.addNotification(
id.toString(), id.toString(),
title: title, title: title,
body: prefix == null || body == null ? body : "$prefix: $body", body: prefixedBody,
markupBody: prefix == null || body == null markupBody: sender == null
? null ? null
: "<b>${escape.convert(prefix)}</b>: ${escape.convert(body)}", : "<b>${escape.convert(sender.name)}</b>: ${escape.convert(body)}",
defaultAction: "app.event", defaultAction: "app.event",
defaultActionTarget: payload, defaultActionTarget: payload,
sound: sound ? null : const XdgNotificationSoundSilent(), sound: sound ? null : const XdgNotificationSoundSilent(),
category: XdgNotificationCategory.imReceived, category: message == null ? null : XdgNotificationCategory.imReceived,
priority: .normal, priority: .normal,
buttons: [ buttons: [
// TODO: Impl when notif daemons actualy support imReplyWithText // TODO: Impl when notif daemons actualy support imReplyWithText
@ -184,11 +192,13 @@ class NotificationController
// ), // ),
], ],
); );
} else { return;
}
final controller = await future; final controller = await future;
// Append to the conversation already shown under this id, if any // Append to the conversation already shown under this id, if any
final existingStyle = Platform.isAndroid final existingStyle = Platform.isAndroid && message != null
? await controller ? await controller
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin AndroidFlutterLocalNotificationsPlugin
@ -201,28 +211,50 @@ class NotificationController
presentSound: sound, presentSound: sound,
threadIdentifier: threadId, threadIdentifier: threadId,
); );
final notificationDetails = NotificationDetails(
android: .new( await controller.show(
id: id,
title: title,
body: prefixedBody,
payload: payload,
notificationDetails: .new(
android: message == null
? .new(
"general",
"General",
silent: !sound,
when: timestamp?.millisecondsSinceEpoch,
largeIcon: icon == null
? null
: FilePathAndroidBitmap(icon.path),
styleInformation: BigTextStyleInformation(body),
)
: .new(
"messages", "messages",
"Messages", "Messages",
silent: !sound, silent: !sound,
largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path), when: timestamp?.millisecondsSinceEpoch,
styleInformation: senderName == null || body == null category: .message,
largeIcon: icon == null
? null ? null
: MessagingStyleInformation( : FilePathAndroidBitmap(icon.path),
.new(name: selfName ?? "You"), styleInformation: MessagingStyleInformation(
conversationTitle: isGroup ? title : null, .new(key: message.self.id, name: message.self.name),
groupConversation: isGroup, conversationTitle: message.isDm ? null : title,
groupConversation: !message.isDm,
messages: [ messages: [
...?existingStyle?.messages, ...?existingStyle?.messages,
.new( .new(
body, body,
timestamp ?? .now(), timestamp ?? .now(),
.new( .new(
name: senderName, key: message.sender.id,
icon: senderAvatar == null name: message.sender.name,
icon: message.senderAvatar == null
? null ? null
: BitmapFilePathAndroidIcon(senderAvatar.path), : BitmapFilePathAndroidIcon(
message.senderAvatar!.path,
),
), ),
), ),
], ],
@ -237,16 +269,8 @@ class NotificationController
? null ? null
: .new(id: threadId, title: title, arguments: payload ?? ""), : .new(id: threadId, title: title, arguments: payload ?? ""),
), ),
),
); );
await controller.show(
id: id,
title: title,
body: prefix == null || body == null ? body : "$prefix: $body",
notificationDetails: notificationDetails,
payload: payload,
);
}
} }
static final provider = static final provider =

View file

@ -122,17 +122,21 @@ class UnifiedPushController extends AsyncNotifier<String?> {
: notification.eventId) : notification.eventId)
.hashCode & .hashCode &
0x7fffffff, 0x7fffffff,
title: notification.roomName,
icon: icon,
payload: notification.eventRowId.toString(),
body: notification.text,
senderName: pmp?.name ?? notification.sender.name,
senderAvatar: senderAvatar,
selfName: notification.self.name,
isGroup: !notification.isDm,
sound: notification.sound,
timestamp: notification.timestamp,
threadId: notification.roomId, threadId: notification.roomId,
title: notification.roomName,
body: notification.text,
payload: notification.eventRowId.toString(),
timestamp: notification.timestamp,
icon: icon,
sound: notification.sound,
message: (
sender: pmp == null
? notification.sender
: pmp.copyWith(id: "${notification.sender.id}/${pmp.id}"),
self: notification.self,
senderAvatar: senderAvatar,
isDm: notification.isDm,
),
); );
if (isInBackground) exit(0); if (isInBackground) exit(0);