Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion flutter_local_notifications/docs/windows-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ The [msix package](https://pub.dev/packages/msix) can help generate an MSIX inst
| Package name | `identity_name` | `appUserModelId` |
| Unique ID | `toast_activator.clsid` | `guid` |

The display name is set in the MSIX and cannot be changed in Dart. The GUID/CLSID, as the name implies, needs to be _globally unique_. Avoid using IDs from tutorials as other apps on the user's device may be using them as well. Instead, use [online GUID generators](https://guidgenerator.com) to generate a new, unique GUID, and use that for your MSIX and Dart options.
The display name is set in the MSIX and cannot be changed in Dart. The GUID/CLSID, as the name implies, needs to be _globally unique_. Avoid using IDs from tutorials as other apps on the user's device may be using them as well. Instead, use [online GUID generators](https://guidgenerator.com) to generate a new, unique GUID, and use that for your MSIX and Dart options.

For a full example, see the `msix_config` section of [the example app's `pubspec.yaml`](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/pubspec.yaml).

## Custom icons

For MSIX builds, you'll need to specify the `logo_path` variable in your MSIX configuration (see above). For debug and non-MSIX release builds, your icon will need to be a Flutter asset. To show a custom icon for a specific notification, use a [WindowsImage] with [WindowsImagePlacement.appLogoOverride] in your notification details.

> [!Note]
>
> For non-MSIX builds, the path to the icon will be set at runtime and won't change until your app is launched again. If, for example, your app is launched, a notification is scheduled, your app is closed, then your app's installation directory changes, the notification will not have an icon.
2 changes: 1 addition & 1 deletion flutter_local_notifications/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ class _HomePageState extends State<HomePage> {
WindowsAction(
content: 'Image',
arguments: 'image',
imageUri: WindowsImage.getAssetUri('icons/coworker.png'),
imageUri: WindowsAssetUtils.getAssetUri('icons/coworker.png'),
),
const WindowsAction(
content: 'Context',
Expand Down
5 changes: 3 additions & 2 deletions flutter_local_notifications/example/lib/windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const WindowsInitializationSettings initSettings =
appName: 'Flutter Local Notifications Example',
appUserModelId: 'Com.Dexterous.FlutterLocalNotificationsExample',
guid: 'd49b0314-ee7a-4626-bf79-97cdb8a991bb',
iconAssetPath: 'icons/coworker.png',
);

class _WindowsXmlBuilder extends StatefulWidget {
Expand Down Expand Up @@ -240,7 +241,7 @@ Future<void> _showWindowsNotificationWithImages() =>
windows: WindowsNotificationDetails(
images: <WindowsImage>[
WindowsImage(
WindowsImage.getAssetUri('icons/4.0x/app_icon_density.png'),
WindowsAssetUtils.getAssetUri('icons/4.0x/app_icon_density.png'),
altText: 'A beautiful image',
),
],
Expand All @@ -260,7 +261,7 @@ Future<void> _showWindowsNotificationWithGroups() =>
WindowsRow(<WindowsColumn>[
WindowsColumn(<WindowsNotificationPart>[
WindowsImage(
WindowsImage.getAssetUri('icons/coworker.png'),
WindowsAssetUtils.getAssetUri('icons/coworker.png'),
altText: 'A local image',
),
const WindowsNotificationText(
Expand Down
1 change: 1 addition & 0 deletions flutter_local_notifications/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ msix_config:
store: false
install_certificate: false
output_name: example
logo_path: icons/app_icon.png
toast_activator:
clsid: "d49b0314-ee7a-4626-bf79-97cdb8a991bb"
arguments: "msix-args"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'src/details.dart';
export 'src/msix/stub.dart' if (dart.library.ffi) 'src/msix/ffi.dart';
export 'src/msix.dart';
export 'src/plugin/stub.dart' if (dart.library.ffi) 'src/plugin/ffi.dart';
export 'src/utils.dart';
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'notification_parts.dart';

/// Plugin initialization settings for Windows.
class WindowsInitializationSettings {
/// Creates a new settings object for initializing this plugin on Windows.
const WindowsInitializationSettings({
required this.appName,
required this.appUserModelId,
required this.guid,
this.iconPath,
required this.iconAssetPath,
});

/// The name of the app that should be shown in the notification toast.
Expand All @@ -21,6 +23,14 @@ class WindowsInitializationSettings {
/// The GUID that identifies the notification activation callback.
final String guid;

/// The path to the icon of the notification.
final String? iconPath;
/// The asset path for the default icon.
///
/// This icon must be a Flutter asset declared in the Pubspec, and will be
/// shown by default. To override it on a specific notification (say, to show
/// a user profile picture instead), use a [WindowsImage] with
/// [WindowsImagePlacement.appLogoOverride] in your notification details.
///
/// Note that for MSIX releases, you must configure the default icon in your
/// MSIX configuration. See the Windows Setup Guide for more details.
final String iconAssetPath;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '../../flutter_local_notifications_windows.dart';
import '../msix.dart';

/// A preset sound for a Windows notification.
enum WindowsNotificationSound {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import '../../flutter_local_notifications_windows.dart';
import '../utils.dart';

/// A text or image element in a Windows notification.
///
Expand Down Expand Up @@ -37,15 +34,16 @@ enum WindowsImageCrop {
/// depending on if your app is packaged as an MSIX. Refer to the following:
///
/// | URI | Debug | Release (EXE) | Release (MSIX) |
/// |--------|--------|--------|--------|
/// | `http(s)://` | ❌ | ❌ | ✅ |
/// | `ms-appx://` | ❌ | ❌ | ✅ |
/// | `file:///` | ✅ | ✅ | 🟨 |
/// |-----------------|-- --|----|-----|
/// | `http(s)://` | ❌ | ❌ | ✅ |
/// | `ms-appx://` | ❌ | ❌ | ✅ |
/// | `file:///` | ✅ | ✅ | 🟨 |
/// | `getAssetUri()` | ✅ | ✅ | ✅ |
///
/// Each URI type has different uses:
/// - 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 Flutter assets, use [WindowsAssetUtils.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
/// MSIX, consider downloading it directly and using a file URI after. Also
Expand All @@ -67,24 +65,6 @@ class WindowsImage extends WindowsNotificationPart {
this.crop,
});

/// 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.getAssetUri].
static Uri getAssetUri(String assetName) {
if (kDebugMode) {
return Uri.file(File(assetName).absolute.path, windows: true);
} else if (MsixUtils.hasPackageIdentity()) {
return MsixUtils.getAssetUri(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;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import '../../flutter_local_notifications_windows.dart';

/// A progress bar in a Windows notification.
///
/// To update the progress after the notification has been shown,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:xml/xml.dart';

import '../../flutter_local_notifications_windows.dart';
import '../details.dart';
import 'xml/details.dart';

export 'xml/progress.dart';
Expand Down
1 change: 1 addition & 0 deletions flutter_local_notifications_windows/lib/src/msix.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'msix/stub.dart' if (dart.library.ffi) 'msix/ffi.dart';
2 changes: 1 addition & 1 deletion flutter_local_notifications_windows/lib/src/msix/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class MsixUtils {
///
/// These functions will simply do nothing or return empty data in apps
/// without package identity. Additionally:
/// - [WindowsImage.getAssetUri] will return a `file:///` or `ms-appx:///` URI,
/// - [WindowsAssetUtils.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.
Expand Down
2 changes: 1 addition & 1 deletion flutter_local_notifications_windows/lib/src/msix/stub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MsixUtils {
///
/// These functions will simply do nothing or return empty data in apps
/// without package identity. Additionally:
/// - [WindowsImage.getAssetUri] will return a `file:///` or `ms-appx:///` URI,
/// - [WindowsAssetUtils.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.
Expand Down
5 changes: 4 additions & 1 deletion flutter_local_notifications_windows/lib/src/plugin/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../details.dart';
import '../details/notification_to_xml.dart';
import '../ffi/bindings.dart';
import '../ffi/utils.dart';
import '../utils.dart';

import 'base.dart';

Expand Down Expand Up @@ -85,7 +86,9 @@ class FlutterLocalNotificationsWindows extends WindowsNotificationsBase {
settings.appUserModelId.toNativeUtf8(allocator: arena);
final Pointer<Utf8> guid = settings.guid.toNativeUtf8(allocator: arena);
final Pointer<Utf8> iconPath =
settings.iconPath?.toNativeUtf8(allocator: arena) ?? nullptr;
WindowsAssetUtils.getAssetFile(settings.iconAssetPath)
?.windowsPath.toNativeUtf8(allocator: arena)
?? nullptr;
final NativeNotificationCallback callback =
NativeCallable<NativeNotificationCallbackFunction>.listener(
_globalLaunchCallback,
Expand Down
48 changes: 48 additions & 0 deletions flutter_local_notifications_windows/lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:io';

import 'package:flutter/foundation.dart';

import 'msix.dart';

/// Utility methods for resolving Flutter assets for different build modes.
class WindowsAssetUtils {
/// Returns a [File] for a [Flutter asset](https://docs.flutter.dev/ui/assets/assets-and-images#loading-images).
///
/// - In debug mode, resolves to a file to the asset itself
/// - In non-MSIX release builds, resolves to a file to the bundled asset
/// - In MSIX releases, resolves to null
///
/// MSIX bundles don't support getting the file path for assets. Use
/// [getAssetUri] to get an `ms-appx`-style [Uri] instead of a [File].
static File? getAssetFile(String assetPath) {
if (kDebugMode) {
return File(assetPath);
} else if (MsixUtils.hasPackageIdentity()) {
return null; // msix has its own icon in the msix_config
} else {
return File('data/flutter_assets/$assetPath');
}
}

/// 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 `ms-appx` URI from [MsixUtils.getAssetUri]
static Uri getAssetUri(String assetPath) {
if (kDebugMode) {
return Uri.file(File(assetPath).windowsPath, windows: true);
} else if (MsixUtils.hasPackageIdentity()) {
return MsixUtils.getAssetUri(assetPath);
} else {
final File file = File('data/flutter_assets/$assetPath');
return Uri.file(file.windowsPath, windows: true);
}
}
}

/// Utility methods for files on Windows.
extension WindowsFileUtils on File {
/// Returns a Windows-style path for this file.
String get windowsPath => absolute.path.replaceAll('/', r'\');
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const WindowsInitializationSettings settings = WindowsInitializationSettings(
appName: 'Test app',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8',
iconAssetPath: 'icon.png',
);

const Map<String, String> bindings = <String, String>{
Expand Down
7 changes: 4 additions & 3 deletions flutter_local_notifications_windows/test/details_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const WindowsInitializationSettings settings = WindowsInitializationSettings(
appName: 'Test app',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8',
iconAssetPath: 'icon.png',
);

extension PluginUtils on FlutterLocalNotificationsWindows {
Expand Down Expand Up @@ -66,7 +67,7 @@ void main() => group('Details:', () {
buttonStyle: WindowsButtonStyle.success,
inputId: 'input-id',
tooltip: 'tooltip',
imageUri: WindowsImage.getAssetUri('test/icon.png'),
imageUri: WindowsAssetUtils.getAssetUri('test/icon.png'),
);
plugin
..testDetails(const WindowsNotificationDetails(
Expand Down Expand Up @@ -97,7 +98,7 @@ void main() => group('Details:', () {
const WindowsColumn emptyColumn =
WindowsColumn(<WindowsNotificationPart>[]);
final WindowsImage image = WindowsImage(
WindowsImage.getAssetUri('test/icon.png'),
WindowsAssetUtils.getAssetUri('test/icon.png'),
altText: 'an icon',
);
const WindowsNotificationText text =
Expand Down Expand Up @@ -136,7 +137,7 @@ void main() => group('Details:', () {

test('Images', () async {
final WindowsImage simpleImage = WindowsImage(
WindowsImage.getAssetUri('asset.png'),
WindowsAssetUtils.getAssetUri('asset.png'),
altText: 'an icon',
);
final WindowsImage complexImage = WindowsImage(
Expand Down
2 changes: 2 additions & 0 deletions flutter_local_notifications_windows/test/plugin_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ const WindowsInitializationSettings goodSettings =
appName: 'test',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8',
iconAssetPath: 'icon.png',
);

const WindowsInitializationSettings badSettings = WindowsInitializationSettings(
appName: 'test',
appUserModelId: 'com.test.test',
guid: '123',
iconAssetPath: 'icon.png',
);

void main() => group('Plugin', () {
Expand Down
8 changes: 5 additions & 3 deletions flutter_local_notifications_windows/test/scheduled_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import 'package:timezone/data/latest_all.dart';
import 'package:timezone/standalone.dart';

const WindowsInitializationSettings settings = WindowsInitializationSettings(
appName: 'Test app',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8');
appName: 'Test app',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8',
iconAssetPath: 'icon.png',
);

void main() => group('Schedules', () {
final FlutterLocalNotificationsWindows plugin =
Expand Down
1 change: 1 addition & 0 deletions flutter_local_notifications_windows/test/xml_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const WindowsInitializationSettings settings = WindowsInitializationSettings(
appName: 'test',
appUserModelId: 'com.test.test',
guid: 'a8c22b55-049e-422f-b30f-863694de08c8',
iconAssetPath: 'icon.png',
);

const String emptyXml = '';
Expand Down
Loading