From 9aa081223ac3d2a912b58725eae2871b37479441 Mon Sep 17 00:00:00 2001 From: Lars Werner Date: Thu, 20 Nov 2025 01:27:16 +0100 Subject: [PATCH] Added the Notification.permission raw value. Notification.permission is a three state value; "default,granted,denied" where "default" is the only state a user will be shown a popup to accept or block notifications. Added as a simple string, to handle states that might be added in the future. Removed the dart:html package to notification_api.dart since it depreciated. Made a hacky "isSupported" since that is no longer a function for package:web/web.dart anymore. Modified the example to show Notification.permission and a simple request button, so end user can test states. Also added tapStream and moved the "default" values from actionStream to it. Now links will be opened when tapped. Removed the dart:html package replaced it with package:web/web.dart Bumped the version to 0.0.6 --- example/lib/main.dart | 62 ++++++++++++++----- example/pubspec.yaml | 5 +- example/web/js_notifications-sw.js | 2 + .../notifications_api/notification_api.dart | 25 ++++++-- lib/js_notifications_web.dart | 3 + .../js_notifications_method_channel.dart | 4 ++ .../js_notifications_platform_interface.dart | 8 +++ pubspec.yaml | 4 +- test/js_notifications_test.dart | 4 ++ 9 files changed, 91 insertions(+), 26 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 67894a6..f885d36 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,5 @@ import 'dart:async'; -//ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; +import 'package:web/web.dart' as web; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -40,6 +39,20 @@ class _MyAppState extends State { @override void initState() { super.initState(); + + //Callback on taps + _jsNotificationsPlugin.tapStream.listen((event) { + switch (event.action) { + default: + if (event.tag == "rick_roll") { + openNewWindow("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + } else if (event.tag == "star_wars_channel") { + openNewWindow("https://www.youtube.com/@StarWars"); + } + } + }); + + //Callback on actions _jsNotificationsPlugin.actionStream.listen((event) { switch (event.action) { case "unexpected": @@ -115,15 +128,11 @@ class _MyAppState extends State { } default: - { - if (event.tag == "rick_roll") { - openNewWindow("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); - } else if (event.tag == "star_wars_channel") { - openNewWindow("https://www.youtube.com/@StarWars"); - } - } + {} } }); + + //Callback on dismiss _jsNotificationsPlugin.dismissStream.listen((event) { switch (event.tag) { case "data-notification": @@ -190,10 +199,32 @@ class _MyAppState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( + spacing: 12, children: [ - Text("Test Notification", style: _boldTextStyle), + Text("Notification permission", style: _boldTextStyle), + Text( + "Permission status: ${_jsNotificationsPlugin.permission ?? "null"}"), + ElevatedButton( + onPressed: () async { + //requestPermissions() will only show modal if permission is + // "default". + //The three states are important regarding feedback to + //users: + //1. default = show request button + //2. granted = ready to show + //3. denied = blocked, you have to show instructions on how + //to manually turn on notifications. + await _jsNotificationsPlugin.requestPermissions(); + + //Update permission text after request was sent + setState(() {}); + }, + child: const Text("Request permission"), + ), const SizedBox(height: 8), + Text("Test Notification", style: _boldTextStyle), Row( + spacing: 8, mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( @@ -203,7 +234,6 @@ class _MyAppState extends State { }, child: const Text("Test Notification"), ), - const SizedBox(width: 4), ElevatedButton( onPressed: () { _dismissBasicNotification(); @@ -212,19 +242,18 @@ class _MyAppState extends State { ), ], ), - const SizedBox(height: 24), - Text("Data Notification", style: _boldTextStyle), const SizedBox(height: 8), + Text("Data Notification", style: _boldTextStyle), ElevatedButton( onPressed: () { _sendDataNotification(); }, child: const Text("Data Notification"), ), - const SizedBox(height: 24), - Text("Custom Notifications", style: _boldTextStyle), const SizedBox(height: 8), + Text("Custom Notifications", style: _boldTextStyle), Row( + spacing: 8, mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( @@ -233,7 +262,6 @@ class _MyAppState extends State { }, child: const Text("Expect the unexpected."), ), - const SizedBox(width: 4), ElevatedButton( onPressed: () { _sendGrievous(); @@ -390,7 +418,7 @@ class _MyAppState extends State { } void openNewWindow(String url) { - html.window.open(url, "", 'noopener,noreferrer'); + web.window.open(url, "", 'noopener,noreferrer'); } Future _showTimerNotification() { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 06bd713..9c8578b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -28,7 +28,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 - stop_watch_timer: ^3.2.1 + stop_watch_timer: ^3.2.2 + + # For window.open support + web: ^1.1.1 dev_dependencies: integration_test: diff --git a/example/web/js_notifications-sw.js b/example/web/js_notifications-sw.js index 7a30eaf..29d0dc5 100644 --- a/example/web/js_notifications-sw.js +++ b/example/web/js_notifications-sw.js @@ -83,6 +83,8 @@ const getClients = () => self.clients.matchAll(matchOptions); const hasPermissions = () => Notification.permission === 'granted'; +const permission = () => Notification.permission; + // function showNotification(title, options, timer) { const showNotification = async (title, options) => { if (!hasPermissions()) { diff --git a/lib/interop/notifications_api/notification_api.dart b/lib/interop/notifications_api/notification_api.dart index 06aa03e..165cd92 100644 --- a/lib/interop/notifications_api/notification_api.dart +++ b/lib/interop/notifications_api/notification_api.dart @@ -1,5 +1,5 @@ -import 'dart:html'; - +import 'package:web/web.dart' as web; +import 'dart:js_interop'; //For .toDart support on JSPromise import 'package:simple_print/simple_print.dart'; class NotificationsAPI { @@ -11,7 +11,10 @@ class NotificationsAPI { static NotificationsAPI get instance => _instance; - bool get isSupported => Notification.supported; + //package:web/web.dart do not have the supported value as dart:html + //https://api.dart.dev/dart-html/Notification/supported.html + //If permission has any value, it is "supported" + bool get isSupported => web.Notification.permission.isNotEmpty; Future requestPermission() async { try { @@ -19,8 +22,8 @@ class NotificationsAPI { printDebug("Notifications not supported", tag: tag); return false; } - final perm = await Notification.requestPermission(); - return (perm == "granted"); + final perm = await web.Notification.requestPermission().toDart; + return (perm.toString() == "granted"); } catch (e) { printDebug("Failed to request notifications permission", tag: tag); printDebug(e); @@ -30,7 +33,7 @@ class NotificationsAPI { bool get hasPermission { try { - final perm = Notification.permission; + final perm = web.Notification.permission; return (perm == "granted"); } catch (e) { printDebug("Failed to query notifications permission", tag: tag); @@ -38,4 +41,14 @@ class NotificationsAPI { return false; } } + + String? get permission { + try { + return web.Notification.permission; + } catch (e) { + printDebug("Failed to query notifications permission", tag: tag); + printDebug(e); + return null; + } + } } diff --git a/lib/js_notifications_web.dart b/lib/js_notifications_web.dart index bee46bb..d5b0064 100644 --- a/lib/js_notifications_web.dart +++ b/lib/js_notifications_web.dart @@ -115,6 +115,9 @@ class JsNotificationsWeb extends JsNotificationsPlatform { @override bool get hasPermissions => notificationsAPI.hasPermission; + @override + String? get permission => notificationsAPI.permission; + @override bool get isSupported => notificationsAPI.isSupported; diff --git a/lib/method_channel/js_notifications_method_channel.dart b/lib/method_channel/js_notifications_method_channel.dart index 92ac647..e1190e2 100644 --- a/lib/method_channel/js_notifications_method_channel.dart +++ b/lib/method_channel/js_notifications_method_channel.dart @@ -39,6 +39,10 @@ class MethodChannelJsNotifications extends JsNotificationsPlatform { // TODO: implement hasPermissions bool get hasPermissions => throw UnimplementedError(); + @override + // TODO: implement permissions + String? get permission => throw UnimplementedError(); + @override // TODO: implement isSupported bool get isSupported => throw UnimplementedError(); diff --git a/lib/platform_interface/js_notifications_platform_interface.dart b/lib/platform_interface/js_notifications_platform_interface.dart index 3538a6b..1f5b331 100644 --- a/lib/platform_interface/js_notifications_platform_interface.dart +++ b/lib/platform_interface/js_notifications_platform_interface.dart @@ -40,6 +40,14 @@ abstract class JsNotificationsPlatform extends PlatformInterface { /// See: https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission_static bool get hasPermissions; + /// Convenience method for checking browser notification raw status + /// wrapper for Dart's native JS notification [Notification.permission] + /// + /// Return values: granted, denied, default or [null] + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission_static + String? get permission; + /// Convenience method checking browser notification support, /// wrapper for Dart's native JS notification [Notification.requestPermission()]. /// Returns [true] if response matches 'granted'. diff --git a/pubspec.yaml b/pubspec.yaml index 489f24b..6fa2beb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: js_notifications description: "An extended NotificationsAPI for Dart managing web notifications." -version: 0.0.5 +version: 0.0.6 homepage: https://github.com/cybex-dev/js_notifications platforms: web: @@ -19,7 +19,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - web: ^1.1.0 + web: ^1.1.1 plugin_platform_interface: ^2.1.8 uuid: ^4.5.1 simple_print: ^0.0.1+2 diff --git a/test/js_notifications_test.dart b/test/js_notifications_test.dart index 8c5b661..78c5461 100644 --- a/test/js_notifications_test.dart +++ b/test/js_notifications_test.dart @@ -54,6 +54,10 @@ class MockJsNotificationsPlatform // TODO: implement hasPermissions bool get hasPermissions => throw UnimplementedError(); + @override + // TODO: implement permission + String? get permission => throw UnimplementedError(); + @override // TODO: implement isSupported bool get isSupported => throw UnimplementedError();