From fea39e4191a0d72d66835700c7b470cd861d1896 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Mon, 21 Sep 2026 15:43:01 -0400 Subject: [PATCH] use xdg_desktop_portal over flutter_local_notifications on linux --- lib/controllers/notification.dart | 134 +++++++++++++++++++--------- lib/controllers/portal.dart | 17 ++++ linux/nexus.federated.nexus.desktop | 4 +- linux/nexus.federated.nexus.service | 2 +- pubspec.lock | 37 ++++---- pubspec.yaml | 14 +-- 6 files changed, 132 insertions(+), 76 deletions(-) create mode 100644 lib/controllers/portal.dart diff --git a/lib/controllers/notification.dart b/lib/controllers/notification.dart index 7c40d18..67f2ed4 100644 --- a/lib/controllers/notification.dart +++ b/lib/controllers/notification.dart @@ -1,10 +1,13 @@ import "dart:io"; +import "package:flutter/foundation.dart"; import "package:material_ui/material_ui.dart"; +import "package:nexus/controllers/portal.dart"; import "package:nexus/main.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/pages/notifications.dart"; +import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; class NotificationController extends AsyncNotifier { @@ -12,40 +15,77 @@ class NotificationController Future build() async { final notifications = FlutterLocalNotificationsPlugin(); - final darwin = DarwinInitializationSettings(); + if (!Platform.isLinux) { + final darwin = DarwinInitializationSettings(); - await notifications.initialize( - settings: .new( - windows: .new( - appName: "Nexus", - appUserModelId: "nexus.federated.nexus", - guid: "dde78daf-130f-4e46-a80a-e31deeab45d7", + await notifications.initialize( + settings: .new( + windows: .new( + appName: "Nexus", + appUserModelId: "nexus.federated.nexus", + guid: "dde78daf-130f-4e46-a80a-e31deeab45d7", + ), + android: .new("ic_launcher_foreground"), + iOS: darwin, + macOS: darwin, ), - android: .new("ic_launcher_foreground"), - iOS: darwin, - macOS: darwin, - linux: .new(defaultActionName: "Open"), - ), - onDidReceiveNotificationResponse: (details) { - if (details.payload case final eventId?) { - if (navigatorKey.currentContext case final context?) { + onDidReceiveNotificationResponse: (details) { + if (details.payload case final eventId?) { + if (navigatorKey.currentContext case final context?) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => NotificationsPage( + highlightedEventId: eventId, + defaultToAllNotifications: true, + ), + ), + ); + } + } + }, + ); + } + + if (Platform.isLinux) { + final portal = await ref.watch(PortalController.provider.future); + + portal.notification.actionInvoked.listen((event) { + if (event.action != "app.event") { + return; + } + + if (navigatorKey.currentContext case final context?) { + if (context.mounted) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => NotificationsPage( - highlightedEventId: eventId, + highlightedEventId: event.id, defaultToAllNotifications: true, ), ), ); } } - }, - ); + }); + } + + ref.onDispose(() { + // The portal client owns its D-Bus connection. + if (Platform.isLinux) { + ref.read(PortalController.provider.future).then((portal) { + portal.close(); + }); + } + }); return notifications; } Future requestPermissions() async { + if (Platform.isLinux) { + return true; + } + final controller = await future; if (Platform.isIOS) { @@ -82,31 +122,41 @@ class NotificationController String? payload, }) async { debugPrint("Sending notification for $id"); - final notificationDetails = NotificationDetails( - android: .new( - "messages", - "Messages", - largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path), - ), - linux: .new( - category: LinuxNotificationCategory.imReceived, - defaultActionName: "app.event", - icon: icon == null ? null : FilePathLinuxIcon(icon.path), - ), - // TODO: See if icons can be added to iOS, macOS, and Windows notifications (#68) - iOS: .new(), - macOS: .new(), - windows: .new(), - ); - final controller = await future; - await controller.show( - id: id, - title: title, - body: body, - notificationDetails: notificationDetails, - payload: payload, - ); + if (Platform.isLinux) { + final portal = await ref.read(PortalController.provider.future); + + await portal.notification.addNotification( + id.toString(), + title: title, + body: body, + icon: icon == null ? null : XdgNotificationIconFile(icon.path), + defaultAction: "app.event", + ); + } else { + final notificationDetails = NotificationDetails( + android: .new( + "messages", + "Messages", + largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path), + ), + // TODO: See if icons can be added to iOS, macOS, and Windows + // notifications (#68) + iOS: .new(), + macOS: .new(), + windows: .new(), + ); + + final controller = await future; + + await controller.show( + id: id, + title: title, + body: body, + notificationDetails: notificationDetails, + payload: payload, + ); + } } static final provider = diff --git a/lib/controllers/portal.dart b/lib/controllers/portal.dart new file mode 100644 index 0000000..9fbc491 --- /dev/null +++ b/lib/controllers/portal.dart @@ -0,0 +1,17 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; + +class PortalController extends AsyncNotifier { + @override + Future build() async { + final portal = XdgDesktopPortalClient(); + await portal.registerApplication("nexus.federated.nexus"); + + return portal; + } + + static final provider = + AsyncNotifierProvider( + PortalController.new, + ); +} diff --git a/linux/nexus.federated.nexus.desktop b/linux/nexus.federated.nexus.desktop index ac01719..a7a548a 100644 --- a/linux/nexus.federated.nexus.desktop +++ b/linux/nexus.federated.nexus.desktop @@ -8,6 +8,4 @@ Terminal=false Type=Application Categories=Chat;Network;InstantMessaging; MimeType=x-scheme-handler/nexus.federated.nexus; -StartupNotify=true -DBusActivatable=true -X-GNOME-UsesNotifications=true \ No newline at end of file +DBusActivatable=true \ No newline at end of file diff --git a/linux/nexus.federated.nexus.service b/linux/nexus.federated.nexus.service index 65e2e79..1757c2c 100644 --- a/linux/nexus.federated.nexus.service +++ b/linux/nexus.federated.nexus.service @@ -1,3 +1,3 @@ [D-BUS Service] Name=nexus.federated.nexus -Exec=/usr/bin/env FLUTTER_HEADLESS=1 nexus +Exec=/usr/bin/env nexus diff --git a/pubspec.lock b/pubspec.lock index 278d3b4..9a917a4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -495,23 +495,21 @@ packages: source: hosted version: "22.3.0" flutter_local_notifications_linux: - dependency: "direct main" + dependency: transitive description: - path: flutter_local_notifications_linux - ref: "quad/feat/xdg-portal-notifs" - resolved-ref: d853ff4997f6dd2b9ecdc53d37fb337cebe63863 - url: "https://github.com/Henry-Hiles/flutter_local_notifications" - source: git - version: "9.0.0-dev.1" + name: flutter_local_notifications_linux + sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b" + url: "https://pub.dev" + source: hosted + version: "8.0.1" flutter_local_notifications_platform_interface: - dependency: "direct overridden" + dependency: transitive description: - path: flutter_local_notifications_platform_interface - ref: "quad/feat/xdg-portal-notifs" - resolved-ref: d853ff4997f6dd2b9ecdc53d37fb337cebe63863 - url: "https://github.com/Henry-Hiles/flutter_local_notifications" - source: git - version: "13.0.0-dev.1" + name: flutter_local_notifications_platform_interface + sha256: "43c3761d916c9bd3d5c7ebbc44d82f4990329840c0c5d62ad5260cc1b5d399bd" + url: "https://pub.dev" + source: hosted + version: "12.2.0" flutter_local_notifications_web: dependency: transitive description: @@ -1745,12 +1743,13 @@ packages: source: hosted version: "0.5.2" xdg_desktop_portal: - dependency: transitive + dependency: "direct main" description: - name: xdg_desktop_portal - sha256: "4fae74b6ed63a449d4c796a74265cebdd912c390336dedc06d705142f3b939d7" - url: "https://pub.dev" - source: hosted + path: "." + ref: HEAD + resolved-ref: "6d070b677f908b01065b4dce79ddc9a208daadbf" + url: "https://github.com/Henry-Hiles/xdg_desktop_portal.dart" + source: git version: "0.1.14" xdg_directories: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index 2a2b2ff..403fe72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,16 +35,6 @@ dependency_overrides: git: url: https://github.com/appelladev/linkify ref: fix/consecutive-periods-loose-url - flutter_local_notifications_linux: - git: - url: https://github.com/Henry-Hiles/flutter_local_notifications - ref: quad/feat/xdg-portal-notifs - path: flutter_local_notifications_linux - flutter_local_notifications_platform_interface: - git: - url: https://github.com/Henry-Hiles/flutter_local_notifications - ref: quad/feat/xdg-portal-notifs - path: flutter_local_notifications_platform_interface dependencies: flutter: @@ -99,8 +89,10 @@ dependencies: unifiedpush: 6.2.0 unifiedpush_storage_shared_preferences: 1.0.0 flutter_local_notifications: 22.3.0 - flutter_local_notifications_linux: 8.0.1 material_emoji_picker: 1.1.1 + xdg_desktop_portal: + git: + url: https://github.com/Henry-Hiles/xdg_desktop_portal.dart dev_dependencies: build_runner: 2.16.1