Skip to content

Commit

Permalink
[flutter_local_notifications_windows] Windows FFI plugin (#2366)
Browse files Browse the repository at this point in the history
* Start impl of Windows notifications

* Remove dependency override

* Fix notification content not showing

* Implement initialize method

* WIP impl of cancelling notifications

* Add support for optional title/body

* Impl Registry logic for notification handling

* Tidy up code

* Remove dependency overrides

* Implement cancel/cancelAll

* WIP: Impl onSelectNotification

* Fix read access violation

* Small fix on notification.

* Pass payload to Dart on notification selected

* Remove melos files

* Attempting to handle msix

* Forgot to check for optional value

* Fix Initialize not returning value

* temp fix issue about GetCurrentPackageFullName

* fix: windows impl with upstream merge

* feat: support specify guid in windows

* fix: ERROR_INVALID_PARAMETER when GetCurrentPackageFullName.

* feat: support raw xml in windows

* Init error

* Catch issues with GUIDs

* Support all of the Windows Toast Notification API

* Send user input back to plugin

* Fixed typos

* Added getActiveNotifications for Windows

* All examples working!

* Added update, not working on unpackaged

* Copied NotificationManager

* Hiding old code for now

* one more try at progress

* Experimental update support

* V2

* Working progress updates

* Formatting

* All notifications working

* Moved Windows examples out of main.dart;

* cleanup

* Hide repeating examples on Windows

* Moved example XML box to bottom

* Made WindowsImage.file constructor

* getLaunchDetails working

* Cleanup

* Made all images msix safe

* Catch when getLaunchDetails() is called before init() and add dynamic example

* Fixed issue with special characters in file

* Added Windows FFI package

* moved to FFI plugin

* Reduced C <--> C++ logic, embraced C API

* Upgraded Dart to 3.1.0

* Documented the C++ code

* Made more constructors const

* Updated example

* Documented Dart APIs

* Added unit tests and polished

* Configured exampleand Melos for Windows

* Documented everything and removed Flutter dependency from _windows

* Added retry to flaky test

* Documented the crash test

* Removed flutter from _platform_interface and bumped

* Removed XML from main plugin

* Simplify C++ code and use ffigen 13.0 for Dart enums

* Made attributes and notifToXml() private

* Fixed readme

* Fixed typo

* WindowsTextInput.hintText -> .placeHolderContent;

* Improved docs on WindowsNotificationText.placement

* Removed WindowsActivationType.background as it's only supported on UWP

* Fixed warning about mixing struct and class

* Changed docs for ffi plugin

* Added zonedScheduleRawXml

* Doc comment tweak

* Tweaked another doc comment

* Removed custom lints, using 80 chars per line and standard lints

* Changed all C int-bools to plain bools

* Changed C++ side to use bools as well

* Experimental multithreading support

* Responded to feedback

- Changed `toXml` to `buildXml`
- Fixed comments on stub class
- Bumped Dart version to ^3.2
- Fixed `enableMultithreading` not being always annotated
- Only test on Windows

* Updated minimum Flutter version to 3.16

* Added _windows to dependabot

* Changed main package to use Dart 3.2, Flutter 3.16

* Moved all XML code to private files

* Cleanup

* Sealed WindowsNotificationPart

* Restrict tests to Windows platforms

* Update CI to use windows-latest for Windows tests + build

* Formatted repo

* Formatted again

* Fix Windows CI to not use .sh script

* Fix workflow name

* Downgraded _windows meta to 1.11

* Added parseGuid function for SDKs that don't have it built-in

* Use a vector instead of malloc for plugin.cpp

* Updated pubspec to include Flutter and updated platform_interface

* Test renaming repo

* Shorter names

* Shorter names on Windows build version and stable

* Added clang-format to _windows

* Fixed yaml

* Print clang format version

* Latest clang-format

* sudo apt update

* Add universe to apt

* Use clang from pip for new version

* More formatting

* Commit back any changes that are made by clang

* Bad formatting to lure clang into re-formatting

* Use auto-commit action

* Clang format

---------

Co-authored-by: Kenneth <kenneth.ng.5226@outlook.com>
Co-authored-by: Alexandre Zollinger Chohfi <alzollin@microsoft.com>
Co-authored-by: lightrabbit <lightpacerabbit@gmail.com>
Co-authored-by: Levi Lesches <levi.lesches@kdab.com>
Co-authored-by: Levi-Lesches <Levi-Lesches@users.noreply.github.com>
  • Loading branch information
6 people authored Nov 12, 2024
1 parent c15783f commit 697b007
Show file tree
Hide file tree
Showing 96 changed files with 5,638 additions and 342 deletions.
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ updates:
directory: "/flutter_local_notifications_linux"
schedule:
interval: "daily"
- package-ecosystem: "pub"
directory: "/flutter_local_notifications_windows"
schedule:
interval: "daily"
- package-ecosystem: "pub"
directory: "/flutter_local_notifications"
schedule:
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,26 @@ jobs:
which swiftlint || brew install swiftlint
swiftlint --fix
git diff --exit-code || (git commit --all -m "Swift Format" && git push)
windows_cpp_format:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Install latest clang-format
run: |
sudo apt install python3-pip -y
python3 -m pip install clang-format
- name: Format C++ code
run: |
cd flutter_local_notifications_windows/src
~/.local/bin/clang-format --version
~/.local/bin/clang-format *.cpp *.hpp *.h -i
# git diff --exit-code || (git commit --all -m "Clang Format" && git push)
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Clang format
94 changes: 79 additions & 15 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
run: melos run build:example_android
build_example_android_3_13:
name: Build Android example app (3.13)
build_example_android_3_19:
name: Build Android example app (3.19)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: 3.13.0
flutter-version: 3.19.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
Expand All @@ -90,15 +90,15 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
run: melos run build:example_ios
build_example_ios_3_13:
name: Build iOS example app (3.13)
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.13.0
flutter-version: 3.19.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
Expand All @@ -119,15 +119,15 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
run: melos run build:example_macos
build_example_macos_3_13:
name: Build macOS example app (3.13)
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.13.0
flutter-version: 3.19.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
Expand All @@ -152,15 +152,15 @@ jobs:
- run: flutter config --enable-linux-desktop
- name: Build
run: melos run build:example_linux
build_example_linux_3_13:
name: Build Linux example app (3.13)
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.13.0
flutter-version: 3.19.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
Expand All @@ -171,6 +171,57 @@ jobs:
- run: flutter config --enable-linux-desktop
- name: Build
run: melos run build:example_linux
build_example_windows_stable:
name: Build Windows example app (stable channel)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
run: |
dart pub global activate melos
melos bootstrap
# Windows has a filename length limit, which this repo just hits
# This saves us precious characters during the compilation
- name: Rename directory
run: |
move flutter_local_notifications f
move f\example f\e
- name: Build
run: |
cd f\e
dart pub get
dart run msix:create
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.19.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
run: |
dart pub global activate melos
melos bootstrap
# Windows has a filename length limit, which this repo just hits
# This saves us precious characters during the compilation
- name: Rename directory
run: |
move flutter_local_notifications f
move f\example f\e
- name: Build
run: |
cd f\e
dart pub get
dart run msix:create
unit_tests_dart:
name: Run all unit tests (Dart)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -199,6 +250,22 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Run Tests
run: melos run test:unit:android
unit_tests_windows:
name: Run all unit tests (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install tools
run: |
dart pub global activate melos
melos bootstrap
- name: Run Tests
run: melos run test:unit:windows
integration_tests_android:
name: Run integration tests (Android)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -245,6 +312,3 @@ jobs:
brew install applesimutils
applesimutils --byId ${{ steps.simulator-action.outputs.udid}} --bundle com.dexterous.flutterLocalNotificationsExample --setPermissions notifications=YES
- run: melos run test:integration



42 changes: 33 additions & 9 deletions flutter_local_notifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

A cross platform plugin for displaying local notifications.

>[!IMPORTANT]
>[!IMPORTANT]
> Given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach e.g. have a minimum Flutter SDK version of 3.13. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future
>[!IMPORTANT]
> Given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will occasionally be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future
## Table of contents
Expand Down Expand Up @@ -59,6 +61,7 @@ A cross platform plugin for displaying local notifications.
* **iOS** Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework)
* **macOS** Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework)
* **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)

Expand Down Expand Up @@ -109,6 +112,10 @@ Note: the plugin requires Flutter SDK 3.13 at a minimum. The list of support pla
* [Linux] Ability to set custom hints
* [Linux] Ability to suppress sound
* [Linux] Resident and transient notifications
* [Windows] Can show raw XML (see the [Notifications Visualizer](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer))
* [Windows] A full Dart API for all the options supported by toast notifications
* [Windows] Can configure images, buttons, dropdowns, text input, and launch behavior
* [Windows] Can dynamically update notifications after they've been shown

## ⚠ Caveats and limitations

Expand Down Expand Up @@ -154,6 +161,11 @@ Scheduled/pending notifications is currently not supported due to the lack of a

The `onDidReceiveNotificationResponse` callback runs on the main isolate of the running application and cannot be launched in the background if the application is not running. To respond to notification after the application is terminated, your application should be registered as DBus activatable (please see [DBusApplicationLaunching](https://wiki.gnome.org/HowDoI/DBusApplicationLaunching) for more information), and register action before activating the application. This is difficult to do in a plugin because plugins instantiate during application activation, so `getNotificationAppLaunchDetails` can't be implemented without changing the main user application.

### Windows limitations

- Windows does not support repeating notifications, so [`periodicallyShow`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShow.html) and [`periodicallyShowWithDuration`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShowWithDuration.html) will throw `UnsupportedError`s.
- Windows only allows apps with package identity to retrieve previously shown notifications. This means that on an app that was not packaged as an [MSIX](https://learn.microsoft.com/en-us/windows/msix/overview) installer, [`cancel`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancel.html) does nothing and [`getActiveNotifications`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/getActiveNotifications.html) will return an empty list. To package your app as an MSIX, see [`package:msix`](https://pub.dev/packages/msix) and the `msix` section in [the example's `pubspec.yaml`](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/pubspec.yaml).

### Notification payload

Due to some limitations on iOS with how it treats null values in dictionaries, a null notification payload is coalesced to an empty string behind the scenes on all platforms for consistency.
Expand All @@ -166,6 +178,7 @@ Due to some limitations on iOS with how it treats null values in dictionaries, a
| iOS | <img height="414" src="https://github.com/MaikuB/flutter_local_notifications/raw/master/images/ios_notification.png"> |
| macOS | <img src="https://github.com/MaikuB/flutter_local_notifications/raw/master/images/macos_notification.png"> |
| Linux | <img src="https://github.com/MaikuB/flutter_local_notifications/raw/master/images/gnome_linux_notification.png"> <img src="https://github.com/MaikuB/flutter_local_notifications/raw/master/images/kde_linux_notification.png"> |
| Windows | <img src="https://github.com/MaikuB/flutter_local_notifications/raw/master/images/windows_notification.png"> |


## 👏 Acknowledgements
Expand All @@ -174,6 +187,7 @@ Due to some limitations on iOS with how it treats null values in dictionaries, a
* [Jeff Scaturro](https://github.com/JeffScaturro) for submitting the PR to fix the iOS issue around showing daily and weekly notifications and migrating the plugin to AndroidX
* [Ian Cavanaugh](https://github.com/icavanaugh95) for helping create a sample to reproduce the problem reported in [issue #88](https://github.com/MaikuB/flutter_local_notifications/issues/88)
* [Zhang Jing](https://github.com/byrdkm17) for adding 'ticker' support for Android notifications
* [Kenneth](https://github.com/kennethnym), [lightrabbit](https://github.com/lightrabbit), and [Levi Lesches](https://github.com/Levi-Lesches) for adding Windows support
* ...and everyone else for their contributions. They are greatly appreciated

## 🔧 Android Setup
Expand Down Expand Up @@ -270,7 +284,7 @@ For apps that need the following functionality please complete the following in
* Declare the service exposed by the plugin by adding the following between `<application>` tags. An example of what this looks like is below where `<foreground service types>` should be replaced with the foreground service type(s) your app needs. If you want your foreground service to be stopped if your app is stopped, set `android:stopWithTask` to `true`
```xml
<service
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:exported="false"
android:stopWithTask="false"
android:foregroundServiceType="<foreground service types>">
Expand Down Expand Up @@ -386,7 +400,7 @@ then extend `didFinishLaunchingWithOptions` and register the callback:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Add this method
// Add this method
[FlutterLocalNotificationsPlugin setPluginRegistrantCallback:registerPlugins];
}
```
Expand Down Expand Up @@ -529,11 +543,18 @@ final DarwinInitializationSettings initializationSettingsDarwin =
final LinuxInitializationSettings initializationSettingsLinux =
LinuxInitializationSettings(
defaultActionName: 'Open notification');
final WindowsInitializationSettings initializationSettingsWindows =
WindowsInitializationSettings(
appName: 'Flutter Local Notifications Example',
appUserModelId: 'Com.Dexterous.FlutterLocalNotificationsExample',
// Search online for GUID generators to make your own
guid: 'd49b0314-ee7a-4626-bf79-97cdb8a991bb')
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
macOS: initializationSettingsDarwin,
linux: initializationSettingsLinux);
linux: initializationSettingsLinux,
windows: initializationSettingsWindows);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse);
```
Expand All @@ -557,9 +578,9 @@ void onDidReceiveNotificationResponse(NotificationResponse notificationResponse)

In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation is complete, then you can manage the displaying of notifications. Note that this callback is only intended to work when the app is running. For scenarios where your application needs to handle when a notification launched the app refer to [here](#getting-details-on-if-the-app-was-launched-via-a-notification-created-by-this-plugin)

The `DarwinInitializationSettings` class provides default settings on how the notification be presented when it is triggered and the application is in the foreground on iOS/macOS. There are optional named parameters that can be modified to suit your application's purposes. Here, it is omitted and the default values for these named properties is set such that all presentation options (alert, sound, badge) are enabled.
The `DarwinInitializationSettings` class provides default settings on how the notification be presented when it is triggered and the application is in the foreground on iOS/macOS. There are optional named parameters that can be modified to suit your application's purposes. Here, it is omitted and the default values for these named properties is set such that all presentation options (alert, sound, badge) are enabled.

The `LinuxInitializationSettings` class requires a name for the default action that calls the `onDidReceiveNotificationResponse` callback when the notification is clicked.
The `LinuxInitializationSettings` class requires a name for the default action that calls the `onDidReceiveNotificationResponse` callback when the notification is clicked.

On iOS and macOS, initialisation may show a prompt to requires users to give the application permission to display notifications (note: permissions don't need to be requested on Android). Depending on when this happens, this may not be the ideal user experience for your application. If so, please refer to the next section on how to work around this.

Expand Down Expand Up @@ -647,7 +668,7 @@ The details specific to the Android platform are also specified. This includes t

### Scheduling a notification

Starting in version 2.0 of the plugin, scheduling notifications now requires developers to specify a date and time relative to a specific time zone. This is to solve issues with daylight saving time that existed in the `schedule` method that is now deprecated. A new `zonedSchedule` method is provided that expects an instance `TZDateTime` class provided by the [`timezone`](https://pub.dev/packages/timezone) package. Even though the `timezone` package is be a transitive dependency via this plugin, it is recommended based on [this lint rule](https://dart-lang.github.io/linter/lints/depend_on_referenced_packages.html) that you also add the `timezone` package as a direct dependency.
Starting in version 2.0 of the plugin, scheduling notifications now requires developers to specify a date and time relative to a specific time zone. This is to solve issues with daylight saving time that existed in the `schedule` method that is now deprecated. A new `zonedSchedule` method is provided that expects an instance `TZDateTime` class provided by the [`timezone`](https://pub.dev/packages/timezone) package. Even though the `timezone` package is be a transitive dependency via this plugin, it is recommended based on [this lint rule](https://dart-lang.github.io/linter/lints/depend_on_referenced_packages.html) that you also add the `timezone` package as a direct dependency.

Once the depdendency as been added, usage of the `timezone` package requires initialisation that is covered in the package's readme. For convenience the following are code snippets used by the example app.

Expand Down Expand Up @@ -699,6 +720,8 @@ If you are trying to update your code so it doesn't use the deprecated methods f

### Periodically show a notification with a specified interval

**Note** This is not supported on Windows

```dart
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
Expand All @@ -720,8 +743,7 @@ final List<PendingNotificationRequest> pendingNotificationRequests =

### Retrieving active notifications



**Note** On Windows, your app must be packaged as an MSIX to do this. See the limitations section.

```dart
final List<ActiveNotification> activeNotifications =
Expand Down Expand Up @@ -805,6 +827,8 @@ await flutterLocalNotificationsPlugin.show(

### Cancelling/deleting a notification

**Note** On Windows, your app must be packaged as an MSIX to do this. See the limitations section.

```dart
// cancel the notification with id value of zero
await flutterLocalNotificationsPlugin.cancel(0);
Expand Down
Loading

0 comments on commit 697b007

Please sign in to comment.