From 6743e8f464d28a543483db0e35b5c7b17c6f4a96 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 00:16:00 -0500 Subject: [PATCH 01/37] Updated _windows to use proper URIs for images and audio --- .../example/lib/windows.dart | 36 ++++++++++++++----- .../lib/src/details/notification_audio.dart | 2 +- .../lib/src/details/notification_parts.dart | 25 ++++++++++++- .../lib/src/details/xml/image.dart | 2 +- .../pubspec.yaml | 2 ++ .../test/details_test.dart | 6 ++-- 6 files changed, 58 insertions(+), 15 deletions(-) diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index c80a4e70d..5b586c3ad 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; @@ -47,6 +46,12 @@ List examples({ await _showWindowsNotificationWithImages(); }, ), + PaddedElevatedButton( + buttonText: 'Show notification with sound from file', + onPressed: () async { + await _showWindowsNotificationWithSound(); + }, + ), PaddedElevatedButton( buttonText: 'Show notifications with columns', onPressed: () async { @@ -202,12 +207,12 @@ Future _showWindowsNotificationWithImages() => flutterLocalNotificationsPlugin.show( id++, 'This notification has an image', - 'You can only show images from files', + 'You can show images from ms-appx, ms-appdata, http(s), or file URIs. See the columns example for more.', NotificationDetails( windows: WindowsNotificationDetails( images: [ WindowsImage.file( - File('./icons/4.0x/app_icon_density.png').absolute, + Uri.parse('ms-appx:///data/flutter_assets/icons/4.0x/app_icon_density.png'), altText: 'A beautiful image', ), ], @@ -215,6 +220,18 @@ Future _showWindowsNotificationWithImages() => ), ); +Future _showWindowsNotificationWithSound() => + flutterLocalNotificationsPlugin.show( + id++, + 'This notification has custom sound', + 'Not just a Windows preset. Only ms-appx or ms-appdata URIs are allowed though', + NotificationDetails( + windows: WindowsNotificationDetails( + audio: WindowsNotificationAudio.fromFile(file: Uri.parse('ms-appx:///data/flutter_assets/sound/slow_spring_board.mp3')), + ), + ), + ); + Future _showWindowsNotificationWithGroups() => flutterLocalNotificationsPlugin.show( id++, @@ -226,16 +243,17 @@ Future _showWindowsNotificationWithGroups() => rows: [ WindowsRow([ WindowsColumn([ - WindowsImage.file(File('icons/coworker.png').absolute, - altText: 'A coworker'), + WindowsImage.file( + Uri.parse('ms-appx:///data/flutter_assets/icons/coworker.png'), + altText: 'A local image',), const WindowsNotificationText( - text: 'A coworker', isCaption: true), + text: 'A local image', isCaption: true), ]), WindowsColumn([ WindowsImage.file( - File('icons/4.0x/app_icon_density.png').absolute, - altText: 'The icon'), - const WindowsNotificationText(text: 'The icon'), + Uri.parse('https://picsum.photos/100'), + altText: 'A web image',), + const WindowsNotificationText(text: 'A web image'), ]), ]), ], diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index cd2c2e8a9..0bd3d05e7 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -106,7 +106,7 @@ class WindowsNotificationAudio { required Uri file, this.shouldLoop = false, }) : isSilent = false, - source = file.toFilePath() { + source = file.toString() { if (!allowedSchemes.contains(file.scheme)) { throw ArgumentError.value( file.toString(), diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 43dc29d69..e119b6dce 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -1,5 +1,8 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; +import "package:flutter/services.dart"; + /// A text or image element in a Windows notification. /// /// Note: This should not be used for anything else as notification @@ -39,6 +42,26 @@ class WindowsImage extends WindowsNotificationPart { this.crop, }); + WindowsImage.asset( + String assetName, { + required this.altText, + this.addQueryParams = false, + this.placement, + this.crop, + }) : + file = kDebugMode + ? Uri.file(File(assetName).absolute.path, windows: true) + : Uri.parse('ms-appx:///$assetName'); + + /// Allowed Uri schemes for [WindowsImage]. + static const Set allowedSchemes = { + 'http', + 'https', + 'ms-appx', + 'file', + 'ms-appdata' + }; + /// Whether Windows should add URL query parameters when fetching the image. final bool addQueryParams; @@ -46,7 +69,7 @@ class WindowsImage extends WindowsNotificationPart { final String altText; /// The source of the image. - final File file; + final Uri file; /// Where this image will be placed. Null indicates below the notification. final WindowsImagePlacement? placement; diff --git a/flutter_local_notifications_windows/lib/src/details/xml/image.dart b/flutter_local_notifications_windows/lib/src/details/xml/image.dart index e652f3db5..2b89674a4 100644 --- a/flutter_local_notifications_windows/lib/src/details/xml/image.dart +++ b/flutter_local_notifications_windows/lib/src/details/xml/image.dart @@ -18,7 +18,7 @@ extension ImageToXml on WindowsImage { builder.element( 'image', attributes: { - 'src': Uri.file(file.absolute.path, windows: true).toFilePath(), + 'src': file.toString(), 'alt': altText, 'addImageQuery': addQueryParams.toString(), if (placement != null) 'placement': placement!.name, diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index c85e23de6..77d32a580 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -8,6 +8,8 @@ environment: flutter: ">=3.19.0" dependencies: + flutter: + sdk: flutter ffi: ^2.1.2 flutter_local_notifications_platform_interface: ^8.1.0-dev.1 meta: ^1.11.0 diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index ebfc553d7..cb31f4104 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -94,7 +94,7 @@ void main() => group('Details:', () { const WindowsColumn emptyColumn = WindowsColumn([]); final WindowsImage image = WindowsImage.file( - File('test/icon.png').absolute, + Uri.parse('ms-appx:///test/icon.png'), altText: 'an icon'); const WindowsNotificationText text = WindowsNotificationText(text: 'Text'); @@ -132,11 +132,11 @@ void main() => group('Details:', () { test('Images', () async { final WindowsImage simpleImage = WindowsImage.file( - File('test/icon.png').absolute, + Uri.parse('ms-appx:///asset.png'), altText: 'an icon', ); final WindowsImage complexImage = WindowsImage.file( - File('test/icon.png').absolute, + Uri.parse('https://picsum.photos/500'), altText: 'an icon', addQueryParams: true, crop: WindowsImageCrop.circle, From 14aa3b7043d32b2c71c0a865ca5bba11ce4c287c Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 00:17:21 -0500 Subject: [PATCH 02/37] Lints and format --- .../example/lib/windows.dart | 18 ++++++++++++------ .../lib/src/details/notification_parts.dart | 9 ++++----- .../test/details_test.dart | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index 5b586c3ad..b54132f37 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -212,7 +212,8 @@ Future _showWindowsNotificationWithImages() => windows: WindowsNotificationDetails( images: [ WindowsImage.file( - Uri.parse('ms-appx:///data/flutter_assets/icons/4.0x/app_icon_density.png'), + Uri.parse( + 'ms-appx:///data/flutter_assets/icons/4.0x/app_icon_density.png'), altText: 'A beautiful image', ), ], @@ -227,7 +228,9 @@ Future _showWindowsNotificationWithSound() => 'Not just a Windows preset. Only ms-appx or ms-appdata URIs are allowed though', NotificationDetails( windows: WindowsNotificationDetails( - audio: WindowsNotificationAudio.fromFile(file: Uri.parse('ms-appx:///data/flutter_assets/sound/slow_spring_board.mp3')), + audio: WindowsNotificationAudio.fromFile( + file: Uri.parse( + 'ms-appx:///data/flutter_assets/sound/slow_spring_board.mp3')), ), ), ); @@ -244,15 +247,18 @@ Future _showWindowsNotificationWithGroups() => WindowsRow([ WindowsColumn([ WindowsImage.file( - Uri.parse('ms-appx:///data/flutter_assets/icons/coworker.png'), - altText: 'A local image',), + Uri.parse( + 'ms-appx:///data/flutter_assets/icons/coworker.png'), + altText: 'A local image', + ), const WindowsNotificationText( text: 'A local image', isCaption: true), ]), WindowsColumn([ WindowsImage.file( - Uri.parse('https://picsum.photos/100'), - altText: 'A web image',), + Uri.parse('https://picsum.photos/100'), + altText: 'A web image', + ), const WindowsNotificationText(text: 'A web image'), ]), ]), diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index e119b6dce..d8386553e 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; -import "package:flutter/services.dart"; /// A text or image element in a Windows notification. /// @@ -42,16 +41,16 @@ class WindowsImage extends WindowsNotificationPart { this.crop, }); + /// Creates a Windows notification image from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). WindowsImage.asset( String assetName, { required this.altText, this.addQueryParams = false, this.placement, this.crop, - }) : - file = kDebugMode - ? Uri.file(File(assetName).absolute.path, windows: true) - : Uri.parse('ms-appx:///$assetName'); + }) : file = kDebugMode + ? Uri.file(File(assetName).absolute.path, windows: true) + : Uri.parse('ms-appx:///$assetName'); /// Allowed Uri schemes for [WindowsImage]. static const Set allowedSchemes = { diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index cb31f4104..c1240c714 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -94,7 +94,7 @@ void main() => group('Details:', () { const WindowsColumn emptyColumn = WindowsColumn([]); final WindowsImage image = WindowsImage.file( - Uri.parse('ms-appx:///test/icon.png'), + Uri.parse('ms-appx:///test/icon.png'), altText: 'an icon'); const WindowsNotificationText text = WindowsNotificationText(text: 'Text'); From 86d10287205b2891ffae4d89bbd6e1d446511d69 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 00:54:26 -0500 Subject: [PATCH 03/37] Consolidated consdtructors --- .../example/lib/main.dart | 6 ++- .../example/lib/windows.dart | 36 +++---------- .../lib/src/details/notification_audio.dart | 52 ++++++------------- .../lib/src/details/notification_parts.dart | 24 ++++----- .../lib/src/details/xml/image.dart | 9 +--- .../test/details_test.dart | 13 ++--- 6 files changed, 43 insertions(+), 97 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index bdb1122f3..750fcd306 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -1331,8 +1331,10 @@ class _HomePageState extends State { ); final WindowsNotificationDetails windowsNotificationDetails = WindowsNotificationDetails( - audio: WindowsNotificationAudio.preset( - sound: WindowsNotificationSound.alarm5), + audio: WindowsNotificationAudio.asset( + 'sound/slow_spring_board.mp3', + debugModeFallback: WindowsNotificationSound.alarm5, + ), ); final NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index b54132f37..802765e26 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -46,12 +46,6 @@ List examples({ await _showWindowsNotificationWithImages(); }, ), - PaddedElevatedButton( - buttonText: 'Show notification with sound from file', - onPressed: () async { - await _showWindowsNotificationWithSound(); - }, - ), PaddedElevatedButton( buttonText: 'Show notifications with columns', onPressed: () async { @@ -207,13 +201,12 @@ Future _showWindowsNotificationWithImages() => flutterLocalNotificationsPlugin.show( id++, 'This notification has an image', - 'You can show images from ms-appx, ms-appdata, http(s), or file URIs. See the columns example for more.', + 'You can show images from assets or the network. See the columns example as well.', NotificationDetails( windows: WindowsNotificationDetails( images: [ - WindowsImage.file( - Uri.parse( - 'ms-appx:///data/flutter_assets/icons/4.0x/app_icon_density.png'), + WindowsImage.asset( + 'icons/4.0x/app_icon_density.png', altText: 'A beautiful image', ), ], @@ -221,41 +214,26 @@ Future _showWindowsNotificationWithImages() => ), ); -Future _showWindowsNotificationWithSound() => - flutterLocalNotificationsPlugin.show( - id++, - 'This notification has custom sound', - 'Not just a Windows preset. Only ms-appx or ms-appdata URIs are allowed though', - NotificationDetails( - windows: WindowsNotificationDetails( - audio: WindowsNotificationAudio.fromFile( - file: Uri.parse( - 'ms-appx:///data/flutter_assets/sound/slow_spring_board.mp3')), - ), - ), - ); - Future _showWindowsNotificationWithGroups() => flutterLocalNotificationsPlugin.show( id++, 'This notification has many groups', - 'Each group stays together', + 'Each group stays together. Web images only load in MSIX builds', NotificationDetails( windows: WindowsNotificationDetails( subtitle: 'Caption text is fainter', rows: [ WindowsRow([ WindowsColumn([ - WindowsImage.file( - Uri.parse( - 'ms-appx:///data/flutter_assets/icons/coworker.png'), + WindowsImage.asset( + 'icons/coworker.png', altText: 'A local image', ), const WindowsNotificationText( text: 'A local image', isCaption: true), ]), WindowsColumn([ - WindowsImage.file( + WindowsImage.network( Uri.parse('https://picsum.photos/100'), altText: 'A web image', ), diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 0bd3d05e7..2aa8072d3 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -1,7 +1,4 @@ -extension on Uri { - String get filename => pathSegments.last; - String get extension => pathSegments.last.split('.').last; -} +import 'package:flutter/foundation.dart'; /// A preset sound for a Windows notification. enum WindowsNotificationSound { @@ -101,41 +98,22 @@ class WindowsNotificationAudio { }) : isSilent = false, source = sound.name; - /// Audio from a file. See [allowedSchemes] and [allowedExtensions]. - WindowsNotificationAudio.fromFile({ - required Uri file, + /// Uses an audio file from a Flutter asset. + /// + /// Note that this will only work in release builds that have been packaged as + /// an MSIX installer. Currently, there is no way to test this in debug mode. + /// + /// Windows supports the following formats: `.aac`, `.flac`, `.m4a`, `.mp3`, + /// `.wav`, and `.wma`. + WindowsNotificationAudio.asset( + String assetName, { this.shouldLoop = false, + WindowsNotificationSound debugModeFallback = + WindowsNotificationSound.defaultSound, }) : isSilent = false, - source = file.toString() { - if (!allowedSchemes.contains(file.scheme)) { - throw ArgumentError.value( - file.toString(), - 'WindowsNotificationAudio.file', - 'URI scheme must be one of the following schemes: $allowedSchemes', - ); - } - if (!file.filename.contains('.') || - !allowedExtensions.contains(file.extension)) { - throw ArgumentError.value( - file.toString(), - 'WindowsNotificationAudio.file', - 'File extension must be one of the following: $allowedExtensions', - ); - } - } - - /// Allowed Uri schemes for [WindowsNotificationAudio.fromFile]. - static const Set allowedSchemes = {'ms-appx', 'ms-resource'}; - - /// Allowed file extensions for [WindowsNotificationAudio.fromFile]. - static const Set allowedExtensions = { - 'aac', - 'flac', - 'm4a', - 'mp3', - 'wav', - 'wma' - }; + source = kDebugMode + ? debugModeFallback.name + : 'ms-appx:///data/flutter_assets/$assetName'; /// Whether this audio should loop. final bool shouldLoop; diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index d8386553e..c2d7ab954 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -32,9 +32,9 @@ enum WindowsImageCrop { /// An image in a Windows notification. class WindowsImage extends WindowsNotificationPart { - /// Creates a Windows notification image. - const WindowsImage.file( - this.file, { + /// Creates a Windows notification image from a network image. + const WindowsImage.network( + this.uri, { required this.altText, this.addQueryParams = false, this.placement, @@ -42,24 +42,18 @@ class WindowsImage extends WindowsNotificationPart { }); /// Creates a Windows notification image from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). + /// + /// In debug builds, this will use a `file:///` URI, but in release builds, it will use an + /// `ms-appx:///` URI. Note that release builds must be packaged in an MSIX installer to work. WindowsImage.asset( String assetName, { required this.altText, this.addQueryParams = false, this.placement, this.crop, - }) : file = kDebugMode + }) : uri = kDebugMode ? Uri.file(File(assetName).absolute.path, windows: true) - : Uri.parse('ms-appx:///$assetName'); - - /// Allowed Uri schemes for [WindowsImage]. - static const Set allowedSchemes = { - 'http', - 'https', - 'ms-appx', - 'file', - 'ms-appdata' - }; + : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); /// Whether Windows should add URL query parameters when fetching the image. final bool addQueryParams; @@ -68,7 +62,7 @@ class WindowsImage extends WindowsNotificationPart { final String altText; /// The source of the image. - final Uri file; + final Uri uri; /// Where this image will be placed. Null indicates below the notification. final WindowsImagePlacement? placement; diff --git a/flutter_local_notifications_windows/lib/src/details/xml/image.dart b/flutter_local_notifications_windows/lib/src/details/xml/image.dart index 2b89674a4..cbf869654 100644 --- a/flutter_local_notifications_windows/lib/src/details/xml/image.dart +++ b/flutter_local_notifications_windows/lib/src/details/xml/image.dart @@ -8,17 +8,10 @@ extension ImageToXml on WindowsImage { /// /// See: https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-image void buildXml(XmlBuilder builder) { - if (!file.isAbsolute) { - throw ArgumentError.value( - file.path, - 'WindowsImage.file', - 'File path must be absolute', - ); - } builder.element( 'image', attributes: { - 'src': file.toString(), + 'src': uri.toString(), 'alt': altText, 'addImageQuery': addQueryParams.toString(), if (placement != null) 'placement': placement!.name, diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index c1240c714..18d1fd593 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -93,9 +93,10 @@ void main() => group('Details:', () { test('Rows', () { const WindowsColumn emptyColumn = WindowsColumn([]); - final WindowsImage image = WindowsImage.file( - Uri.parse('ms-appx:///test/icon.png'), - altText: 'an icon'); + final WindowsImage image = WindowsImage.asset( + 'test/icon.png', + altText: 'an icon', + ); const WindowsNotificationText text = WindowsNotificationText(text: 'Text'); final WindowsColumn simpleColumn = @@ -131,11 +132,11 @@ void main() => group('Details:', () { }); test('Images', () async { - final WindowsImage simpleImage = WindowsImage.file( - Uri.parse('ms-appx:///asset.png'), + final WindowsImage simpleImage = WindowsImage.asset( + 'asset.png', altText: 'an icon', ); - final WindowsImage complexImage = WindowsImage.file( + final WindowsImage complexImage = WindowsImage.network( Uri.parse('https://picsum.photos/500'), altText: 'an icon', addQueryParams: true, From 4e1bf65e2a270f4a8c8bbd7013544a3d2a7322c1 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:01:37 -0500 Subject: [PATCH 04/37] Bumped versions --- flutter_local_notifications/CHANGELOG.md | 28 +++++++++++-------- flutter_local_notifications/pubspec.yaml | 4 +-- .../CHANGELOG.md | 4 +++ .../pubspec.yaml | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 160e1c52d..3d7243862 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,3 +1,7 @@ +## [19.0.0-dev.4] + +* [Windows] Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors + ## [19.0.0-dev.3] * [iOS][macOS] **Breaking changes** the `DarwinNotificationActionOption` and `DarwinNotificationCategoryOption` are now enhanced enums with values accessible through the `value` property that are exactly the same as their native representations. Previously a bitwise left shift operation was applied to the index value @@ -13,7 +17,7 @@ * **Breaking change** bumped minimum Flutter SDK requirement to 3.19.0 and Dart SDK requirement to 3.3.0 * **Breaking change** [iOS] removed `uiLocalNotificationDateInterpretation` parameter from `zonedSchedule()` method. This was done as it actually no relevant as of the 18.0.0 that dropped support for iOS versions older than 10, which in turn meant that the deprecated `UILocalNotification` APIs from Apple were no longer used. The corresponding `UILocalNotificationDateInterpretation` enum has already been removed as well -* [Windows] Added support for Windows. Thanks to the PR from [Levi Lesches](https://github.com/Levi-Lesches/) that continued from the contributions from +* [Windows] Added support for Windows. Thanks to the PR from [Levi Lesches](https://github.com/Levi-Lesches/) that continued from the contributions from * Bumped `timezone` dependency so that minimum version is now 0.10.0 ## [18.0.1] @@ -167,7 +171,7 @@ # [15.1.0] * [iOS][macOS] added the ability to request provisional permissions. On iOS, this is only applicable to iOS 12 or newer. On macOS, this property is only applicable to macOS 10.14 or newer. Thanks to the PR from [Tokenyet](https://github.com/MaikuB/flutter_local_notifications/pull/2022) - + # [15.0.1] * [Android] fixed issue [2033](https://github.com/MaikuB/flutter_local_notifications/issues/2033) where notifications on scheduled using older version of the plugin would fail to have the next subsequent ones scheduled. This issue started occuring in 14.0 where support for inexact notifications was added using the `ScheduleMode` enum that was added and resulted in the deprecation of `androidAllowWhileIdle`. A mechanism was added to help "migrate" old notifications that had `androidAllowWhileIdle` specified but didn't account for how there are recurring notifications that were scheduled using older versions of the plugin prior to `androidAllowWhile` being added. This was also released part of the 14.1.2 hotfix release @@ -176,7 +180,7 @@ * **Breaking change** removed deprecated `schedule()`, `showDailyAtTime()` and `showWeeklyAtDayAndTime()` methods. Notifications that were scheduled prior to this release should still work * **Breaking change** removed `Time` class -* [Linux] **Breaking change** calling `zonedSchedule()` on Linux will now throw an `UnimplementedError` to align with how their is a Linux implementation but the method hasn't been implemented +* [Linux] **Breaking change** calling `zonedSchedule()` on Linux will now throw an `UnimplementedError` to align with how their is a Linux implementation but the method hasn't been implemented * [iOS][macOS] **Breaking change** added supported for banner and list presentation options for iOS and macOS that is applicable for iOS 14.0 or newer and macOS 11 or newer. This is a breaking change as the values default to true and the alert presentation option is no longer applicable on these OS versions as Apple has deprecated it to be replaced by the banner and list presentations. Please ensure that if you target these OS versions that you configure the options appropriately for your application. * [Android] updated tags used when writing error logs. For corrupt scheduled notifications and error is logged the tag is now `ScheduledNotifReceiver` instead of `ScheduledNotifReceiver`. When logging that exact alarm permissions have been revoked the the tag is now `FLTLocalNotifPlugin` instead of `notification` * Updated API documentation related to the iOS/macOS notification presentation options to include links to Apple's documentations to show what they correspond to @@ -272,10 +276,10 @@ ... } ``` - + # [12.0.4] -* Fixed issue [1796](https://github.com/MaikuB/flutter_local_notifications/issues/1796) where a `java.lang.ClassCastException` may be thrown on some Android devices when the `onDidReceiveBackgroundNotificationResponse` has been specified when calling `initialize()` +* Fixed issue [1796](https://github.com/MaikuB/flutter_local_notifications/issues/1796) where a `java.lang.ClassCastException` may be thrown on some Android devices when the `onDidReceiveBackgroundNotificationResponse` has been specified when calling `initialize()` # [12.0.3+1] @@ -299,7 +303,7 @@ # [12.0.1] -* [Android][iOS] fixed issue [1721](https://github.com/MaikuB/flutter_local_notifications/issues/1721) where a crash occurs upon tapping on a notification action fbut the `onDidReceiveBackgroundNotificationResponse` optional callback hasn't been specified. +* [Android][iOS] fixed issue [1721](https://github.com/MaikuB/flutter_local_notifications/issues/1721) where a crash occurs upon tapping on a notification action fbut the `onDidReceiveBackgroundNotificationResponse` optional callback hasn't been specified. * [iOS] suppressed deprecation warnings where plugin was Apple's old notification APIs to support older iOS devices # [12.0.0] @@ -333,7 +337,7 @@ * `GET_ACTIVE_NOTIFICATION_MESSAGING_STYLE_ERROR_CODE` -> `getActiveNotificationMessagingStyle` * `PERMISSION_REQUEST_IN_PROGRESS` -> `permissionRequestInProgress` * [Android] **Breaking change** the `category` of the `AndroidNotificationDetails` now requires an instance of the newly added `AndroidNotificationCategory` class instead of a string. This was to improve the discoverability of the APIs and improve the semantics as the category can specified in a similar fashion to using an enum value -* **Breaking change** callbacks have now been reworked. There are now the following callbacks and both will pass an instance of the `NotificationResponse` class +* **Breaking change** callbacks have now been reworked. There are now the following callbacks and both will pass an instance of the `NotificationResponse` class * `onDidReceiveNotificationResponse`: invoked only when the app is running. This works for when a user has selected a notification or notification action. This replaces the `onSelectNotification` callback that existed before. For notification actions, the action needs to be configured to indicate the the app or user interface should be shown on invoking the action for this callback to be invoked i.e. by specifying the `DarwinNotificationActionOption.foreground` option on iOS and the `showsUserInterface` property on Android. On macOS and Linux, as there's no support for background isolates it will always invoke this callback * `onDidReceiveBackgroundNotificationResponse`: invoked on a background isolate for when a user has selected a notification action. This replaces the `onSelectNotificationAction` callback * **Breaking change** the `NotificationAppLaunchDetails` has been updated to contain an instance `NotificationResponse` class with the `payload` belonging to the `NotificationResponse` class. This is to allow knowing more details about what caused the app to launch e.g. if a notification action was used to do so @@ -435,7 +439,7 @@ # [9.2.0] * [Android] Added `areNotificationsEnabled()` method to `AndroidFlutterLocalNotificationsPlugin`. This allows querying if notifications are enabled for the app calling the method. Thanks to the PR from [Konstantin Pelz](https://github.com/komape) -* [Linux] Fix `initialize()` returning null all the time instead of returning an appropriate boolean value to indicate if plugin has been initialised +* [Linux] Fix `initialize()` returning null all the time instead of returning an appropriate boolean value to indicate if plugin has been initialised # [9.1.5] @@ -836,7 +840,7 @@ Please note that there are a number of breaking changes in this release to impro * `BitmapSource.Drawable` -> `DrawableResourceAndroidBitmap` * `BitmapSource.FilePath` -> `FilePathAndroidBitmap` - Each of these subclasses has a constructor that an argument referring to the bitmap itself. For example, if you previously had the following code + Each of these subclasses has a constructor that an argument referring to the bitmap itself. For example, if you previously had the following code ```dart var androidPlatformChannelSpecifics = AndroidNotificationDetails( @@ -888,7 +892,7 @@ Please note that there are a number of breaking changes in this release to impro * The `DefaultStyleInformation` class now implements the `StyleInformation` class instead of extending it * Where possible, classes in the plugins have been updated to provide `const` constructors * Updates to API docs and readme -* Bump Android dependencies +* Bump Android dependencies * Fixed a grammar issue 0.9.1 changelog entry # [1.3.0] @@ -1164,7 +1168,7 @@ Please note that there are a number of breaking changes in this release to impro # [0.4.2] -* **Breaking change** Fix issue [127](https://github.com/MaikuB/flutter_local_notifications/issues/127) by changing plugin to Android Support Library version 27.1.1, compile and target SDK version to 27 due to issues Flutter has with API 28. +* **Breaking change** Fix issue [127](https://github.com/MaikuB/flutter_local_notifications/issues/127) by changing plugin to Android Support Library version 27.1.1, compile and target SDK version to 27 due to issues Flutter has with API 28. # [0.4.1+1] * Remove unused code in example app @@ -1174,7 +1178,7 @@ Please note that there are a number of breaking changes in this release to impro * **Breaking change** renamed the `selectNotification` callback exposed by the `initialize` function to `onSelectNotification` * **Breaking change** renamed the `MessageHandler` typedef to `SelectNotificationCallback` * **Breaking change** updated plugin to Android Support Library version 28.0, compile and target SDK version to 28 -* Address issue [115](https://github.com/MaikuB/flutter_local_notifications/issues/115) by adding validation to the notification ID values. This ensure they're within the range of a 32-bit integer as notification IDs on Android need to be within that range. Note that an `ArgumentError` is thrown when a value is out of range. +* Address issue [115](https://github.com/MaikuB/flutter_local_notifications/issues/115) by adding validation to the notification ID values. This ensure they're within the range of a 32-bit integer as notification IDs on Android need to be within that range. Note that an `ArgumentError` is thrown when a value is out of range. * Updated the Android Integration section around registering receivers via the Android manifest as per the suggestion in [116](https://github.com/MaikuB/flutter_local_notifications/issues/116) * Updated version of the http dependency for used by the example app diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index ef112b444..87ec44f7c 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_local_notifications description: A cross platform plugin for displaying and scheduling local notifications for Flutter applications with the ability to customise for each platform. -version: 19.0.0-dev.3 +version: 19.0.0-dev.4 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications issue_tracker: https://github.com/MaikuB/flutter_local_notifications/issues @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter flutter_local_notifications_linux: ^5.0.1-dev.1 - flutter_local_notifications_windows: ^1.0.0-dev.2 + flutter_local_notifications_windows: ^1.0.0-dev.3 flutter_local_notifications_platform_interface: ^8.1.0-dev.1 timezone: ^0.10.0 diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 3fa86b430..85c934a3a 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## [1.0.0-dev.3] + +* Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors + ## [1.0.0-dev.2] * Fixed an issue that happens with MSIX app builds. Thanks to PR from [Levi Lesches](https://github.com/Levi-Lesches) diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 77d32a580..6352692f6 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_local_notifications_windows description: Windows implementation of the flutter_local_notifications plugin -version: 1.0.0-dev.2 +version: 1.0.0-dev.3 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_windows environment: From 3b9caadb7090e8019ab764eb011ae6568d765e28 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:13:05 -0500 Subject: [PATCH 05/37] Configure melos to use flutter test for Windows --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index c51fd59f0..db1c14cca 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,7 +32,7 @@ scripts: scope: "*example*" test:unit:windows: description: Runs Windows-specific unit tests - run: melos exec -c 1 -- "dart test" + run: melos exec -c 1 -- "flutter test" packageFilters: scope: '*_windows' test:integration: From 0e3da90306ea2f44d8ebbe81ff9550848ae745c4 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:14:04 -0500 Subject: [PATCH 06/37] Update changelog --- flutter_local_notifications/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 3d7243862..19f63ee57 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,6 +1,6 @@ ## [19.0.0-dev.4] -* [Windows] Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors +* [Windows] Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors. This is a breaking change from `19.0.0-dev.1`. ## [19.0.0-dev.3] From 0cdb65d362431bb29343c573ed71cf28d162e119 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:31:31 -0500 Subject: [PATCH 07/37] Updated changelog again --- flutter_local_notifications_windows/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 85c934a3a..c6ca9544b 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## [1.0.0-dev.3] -* Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors +* Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors. This is a breaking change from `1.0.0-dev.2`. ## [1.0.0-dev.2] From 17d175cf40d108e89a94b71e7845271480811993 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:45:48 -0500 Subject: [PATCH 08/37] Elaborated on changelogs --- flutter_local_notifications/CHANGELOG.md | 4 +++- flutter_local_notifications_windows/CHANGELOG.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 19f63ee57..7315157f9 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,6 +1,8 @@ ## [19.0.0-dev.4] -* [Windows] Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors. This is a breaking change from `19.0.0-dev.1`. +* [Windows] **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: + * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` + * Instead of `WindowsImage.file()`, use `WindowsImage.asset()` or `WindowsImage.network()` ## [19.0.0-dev.3] diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index c6ca9544b..4f67b34e5 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,6 +1,8 @@ ## [1.0.0-dev.3] -* Reworked the APIs around custom images and audio. Use the new `.asset()` or `.network()` constructors. This is a breaking change from `1.0.0-dev.2`. +* **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: + * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` + * Instead of `WindowsImage.file()`, use `WindowsImage.asset()` or `WindowsImage.network()` ## [1.0.0-dev.2] From 46e5ce38d5e1b8de07fbb0f204fdaf2fe8a9d505 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 01:47:50 -0500 Subject: [PATCH 09/37] Updated docs --- .../lib/src/details/notification_audio.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 2aa8072d3..1ded6c09e 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -101,7 +101,9 @@ class WindowsNotificationAudio { /// Uses an audio file from a Flutter asset. /// /// Note that this will only work in release builds that have been packaged as - /// an MSIX installer. Currently, there is no way to test this in debug mode. + /// an MSIX installer. There is no way to play a custom audio file in debug + /// mode, but if you pass a [WindowsNotificationSound] for `debugModeFallback` + /// it will be used instead, which can still be useful. /// /// Windows supports the following formats: `.aac`, `.flac`, `.m4a`, `.mp3`, /// `.wav`, and `.wma`. From 155f17fd3f5d4b60aaa8e1f7c42f420d4509f0c7 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 02:32:52 -0500 Subject: [PATCH 10/37] Converted WindowsImage back to URIs --- .../lib/src/details/notification_action.dart | 10 ++-- .../lib/src/details/notification_parts.dart | 56 ++++++++++++++----- .../lib/src/details/xml/action.dart | 11 +--- .../test/details_test.dart | 24 ++++---- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/details/notification_action.dart b/flutter_local_notifications_windows/lib/src/details/notification_action.dart index 946785470..0b0ab1478 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_action.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_action.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +import 'notification_parts.dart'; // NOTE: All enum values in this file have Windows RT-specific names. // If you change their Dart names, be sure to override [Enum.name]. @@ -51,13 +51,13 @@ enum WindowsActionPlacement { /// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-action#attributes class WindowsAction { /// Constructs a Windows notification button from parameters. - const WindowsAction({ + WindowsAction({ required this.content, required this.arguments, this.activationType = WindowsActivationType.foreground, this.activationBehavior = WindowsNotificationBehavior.dismiss, this.placement, - this.image, + this.imageUri, this.inputId, this.buttonStyle, this.tooltip, @@ -89,7 +89,9 @@ class WindowsAction { /// Images must be white with a transparent background, and should be /// 16x16 pixels with no padding. If you provide an image for one button, /// you should provide images for all your buttons. - final File? image; + /// + /// Check the docs for [WindowsImage] for an explanation of supported URIs. + final Uri? imageUri; /// The ID of an input box. /// diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index c2d7ab954..4a2c47d49 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -33,27 +33,55 @@ enum WindowsImageCrop { /// An image in a Windows notification. class WindowsImage extends WindowsNotificationPart { /// Creates a Windows notification image from a network image. - const WindowsImage.network( + /// + /// Windows supports a few different URI types, and supports them differently + /// depending on if your app is packaged as an MSIX. Refer to the following: + /// + /// | URI | Debug | Release (EXE) | Release (MSIX) | + /// |--------|--------|--------|--------| + /// | `file:///` | ✅ | ✅| ✅ | + /// | `http(s)://` | ❌ | ❌ | ✅ | + /// | `ms-appx://` | ❌ | ❌ | ✅ | + /// | `assetUri()` | ✅ | ❌ | ✅ | + /// + /// Each URI type has different uses: + /// - For images that are known ahead of time and can be used as Flutter + /// assets, use [assetUri], which will return a file URI in debug + /// mode and an `ms-appx` URI in release mode, for the best of both worlds. + /// - For images from the web, use an `https` or `http` URI, but note that + /// these only work in MSIX apps. If you need a network image without using + /// MSIX, consider downloading it directly and using a file URI after. + /// - For images that come from the user's device, or have to be retrieved at + /// runtime, use a file URI, but as always, be aware of how paths might change + /// from your device to your users. Note that file URIs must be absolute + /// paths, not relative, which can be complicated if referring to MSIX assets. + /// - For images that are bundled with your app but not through Flutter, use + /// an `ms-appx` URI. + WindowsImage( this.uri, { required this.altText, this.addQueryParams = false, this.placement, this.crop, - }); + }) { + if (!_supportedSchemes.contains(uri.scheme)) { + throw UnsupportedError('WindowsImage.uri: Invalid uri. Check the docs'); + } + } + + static const Set _supportedSchemes = { + 'file', 'http', 'https', 'ms-appx', 'ms-appdata', + }; - /// Creates a Windows notification image from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). + /// Creates a URI for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). /// - /// In debug builds, this will use a `file:///` URI, but in release builds, it will use an - /// `ms-appx:///` URI. Note that release builds must be packaged in an MSIX installer to work. - WindowsImage.asset( - String assetName, { - required this.altText, - this.addQueryParams = false, - this.placement, - this.crop, - }) : uri = kDebugMode - ? Uri.file(File(assetName).absolute.path, windows: true) - : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); + /// This URI resolves to a file URI in debug mode, and an `ms-appx` URI in + /// release mode. Note that this function just assumes your release build will + /// be packaged as an MSIX. It is highly recommended that you use an MSIX for + /// your release, but if you can't, don't use this function. + static Uri assetUri(String assetName) => kDebugMode + ? Uri.file(File(assetName).absolute.path, windows: true) + : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); /// Whether Windows should add URL query parameters when fetching the image. final bool addQueryParams; diff --git a/flutter_local_notifications_windows/lib/src/details/xml/action.dart b/flutter_local_notifications_windows/lib/src/details/xml/action.dart index 67a39d024..d31a3468a 100644 --- a/flutter_local_notifications_windows/lib/src/details/xml/action.dart +++ b/flutter_local_notifications_windows/lib/src/details/xml/action.dart @@ -7,13 +7,6 @@ extension ActionToXml on WindowsAction { /// /// See: https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-action#syntax void buildXml(XmlBuilder builder) { - if (image != null && !image!.isAbsolute) { - throw ArgumentError.value( - image!.path, - 'WindowsImage.file', - 'File path must be absolute', - ); - } builder.element( 'action', attributes: { @@ -22,9 +15,7 @@ extension ActionToXml on WindowsAction { 'activationType': activationType.name, 'afterActivationBehavior': activationBehavior.name, if (placement != null) 'placement': placement!.name, - if (image != null) - 'imageUri': - Uri.file(image!.absolute.path, windows: true).toFilePath(), + if (imageUri != null) 'imageUri': imageUri!.toString(), if (inputId != null) 'hint-inputId': inputId!, if (buttonStyle != null) 'hint-buttonStyle': buttonStyle!.name, if (tooltip != null) 'hint-toolTip': tooltip!, diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index 18d1fd593..81f9a2076 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter_local_notifications_windows/flutter_local_notifications_windows.dart'; import 'package:test/test.dart'; @@ -53,7 +51,7 @@ void main() => group('Details:', () { bindings: {'message': 'Hello, Mr. Person'}))); test('Actions', () { - const WindowsAction simpleAction = + final WindowsAction simpleAction = WindowsAction(content: 'Press me', arguments: '123'); final WindowsAction complexAction = WindowsAction( content: 'content', @@ -62,10 +60,10 @@ void main() => group('Details:', () { buttonStyle: WindowsButtonStyle.success, inputId: 'input-id', tooltip: 'tooltip', - image: File('test/icon.png').absolute, + imageUri: WindowsImage.assetUri('test/icon.png'), ); plugin - ..testDetails(const WindowsNotificationDetails( + ..testDetails(WindowsNotificationDetails( actions: [simpleAction])) ..testDetails(WindowsNotificationDetails( actions: [complexAction])) @@ -93,8 +91,8 @@ void main() => group('Details:', () { test('Rows', () { const WindowsColumn emptyColumn = WindowsColumn([]); - final WindowsImage image = WindowsImage.asset( - 'test/icon.png', + final WindowsImage image = WindowsImage( + WindowsImage.assetUri('test/icon.png'), altText: 'an icon', ); const WindowsNotificationText text = @@ -132,11 +130,11 @@ void main() => group('Details:', () { }); test('Images', () async { - final WindowsImage simpleImage = WindowsImage.asset( - 'asset.png', + final WindowsImage simpleImage = WindowsImage( + WindowsImage.assetUri('asset.png'), altText: 'an icon', ); - final WindowsImage complexImage = WindowsImage.network( + final WindowsImage complexImage = WindowsImage( Uri.parse('https://picsum.photos/500'), altText: 'an icon', addQueryParams: true, @@ -169,7 +167,7 @@ void main() => group('Details:', () { WindowsSelection(id: 'item3', content: 'Item 3'), ], ); - const WindowsAction action = WindowsAction( + final WindowsAction action = WindowsAction( content: 'Submit', arguments: 'submit', inputId: 'input', @@ -184,10 +182,10 @@ void main() => group('Details:', () { inputs: List.filled(5, textInput), ), ) - ..testDetails(const WindowsNotificationDetails( + ..testDetails(WindowsNotificationDetails( inputs: [textInput], actions: [action])) - ..testDetails(const WindowsNotificationDetails( + ..testDetails(WindowsNotificationDetails( inputs: [selection, textInput], actions: [action])); expect( From f5809e9c5a9d55f30cca57e308dce2bd55804125 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 02:35:38 -0500 Subject: [PATCH 11/37] Updated examples and tests --- .../example/lib/main.dart | 2 +- .../example/lib/windows.dart | 16 ++++++++-------- .../lib/src/details/notification_action.dart | 2 +- .../lib/src/details/notification_parts.dart | 12 ++---------- .../test/details_test.dart | 10 +++++----- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 750fcd306..053823219 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -1061,7 +1061,7 @@ class _HomePageState extends State { WindowsAction( content: 'Image', arguments: 'image', - image: File('icons/coworker.png').absolute, + imageUri: WindowsImage.assetUri('icons/coworker.png'), ), const WindowsAction( content: 'Context', diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index 802765e26..8136ab0c9 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -142,7 +142,7 @@ Future _showWindowsNotificationWithScenarios() async { scenario: WindowsNotificationScenario.alarm, actions: [ WindowsAction(content: 'Button', arguments: 'button') - ]), + ],), ), ); await flutterLocalNotificationsPlugin.show( @@ -154,7 +154,7 @@ Future _showWindowsNotificationWithScenarios() async { scenario: WindowsNotificationScenario.incomingCall, actions: [ WindowsAction(content: 'Button', arguments: 'button') - ]), + ],), ), ); await flutterLocalNotificationsPlugin.show( @@ -205,8 +205,8 @@ Future _showWindowsNotificationWithImages() => NotificationDetails( windows: WindowsNotificationDetails( images: [ - WindowsImage.asset( - 'icons/4.0x/app_icon_density.png', + WindowsImage( + WindowsImage.assetUri('icons/4.0x/app_icon_density.png'), altText: 'A beautiful image', ), ], @@ -225,15 +225,15 @@ Future _showWindowsNotificationWithGroups() => rows: [ WindowsRow([ WindowsColumn([ - WindowsImage.asset( - 'icons/coworker.png', + WindowsImage( + WindowsImage.assetUri('icons/coworker.png'), altText: 'A local image', ), const WindowsNotificationText( - text: 'A local image', isCaption: true), + text: 'A local image', isCaption: true,), ]), WindowsColumn([ - WindowsImage.network( + WindowsImage( Uri.parse('https://picsum.photos/100'), altText: 'A web image', ), diff --git a/flutter_local_notifications_windows/lib/src/details/notification_action.dart b/flutter_local_notifications_windows/lib/src/details/notification_action.dart index 0b0ab1478..49ac5dc6c 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_action.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_action.dart @@ -51,7 +51,7 @@ enum WindowsActionPlacement { /// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-action#attributes class WindowsAction { /// Constructs a Windows notification button from parameters. - WindowsAction({ + const WindowsAction({ required this.content, required this.arguments, this.activationType = WindowsActivationType.foreground, diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 4a2c47d49..d6de5400d 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -57,21 +57,13 @@ class WindowsImage extends WindowsNotificationPart { /// paths, not relative, which can be complicated if referring to MSIX assets. /// - For images that are bundled with your app but not through Flutter, use /// an `ms-appx` URI. - WindowsImage( + const WindowsImage( this.uri, { required this.altText, this.addQueryParams = false, this.placement, this.crop, - }) { - if (!_supportedSchemes.contains(uri.scheme)) { - throw UnsupportedError('WindowsImage.uri: Invalid uri. Check the docs'); - } - } - - static const Set _supportedSchemes = { - 'file', 'http', 'https', 'ms-appx', 'ms-appdata', - }; + }); /// Creates a URI for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). /// diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index 81f9a2076..bdd715fa6 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -51,7 +51,7 @@ void main() => group('Details:', () { bindings: {'message': 'Hello, Mr. Person'}))); test('Actions', () { - final WindowsAction simpleAction = + const WindowsAction simpleAction = WindowsAction(content: 'Press me', arguments: '123'); final WindowsAction complexAction = WindowsAction( content: 'content', @@ -63,7 +63,7 @@ void main() => group('Details:', () { imageUri: WindowsImage.assetUri('test/icon.png'), ); plugin - ..testDetails(WindowsNotificationDetails( + ..testDetails(const WindowsNotificationDetails( actions: [simpleAction])) ..testDetails(WindowsNotificationDetails( actions: [complexAction])) @@ -167,7 +167,7 @@ void main() => group('Details:', () { WindowsSelection(id: 'item3', content: 'Item 3'), ], ); - final WindowsAction action = WindowsAction( + const WindowsAction action = WindowsAction( content: 'Submit', arguments: 'submit', inputId: 'input', @@ -182,10 +182,10 @@ void main() => group('Details:', () { inputs: List.filled(5, textInput), ), ) - ..testDetails(WindowsNotificationDetails( + ..testDetails(const WindowsNotificationDetails( inputs: [textInput], actions: [action])) - ..testDetails(WindowsNotificationDetails( + ..testDetails(const WindowsNotificationDetails( inputs: [selection, textInput], actions: [action])); expect( From 016fcc03d0d7794431473420215f59134fd95a2b Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 02:42:36 -0500 Subject: [PATCH 12/37] Moved docs --- .../lib/src/details/notification_parts.dart | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index d6de5400d..02571ac06 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -31,32 +31,32 @@ enum WindowsImageCrop { } /// An image in a Windows notification. -class WindowsImage extends WindowsNotificationPart { - /// Creates a Windows notification image from a network image. - /// +/// /// Windows supports a few different URI types, and supports them differently - /// depending on if your app is packaged as an MSIX. Refer to the following: - /// - /// | URI | Debug | Release (EXE) | Release (MSIX) | - /// |--------|--------|--------|--------| - /// | `file:///` | ✅ | ✅| ✅ | - /// | `http(s)://` | ❌ | ❌ | ✅ | - /// | `ms-appx://` | ❌ | ❌ | ✅ | - /// | `assetUri()` | ✅ | ❌ | ✅ | - /// - /// Each URI type has different uses: - /// - For images that are known ahead of time and can be used as Flutter - /// assets, use [assetUri], which will return a file URI in debug - /// mode and an `ms-appx` URI in release mode, for the best of both worlds. - /// - For images from the web, use an `https` or `http` URI, but note that - /// these only work in MSIX apps. If you need a network image without using - /// MSIX, consider downloading it directly and using a file URI after. - /// - For images that come from the user's device, or have to be retrieved at - /// runtime, use a file URI, but as always, be aware of how paths might change - /// from your device to your users. Note that file URIs must be absolute - /// paths, not relative, which can be complicated if referring to MSIX assets. - /// - For images that are bundled with your app but not through Flutter, use - /// an `ms-appx` URI. +/// depending on if your app is packaged as an MSIX. Refer to the following: +/// +/// | URI | Debug | Release (EXE) | Release (MSIX) | +/// |--------|--------|--------|--------| +/// | `file:///` | ✅ | ✅| ✅ | +/// | `http(s)://` | ❌ | ❌ | ✅ | +/// | `ms-appx://` | ❌ | ❌ | ✅ | +/// | `assetUri()` | ✅ | ❌ | ✅ | +/// +/// Each URI type has different uses: +/// - For images that are known ahead of time and can be used as Flutter +/// assets, use [assetUri], which will return a file URI in debug +/// mode and an `ms-appx` URI in release mode, for the best of both worlds. +/// - For images from the web, use an `https` or `http` URI, but note that +/// these only work in MSIX apps. If you need a network image without using +/// MSIX, consider downloading it directly and using a file URI after. +/// - For images that come from the user's device, or have to be retrieved at +/// runtime, use a file URI, but as always, be aware of how paths might change +/// from your device to your users. Note that file URIs must be absolute +/// paths, not relative, which can be complicated if referring to MSIX assets. +/// - For images that are bundled with your app but not through Flutter, use +/// an `ms-appx` URI. +class WindowsImage extends WindowsNotificationPart { + /// Creates a Windows notification image from an image URI. const WindowsImage( this.uri, { required this.altText, From 7b72c88542fe1a2a4deea5dec5a2afc02350d9a8 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 02:45:27 -0500 Subject: [PATCH 13/37] Updated changelogs --- flutter_local_notifications/CHANGELOG.md | 2 +- flutter_local_notifications_windows/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 7315157f9..84077a6b0 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -2,7 +2,7 @@ * [Windows] **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` - * Instead of `WindowsImage.file()`, use `WindowsImage.asset()` or `WindowsImage.network()` + * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported ## [19.0.0-dev.3] diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 4f67b34e5..a93ca049b 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -2,7 +2,7 @@ * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` - * Instead of `WindowsImage.file()`, use `WindowsImage.asset()` or `WindowsImage.network()` + * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported ## [1.0.0-dev.2] From 97c38277e927b18a2bb84f0604c9df2bb6938673 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 02:45:51 -0500 Subject: [PATCH 14/37] Formatted --- .../example/lib/windows.dart | 22 +++++++++++-------- .../lib/src/details/notification_parts.dart | 6 ++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index 8136ab0c9..f7a373c07 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -139,10 +139,11 @@ Future _showWindowsNotificationWithScenarios() async { null, const NotificationDetails( windows: WindowsNotificationDetails( - scenario: WindowsNotificationScenario.alarm, - actions: [ - WindowsAction(content: 'Button', arguments: 'button') - ],), + scenario: WindowsNotificationScenario.alarm, + actions: [ + WindowsAction(content: 'Button', arguments: 'button') + ], + ), ), ); await flutterLocalNotificationsPlugin.show( @@ -151,10 +152,11 @@ Future _showWindowsNotificationWithScenarios() async { null, const NotificationDetails( windows: WindowsNotificationDetails( - scenario: WindowsNotificationScenario.incomingCall, - actions: [ - WindowsAction(content: 'Button', arguments: 'button') - ],), + scenario: WindowsNotificationScenario.incomingCall, + actions: [ + WindowsAction(content: 'Button', arguments: 'button') + ], + ), ), ); await flutterLocalNotificationsPlugin.show( @@ -230,7 +232,9 @@ Future _showWindowsNotificationWithGroups() => altText: 'A local image', ), const WindowsNotificationText( - text: 'A local image', isCaption: true,), + text: 'A local image', + isCaption: true, + ), ]), WindowsColumn([ WindowsImage( diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 02571ac06..9608e1c0d 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -32,7 +32,7 @@ enum WindowsImageCrop { /// An image in a Windows notification. /// - /// Windows supports a few different URI types, and supports them differently +/// Windows supports a few different URI types, and supports them differently /// depending on if your app is packaged as an MSIX. Refer to the following: /// /// | URI | Debug | Release (EXE) | Release (MSIX) | @@ -72,8 +72,8 @@ class WindowsImage extends WindowsNotificationPart { /// be packaged as an MSIX. It is highly recommended that you use an MSIX for /// your release, but if you can't, don't use this function. static Uri assetUri(String assetName) => kDebugMode - ? Uri.file(File(assetName).absolute.path, windows: true) - : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); + ? Uri.file(File(assetName).absolute.path, windows: true) + : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); /// Whether Windows should add URL query parameters when fetching the image. final bool addQueryParams; From ae6368a7b8b80d5d55571765cc3b99319fdac150 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 31 Dec 2024 20:28:30 +1100 Subject: [PATCH 15/37] temporarily enable verbose logging --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index db1c14cca..81709b8e0 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,7 +32,7 @@ scripts: scope: "*example*" test:unit:windows: description: Runs Windows-specific unit tests - run: melos exec -c 1 -- "flutter test" + run: melos exec -c 1 --verbose -- "flutter test -v" packageFilters: scope: '*_windows' test:integration: From 0e128a243093fb4315d384c99e6bc0e0c4403160 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 31 Dec 2024 18:13:40 -0500 Subject: [PATCH 16/37] Updated to use new package:msix functions --- .../lib/src/details/notification_parts.dart | 33 +++++++++++-------- .../pubspec.yaml | 4 +++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 9608e1c0d..9431ba84d 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:msix/msix.dart'; /// A text or image element in a Windows notification. /// @@ -37,18 +38,19 @@ enum WindowsImageCrop { /// /// | URI | Debug | Release (EXE) | Release (MSIX) | /// |--------|--------|--------|--------| -/// | `file:///` | ✅ | ✅| ✅ | +/// | `file:///` | ✅ | ✅ | 🟨 | /// | `http(s)://` | ❌ | ❌ | ✅ | /// | `ms-appx://` | ❌ | ❌ | ✅ | -/// | `assetUri()` | ✅ | ❌ | ✅ | +/// | `assetUri()` | ✅ | ✅ | ✅ | /// /// Each URI type has different uses: -/// - For images that are known ahead of time and can be used as Flutter -/// assets, use [assetUri], which will return a file URI in debug -/// mode and an `ms-appx` URI in release mode, for the best of both worlds. +/// - For Flutter assets, use [assetUri], which return the correct file URI +/// for debug and release (exe) builds, and an `ms-appx` URI in MSIX builds. /// - For images from the web, use an `https` or `http` URI, but note that /// these only work in MSIX apps. If you need a network image without using -/// MSIX, consider downloading it directly and using a file URI after. +/// MSIX, consider downloading it directly and using a file URI after. Also +/// note that showing the notification will cause the image to be downloaded, +/// which could cause a small delay. Try to use small images. /// - For images that come from the user's device, or have to be retrieved at /// runtime, use a file URI, but as always, be aware of how paths might change /// from your device to your users. Note that file URIs must be absolute @@ -67,13 +69,18 @@ class WindowsImage extends WindowsNotificationPart { /// Creates a URI for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). /// - /// This URI resolves to a file URI in debug mode, and an `ms-appx` URI in - /// release mode. Note that this function just assumes your release build will - /// be packaged as an MSIX. It is highly recommended that you use an MSIX for - /// your release, but if you can't, don't use this function. - static Uri assetUri(String assetName) => kDebugMode - ? Uri.file(File(assetName).absolute.path, windows: true) - : Uri.parse('ms-appx:///data/flutter_assets/$assetName'); + /// - In debug mode, resolves to a file URI to the asset itself + /// - In non-MSIX release builds, resolves to a file URI to the bundled asset + /// - In MSIX releases, resolves to an `ms-appx` URI from [Msix.assetUri]. + static Uri assetUri(String assetName) { + if (kDebugMode) { + return Uri.file(File(assetName).absolute.path, windows: true); + } else if (Msix.hasPackageIdentity()) { + return Msix.assetUri(assetName); + } else { + return Uri.file(File('data/flutter_assets/$assetName').absolute.path, windows: true); + } + } /// Whether Windows should add URL query parameters when fetching the image. final bool addQueryParams; diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 6352692f6..2e2a9ddd7 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -15,6 +15,10 @@ dependencies: meta: ^1.11.0 timezone: ^0.10.0 xml: ^6.5.0 + msix: + git: + url: https://github.com/Levi-Lesches/msix + ref: is-msix dev_dependencies: ffigen: ^13.0.0 From 052d9c6cf4a3ca6cbf50cf418e8c5e5dcbf93a80 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 08:04:28 -0500 Subject: [PATCH 17/37] Use Msix.hasPackageIdentity for WindowsNotificationAudio.asset --- flutter_local_notifications/example/lib/main.dart | 2 +- .../lib/src/details/notification_audio.dart | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 053823219..77989e7a6 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -1333,7 +1333,7 @@ class _HomePageState extends State { WindowsNotificationDetails( audio: WindowsNotificationAudio.asset( 'sound/slow_spring_board.mp3', - debugModeFallback: WindowsNotificationSound.alarm5, + fallback: WindowsNotificationSound.alarm5, ), ); final NotificationDetails notificationDetails = NotificationDetails( diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 1ded6c09e..565c52690 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -1,4 +1,4 @@ -import 'package:flutter/foundation.dart'; +import 'package:msix/msix.dart'; /// A preset sound for a Windows notification. enum WindowsNotificationSound { @@ -101,21 +101,20 @@ class WindowsNotificationAudio { /// Uses an audio file from a Flutter asset. /// /// Note that this will only work in release builds that have been packaged as - /// an MSIX installer. There is no way to play a custom audio file in debug - /// mode, but if you pass a [WindowsNotificationSound] for `debugModeFallback` - /// it will be used instead, which can still be useful. + /// an MSIX installer. If you pass a [WindowsNotificationSound] for `fallback` + /// it will be used in debug and releases without MSIX. /// /// Windows supports the following formats: `.aac`, `.flac`, `.m4a`, `.mp3`, /// `.wav`, and `.wma`. WindowsNotificationAudio.asset( String assetName, { this.shouldLoop = false, - WindowsNotificationSound debugModeFallback = + WindowsNotificationSound fallback = WindowsNotificationSound.defaultSound, }) : isSilent = false, - source = kDebugMode - ? debugModeFallback.name - : 'ms-appx:///data/flutter_assets/$assetName'; + source = Msix.hasPackageIdentity() + ? Msix.assetUri(assetName).toString() + : fallback.name; /// Whether this audio should loop. final bool shouldLoop; From 5b0dacda4ef8b3fa6157ca87ba770df134ef4b30 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 10:39:27 -0500 Subject: [PATCH 18/37] Implemented hasPackageIdentity() and assetUri() --- .../example/lib/windows.dart | 4 ++ .../flutter_local_notifications_windows.dart | 5 +- .../lib/src/details/notification_audio.dart | 6 +-- .../lib/src/details/notification_parts.dart | 8 +-- .../lib/src/msix/ffi.dart | 49 +++++++++++++++++++ .../lib/src/msix/stub.dart | 28 +++++++++++ .../lib/src/plugin/ffi.dart | 1 + .../pubspec.yaml | 5 +- 8 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 flutter_local_notifications_windows/lib/src/msix/ffi.dart create mode 100644 flutter_local_notifications_windows/lib/src/msix/stub.dart diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index f7a373c07..878f7d82b 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -22,6 +22,10 @@ List examples({ 'Windows-specific examples', style: TextStyle(fontWeight: FontWeight.bold), ), + if (MsixUtils.hasPackageIdentity()) + const Text('Running as an MSIX, all features are available') + else + const Text('Running as an EXE, some features are not available'), PaddedElevatedButton( buttonText: 'Show short and long notifications notification', onPressed: () async { diff --git a/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart b/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart index 06a875848..430be5b20 100644 --- a/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart +++ b/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart @@ -1,2 +1,5 @@ export 'src/details.dart'; -export 'src/plugin/stub.dart' if (dart.library.ffi) 'src/plugin/ffi.dart'; +export 'src/msix/stub.dart' + if (dart.library.ffi) 'src/msix/ffi.dart'; +export 'src/plugin/stub.dart' + if (dart.library.ffi) 'src/plugin/ffi.dart'; diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 565c52690..92a4ef704 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -1,4 +1,4 @@ -import 'package:msix/msix.dart'; +import '../../flutter_local_notifications_windows.dart'; /// A preset sound for a Windows notification. enum WindowsNotificationSound { @@ -112,8 +112,8 @@ class WindowsNotificationAudio { WindowsNotificationSound fallback = WindowsNotificationSound.defaultSound, }) : isSilent = false, - source = Msix.hasPackageIdentity() - ? Msix.assetUri(assetName).toString() + source = MsixUtils.hasPackageIdentity() + ? MsixUtils.assetUri(assetName).toString() : fallback.name; /// Whether this audio should loop. diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 9431ba84d..a2b443f6d 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:msix/msix.dart'; +import '../../flutter_local_notifications_windows.dart'; /// A text or image element in a Windows notification. /// @@ -38,9 +38,9 @@ enum WindowsImageCrop { /// /// | URI | Debug | Release (EXE) | Release (MSIX) | /// |--------|--------|--------|--------| -/// | `file:///` | ✅ | ✅ | 🟨 | /// | `http(s)://` | ❌ | ❌ | ✅ | /// | `ms-appx://` | ❌ | ❌ | ✅ | +/// | `file:///` | ✅ | ✅ | 🟨 | /// | `assetUri()` | ✅ | ✅ | ✅ | /// /// Each URI type has different uses: @@ -75,8 +75,8 @@ class WindowsImage extends WindowsNotificationPart { static Uri assetUri(String assetName) { if (kDebugMode) { return Uri.file(File(assetName).absolute.path, windows: true); - } else if (Msix.hasPackageIdentity()) { - return Msix.assetUri(assetName); + } else if (MsixUtils.hasPackageIdentity()) { + return MsixUtils.assetUri(assetName); } else { return Uri.file(File('data/flutter_assets/$assetName').absolute.path, windows: true); } diff --git a/flutter_local_notifications_windows/lib/src/msix/ffi.dart b/flutter_local_notifications_windows/lib/src/msix/ffi.dart new file mode 100644 index 000000000..74d95090b --- /dev/null +++ b/flutter_local_notifications_windows/lib/src/msix/ffi.dart @@ -0,0 +1,49 @@ +import 'dart:ffi'; +import 'dart:io'; +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +import '../../flutter_local_notifications_windows.dart'; + +// ignore: avoid_classes_with_only_static_members +/// Helpful methods to support MSIX and package identity. +class MsixUtils { + /// Returns whether the current app was installed with an MSIX installer. + /// + /// Using an MSIX grants your application [package identity](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview), + /// which allows it to use [certain APIs](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps). + /// + /// Specifically, using an MSIX installer allows your app to: + /// - use [FlutterLocalNotificationsWindows.getActiveNotifications] + /// - use [FlutterLocalNotificationsWindows.cancel] + /// - use custom files for notification sounds + /// - use network sources for notifications + /// - use `ms-appx:///` URIs for resources + /// + /// These functions will simply do nothing or return empty data in apps + /// without package identity. Additionally: + /// - [WindowsImage.assetUri] will return a `file:///` or `ms-appx:///` URI, + /// depending on whether the app is running in debug, release, or as an MSIX. + /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps + /// with package identity, and a preset fallbacks for apps without. + static bool hasPackageIdentity() => using((Arena arena) { + final bool? cached = _hasPackageIdentity; + if (cached != null) { + return cached; + } else if (!Platform.isWindows) { + return false; + } else if (IsWindows8OrGreater() != 1) { + return false; + } + final Pointer length = arena(); + final int error = GetCurrentPackageFullName(length, nullptr); + final bool result = error != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; + _hasPackageIdentity = result; + return result; + }); + + static bool? _hasPackageIdentity; + + /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). + static Uri assetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); +} diff --git a/flutter_local_notifications_windows/lib/src/msix/stub.dart b/flutter_local_notifications_windows/lib/src/msix/stub.dart new file mode 100644 index 000000000..550552866 --- /dev/null +++ b/flutter_local_notifications_windows/lib/src/msix/stub.dart @@ -0,0 +1,28 @@ +import '../../flutter_local_notifications_windows.dart'; + +// ignore: avoid_classes_with_only_static_members +/// Helpful methods to support MSIX and package identity. +class MsixUtils { + /// Returns whether the current app was installed with an MSIX installer. + /// + /// Using an MSIX grants your application [package identity](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview), + /// which allows it to use [certain APIs](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps). + /// + /// Specifically, using an MSIX installer allows your app to: + /// - use [FlutterLocalNotificationsWindows.getActiveNotifications] + /// - use [FlutterLocalNotificationsWindows.cancel] + /// - use custom files for notification sounds + /// - use network sources for notifications + /// - use `ms-appx:///` URIs for resources + /// + /// These functions will simply do nothing or return empty data in apps + /// without package identity. Additionally: + /// - [WindowsImage.assetUri] will return a `file:///` or `ms-appx:///` URI, + /// depending on whether the app is running in debug, release, or as an MSIX. + /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps + /// with package identity, and a preset fallbacks for apps without. + static bool hasPackageIdentity() => false; // platforms without FFI + + /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). + static Uri assetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); +} diff --git a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart index 2a2967317..34e34b65a 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; + import 'package:ffi/ffi.dart'; import 'package:meta/meta.dart'; diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 2e2a9ddd7..5b49f6685 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -14,11 +14,8 @@ dependencies: flutter_local_notifications_platform_interface: ^8.1.0-dev.1 meta: ^1.11.0 timezone: ^0.10.0 + win32: ^5.10.0 xml: ^6.5.0 - msix: - git: - url: https://github.com/Levi-Lesches/msix - ref: is-msix dev_dependencies: ffigen: ^13.0.0 From 8e4b5cae4b748042a77b7f18b911c0013e0c0fbf Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 10:41:10 -0500 Subject: [PATCH 19/37] Formatted --- .../flutter_local_notifications_windows.dart | 6 ++-- .../lib/src/details/notification_audio.dart | 7 ++--- .../lib/src/details/notification_parts.dart | 5 ++- .../lib/src/msix/ffi.dart | 31 ++++++++++--------- .../lib/src/msix/stub.dart | 5 +-- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart b/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart index 430be5b20..ed3b9c824 100644 --- a/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart +++ b/flutter_local_notifications_windows/lib/flutter_local_notifications_windows.dart @@ -1,5 +1,3 @@ export 'src/details.dart'; -export 'src/msix/stub.dart' - if (dart.library.ffi) 'src/msix/ffi.dart'; -export 'src/plugin/stub.dart' - if (dart.library.ffi) 'src/plugin/ffi.dart'; +export 'src/msix/stub.dart' if (dart.library.ffi) 'src/msix/ffi.dart'; +export 'src/plugin/stub.dart' if (dart.library.ffi) 'src/plugin/ffi.dart'; diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 92a4ef704..58e2221a8 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -109,12 +109,11 @@ class WindowsNotificationAudio { WindowsNotificationAudio.asset( String assetName, { this.shouldLoop = false, - WindowsNotificationSound fallback = - WindowsNotificationSound.defaultSound, + WindowsNotificationSound fallback = WindowsNotificationSound.defaultSound, }) : isSilent = false, source = MsixUtils.hasPackageIdentity() - ? MsixUtils.assetUri(assetName).toString() - : fallback.name; + ? MsixUtils.assetUri(assetName).toString() + : fallback.name; /// Whether this audio should loop. final bool shouldLoop; diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index a2b443f6d..847a61350 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -78,7 +78,10 @@ class WindowsImage extends WindowsNotificationPart { } else if (MsixUtils.hasPackageIdentity()) { return MsixUtils.assetUri(assetName); } else { - return Uri.file(File('data/flutter_assets/$assetName').absolute.path, windows: true); + return Uri.file( + File('data/flutter_assets/$assetName').absolute.path, + windows: true, + ); } } diff --git a/flutter_local_notifications_windows/lib/src/msix/ffi.dart b/flutter_local_notifications_windows/lib/src/msix/ffi.dart index 74d95090b..e5154fbe5 100644 --- a/flutter_local_notifications_windows/lib/src/msix/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/msix/ffi.dart @@ -27,23 +27,24 @@ class MsixUtils { /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps /// with package identity, and a preset fallbacks for apps without. static bool hasPackageIdentity() => using((Arena arena) { - final bool? cached = _hasPackageIdentity; - if (cached != null) { - return cached; - } else if (!Platform.isWindows) { - return false; - } else if (IsWindows8OrGreater() != 1) { - return false; - } - final Pointer length = arena(); - final int error = GetCurrentPackageFullName(length, nullptr); - final bool result = error != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; - _hasPackageIdentity = result; - return result; - }); + final bool? cached = _hasPackageIdentity; + if (cached != null) { + return cached; + } else if (!Platform.isWindows) { + return false; + } else if (IsWindows8OrGreater() != 1) { + return false; + } + final Pointer length = arena(); + final int error = GetCurrentPackageFullName(length, nullptr); + final bool result = error != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; + _hasPackageIdentity = result; + return result; + }); static bool? _hasPackageIdentity; /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). - static Uri assetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); + static Uri assetUri(String path) => + Uri.parse('ms-appx:///data/flutter_assets/$path'); } diff --git a/flutter_local_notifications_windows/lib/src/msix/stub.dart b/flutter_local_notifications_windows/lib/src/msix/stub.dart index 550552866..493af5bae 100644 --- a/flutter_local_notifications_windows/lib/src/msix/stub.dart +++ b/flutter_local_notifications_windows/lib/src/msix/stub.dart @@ -21,8 +21,9 @@ class MsixUtils { /// depending on whether the app is running in debug, release, or as an MSIX. /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps /// with package identity, and a preset fallbacks for apps without. - static bool hasPackageIdentity() => false; // platforms without FFI + static bool hasPackageIdentity() => false; // platforms without FFI /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). - static Uri assetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); + static Uri assetUri(String path) => + Uri.parse('ms-appx:///data/flutter_assets/$path'); } From dd7197643824ef2fbf1bc8a799c79de548b151b1 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 10:44:16 -0500 Subject: [PATCH 20/37] Updated changelogs --- flutter_local_notifications/CHANGELOG.md | 1 + flutter_local_notifications_windows/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 84077a6b0..942d61b2e 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -3,6 +3,7 @@ * [Windows] **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported +* [Windows] Added `MsixUtils.hasPackageIdentity()` and `MsixUtils.assetUri()`. You shouldn't need to use `.assetUri()` directly, but it may be helpful to check `.hasPackageIdentity()` to know what features your application can support. ## [19.0.0-dev.3] diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index a93ca049b..7fd53a282 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -3,6 +3,7 @@ * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported +* [Windows] Added `MsixUtils.hasPackageIdentity()` and `MsixUtils.assetUri()`. You shouldn't need to use `.assetUri()` directly, but it may be helpful to check `.hasPackageIdentity()` to know what features your application can support. ## [1.0.0-dev.2] From ba19492f6b87bcd9157acc3fbd377bf43b6aedb4 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 10:52:45 -0500 Subject: [PATCH 21/37] Disabled avoid_classes_with_only_static_members --- analysis_options.yaml | 1 - flutter_local_notifications_windows/lib/src/msix/ffi.dart | 1 - flutter_local_notifications_windows/lib/src/msix/stub.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 343acd642..955eee9f9 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -10,7 +10,6 @@ linter: - avoid_bool_literals_in_conditional_expressions - avoid_catches_without_on_clauses - avoid_catching_errors - - avoid_classes_with_only_static_members - avoid_double_and_int_checks - avoid_empty_else - avoid_equals_and_hash_code_on_mutable_classes diff --git a/flutter_local_notifications_windows/lib/src/msix/ffi.dart b/flutter_local_notifications_windows/lib/src/msix/ffi.dart index e5154fbe5..09b9c1c01 100644 --- a/flutter_local_notifications_windows/lib/src/msix/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/msix/ffi.dart @@ -5,7 +5,6 @@ import 'package:win32/win32.dart'; import '../../flutter_local_notifications_windows.dart'; -// ignore: avoid_classes_with_only_static_members /// Helpful methods to support MSIX and package identity. class MsixUtils { /// Returns whether the current app was installed with an MSIX installer. diff --git a/flutter_local_notifications_windows/lib/src/msix/stub.dart b/flutter_local_notifications_windows/lib/src/msix/stub.dart index 493af5bae..da93ac072 100644 --- a/flutter_local_notifications_windows/lib/src/msix/stub.dart +++ b/flutter_local_notifications_windows/lib/src/msix/stub.dart @@ -1,6 +1,5 @@ import '../../flutter_local_notifications_windows.dart'; -// ignore: avoid_classes_with_only_static_members /// Helpful methods to support MSIX and package identity. class MsixUtils { /// Returns whether the current app was installed with an MSIX installer. From 35a1324eea86cbdae3321ecbbf74297586808f0e Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 11:10:41 -0500 Subject: [PATCH 22/37] Bumped minimum dart: ^3.4, flutter: ^3.22 --- .github/workflows/validate.yml | 32 +++++++++---------- flutter_local_notifications/CHANGELOG.md | 1 + flutter_local_notifications/README.md | 2 +- .../example/pubspec.yaml | 4 +-- flutter_local_notifications/pubspec.yaml | 4 +-- .../CHANGELOG.md | 1 + .../pubspec.yaml | 4 +-- pubspec.yaml | 2 +- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 65819d4af..a95bb2026 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,7 +24,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -66,8 +66,8 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_android - build_example_android_3_19: - name: Build Android example app (3.19) + build_example_android_3_22: + name: Build Android example app (3.22) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -78,7 +78,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -99,15 +99,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_ios - build_example_ios_3_19: - name: Build iOS example app (3.19) + build_example_ios_3_22: + name: Build iOS example app (3.22) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -128,15 +128,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_macos - build_example_macos_3_19: - name: Build macOS example app (3.19) + build_example_macos_3_22: + name: Build macOS example app (3.22) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -161,15 +161,15 @@ jobs: - run: flutter config --enable-linux-desktop - name: Build run: melos run build:example_linux - build_example_linux_3_19: - name: Build Linux example app (3.19) + build_example_linux_3_22: + name: Build Linux example app (3.22) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -205,15 +205,15 @@ jobs: cd f\e dart pub get dart run msix:create - build_example_windows_3_19: - name: Build Windows example app (3.19) + build_example_windows_3_22: + name: Build Windows example app (3.22) runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.19.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 942d61b2e..4deba9457 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,5 +1,6 @@ ## [19.0.0-dev.4] +* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` * [Windows] **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index b96305fb8..275fd606a 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -61,7 +61,7 @@ A cross platform plugin for displaying local notifications. * **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) * **Windows** Uses the [C++/WinRT](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/) implementation of [Toast Notifications](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) -Note: the plugin requires Flutter SDK 3.13 at a minimum. The list of support platforms for Flutter 3.13 itself can be found [here](https://github.com/flutter/website/blob/3d18ab48218101493af84953b71eac0cc6781fdd/src/reference/supported-platforms.md) +Note: the plugin requires Flutter SDK 3.22 at a minimum. The list of support platforms for Flutter 3.22 itself can be found [here](https://github.com/flutter/website/blob/ae27bea8bebdec2825698dca5da49d0c0ef6235b/src/_data/platforms.yml) ## ✨ Features diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml index 4e0b39770..b8301b99b 100644 --- a/flutter_local_notifications/example/pubspec.yaml +++ b/flutter_local_notifications/example/pubspec.yaml @@ -31,8 +31,8 @@ flutter: - sound/ environment: - sdk: ^3.1.0 - flutter: ">=3.1.3" + sdk: ^3.4.0 + flutter: ">=3.22.0" msix_config: display_name: Flutter Local Notifications Example diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index 87ec44f7c..48e6c8c7a 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -42,5 +42,5 @@ flutter: default_package: flutter_local_notifications_windows environment: - sdk: ^3.3.0 - flutter: ">=3.19.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 7fd53a282..702f40457 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,5 +1,6 @@ ## [1.0.0-dev.3] +* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 5b49f6685..8ba306f7b 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -4,8 +4,8 @@ version: 1.0.0-dev.3 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_windows environment: - sdk: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + sdk: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index f913619b0..894db93b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_local_notifications_workspace environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.4.0 <4.0.0' dev_dependencies: melos: ^6.1.0 From b966b1d1aa2159ce9630aa01597ad0e97d8aa5f5 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 11:17:45 -0500 Subject: [PATCH 23/37] Bumped to dart: 3.5, flutter: 3.24 --- .github/workflows/validate.yml | 32 +++++++++---------- flutter_local_notifications/README.md | 2 +- .../example/pubspec.yaml | 4 +-- flutter_local_notifications/pubspec.yaml | 4 +-- .../CHANGELOG.md | 2 +- .../pubspec.yaml | 4 +-- pubspec.yaml | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a95bb2026..08080a050 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,7 +24,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -66,8 +66,8 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_android - build_example_android_3_22: - name: Build Android example app (3.22) + build_example_android_3_24: + name: Build Android example app (3.24) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -78,7 +78,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -99,15 +99,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_ios - build_example_ios_3_22: - name: Build iOS example app (3.22) + build_example_ios_3_24: + name: Build iOS example app (3.24) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -128,15 +128,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_macos - build_example_macos_3_22: - name: Build macOS example app (3.22) + build_example_macos_3_24: + name: Build macOS example app (3.24) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -161,15 +161,15 @@ jobs: - run: flutter config --enable-linux-desktop - name: Build run: melos run build:example_linux - build_example_linux_3_22: - name: Build Linux example app (3.22) + build_example_linux_3_24: + name: Build Linux example app (3.24) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -205,15 +205,15 @@ jobs: cd f\e dart pub get dart run msix:create - build_example_windows_3_22: - name: Build Windows example app (3.22) + build_example_windows_3_24: + name: Build Windows example app (3.24) runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.24.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index 275fd606a..ee2ebc012 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -61,7 +61,7 @@ A cross platform plugin for displaying local notifications. * **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) * **Windows** Uses the [C++/WinRT](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/) implementation of [Toast Notifications](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) -Note: the plugin requires Flutter SDK 3.22 at a minimum. The list of support platforms for Flutter 3.22 itself can be found [here](https://github.com/flutter/website/blob/ae27bea8bebdec2825698dca5da49d0c0ef6235b/src/_data/platforms.yml) +Note: the plugin requires Flutter SDK 3.24 at a minimum. The list of support platforms for Flutter 3.24 itself can be found [here](https://github.com/flutter/website/blob/ee37ad99ab9aa95bf6b51311edea36c0f671ad78/src/_data/platforms.yml) ## ✨ Features diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml index b8301b99b..68aa2d0a8 100644 --- a/flutter_local_notifications/example/pubspec.yaml +++ b/flutter_local_notifications/example/pubspec.yaml @@ -31,8 +31,8 @@ flutter: - sound/ environment: - sdk: ^3.4.0 - flutter: ">=3.22.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" msix_config: display_name: Flutter Local Notifications Example diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index 48e6c8c7a..f4d820fdd 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -42,5 +42,5 @@ flutter: default_package: flutter_local_notifications_windows environment: - sdk: ^3.4.0 - flutter: ">=3.22.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 702f40457..ba2ac483c 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## [1.0.0-dev.3] -* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` +* **Breaking change** Bumped the minimum Flutter SDK to `3.24.0` and Dart to `3.5.0` * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 8ba306f7b..1a2ea3dd6 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -4,8 +4,8 @@ version: 1.0.0-dev.3 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_windows environment: - sdk: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + sdk: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index 894db93b1..cd826dae1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_local_notifications_workspace environment: - sdk: '>=3.4.0 <4.0.0' + sdk: '>=3.5.0 <4.0.0' dev_dependencies: melos: ^6.1.0 From 1fa715284ec38f3a8cc305582a0feaaa208a01fe Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 11:49:39 -0500 Subject: [PATCH 24/37] Moved FFI code around to not need package:win32 --- .../lib/src/ffi/bindings.dart | 17 ++++++++- .../lib/src/msix/ffi.dart | 35 ++++++++++--------- .../lib/src/plugin/ffi.dart | 7 ++-- .../pubspec.yaml | 1 - .../src/ffi_api.cpp | 11 ++++-- .../src/ffi_api.h | 7 ++++ .../src/plugin.cpp | 15 -------- .../src/plugin.hpp | 9 +---- .../src/utils.hpp | 2 ++ 9 files changed, 55 insertions(+), 49 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart index 90c788061..510e17f2f 100644 --- a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart +++ b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart @@ -28,6 +28,20 @@ class NotificationsPluginBindings { lookup) : _lookup = lookup; + /// Checks whether the current application has package identity. + /// + /// This impacts whether apps can query active notifications or cancel them. + /// For more details, see + /// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview. + bool hasPackageIdentity() { + return _hasPackageIdentity(); + } + + late final _hasPackageIdentityPtr = + _lookup>('hasPackageIdentity'); + late final _hasPackageIdentity = + _hasPackageIdentityPtr.asFunction(); + /// Allocates a new plugin that must be released with [disposePlugin]. ffi.Pointer createPlugin() { return _createPlugin(); @@ -91,7 +105,8 @@ class NotificationsPluginBindings { ffi.Pointer, NativeNotificationCallback)>(); - /// Shows the XML as a notification with the given ID. See [updateNotification] for details on bindings. + /// Shows the XML as a notification with the given ID. See [updateNotification] for details on + /// bindings. bool showNotification( ffi.Pointer plugin, int id, diff --git a/flutter_local_notifications_windows/lib/src/msix/ffi.dart b/flutter_local_notifications_windows/lib/src/msix/ffi.dart index 09b9c1c01..bc0671707 100644 --- a/flutter_local_notifications_windows/lib/src/msix/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/msix/ffi.dart @@ -1,9 +1,8 @@ import 'dart:ffi'; import 'dart:io'; -import 'package:ffi/ffi.dart'; -import 'package:win32/win32.dart'; import '../../flutter_local_notifications_windows.dart'; +import '../ffi/bindings.dart'; /// Helpful methods to support MSIX and package identity. class MsixUtils { @@ -25,21 +24,23 @@ class MsixUtils { /// depending on whether the app is running in debug, release, or as an MSIX. /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps /// with package identity, and a preset fallbacks for apps without. - static bool hasPackageIdentity() => using((Arena arena) { - final bool? cached = _hasPackageIdentity; - if (cached != null) { - return cached; - } else if (!Platform.isWindows) { - return false; - } else if (IsWindows8OrGreater() != 1) { - return false; - } - final Pointer length = arena(); - final int error = GetCurrentPackageFullName(length, nullptr); - final bool result = error != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; - _hasPackageIdentity = result; - return result; - }); + static bool hasPackageIdentity() { + final bool? cached = _hasPackageIdentity; + if (cached != null) { + return cached; + } else if (!Platform.isWindows) { + return false; + } else { + final DynamicLibrary lib = DynamicLibrary.open( + 'flutter_local_notifications_windows.dll', + ); + final NotificationsPluginBindings bindings = + NotificationsPluginBindings(lib); + final bool result = bindings.hasPackageIdentity(); + _hasPackageIdentity = result; + return result; + } + } static bool? _hasPackageIdentity; diff --git a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart index 34e34b65a..a9a17b406 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart @@ -87,11 +87,10 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { final Pointer guid = settings.guid.toNativeUtf8(allocator: arena); final Pointer iconPath = settings.iconPath?.toNativeUtf8(allocator: arena) ?? nullptr; - final Pointer> - callback = + final NativeNotificationCallback callback = NativeCallable.listener( - _globalLaunchCallback) - .nativeFunction; + _globalLaunchCallback, + ).nativeFunction; final bool result = _bindings.init(_plugin, appName, aumId, guid, iconPath, callback); _isReady = result; diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 1a2ea3dd6..763c39bc0 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: flutter_local_notifications_platform_interface: ^8.1.0-dev.1 meta: ^1.11.0 timezone: ^0.10.0 - win32: ^5.10.0 xml: ^6.5.0 dev_dependencies: diff --git a/flutter_local_notifications_windows/src/ffi_api.cpp b/flutter_local_notifications_windows/src/ffi_api.cpp index 33bd3b1e7..aae9aa9da 100644 --- a/flutter_local_notifications_windows/src/ffi_api.cpp +++ b/flutter_local_notifications_windows/src/ffi_api.cpp @@ -8,6 +8,13 @@ using winrt::Windows::Data::Xml::Dom::XmlDocument; +bool hasPackageIdentity() { + if (!IsWindows8OrGreater()) return false; + uint32_t length = 0; + int error = GetCurrentPackageFullName(&length, nullptr); + return error != APPMODEL_ERROR_NO_PACKAGE; +} + NativePlugin* createPlugin() { return new NativePlugin(); } void disposePlugin(NativePlugin* plugin) { delete plugin; } @@ -20,9 +27,7 @@ bool init( if (iconPath != nullptr) icon = string(iconPath); const auto didRegister = plugin->registerApp(aumId, appName, guid, icon, callback); if (!didRegister) return false; - const auto identity = plugin->checkIdentity(); - if (!identity.has_value()) return false; - plugin->hasIdentity = identity.value(); + plugin->hasIdentity = hasPackageIdentity(); plugin->aumid = winrt::to_hstring(aumId); plugin->notifier = plugin->hasIdentity ? ToastNotificationManager::CreateToastNotifier() diff --git a/flutter_local_notifications_windows/src/ffi_api.h b/flutter_local_notifications_windows/src/ffi_api.h index 8cfbfa8b4..0d776cb1b 100644 --- a/flutter_local_notifications_windows/src/ffi_api.h +++ b/flutter_local_notifications_windows/src/ffi_api.h @@ -71,6 +71,13 @@ typedef enum NativeUpdateResult { notFound = 2, } NativeUpdateResult; +/// Checks whether the current application has package identity. +/// +/// This impacts whether apps can query active notifications or cancel them. +/// For more details, see +/// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview. +FFI_PLUGIN_EXPORT bool hasPackageIdentity(); + /// Allocates a new plugin that must be released with [disposePlugin]. FFI_PLUGIN_EXPORT NativePlugin* createPlugin(); diff --git a/flutter_local_notifications_windows/src/plugin.cpp b/flutter_local_notifications_windows/src/plugin.cpp index e1ed6bd7e..c96b17b28 100644 --- a/flutter_local_notifications_windows/src/plugin.cpp +++ b/flutter_local_notifications_windows/src/plugin.cpp @@ -176,18 +176,3 @@ bool NativePlugin::registerApp( UpdateRegistry(aumid, appName, guid, iconPath); return RegisterCallback(guid, callback); } - -std::optional NativePlugin::checkIdentity() { - if (!IsWindows8OrGreater()) return false; - uint32_t length = 0; - auto error = GetCurrentPackageFullName(&length, nullptr); - if (error == APPMODEL_ERROR_NO_PACKAGE) { - return false; - } else if (error != ERROR_INSUFFICIENT_BUFFER) { - return std::nullopt; - } - std::vector fullName(length); - error = GetCurrentPackageFullName(&length, fullName.data()); - if (error != ERROR_SUCCESS) return std::nullopt; - return true; -} diff --git a/flutter_local_notifications_windows/src/plugin.hpp b/flutter_local_notifications_windows/src/plugin.hpp index fba730a50..97d53dbc3 100644 --- a/flutter_local_notifications_windows/src/plugin.hpp +++ b/flutter_local_notifications_windows/src/plugin.hpp @@ -22,9 +22,7 @@ struct NativePlugin { /// Whether the current application has package identity (ie, was packaged with an MSIX). /// - /// This impacts whether apps can query active notifications or cancel them. - /// For more details, see - /// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview. + /// See [hasPackageIdentity]. bool hasIdentity = false; /// The app user model ID. Used instead of package identity when [hasIdentity] is false. @@ -44,11 +42,6 @@ struct NativePlugin { NativePlugin() {} ~NativePlugin() {} - /// Checks whether the current application has package identity. See [hasIdentity] for details. - /// - /// Returns true or false if the package has identity, or null if an error occurred. - std::optional checkIdentity(); - /// Registers the given [callback] to run when a notification is pressed. bool registerApp( const string& aumid, const string& appName, const string& guid, diff --git a/flutter_local_notifications_windows/src/utils.hpp b/flutter_local_notifications_windows/src/utils.hpp index fa4ca5780..0e0500359 100644 --- a/flutter_local_notifications_windows/src/utils.hpp +++ b/flutter_local_notifications_windows/src/utils.hpp @@ -4,6 +4,8 @@ #include #include // <-- This must be the first Windows header +#include +#include #include #include "ffi_api.h" From 7428d928210a4a594d57517340bd19795959c174 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 11:50:59 -0500 Subject: [PATCH 25/37] Revert "Bumped to dart: 3.5, flutter: 3.24" This reverts commit b966b1d1aa2159ce9630aa01597ad0e97d8aa5f5. --- .github/workflows/validate.yml | 32 +++++++++---------- flutter_local_notifications/README.md | 2 +- .../example/pubspec.yaml | 4 +-- flutter_local_notifications/pubspec.yaml | 4 +-- .../CHANGELOG.md | 2 +- .../pubspec.yaml | 4 +-- pubspec.yaml | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 08080a050..a95bb2026 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,7 +24,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -66,8 +66,8 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_android - build_example_android_3_24: - name: Build Android example app (3.24) + build_example_android_3_22: + name: Build Android example app (3.22) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -78,7 +78,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -99,15 +99,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_ios - build_example_ios_3_24: - name: Build iOS example app (3.24) + build_example_ios_3_22: + name: Build iOS example app (3.22) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -128,15 +128,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_macos - build_example_macos_3_24: - name: Build macOS example app (3.24) + build_example_macos_3_22: + name: Build macOS example app (3.22) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -161,15 +161,15 @@ jobs: - run: flutter config --enable-linux-desktop - name: Build run: melos run build:example_linux - build_example_linux_3_24: - name: Build Linux example app (3.24) + build_example_linux_3_22: + name: Build Linux example app (3.22) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -205,15 +205,15 @@ jobs: cd f\e dart pub get dart run msix:create - build_example_windows_3_24: - name: Build Windows example app (3.24) + build_example_windows_3_22: + name: Build Windows example app (3.22) runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.24.0 + flutter-version: 3.22.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index ee2ebc012..275fd606a 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -61,7 +61,7 @@ A cross platform plugin for displaying local notifications. * **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) * **Windows** Uses the [C++/WinRT](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/) implementation of [Toast Notifications](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) -Note: the plugin requires Flutter SDK 3.24 at a minimum. The list of support platforms for Flutter 3.24 itself can be found [here](https://github.com/flutter/website/blob/ee37ad99ab9aa95bf6b51311edea36c0f671ad78/src/_data/platforms.yml) +Note: the plugin requires Flutter SDK 3.22 at a minimum. The list of support platforms for Flutter 3.22 itself can be found [here](https://github.com/flutter/website/blob/ae27bea8bebdec2825698dca5da49d0c0ef6235b/src/_data/platforms.yml) ## ✨ Features diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml index 68aa2d0a8..b8301b99b 100644 --- a/flutter_local_notifications/example/pubspec.yaml +++ b/flutter_local_notifications/example/pubspec.yaml @@ -31,8 +31,8 @@ flutter: - sound/ environment: - sdk: ^3.5.0 - flutter: ">=3.24.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" msix_config: display_name: Flutter Local Notifications Example diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index f4d820fdd..48e6c8c7a 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -42,5 +42,5 @@ flutter: default_package: flutter_local_notifications_windows environment: - sdk: ^3.5.0 - flutter: ">=3.24.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index ba2ac483c..702f40457 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## [1.0.0-dev.3] -* **Breaking change** Bumped the minimum Flutter SDK to `3.24.0` and Dart to `3.5.0` +* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 763c39bc0..8ec56f470 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -4,8 +4,8 @@ version: 1.0.0-dev.3 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_windows environment: - sdk: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + sdk: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index cd826dae1..894db93b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_local_notifications_workspace environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.4.0 <4.0.0' dev_dependencies: melos: ^6.1.0 From cca327bf5c4ca5fa4d74e53bbd1000efdc85149d Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 11:51:11 -0500 Subject: [PATCH 26/37] Revert "Bumped minimum dart: ^3.4, flutter: ^3.22" This reverts commit 35a1324eea86cbdae3321ecbbf74297586808f0e. --- .github/workflows/validate.yml | 32 +++++++++---------- flutter_local_notifications/CHANGELOG.md | 1 - flutter_local_notifications/README.md | 2 +- .../example/pubspec.yaml | 4 +-- flutter_local_notifications/pubspec.yaml | 4 +-- .../CHANGELOG.md | 1 - .../pubspec.yaml | 4 +-- pubspec.yaml | 2 +- 8 files changed, 24 insertions(+), 26 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a95bb2026..65819d4af 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,7 +24,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -66,8 +66,8 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_android - build_example_android_3_22: - name: Build Android example app (3.22) + build_example_android_3_19: + name: Build Android example app (3.19) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -78,7 +78,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -99,15 +99,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_ios - build_example_ios_3_22: - name: Build iOS example app (3.22) + build_example_ios_3_19: + name: Build iOS example app (3.19) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -128,15 +128,15 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_macos - build_example_macos_3_22: - name: Build macOS example app (3.22) + build_example_macos_3_19: + name: Build macOS example app (3.19) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -161,15 +161,15 @@ jobs: - run: flutter config --enable-linux-desktop - name: Build run: melos run build:example_linux - build_example_linux_3_22: - name: Build Linux example app (3.22) + build_example_linux_3_19: + name: Build Linux example app (3.19) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -205,15 +205,15 @@ jobs: cd f\e dart pub get dart run msix:create - build_example_windows_3_22: - name: Build Windows example app (3.22) + build_example_windows_3_19: + name: Build Windows example app (3.19) runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.22.0 + flutter-version: 3.19.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 4deba9457..942d61b2e 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,6 +1,5 @@ ## [19.0.0-dev.4] -* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` * [Windows] **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index 275fd606a..b96305fb8 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -61,7 +61,7 @@ A cross platform plugin for displaying local notifications. * **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) * **Windows** Uses the [C++/WinRT](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/) implementation of [Toast Notifications](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) -Note: the plugin requires Flutter SDK 3.22 at a minimum. The list of support platforms for Flutter 3.22 itself can be found [here](https://github.com/flutter/website/blob/ae27bea8bebdec2825698dca5da49d0c0ef6235b/src/_data/platforms.yml) +Note: the plugin requires Flutter SDK 3.13 at a minimum. The list of support platforms for Flutter 3.13 itself can be found [here](https://github.com/flutter/website/blob/3d18ab48218101493af84953b71eac0cc6781fdd/src/reference/supported-platforms.md) ## ✨ Features diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml index b8301b99b..4e0b39770 100644 --- a/flutter_local_notifications/example/pubspec.yaml +++ b/flutter_local_notifications/example/pubspec.yaml @@ -31,8 +31,8 @@ flutter: - sound/ environment: - sdk: ^3.4.0 - flutter: ">=3.22.0" + sdk: ^3.1.0 + flutter: ">=3.1.3" msix_config: display_name: Flutter Local Notifications Example diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index 48e6c8c7a..87ec44f7c 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -42,5 +42,5 @@ flutter: default_package: flutter_local_notifications_windows environment: - sdk: ^3.4.0 - flutter: ">=3.22.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 702f40457..7fd53a282 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -1,6 +1,5 @@ ## [1.0.0-dev.3] -* **Breaking change** Bumped the minimum Flutter SDK to `3.22.0` and Dart to `3.4.0` * **Breaking change** Reworked the APIs around custom images and audio. Check the updated example for more details, but in short: * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported diff --git a/flutter_local_notifications_windows/pubspec.yaml b/flutter_local_notifications_windows/pubspec.yaml index 8ec56f470..6352692f6 100644 --- a/flutter_local_notifications_windows/pubspec.yaml +++ b/flutter_local_notifications_windows/pubspec.yaml @@ -4,8 +4,8 @@ version: 1.0.0-dev.3 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_windows environment: - sdk: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index 894db93b1..f913619b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_local_notifications_workspace environment: - sdk: '>=3.4.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dev_dependencies: melos: ^6.1.0 From db49078f18c31e0c20e64b04f7ed27f53be10d89 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 12:25:43 -0500 Subject: [PATCH 27/37] Made WindowsPlugin.isValidXml --- .../dart_test.yaml | 2 +- .../lib/src/ffi/bindings.dart | 14 +++++++ .../lib/src/plugin/base.dart | 9 ++++- .../lib/src/plugin/ffi.dart | 6 +++ .../lib/src/plugin/stub.dart | 3 ++ .../src/ffi_api.cpp | 10 +++++ .../src/ffi_api.h | 2 + .../test/xml_test.dart | 39 ++++++++++--------- 8 files changed, 63 insertions(+), 22 deletions(-) diff --git a/flutter_local_notifications_windows/dart_test.yaml b/flutter_local_notifications_windows/dart_test.yaml index 2305630e4..e65d66d4f 100644 --- a/flutter_local_notifications_windows/dart_test.yaml +++ b/flutter_local_notifications_windows/dart_test.yaml @@ -1,3 +1,3 @@ platforms: [vm] test_on: windows -retry: 5 # These tests have concurrency issues. See bin/crash.dart +# retry: 5 # These tests have concurrency issues. See bin/crash.dart diff --git a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart index 510e17f2f..daec31d4a 100644 --- a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart +++ b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart @@ -42,6 +42,20 @@ class NotificationsPluginBindings { late final _hasPackageIdentity = _hasPackageIdentityPtr.asFunction(); + bool isValidXml( + ffi.Pointer xml, + ) { + return _isValidXml( + xml, + ); + } + + late final _isValidXmlPtr = + _lookup)>>( + 'isValidXml'); + late final _isValidXml = + _isValidXmlPtr.asFunction)>(); + /// Allocates a new plugin that must be released with [disposePlugin]. ffi.Pointer createPlugin() { return _createPlugin(); diff --git a/flutter_local_notifications_windows/lib/src/plugin/base.dart b/flutter_local_notifications_windows/lib/src/plugin/base.dart index a86128aaf..0ee3f876d 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/base.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/base.dart @@ -22,8 +22,7 @@ abstract class WindowsNotificationsBase /// Shows a notification using raw XML passed to the Windows APIs. /// - /// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root. - /// For validation, see [the Windows Notifications Visualizer](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer). + /// To check if the XML is valid, use [isValidXml]. Future showRawXml({ required int id, required String xml, @@ -90,4 +89,10 @@ abstract class WindowsNotificationsBase /// configuration for Windows APIs. @visibleForTesting void enableMultithreading(); + + /// Checks if some XML is a valid Windows notification. + /// + /// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root. + /// For validation, see [the Windows Notifications Visualizer](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer). + bool isValidXml(String xml); } diff --git a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart index a9a17b406..ec1d8ea9f 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart @@ -280,6 +280,12 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { } }); + @override + bool isValidXml(String xml) => using((Arena arena) { + final Pointer nativeXml = xml.toNativeUtf8(allocator: arena); + return _bindings.isValidXml(nativeXml); + }); + @override Future zonedSchedule( int id, diff --git a/flutter_local_notifications_windows/lib/src/plugin/stub.dart b/flutter_local_notifications_windows/lib/src/plugin/stub.dart index 7aba9e34a..0331fadb3 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/stub.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/stub.dart @@ -96,4 +96,7 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { @override @visibleForTesting void enableMultithreading() {} + + @override + bool isValidXml(String xml) => false; } diff --git a/flutter_local_notifications_windows/src/ffi_api.cpp b/flutter_local_notifications_windows/src/ffi_api.cpp index aae9aa9da..809558cc1 100644 --- a/flutter_local_notifications_windows/src/ffi_api.cpp +++ b/flutter_local_notifications_windows/src/ffi_api.cpp @@ -37,6 +37,16 @@ bool init( return true; } +bool isValidXml(char* xml) { + XmlDocument doc = XmlDocument(); + try { + doc.LoadXml(winrt::to_hstring(xml)); + return true; + } catch (winrt::hresult_error error) { + return false; + } +} + bool showNotification(NativePlugin* plugin, int id, char* xml, NativeStringMap bindings) { if (!plugin->isReady) return false; XmlDocument doc; diff --git a/flutter_local_notifications_windows/src/ffi_api.h b/flutter_local_notifications_windows/src/ffi_api.h index 0d776cb1b..618c09954 100644 --- a/flutter_local_notifications_windows/src/ffi_api.h +++ b/flutter_local_notifications_windows/src/ffi_api.h @@ -78,6 +78,8 @@ typedef enum NativeUpdateResult { /// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview. FFI_PLUGIN_EXPORT bool hasPackageIdentity(); +FFI_PLUGIN_EXPORT bool isValidXml(char* xml); + /// Allocates a new plugin that must be released with [disposePlugin]. FFI_PLUGIN_EXPORT NativePlugin* createPlugin(); diff --git a/flutter_local_notifications_windows/test/xml_test.dart b/flutter_local_notifications_windows/test/xml_test.dart index c9b100c58..6f425b268 100644 --- a/flutter_local_notifications_windows/test/xml_test.dart +++ b/flutter_local_notifications_windows/test/xml_test.dart @@ -56,25 +56,26 @@ const String complexXml = ''' '''; -void main() => group('XML', () { - FlutterLocalNotificationsWindows().enableMultithreading(); +void main() { + group('XML', () { + FlutterLocalNotificationsWindows().enableMultithreading(); - final FlutterLocalNotificationsWindows plugin = - FlutterLocalNotificationsWindows(); - setUpAll(() => plugin.initialize(settings)); - tearDownAll(() async { - await plugin.cancelAll(); - plugin.dispose(); - }); + final FlutterLocalNotificationsWindows plugin = + FlutterLocalNotificationsWindows(); - test('catches invalid XML', () async { - expect(plugin.showRawXml(id: 0, xml: emptyXml), throwsArgumentError); - expect(plugin.showRawXml(id: 1, xml: invalidXml), throwsArgumentError); - expect( - plugin.showRawXml(id: 2, xml: notWindowsXml), throwsArgumentError); - expect( - plugin.showRawXml(id: 3, xml: unmatchedXml), throwsArgumentError); - expect(plugin.showRawXml(id: 4, xml: validXml), completes); - expect(plugin.showRawXml(id: 5, xml: complexXml), completes); - }); + setUpAll(() => plugin.initialize(settings)); + tearDownAll(() async { + await plugin.cancelAll(); + plugin.dispose(); }); + + test('catches invalid XML', () async { + expect(plugin.isValidXml(emptyXml), isFalse); + expect(plugin.isValidXml(invalidXml), isFalse); + expect(plugin.isValidXml(notWindowsXml), isFalse); + expect(plugin.isValidXml(unmatchedXml), isFalse); + expect(plugin.isValidXml(validXml), isTrue); + expect(plugin.isValidXml(complexXml), isTrue); + }); + }); +} From fd38bdbd5675fc4d627aa4c05283a25a68d6b80c Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 12:47:01 -0500 Subject: [PATCH 28/37] Stabilized flaky tests --- .../lib/src/plugin/ffi.dart | 6 +-- .../test/bindings_test.dart | 2 +- .../test/details_test.dart | 37 +++++++++----- .../test/plugin_test.dart | 49 ++++++++++++------- 4 files changed, 60 insertions(+), 34 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart index ec1d8ea9f..d439d6908 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart @@ -282,9 +282,9 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { @override bool isValidXml(String xml) => using((Arena arena) { - final Pointer nativeXml = xml.toNativeUtf8(allocator: arena); - return _bindings.isValidXml(nativeXml); - }); + final Pointer nativeXml = xml.toNativeUtf8(allocator: arena); + return _bindings.isValidXml(nativeXml); + }); @override Future zonedSchedule( diff --git a/flutter_local_notifications_windows/test/bindings_test.dart b/flutter_local_notifications_windows/test/bindings_test.dart index 506ad202e..9f0931801 100644 --- a/flutter_local_notifications_windows/test/bindings_test.dart +++ b/flutter_local_notifications_windows/test/bindings_test.dart @@ -45,7 +45,7 @@ void main() => group('Bindings', () { ); }); - test('fail when notification has been cancelled', retry: 5, () async { + test('fail when notification has been cancelled', () async { await Future.delayed(const Duration(milliseconds: 200)); await plugin.show(503, '{title}', '{body}'); final NotificationUpdateResult result = diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index bdd715fa6..bb3a33a57 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -1,4 +1,5 @@ import 'package:flutter_local_notifications_windows/flutter_local_notifications_windows.dart'; +import 'package:flutter_local_notifications_windows/src/details/notification_to_xml.dart'; import 'package:test/test.dart'; const WindowsInitializationSettings settings = WindowsInitializationSettings( @@ -10,11 +11,17 @@ const WindowsInitializationSettings settings = WindowsInitializationSettings( extension PluginUtils on FlutterLocalNotificationsWindows { static int id = 15; - Future showDetails(WindowsNotificationDetails details) => - show(id++, 'Title', 'Body', details: details); - - void testDetails(WindowsNotificationDetails details) => - expect(showDetails(details), completes); + void testDetails(WindowsNotificationDetails details) => expect( + isValidXml( + notificationToXml( + title: 'title', + body: 'body', + payload: 'payload', + details: details, + ), + ), + isTrue, + ); } void main() => group('Details:', () { @@ -70,11 +77,10 @@ void main() => group('Details:', () { ..testDetails(WindowsNotificationDetails( actions: List.filled(5, simpleAction))); expect( - plugin.showDetails( - WindowsNotificationDetails( - actions: List.filled(6, simpleAction), - ), - ), + () => notificationToXml( + details: WindowsNotificationDetails( + actions: List.filled(6, simpleAction), + )), throwsArgumentError, ); }); @@ -189,8 +195,8 @@ void main() => group('Details:', () { inputs: [selection, textInput], actions: [action])); expect( - plugin.showDetails( - WindowsNotificationDetails( + () => notificationToXml( + details: WindowsNotificationDetails( inputs: List.filled(6, textInput), ), ), @@ -198,7 +204,7 @@ void main() => group('Details:', () { ); }); - test('Progress', retry: 5, () async { + test('Progress', () async { final WindowsProgressBar simple = WindowsProgressBar( id: 'simple', status: 'Testing...', @@ -243,5 +249,10 @@ void main() => group('Details:', () { expect(result, NotificationUpdateResult.success); await Future.delayed(const Duration(milliseconds: 10)); } + expect( + await plugin.updateProgressBar( + notificationId: 202, progressBar: dynamic), + NotificationUpdateResult.notFound, + ); }); }); diff --git a/flutter_local_notifications_windows/test/plugin_test.dart b/flutter_local_notifications_windows/test/plugin_test.dart index 06de35d27..1352ceef5 100644 --- a/flutter_local_notifications_windows/test/plugin_test.dart +++ b/flutter_local_notifications_windows/test/plugin_test.dart @@ -6,11 +6,16 @@ import 'package:timezone/standalone.dart'; const WindowsInitializationSettings goodSettings = WindowsInitializationSettings( - appName: 'test', - appUserModelId: 'com.test.test', - guid: 'a8c22b55-049e-422f-b30f-863694de08c8'); + appName: 'test', + appUserModelId: 'com.test.test', + guid: 'a8c22b55-049e-422f-b30f-863694de08c8', +); + const WindowsInitializationSettings badSettings = WindowsInitializationSettings( - appName: 'test', appUserModelId: 'com.test.test', guid: '123'); + appName: 'test', + appUserModelId: 'com.test.test', + guid: '123', +); void main() => group('Plugin', () { FlutterLocalNotificationsWindows().enableMultithreading(); @@ -45,13 +50,18 @@ void main() => group('Plugin', () { expect(plugin.pendingNotificationRequests(), throwsStateError); expect(plugin.show(0, 'Title', 'Body'), throwsStateError); expect(plugin.showRawXml(id: 0, xml: ''), throwsStateError); - expect(plugin.updateBindings(id: 0, bindings: {}), - throwsStateError); expect( - plugin.updateProgressBar(progressBar: progress, notificationId: 0), - throwsStateError); + plugin.updateBindings(id: 0, bindings: {}), + throwsStateError, + ); expect( - plugin.zonedSchedule(0, null, null, now, null), throwsStateError); + plugin.updateProgressBar(progressBar: progress, notificationId: 0), + throwsStateError, + ); + expect( + plugin.zonedSchedule(0, null, null, now, null), + throwsStateError, + ); plugin.dispose(); }); @@ -70,11 +80,14 @@ void main() => group('Plugin', () { expect(plugin.pendingNotificationRequests(), throwsStateError); expect(plugin.show(0, 'Title', 'Body'), throwsStateError); expect(plugin.showRawXml(id: 0, xml: ''), throwsStateError); - expect(plugin.updateBindings(id: 0, bindings: {}), - throwsStateError); expect( - plugin.updateProgressBar(progressBar: progress, notificationId: 0), - throwsStateError); + plugin.updateBindings(id: 0, bindings: {}), + throwsStateError, + ); + expect( + plugin.updateProgressBar(progressBar: progress, notificationId: 0), + throwsStateError, + ); expect( plugin.zonedSchedule(0, null, null, now, null), throwsStateError); plugin.dispose(); @@ -85,11 +98,13 @@ void main() => group('Plugin', () { FlutterLocalNotificationsWindows(); await plugin.initialize(goodSettings); expect( - plugin.periodicallyShow(0, null, null, RepeatInterval.everyMinute), - throwsUnsupportedError); + plugin.periodicallyShow(0, null, null, RepeatInterval.everyMinute), + throwsUnsupportedError, + ); expect( - plugin.periodicallyShowWithDuration(0, null, null, Duration.zero), - throwsUnsupportedError); + plugin.periodicallyShowWithDuration(0, null, null, Duration.zero), + throwsUnsupportedError, + ); plugin.dispose(); }); }); From 3cc7e61797728221bcab062755cfd8da277617c4 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 12:49:00 -0500 Subject: [PATCH 29/37] Updated melos and changelogs --- flutter_local_notifications/CHANGELOG.md | 1 + flutter_local_notifications_windows/CHANGELOG.md | 1 + melos.yaml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index 942d61b2e..7e36a6c23 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -4,6 +4,7 @@ * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported * [Windows] Added `MsixUtils.hasPackageIdentity()` and `MsixUtils.assetUri()`. You shouldn't need to use `.assetUri()` directly, but it may be helpful to check `.hasPackageIdentity()` to know what features your application can support. +* [Windows] Added `FlutterLocalNotificationsWindows.isValidXml()` for testing raw XML. ## [19.0.0-dev.3] diff --git a/flutter_local_notifications_windows/CHANGELOG.md b/flutter_local_notifications_windows/CHANGELOG.md index 7fd53a282..ff6a01855 100644 --- a/flutter_local_notifications_windows/CHANGELOG.md +++ b/flutter_local_notifications_windows/CHANGELOG.md @@ -4,6 +4,7 @@ * Instead of `WindowsNotificationAudio.fromFile()`, use `WindowsNotificationAudio.asset()` * Instead of `WindowsImage.file()`, use `WindowsImage()`. See the docs for what URIs are supported * [Windows] Added `MsixUtils.hasPackageIdentity()` and `MsixUtils.assetUri()`. You shouldn't need to use `.assetUri()` directly, but it may be helpful to check `.hasPackageIdentity()` to know what features your application can support. +* [Windows] Added `FlutterLocalNotificationsWindows.isValidXml()` for testing raw XML. ## [1.0.0-dev.2] diff --git a/melos.yaml b/melos.yaml index 81709b8e0..59489e068 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,7 +32,7 @@ scripts: scope: "*example*" test:unit:windows: description: Runs Windows-specific unit tests - run: melos exec -c 1 --verbose -- "flutter test -v" + run: melos exec -c 1 --verbose -- "flutter test -j 1 -v" packageFilters: scope: '*_windows' test:integration: From c67ee5a03eace88d233dd8eb8aeb78cee007130d Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 13:31:30 -0500 Subject: [PATCH 30/37] Cancel notif after schedule test --- .../test/scheduled_test.dart | 2 ++ flutter_local_notifications_windows/test/xml_test.dart | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/flutter_local_notifications_windows/test/scheduled_test.dart b/flutter_local_notifications_windows/test/scheduled_test.dart index f810b8da3..3d85d8b5a 100644 --- a/flutter_local_notifications_windows/test/scheduled_test.dart +++ b/flutter_local_notifications_windows/test/scheduled_test.dart @@ -34,6 +34,8 @@ void main() => group('Schedules', () { expect(await countPending(), 2); expect(plugin.zonedSchedule(302, null, null, later, null), completes); expect(await countPending(), 3); + await plugin.cancelAll(); + expect(await countPending(), 0); }); test('do not work with earlier time', () async { diff --git a/flutter_local_notifications_windows/test/xml_test.dart b/flutter_local_notifications_windows/test/xml_test.dart index 6f425b268..656fe6208 100644 --- a/flutter_local_notifications_windows/test/xml_test.dart +++ b/flutter_local_notifications_windows/test/xml_test.dart @@ -2,9 +2,11 @@ import 'package:flutter_local_notifications_windows/flutter_local_notifications_ import 'package:test/test.dart'; const WindowsInitializationSettings settings = WindowsInitializationSettings( - appName: 'test', - appUserModelId: 'com.test.test', - guid: 'a8c22b55-049e-422f-b30f-863694de08c8'); + appName: 'test', + appUserModelId: 'com.test.test', + guid: 'a8c22b55-049e-422f-b30f-863694de08c8', +); + const String emptyXml = ''; const String invalidXml = 'Blah blah blah'; const String notWindowsXml = 'Hi'; From 78c0733ef8201538efd172095ea1366908d4eac8 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 13:41:40 -0500 Subject: [PATCH 31/37] Skip flaky test --- flutter_local_notifications_windows/test/scheduled_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_local_notifications_windows/test/scheduled_test.dart b/flutter_local_notifications_windows/test/scheduled_test.dart index 3d85d8b5a..0f5a0defd 100644 --- a/flutter_local_notifications_windows/test/scheduled_test.dart +++ b/flutter_local_notifications_windows/test/scheduled_test.dart @@ -23,7 +23,7 @@ void main() => group('Schedules', () { (await plugin.pendingNotificationRequests()).length; late final Location location = getLocation('US/Eastern'); - test('work with basic times', () async { + test('work with basic times', skip: true, () async { await plugin.cancelAll(); expect(await countPending(), 0); final TZDateTime now = TZDateTime.now(location); From 348d5f896dc3e7160fca847db542b639f5f44fdb Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 14:11:02 -0500 Subject: [PATCH 32/37] Added Windows XML validation to the example --- .../example/lib/main.dart | 19 +--- .../example/lib/windows.dart | 92 ++++++++++++------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 77989e7a6..618f770bb 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -218,9 +218,6 @@ class _HomePageState extends State { final TextEditingController _linuxIconPathController = TextEditingController(); - final TextEditingController _windowsRawXmlController = - TextEditingController(); - bool _notificationsEnabled = false; @override @@ -966,11 +963,7 @@ class _HomePageState extends State { }, ), ], - if (!kIsWeb && Platform.isWindows) - ...windows.examples( - xmlController: _windowsRawXmlController, - showXmlNotification: _showWindowsNotificationWithRawXml, - ), + if (!kIsWeb && Platform.isWindows) ...windows.examples(), ], ), ), @@ -2702,16 +2695,6 @@ class _HomePageState extends State { platformChannelSpecifics, ); } - - Future? _showWindowsNotificationWithRawXml() => - flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - FlutterLocalNotificationsWindows>() - ?.showRawXml( - id: id++, - xml: _windowsRawXmlController.text, - bindings: {'message': 'Hello, World!'}, - ); } Future _showLinuxNotificationWithBodyMarkup() async { diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index 878f7d82b..2d744118a 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -13,11 +13,65 @@ const WindowsInitializationSettings initSettings = guid: 'd49b0314-ee7a-4626-bf79-97cdb8a991bb', ); -List examples({ - required TextEditingController xmlController, - required VoidCallback showXmlNotification, -}) => - [ +class _WindowsXmlBuilder extends StatefulWidget { + @override + _WindowsXmlBuilderState createState() => _WindowsXmlBuilderState(); +} + +class _WindowsXmlBuilderState extends State<_WindowsXmlBuilder> { + final TextEditingController xmlController = TextEditingController(); + final Map bindings = { + 'message': 'Hello, World!' + }; + + final FlutterLocalNotificationsWindows? plugin = + flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< + FlutterLocalNotificationsWindows>(); + + String get xml => xmlController.text; + + bool isValid = true; + + void onPressed() { + setState(() => isValid = plugin?.isValidXml(xml) ?? false); + if (isValid) { + plugin?.showRawXml(id: id++, xml: xml, bindings: bindings); + } + } + + @override + Widget build(BuildContext context) => SizedBox( + width: 500, + child: ExpansionTile( + title: const Text('Click to expand raw XML'), + children: [ + TextField( + maxLines: 20, + style: const TextStyle(fontFamily: 'RobotoMono'), + controller: xmlController, + onSubmitted: (_) => onPressed, + decoration: InputDecoration( + hintText: 'Enter the raw xml', + errorText: isValid ? null : 'Invalid XML', + helperText: 'Bindings: {message} --> Hello, World!', + constraints: + const BoxConstraints.tightFor(width: 600, height: 480), + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () => xmlController.clear(), + ), + ), + ), + const SizedBox(height: 8), + PaddedElevatedButton( + buttonText: 'Show notification with raw XML', + onPressed: onPressed, + ), + ]), + ); +} + +List examples() => [ const Text( 'Windows-specific examples', style: TextStyle(fontWeight: FontWeight.bold), @@ -86,33 +140,7 @@ List examples({ await _showWindowsNotificationWithHeader(); }, ), - PaddedElevatedButton( - buttonText: 'Show notification with raw XML', - onPressed: showXmlNotification, - ), - const SizedBox(height: 8), - SizedBox( - width: 500, - child: ExpansionTile( - title: const Text('Click to expand raw XML'), - children: [ - TextField( - maxLines: 20, - style: const TextStyle(fontFamily: 'RobotoMono'), - controller: xmlController, - decoration: InputDecoration( - hintText: 'Enter the raw xml', - helperText: 'Bindings: {message} --> Hello, World!', - constraints: - const BoxConstraints.tightFor(width: 600, height: 480), - suffixIcon: IconButton( - icon: const Icon(Icons.clear), - onPressed: () => xmlController.clear(), - ), - ), - ), - ]), - ), + _WindowsXmlBuilder(), ]; Future _showWindowsNotificationWithDuration() async { From ef5f691d89bfde28aef4a646ded19b5b8d6acb8f Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 14:23:18 -0500 Subject: [PATCH 33/37] Deleted problematic tests --- .../test/scheduled_test.dart | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/flutter_local_notifications_windows/test/scheduled_test.dart b/flutter_local_notifications_windows/test/scheduled_test.dart index 0f5a0defd..401fb7a1e 100644 --- a/flutter_local_notifications_windows/test/scheduled_test.dart +++ b/flutter_local_notifications_windows/test/scheduled_test.dart @@ -23,21 +23,6 @@ void main() => group('Schedules', () { (await plugin.pendingNotificationRequests()).length; late final Location location = getLocation('US/Eastern'); - test('work with basic times', skip: true, () async { - await plugin.cancelAll(); - expect(await countPending(), 0); - final TZDateTime now = TZDateTime.now(location); - final TZDateTime later = now.add(const Duration(days: 1)); - expect(plugin.zonedSchedule(300, null, null, later, null), completes); - expect(await countPending(), 1); - expect(plugin.zonedSchedule(301, null, null, later, null), completes); - expect(await countPending(), 2); - expect(plugin.zonedSchedule(302, null, null, later, null), completes); - expect(await countPending(), 3); - await plugin.cancelAll(); - expect(await countPending(), 0); - }); - test('do not work with earlier time', () async { final TZDateTime now = TZDateTime.now(location); final TZDateTime earlier = now.subtract(const Duration(days: 1)); From e7b5e21285124678128083b68a2e5488473395b7 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:52:40 +1100 Subject: [PATCH 34/37] Update melos.yaml --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index 59489e068..69d137022 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,7 +32,7 @@ scripts: scope: "*example*" test:unit:windows: description: Runs Windows-specific unit tests - run: melos exec -c 1 --verbose -- "flutter test -j 1 -v" + run: melos exec -c 1 -- "flutter test -j 1" packageFilters: scope: '*_windows' test:integration: From d7c78832b16fd423698e1ff8aa41237da6283cde Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 23:22:59 -0500 Subject: [PATCH 35/37] Addressed feedback --- flutter_local_notifications/example/lib/main.dart | 2 +- flutter_local_notifications/example/lib/windows.dart | 4 ++-- flutter_local_notifications_windows/dart_test.yaml | 1 - .../lib/src/details/notification_audio.dart | 2 +- .../lib/src/details/notification_parts.dart | 12 ++++++------ .../lib/src/msix/ffi.dart | 6 +++--- .../lib/src/msix/stub.dart | 4 ++-- .../test/details_test.dart | 6 +++--- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 618f770bb..02e0c0380 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -1054,7 +1054,7 @@ class _HomePageState extends State { WindowsAction( content: 'Image', arguments: 'image', - imageUri: WindowsImage.assetUri('icons/coworker.png'), + imageUri: WindowsImage.getAssetUri('icons/coworker.png'), ), const WindowsAction( content: 'Context', diff --git a/flutter_local_notifications/example/lib/windows.dart b/flutter_local_notifications/example/lib/windows.dart index 2d744118a..025c8b186 100644 --- a/flutter_local_notifications/example/lib/windows.dart +++ b/flutter_local_notifications/example/lib/windows.dart @@ -240,7 +240,7 @@ Future _showWindowsNotificationWithImages() => windows: WindowsNotificationDetails( images: [ WindowsImage( - WindowsImage.assetUri('icons/4.0x/app_icon_density.png'), + WindowsImage.getAssetUri('icons/4.0x/app_icon_density.png'), altText: 'A beautiful image', ), ], @@ -260,7 +260,7 @@ Future _showWindowsNotificationWithGroups() => WindowsRow([ WindowsColumn([ WindowsImage( - WindowsImage.assetUri('icons/coworker.png'), + WindowsImage.getAssetUri('icons/coworker.png'), altText: 'A local image', ), const WindowsNotificationText( diff --git a/flutter_local_notifications_windows/dart_test.yaml b/flutter_local_notifications_windows/dart_test.yaml index e65d66d4f..0675cf5ba 100644 --- a/flutter_local_notifications_windows/dart_test.yaml +++ b/flutter_local_notifications_windows/dart_test.yaml @@ -1,3 +1,2 @@ platforms: [vm] test_on: windows -# retry: 5 # These tests have concurrency issues. See bin/crash.dart diff --git a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart index 58e2221a8..4c5ff8474 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_audio.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_audio.dart @@ -112,7 +112,7 @@ class WindowsNotificationAudio { WindowsNotificationSound fallback = WindowsNotificationSound.defaultSound, }) : isSilent = false, source = MsixUtils.hasPackageIdentity() - ? MsixUtils.assetUri(assetName).toString() + ? MsixUtils.getAssetUri(assetName).toString() : fallback.name; /// Whether this audio should loop. diff --git a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart index 847a61350..54e079292 100644 --- a/flutter_local_notifications_windows/lib/src/details/notification_parts.dart +++ b/flutter_local_notifications_windows/lib/src/details/notification_parts.dart @@ -41,10 +41,10 @@ enum WindowsImageCrop { /// | `http(s)://` | ❌ | ❌ | ✅ | /// | `ms-appx://` | ❌ | ❌ | ✅ | /// | `file:///` | ✅ | ✅ | 🟨 | -/// | `assetUri()` | ✅ | ✅ | ✅ | +/// | `getAssetUri()` | ✅ | ✅ | ✅ | /// /// Each URI type has different uses: -/// - For Flutter assets, use [assetUri], which return the correct file URI +/// - For Flutter assets, use [getAssetUri], which return the correct file URI /// for debug and release (exe) builds, and an `ms-appx` URI in MSIX builds. /// - For images from the web, use an `https` or `http` URI, but note that /// these only work in MSIX apps. If you need a network image without using @@ -67,16 +67,16 @@ class WindowsImage extends WindowsNotificationPart { this.crop, }); - /// Creates a URI for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). + /// Returns a URI for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images). /// /// - In debug mode, resolves to a file URI to the asset itself /// - In non-MSIX release builds, resolves to a file URI to the bundled asset - /// - In MSIX releases, resolves to an `ms-appx` URI from [Msix.assetUri]. - static Uri assetUri(String assetName) { + /// - In MSIX releases, resolves to an `ms-appx` URI from [Msix.getAssetUri]. + static Uri getAssetUri(String assetName) { if (kDebugMode) { return Uri.file(File(assetName).absolute.path, windows: true); } else if (MsixUtils.hasPackageIdentity()) { - return MsixUtils.assetUri(assetName); + return MsixUtils.getAssetUri(assetName); } else { return Uri.file( File('data/flutter_assets/$assetName').absolute.path, diff --git a/flutter_local_notifications_windows/lib/src/msix/ffi.dart b/flutter_local_notifications_windows/lib/src/msix/ffi.dart index bc0671707..a80eb122d 100644 --- a/flutter_local_notifications_windows/lib/src/msix/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/msix/ffi.dart @@ -20,7 +20,7 @@ class MsixUtils { /// /// These functions will simply do nothing or return empty data in apps /// without package identity. Additionally: - /// - [WindowsImage.assetUri] will return a `file:///` or `ms-appx:///` URI, + /// - [WindowsImage.getAssetUri] will return a `file:///` or `ms-appx:///` URI, /// depending on whether the app is running in debug, release, or as an MSIX. /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps /// with package identity, and a preset fallbacks for apps without. @@ -44,7 +44,7 @@ class MsixUtils { static bool? _hasPackageIdentity; - /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). - static Uri assetUri(String path) => + /// Returns an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). + static Uri getAssetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); } diff --git a/flutter_local_notifications_windows/lib/src/msix/stub.dart b/flutter_local_notifications_windows/lib/src/msix/stub.dart index da93ac072..49f148a8e 100644 --- a/flutter_local_notifications_windows/lib/src/msix/stub.dart +++ b/flutter_local_notifications_windows/lib/src/msix/stub.dart @@ -16,13 +16,13 @@ class MsixUtils { /// /// These functions will simply do nothing or return empty data in apps /// without package identity. Additionally: - /// - [WindowsImage.assetUri] will return a `file:///` or `ms-appx:///` URI, + /// - [WindowsImage.getAssetUri] will return a `file:///` or `ms-appx:///` URI, /// depending on whether the app is running in debug, release, or as an MSIX. /// - [WindowsNotificationAudio.asset] takes an audio file to use for apps /// with package identity, and a preset fallbacks for apps without. static bool hasPackageIdentity() => false; // platforms without FFI /// Gets an `ms-appx:///` URI from a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images). - static Uri assetUri(String path) => + static Uri getAssetUri(String path) => Uri.parse('ms-appx:///data/flutter_assets/$path'); } diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index bb3a33a57..1d77db01c 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -67,7 +67,7 @@ void main() => group('Details:', () { buttonStyle: WindowsButtonStyle.success, inputId: 'input-id', tooltip: 'tooltip', - imageUri: WindowsImage.assetUri('test/icon.png'), + imageUri: WindowsImage.getAssetUri('test/icon.png'), ); plugin ..testDetails(const WindowsNotificationDetails( @@ -98,7 +98,7 @@ void main() => group('Details:', () { const WindowsColumn emptyColumn = WindowsColumn([]); final WindowsImage image = WindowsImage( - WindowsImage.assetUri('test/icon.png'), + WindowsImage.getAssetUri('test/icon.png'), altText: 'an icon', ); const WindowsNotificationText text = @@ -137,7 +137,7 @@ void main() => group('Details:', () { test('Images', () async { final WindowsImage simpleImage = WindowsImage( - WindowsImage.assetUri('asset.png'), + WindowsImage.getAssetUri('asset.png'), altText: 'an icon', ); final WindowsImage complexImage = WindowsImage( From 183762a4efe6e8b347e9b8719bdb9bd3d8ca0202 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Wed, 1 Jan 2025 23:35:32 -0500 Subject: [PATCH 36/37] Removed enableMultithreading() --- .../bin/crash.dart | 72 ------------------- .../lib/src/plugin/base.dart | 9 --- .../lib/src/plugin/ffi.dart | 5 -- .../lib/src/plugin/stub.dart | 6 -- .../test/bindings_test.dart | 2 +- .../test/details_test.dart | 2 +- .../test/plugin_test.dart | 2 +- .../test/scheduled_test.dart | 1 - .../test/xml_test.dart | 2 +- 9 files changed, 4 insertions(+), 97 deletions(-) delete mode 100644 flutter_local_notifications_windows/bin/crash.dart diff --git a/flutter_local_notifications_windows/bin/crash.dart b/flutter_local_notifications_windows/bin/crash.dart deleted file mode 100644 index 7354403cd..000000000 --- a/flutter_local_notifications_windows/bin/crash.dart +++ /dev/null @@ -1,72 +0,0 @@ -// This file demonstrates how the plugin is _not_ thread safe. -// -// This crash can happen when running `dart test -j 1`, which would otherwise -// fix other concurrency issues with the tests. This crash is not significant -// for users as it depends on having two plugins instantiated at the same time, -// which is not recommended, but I left it here as a demonstration if needed. -// -// The experimental function `enableMultithreading()` can fix the issues -// demonstrated by this file, but when testing with `dart test -j 1`, a crash -// occurs as `XmlDocument doc;`, a seemingly harmless statement. I have not -// been able to deduce the cause, and `enableMultithreading()` does not fix it. -// If we can figure that out, tests can be run with `-j 1` and race conditions -// would be eliminated from the tests. - -// ignore_for_file: avoid_print - -import 'dart:isolate'; - -import 'package:flutter_local_notifications_windows/flutter_local_notifications_windows.dart'; -import 'package:timezone/standalone.dart'; - -const WindowsInitializationSettings settings = WindowsInitializationSettings( - appName: 'Test app', - appUserModelId: 'com.test.test', - guid: 'a8c22b55-049e-422f-b30f-863694de08c8', -); - -void main() async { - print('Starting tests'); - await Isolate.spawn(bindingsTest, null); - await Isolate.spawn(scheduledTest, null); - - // This is the critical line. Removing this causes crashes in the Windows SDK - // ignore: invalid_use_of_visible_for_testing_member - FlutterLocalNotificationsWindows().enableMultithreading(); - - await Future.delayed(const Duration(seconds: 5)); - print('Done. Scheduled and binding tests should have completed'); -} - -Future scheduledTest(_) async { - print('Starting scheduled test'); - await Future.delayed(const Duration(seconds: 4)); - final FlutterLocalNotificationsWindows plugin = - FlutterLocalNotificationsWindows(); - await plugin.initialize(settings); - await initializeTimeZone(); - final Location location = getLocation('US/Eastern'); - final TZDateTime now = TZDateTime.now(location); - final TZDateTime later = now.add(const Duration(days: 1)); - await plugin.zonedSchedule(300, null, null, later, null); - await plugin.zonedSchedule(301, null, null, later, null); - await plugin.zonedSchedule(302, null, null, later, null); - print('Scheduled test complete'); -} - -Future bindingsTest(_) async { - print('Starting bindings test'); - final Map bindings = { - 'title': 'Bindings title', - 'body': 'Bindings body' - }; - await Future.delayed(const Duration(seconds: 1)); - final FlutterLocalNotificationsWindows plugin = - FlutterLocalNotificationsWindows(); - await plugin.initialize(settings); - await plugin.show(503, '{title}', '{body}'); - await Future.delayed(const Duration(milliseconds: 100)); - await plugin.updateBindings(id: 503, bindings: bindings); - await plugin.updateBindings(id: 503, bindings: bindings); - print('Bindings test complete'); -} diff --git a/flutter_local_notifications_windows/lib/src/plugin/base.dart b/flutter_local_notifications_windows/lib/src/plugin/base.dart index 0ee3f876d..96df12713 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/base.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/base.dart @@ -1,5 +1,4 @@ import 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart'; -import 'package:meta/meta.dart'; import 'package:timezone/timezone.dart'; import '../details.dart'; @@ -82,14 +81,6 @@ abstract class WindowsNotificationsBase required Map bindings, }); - /// EXPERIMENTAL: Enables multithreading - /// - /// NOTE: This is only here to make tests more stable. This has not been - /// tested in an application as it conflicts with Flutter's preferred - /// configuration for Windows APIs. - @visibleForTesting - void enableMultithreading(); - /// Checks if some XML is a valid Windows notification. /// /// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root. diff --git a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart index d439d6908..9e13258eb 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/ffi.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/ffi.dart @@ -1,7 +1,6 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import 'package:meta/meta.dart'; import '../details.dart'; import '../details/notification_to_xml.dart'; @@ -367,8 +366,4 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { _plugin, id, bindings.toNativeMap(arena)); return getUpdateResult(result); }); - - @override - @visibleForTesting - void enableMultithreading() => _bindings.enableMultithreading(); } diff --git a/flutter_local_notifications_windows/lib/src/plugin/stub.dart b/flutter_local_notifications_windows/lib/src/plugin/stub.dart index 0331fadb3..9fc1aeefb 100644 --- a/flutter_local_notifications_windows/lib/src/plugin/stub.dart +++ b/flutter_local_notifications_windows/lib/src/plugin/stub.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - import '../details.dart'; import 'base.dart'; @@ -93,10 +91,6 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase { }) async => NotificationUpdateResult.success; - @override - @visibleForTesting - void enableMultithreading() {} - @override bool isValidXml(String xml) => false; } diff --git a/flutter_local_notifications_windows/test/bindings_test.dart b/flutter_local_notifications_windows/test/bindings_test.dart index 9f0931801..271ce657c 100644 --- a/flutter_local_notifications_windows/test/bindings_test.dart +++ b/flutter_local_notifications_windows/test/bindings_test.dart @@ -13,7 +13,7 @@ const Map bindings = { }; void main() => group('Bindings', () { - FlutterLocalNotificationsWindows().enableMultithreading(); + // FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); setUpAll(() => plugin.initialize(settings)); diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index 1d77db01c..3cfb9d11e 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -25,7 +25,7 @@ extension PluginUtils on FlutterLocalNotificationsWindows { } void main() => group('Details:', () { - FlutterLocalNotificationsWindows().enableMultithreading(); + // FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); setUpAll(() => plugin.initialize(settings)); diff --git a/flutter_local_notifications_windows/test/plugin_test.dart b/flutter_local_notifications_windows/test/plugin_test.dart index 1352ceef5..3e8962c2c 100644 --- a/flutter_local_notifications_windows/test/plugin_test.dart +++ b/flutter_local_notifications_windows/test/plugin_test.dart @@ -18,7 +18,7 @@ const WindowsInitializationSettings badSettings = WindowsInitializationSettings( ); void main() => group('Plugin', () { - FlutterLocalNotificationsWindows().enableMultithreading(); + // FlutterLocalNotificationsWindows().enableMultithreading(); setUpAll(initializeTimeZones); diff --git a/flutter_local_notifications_windows/test/scheduled_test.dart b/flutter_local_notifications_windows/test/scheduled_test.dart index 401fb7a1e..08e2cbbf9 100644 --- a/flutter_local_notifications_windows/test/scheduled_test.dart +++ b/flutter_local_notifications_windows/test/scheduled_test.dart @@ -9,7 +9,6 @@ const WindowsInitializationSettings settings = WindowsInitializationSettings( guid: 'a8c22b55-049e-422f-b30f-863694de08c8'); void main() => group('Schedules', () { - FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); setUpAll(initializeTimeZones); diff --git a/flutter_local_notifications_windows/test/xml_test.dart b/flutter_local_notifications_windows/test/xml_test.dart index 656fe6208..a3bf1ba9e 100644 --- a/flutter_local_notifications_windows/test/xml_test.dart +++ b/flutter_local_notifications_windows/test/xml_test.dart @@ -60,7 +60,7 @@ const String complexXml = ''' void main() { group('XML', () { - FlutterLocalNotificationsWindows().enableMultithreading(); + // FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); From ff7a0ee403e88b1bdae80ef23573f4cfba4b2c3a Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Thu, 2 Jan 2025 00:45:01 -0500 Subject: [PATCH 37/37] remove more enableMultithreading --- .../lib/src/ffi/bindings.dart | 12 ------------ flutter_local_notifications_windows/src/ffi_api.cpp | 2 -- flutter_local_notifications_windows/src/ffi_api.h | 5 ----- .../test/bindings_test.dart | 1 - .../test/details_test.dart | 1 - .../test/plugin_test.dart | 2 -- .../test/xml_test.dart | 2 -- 7 files changed, 25 deletions(-) diff --git a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart index daec31d4a..475670480 100644 --- a/flutter_local_notifications_windows/lib/src/ffi/bindings.dart +++ b/flutter_local_notifications_windows/lib/src/ffi/bindings.dart @@ -298,18 +298,6 @@ class NotificationsPluginBindings { 'freeLaunchDetails'); late final _freeLaunchDetails = _freeLaunchDetailsPtr.asFunction(); - - /// EXPERIMENTAL: Enables multithreading for this application. - /// - /// NOTE: This is only to make tests more stable and is not intended to be used in applications. - void enableMultithreading() { - return _enableMultithreading(); - } - - late final _enableMultithreadingPtr = - _lookup>('enableMultithreading'); - late final _enableMultithreading = - _enableMultithreadingPtr.asFunction(); } final class NativePlugin extends ffi.Opaque {} diff --git a/flutter_local_notifications_windows/src/ffi_api.cpp b/flutter_local_notifications_windows/src/ffi_api.cpp index 809558cc1..df4e4cb1f 100644 --- a/flutter_local_notifications_windows/src/ffi_api.cpp +++ b/flutter_local_notifications_windows/src/ffi_api.cpp @@ -158,5 +158,3 @@ void freeLaunchDetails(NativeLaunchDetails details) { } if (details.data.entries != nullptr) delete[] details.data.entries; } - -void enableMultithreading() { CoInitializeEx(nullptr, COINIT_MULTITHREADED); } diff --git a/flutter_local_notifications_windows/src/ffi_api.h b/flutter_local_notifications_windows/src/ffi_api.h index 618c09954..b2cfe4d7f 100644 --- a/flutter_local_notifications_windows/src/ffi_api.h +++ b/flutter_local_notifications_windows/src/ffi_api.h @@ -136,11 +136,6 @@ FFI_PLUGIN_EXPORT void freeDetailsArray(NativeNotificationDetails* ptr); /// Releases the memory associated with a [NativeLaunchDetails]. FFI_PLUGIN_EXPORT void freeLaunchDetails(NativeLaunchDetails details); -/// EXPERIMENTAL: Enables multithreading for this application. -/// -/// NOTE: This is only to make tests more stable and is not intended to be used in applications. -FFI_PLUGIN_EXPORT void enableMultithreading(); - #ifdef __cplusplus } #endif diff --git a/flutter_local_notifications_windows/test/bindings_test.dart b/flutter_local_notifications_windows/test/bindings_test.dart index 271ce657c..0f1c921ab 100644 --- a/flutter_local_notifications_windows/test/bindings_test.dart +++ b/flutter_local_notifications_windows/test/bindings_test.dart @@ -13,7 +13,6 @@ const Map bindings = { }; void main() => group('Bindings', () { - // FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); setUpAll(() => plugin.initialize(settings)); diff --git a/flutter_local_notifications_windows/test/details_test.dart b/flutter_local_notifications_windows/test/details_test.dart index 3cfb9d11e..ea90159cd 100644 --- a/flutter_local_notifications_windows/test/details_test.dart +++ b/flutter_local_notifications_windows/test/details_test.dart @@ -25,7 +25,6 @@ extension PluginUtils on FlutterLocalNotificationsWindows { } void main() => group('Details:', () { - // FlutterLocalNotificationsWindows().enableMultithreading(); final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows(); setUpAll(() => plugin.initialize(settings)); diff --git a/flutter_local_notifications_windows/test/plugin_test.dart b/flutter_local_notifications_windows/test/plugin_test.dart index 3e8962c2c..7564ec322 100644 --- a/flutter_local_notifications_windows/test/plugin_test.dart +++ b/flutter_local_notifications_windows/test/plugin_test.dart @@ -18,8 +18,6 @@ const WindowsInitializationSettings badSettings = WindowsInitializationSettings( ); void main() => group('Plugin', () { - // FlutterLocalNotificationsWindows().enableMultithreading(); - setUpAll(initializeTimeZones); test('initializes safely', () async { diff --git a/flutter_local_notifications_windows/test/xml_test.dart b/flutter_local_notifications_windows/test/xml_test.dart index a3bf1ba9e..e2998f190 100644 --- a/flutter_local_notifications_windows/test/xml_test.dart +++ b/flutter_local_notifications_windows/test/xml_test.dart @@ -60,8 +60,6 @@ const String complexXml = ''' void main() { group('XML', () { - // FlutterLocalNotificationsWindows().enableMultithreading(); - final FlutterLocalNotificationsWindows plugin = FlutterLocalNotificationsWindows();