diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index efd28b530..50af325fa 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,8 +1,10 @@ -# [vNext] +# [15.0.0-dev.1] * **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 * [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` +* **Breaking change** [iOS][macOS] 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. +* Updated API documentation related to the iOS/macOS notification presentation options to include links to Apple's documentations to show what they correspond to # [14.1.1] diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index b13c83036..0a6b4d4b4 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -872,6 +872,20 @@ class _HomePageState extends State { await _showNotificationWithTimeSensitiveInterruptionLevel(); }, ), + PaddedElevatedButton( + buttonText: 'Show notification with banner but not in ' + 'notification centre', + onPressed: () async { + await _showNotificationWithBannerNotInNotificationCentre(); + }, + ), + PaddedElevatedButton( + buttonText: + 'Show notification in notification centre only', + onPressed: () async { + await _showNotificationInNotificationCentreOnly(); + }, + ), ], if (!kIsWeb && Platform.isLinux) ...[ const Text( @@ -1328,7 +1342,9 @@ class _HomePageState extends State { sound: RawResourceAndroidNotificationSound('slow_spring_board'), ); const DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(sound: 'slow_spring_board.aiff'); + DarwinNotificationDetails( + sound: 'slow_spring_board.aiff', + ); final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( sound: AssetsLinuxSound('sound/slow_spring_board.mp3'), @@ -1413,7 +1429,9 @@ class _HomePageState extends State { playSound: false, styleInformation: DefaultStyleInformation(true, true)); const DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(presentSound: false); + DarwinNotificationDetails( + presentSound: false, + ); const NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails, @@ -2073,7 +2091,9 @@ class _HomePageState extends State { Future _showNotificationWithSubtitle() async { const DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(subtitle: 'the subtitle'); + DarwinNotificationDetails( + subtitle: 'the subtitle', + ); const NotificationDetails notificationDetails = NotificationDetails( iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -2099,7 +2119,9 @@ class _HomePageState extends State { String threadIdentifier, ) { final DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(threadIdentifier: threadIdentifier); + DarwinNotificationDetails( + threadIdentifier: threadIdentifier, + ); return NotificationDetails( iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); } @@ -2127,7 +2149,8 @@ class _HomePageState extends State { Future _showNotificationWithTimeSensitiveInterruptionLevel() async { const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( - interruptionLevel: InterruptionLevel.timeSensitive); + interruptionLevel: InterruptionLevel.timeSensitive, + ); const NotificationDetails notificationDetails = NotificationDetails( iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -2138,6 +2161,38 @@ class _HomePageState extends State { payload: 'item x'); } + Future _showNotificationWithBannerNotInNotificationCentre() async { + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( + presentBanner: true, + presentList: false, + ); + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'title of banner notification', + 'body of banner notification', + notificationDetails, + payload: 'item x'); + } + + Future _showNotificationInNotificationCentreOnly() async { + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( + presentBanner: false, + presentList: true, + ); + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'title of notification shown only in notification centre', + 'body of notification shown only in notification centre', + notificationDetails, + payload: 'item x'); + } + Future _showNotificationWithoutTimestamp() async { const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails('your channel id', 'your channel name', @@ -2212,12 +2267,14 @@ class _HomePageState extends State { final String bigPicturePath = await _downloadAndSaveFile( 'https://dummyimage.com/600x200', 'bigPicture.jpg'); final DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(attachments: [ - DarwinNotificationAttachment( - bigPicturePath, - hideThumbnail: hideThumbnail, - ) - ]); + DarwinNotificationDetails( + attachments: [ + DarwinNotificationAttachment( + bigPicturePath, + hideThumbnail: hideThumbnail, + ) + ], + ); final NotificationDetails notificationDetails = NotificationDetails( iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -2231,19 +2288,21 @@ class _HomePageState extends State { final String bigPicturePath = await _downloadAndSaveFile( 'https://dummyimage.com/600x200', 'bigPicture.jpg'); final DarwinNotificationDetails darwinNotificationDetails = - DarwinNotificationDetails(attachments: [ - DarwinNotificationAttachment( - bigPicturePath, - thumbnailClippingRect: - // lower right quadrant of the attachment - const DarwinNotificationAttachmentThumbnailClippingRect( - x: 0.5, - y: 0.5, - height: 0.5, - width: 0.5, - ), - ) - ]); + DarwinNotificationDetails( + attachments: [ + DarwinNotificationAttachment( + bigPicturePath, + thumbnailClippingRect: + // lower right quadrant of the attachment + const DarwinNotificationAttachmentThumbnailClippingRect( + x: 0.5, + y: 0.5, + height: 0.5, + width: 0.5, + ), + ) + ], + ); final NotificationDetails notificationDetails = NotificationDetails( iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 6e9c8fe93..47b0ef92d 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -5,9 +5,6 @@ @implementation FlutterLocalNotificationsPlugin { FlutterMethodChannel *_channel; - bool _displayAlert; - bool _playSound; - bool _updateBadge; bool _initialized; bool _launchingAppFromNotification; NSObject *_registrar; @@ -45,13 +42,15 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const REQUEST_ALERT_PERMISSION = @"requestAlertPermission"; NSString *const REQUEST_BADGE_PERMISSION = @"requestBadgePermission"; NSString *const REQUEST_CRITICAL_PERMISSION = @"requestCriticalPermission"; +NSString *const DEFAULT_PRESENT_ALERT = @"defaultPresentAlert"; +NSString *const DEFAULT_PRESENT_SOUND = @"defaultPresentSound"; +NSString *const DEFAULT_PRESENT_BADGE = @"defaultPresentBadge"; +NSString *const DEFAULT_PRESENT_BANNER = @"defaultPresentBanner"; +NSString *const DEFAULT_PRESENT_LIST = @"defaultPresentList"; NSString *const SOUND_PERMISSION = @"sound"; NSString *const ALERT_PERMISSION = @"alert"; NSString *const BADGE_PERMISSION = @"badge"; NSString *const CRITICAL_PERMISSION = @"critical"; -NSString *const DEFAULT_PRESENT_ALERT = @"defaultPresentAlert"; -NSString *const DEFAULT_PRESENT_SOUND = @"defaultPresentSound"; -NSString *const DEFAULT_PRESENT_BADGE = @"defaultPresentBadge"; NSString *const CALLBACK_DISPATCHER = @"callbackDispatcher"; NSString *const ON_NOTIFICATION_CALLBACK_DISPATCHER = @"onNotificationCallbackDispatcher"; @@ -71,6 +70,8 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const PRESENT_ALERT = @"presentAlert"; NSString *const PRESENT_SOUND = @"presentSound"; NSString *const PRESENT_BADGE = @"presentBadge"; +NSString *const PRESENT_BANNER = @"presentBanner"; +NSString *const PRESENT_LIST = @"presentList"; NSString *const BADGE_NUMBER = @"badgeNumber"; NSString *const MILLISECONDS_SINCE_EPOCH = @"millisecondsSinceEpoch"; NSString *const REPEAT_INTERVAL = @"repeatInterval"; @@ -89,6 +90,8 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const UNSUPPORTED_OS_VERSION_ERROR_CODE = @"unsupported_os_version"; NSString *const GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE = @"iOS version must be 10.0 or newer to use getActiveNotifications"; +NSString *const PRESENTATION_OPTIONS_USER_DEFAULTS = + @"flutter_local_notifications_presentation_options"; typedef NS_ENUM(NSInteger, RepeatInterval) { EveryMinute, @@ -372,19 +375,39 @@ - (void)getActiveNotifications:(FlutterResult _Nonnull)result { - (void)initialize:(NSDictionary *_Nonnull)arguments result:(FlutterResult _Nonnull)result { + bool requestedSoundPermission = false; + bool requestedAlertPermission = false; + bool requestedBadgePermission = false; + bool requestedCriticalPermission = false; + NSMutableDictionary *presentationOptions = [[NSMutableDictionary alloc] init]; if ([self containsKey:DEFAULT_PRESENT_ALERT forDictionary:arguments]) { - _displayAlert = [[arguments objectForKey:DEFAULT_PRESENT_ALERT] boolValue]; + presentationOptions[PRESENT_ALERT] = + [NSNumber numberWithBool:[[arguments objectForKey:DEFAULT_PRESENT_ALERT] + boolValue]]; } if ([self containsKey:DEFAULT_PRESENT_SOUND forDictionary:arguments]) { - _playSound = [[arguments objectForKey:DEFAULT_PRESENT_SOUND] boolValue]; + presentationOptions[PRESENT_SOUND] = + [NSNumber numberWithBool:[[arguments objectForKey:DEFAULT_PRESENT_SOUND] + boolValue]]; } if ([self containsKey:DEFAULT_PRESENT_BADGE forDictionary:arguments]) { - _updateBadge = [[arguments objectForKey:DEFAULT_PRESENT_BADGE] boolValue]; + presentationOptions[PRESENT_BADGE] = + [NSNumber numberWithBool:[[arguments objectForKey:DEFAULT_PRESENT_BADGE] + boolValue]]; } - bool requestedSoundPermission = false; - bool requestedAlertPermission = false; - bool requestedBadgePermission = false; - bool requestedCriticalPermission = false; + if ([self containsKey:DEFAULT_PRESENT_BANNER forDictionary:arguments]) { + presentationOptions[PRESENT_BANNER] = [NSNumber + numberWithBool:[[arguments objectForKey:DEFAULT_PRESENT_BANNER] + boolValue]]; + } + if ([self containsKey:DEFAULT_PRESENT_LIST forDictionary:arguments]) { + presentationOptions[PRESENT_LIST] = + [NSNumber numberWithBool:[[arguments objectForKey:DEFAULT_PRESENT_LIST] + boolValue]]; + } + [[NSUserDefaults standardUserDefaults] + setObject:presentationOptions + forKey:PRESENTATION_OPTIONS_USER_DEFAULTS]; if ([self containsKey:REQUEST_SOUND_PERMISSION forDictionary:arguments]) { requestedSoundPermission = [arguments[REQUEST_SOUND_PERMISSION] boolValue]; } @@ -521,9 +544,21 @@ - (UILocalNotification *)buildStandardUILocalNotification: } } - bool presentAlert = _displayAlert; - bool presentSound = _playSound; - bool presentBadge = _updateBadge; + NSDictionary *persistedPresentationOptions = + [[NSUserDefaults standardUserDefaults] + dictionaryForKey:PRESENTATION_OPTIONS_USER_DEFAULTS]; + bool presentAlert = false; + bool presentSound = false; + bool presentBadge = false; + bool presentBanner = false; + bool presentList = false; + if (persistedPresentationOptions != nil) { + presentAlert = [persistedPresentationOptions[PRESENT_ALERT] isEqual:@YES]; + presentSound = [persistedPresentationOptions[PRESENT_SOUND] isEqual:@YES]; + presentBadge = [persistedPresentationOptions[PRESENT_BADGE] isEqual:@YES]; + presentBanner = [persistedPresentationOptions[PRESENT_BANNER] isEqual:@YES]; + presentList = [persistedPresentationOptions[PRESENT_LIST] isEqual:@YES]; + } if (arguments[PLATFORM_SPECIFICS] != [NSNull null]) { NSDictionary *platformSpecifics = arguments[PLATFORM_SPECIFICS]; @@ -536,6 +571,13 @@ - (UILocalNotification *)buildStandardUILocalNotification: if ([self containsKey:PRESENT_BADGE forDictionary:platformSpecifics]) { presentBadge = [[platformSpecifics objectForKey:PRESENT_BADGE] boolValue]; } + if ([self containsKey:PRESENT_BANNER forDictionary:platformSpecifics]) { + presentBanner = + [[platformSpecifics objectForKey:PRESENT_BANNER] boolValue]; + } + if ([self containsKey:PRESENT_LIST forDictionary:platformSpecifics]) { + presentList = [[platformSpecifics objectForKey:PRESENT_LIST] boolValue]; + } if ([self containsKey:BADGE_NUMBER forDictionary:platformSpecifics]) { notification.applicationIconBadgeNumber = @@ -559,6 +601,8 @@ - (UILocalNotification *)buildStandardUILocalNotification: presentAlert:presentAlert presentSound:presentSound presentBadge:presentBadge + presentBanner:presentBanner + presentList:presentList payload:arguments[PAYLOAD]]; return notification; } @@ -753,9 +797,21 @@ - (void)cancelAll:(FlutterResult _Nonnull)result { if ([self containsKey:BODY forDictionary:arguments]) { content.body = arguments[BODY]; } - bool presentAlert = _displayAlert; - bool presentSound = _playSound; - bool presentBadge = _updateBadge; + NSDictionary *persistedPresentationOptions = + [[NSUserDefaults standardUserDefaults] + dictionaryForKey:PRESENTATION_OPTIONS_USER_DEFAULTS]; + bool presentAlert = false; + bool presentSound = false; + bool presentBadge = false; + bool presentBanner = false; + bool presentList = false; + if (persistedPresentationOptions != nil) { + presentAlert = [persistedPresentationOptions[PRESENT_ALERT] isEqual:@YES]; + presentSound = [persistedPresentationOptions[PRESENT_SOUND] isEqual:@YES]; + presentBadge = [persistedPresentationOptions[PRESENT_BADGE] isEqual:@YES]; + presentBanner = [persistedPresentationOptions[PRESENT_BANNER] isEqual:@YES]; + presentList = [persistedPresentationOptions[PRESENT_LIST] isEqual:@YES]; + } if (arguments[PLATFORM_SPECIFICS] != [NSNull null]) { NSDictionary *platformSpecifics = arguments[PLATFORM_SPECIFICS]; if ([self containsKey:PRESENT_ALERT forDictionary:platformSpecifics]) { @@ -767,6 +823,13 @@ - (void)cancelAll:(FlutterResult _Nonnull)result { if ([self containsKey:PRESENT_BADGE forDictionary:platformSpecifics]) { presentBadge = [[platformSpecifics objectForKey:PRESENT_BADGE] boolValue]; } + if ([self containsKey:PRESENT_BANNER forDictionary:platformSpecifics]) { + presentBanner = + [[platformSpecifics objectForKey:PRESENT_BANNER] boolValue]; + } + if ([self containsKey:PRESENT_LIST forDictionary:platformSpecifics]) { + presentList = [[platformSpecifics objectForKey:PRESENT_LIST] boolValue]; + } if ([self containsKey:BADGE_NUMBER forDictionary:platformSpecifics]) { content.badge = [platformSpecifics objectForKey:BADGE_NUMBER]; } @@ -854,6 +917,8 @@ - (void)cancelAll:(FlutterResult _Nonnull)result { presentAlert:presentAlert presentSound:presentSound presentBadge:presentBadge + presentBanner:presentBanner + presentList:presentList payload:arguments[PAYLOAD]]; return content; } @@ -950,6 +1015,8 @@ - (NSDictionary *)buildUserDict:(NSNumber *)id presentAlert:(bool)presentAlert presentSound:(bool)presentSound presentBadge:(bool)presentBadge + presentBanner:(bool)presentBanner + presentList:(bool)presentList payload:(NSString *)payload { NSMutableDictionary *userDict = [[NSMutableDictionary alloc] init]; userDict[NOTIFICATION_ID] = id; @@ -959,6 +1026,8 @@ - (NSDictionary *)buildUserDict:(NSNumber *)id userDict[PRESENT_ALERT] = [NSNumber numberWithBool:presentAlert]; userDict[PRESENT_SOUND] = [NSNumber numberWithBool:presentSound]; userDict[PRESENT_BADGE] = [NSNumber numberWithBool:presentBadge]; + userDict[PRESENT_BANNER] = [NSNumber numberWithBool:presentBanner]; + userDict[PRESENT_LIST] = [NSNumber numberWithBool:presentList]; userDict[PAYLOAD] = payload; return userDict; } @@ -1021,11 +1090,26 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center (NSNumber *)notification.request.content.userInfo[PRESENT_SOUND]; NSNumber *presentBadgeValue = (NSNumber *)notification.request.content.userInfo[PRESENT_BADGE]; + NSNumber *presentBannerValue = + (NSNumber *)notification.request.content.userInfo[PRESENT_BANNER]; + NSNumber *presentListValue = + (NSNumber *)notification.request.content.userInfo[PRESENT_LIST]; bool presentAlert = [presentAlertValue boolValue]; bool presentSound = [presentSoundValue boolValue]; bool presentBadge = [presentBadgeValue boolValue]; - if (presentAlert) { - presentationOptions |= UNNotificationPresentationOptionAlert; + bool presentBanner = [presentBannerValue boolValue]; + bool presentList = [presentListValue boolValue]; + if (@available(iOS 14.0, *)) { + if (presentBanner) { + presentationOptions |= UNNotificationPresentationOptionBanner; + } + if (presentList) { + presentationOptions |= UNNotificationPresentationOptionList; + } + } else { + if (presentAlert) { + presentationOptions |= UNNotificationPresentationOptionAlert; + } } if (presentSound) { presentationOptions |= UNNotificationPresentationOptionSound; diff --git a/flutter_local_notifications/lib/src/platform_specifics/darwin/initialization_settings.dart b/flutter_local_notifications/lib/src/platform_specifics/darwin/initialization_settings.dart index 0dd3f3a24..7eeee5bb6 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/darwin/initialization_settings.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/darwin/initialization_settings.dart @@ -13,6 +13,8 @@ class DarwinInitializationSettings { this.defaultPresentAlert = true, this.defaultPresentSound = true, this.defaultPresentBadge = true, + this.defaultPresentBanner = true, + this.defaultPresentList = true, this.onDidReceiveLocalNotification, this.notificationCategories = const [], }); @@ -43,15 +45,23 @@ class DarwinInitializationSettings { /// Configures the default setting on if an alert should be displayed when a /// notification is triggered while app is in the foreground. /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649506-alert + /// /// Default value is true. /// - /// On iOS, this property is only applicable to iOS 10 or newer. - /// On macOS, this property is only applicable to macOS 10.14 or newer. - + /// On iOS, this property is only applicable to iOS 10 or newer but not iOS 14 + /// or beyond (i.e. iOS versions >= 10 and < 14) + /// On macOS, this property is only applicable to macOS 10.14 but not macOS 11 + /// or beyond (i.e. macOS verisons >= 10.14 and < 14) + /// For iOS and macOS versions beyond the aforementioned ranges, + /// [defaultPresentBanner] and [defaultPresentList] are referenced as Apple + /// deprecated the alert presentation option. final bool defaultPresentAlert; /// Configures the default setting on if a sound should be played when a - /// notification is triggered while app is in the foreground by default. + /// notification is triggered while app is in the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649521-sound /// /// Default value is true. /// @@ -60,7 +70,9 @@ class DarwinInitializationSettings { final bool defaultPresentSound; /// Configures the default setting on if a badge value should be applied when - /// a notification is triggered while app is in the foreground by default. + /// a notification is triggered while app is in the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649515-badge /// /// Default value is true. /// @@ -68,6 +80,30 @@ class DarwinInitializationSettings { /// On macOS, this property is only applicable to macOS 10.14 or newer. final bool defaultPresentBadge; + /// Configures the default setting on if the notification should be + /// presented as a banner when a notification is triggered while app is in + /// the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/3564812-banner + /// + /// Default value is true. + /// + /// On iOS, this property is only applicable to iOS 14 or newer. + /// On macOS, this property is only applicable to macOS 11 or newer. + final bool defaultPresentBanner; + + /// Configures the default setting on if the notification should be + /// in the notification centre when notification is triggered while app is in + /// the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/3564813-list + /// + /// Default value is true. + /// + /// On iOS, this property is only applicable to iOS 14 or newer. + /// On macOS, this property is only applicable to macOS 11 or newer. + final bool defaultPresentList; + /// Callback for handling when a notification is triggered while the app is /// in the foreground. /// diff --git a/flutter_local_notifications/lib/src/platform_specifics/darwin/mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/darwin/mappers.dart index 2019985a2..0136a9f1f 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/darwin/mappers.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/darwin/mappers.dart @@ -42,6 +42,8 @@ extension DarwinInitializationSettingsMapper on DarwinInitializationSettings { 'defaultPresentAlert': defaultPresentAlert, 'defaultPresentSound': defaultPresentSound, 'defaultPresentBadge': defaultPresentBadge, + 'defaultPresentBanner': defaultPresentBanner, + 'defaultPresentList': defaultPresentList, 'notificationCategories': notificationCategories .map((e) => e.toMap()) // ignore: always_specify_types .toList(), @@ -71,6 +73,8 @@ extension DarwinNotificationDetailsMapper on DarwinNotificationDetails { 'presentAlert': presentAlert, 'presentSound': presentSound, 'presentBadge': presentBadge, + 'presentBanner': presentBanner, + 'presentList': presentList, 'subtitle': subtitle, 'sound': sound, 'badgeNumber': badgeNumber, diff --git a/flutter_local_notifications/lib/src/platform_specifics/darwin/notification_details.dart b/flutter_local_notifications/lib/src/platform_specifics/darwin/notification_details.dart index 861eb656f..2d2f01eff 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/darwin/notification_details.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/darwin/notification_details.dart @@ -9,6 +9,8 @@ class DarwinNotificationDetails { this.presentAlert, this.presentBadge, this.presentSound, + this.presentBanner, + this.presentList, this.sound, this.badgeNumber, this.attachments, @@ -18,35 +20,67 @@ class DarwinNotificationDetails { this.interruptionLevel, }); - /// Display an alert when the notification is triggered while app is - /// in the foreground. + /// Indicates if an alert should be display when the notification is triggered + /// while app is in the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649506-alert /// /// When this is set to `null`, it will use the default setting given - /// to [IOSInitializationSettings.defaultPresentAlert]. + /// to [DarwinInitializationSettings.defaultPresentAlert]. /// - /// On iOS, this property is only applicable to iOS 10 or newer. - /// On macOS, this This property is only applicable to macOS 10.14 or newer. + /// On iOS, this property is only applicable to iOS 10 to 14. + /// On macOS, this This property is only applicable to macOS 10.14 to 15. + /// On newer versions of iOS and macOS, [presentList] and [presentBanner] final bool? presentAlert; - /// Play a sound when the notification is triggered while app is in - /// the foreground. + /// Indicates if a sound should be played when the notification is triggered + /// while app is in the foreground. /// - /// When this is set to `null`, it will use the default setting given to - /// [IOSInitializationSettings.defaultPresentSound]. + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649521-sound + /// + /// When this is set to `null`, it will use the default setting given + /// to [DarwinInitializationSettings.defaultPresentSound]. /// /// This property is only applicable to iOS 10 or newer. final bool? presentSound; - /// Apply the badge value when the notification is triggered while app is in - /// the foreground. + /// Indicates if badge value should be applied when the notification is + /// triggered while app is in the foreground. /// - /// When this is set to `null`, it will use the default setting given to - /// [IOSInitializationSettings.defaultPresentBadge]. + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/1649515-badge + /// + /// When this is set to `null`, it will use the default setting given + /// to [DarwinInitializationSettings.defaultPresentBadge]. /// /// On iOS, this property is only applicable to iOS 10 or newer. /// On macOS, this This property is only applicable to macOS 10.14 or newer. final bool? presentBadge; + /// Indicates if the notification should be presented as a banner when the + /// notification is triggered while app is in the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/3564812-banner + /// + /// When this is set to `null`, it will use the default setting given + /// to [DarwinInitializationSettings.defaultPresentBanner]. + /// + /// On iOS, this property is only applicable to iOS 14 or newer + /// On macOs, this property is only applicable to macOS 11 or newer. + final bool? presentBanner; + + /// Indicates if the notification should be shown in the notification centre + /// when the notification is + /// triggered while app is in the foreground. + /// + /// Corresponds to https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions/3564813-list + /// + /// When this is set to `null`, it will use the default setting given + /// to [DarwinInitializationSettings.defaultPresentList]. + /// + /// On iOS, this property is only applicable to iOS 14 or newer + /// On macOs, this property is only applicable to macOS 11 or newer. + final bool? presentList; + /// Specifies the name of the file to play for the notification. /// /// Requires setting [presentSound] to true. If [presentSound] is set to true diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index 3bd4d4264..141468877 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -8,14 +8,18 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot static let presentAlert = "presentAlert" static let presentSound = "presentSound" static let presentBadge = "presentBadge" + static let presentBanner = "presentBanner" + static let presentList = "presentList" static let payload = "payload" - static let defaultPresentAlert = "defaultPresentAlert" - static let defaultPresentSound = "defaultPresentSound" - static let defaultPresentBadge = "defaultPresentBadge" static let requestAlertPermission = "requestAlertPermission" static let requestSoundPermission = "requestSoundPermission" static let requestBadgePermission = "requestBadgePermission" static let requestCriticalPermission = "requestCriticalPermission" + static let defaultPresentAlert = "defaultPresentAlert" + static let defaultPresentSound = "defaultPresentSound" + static let defaultPresentBadge = "defaultPresentBadge" + static let defaultPresentBanner = "defaultPresentBanner" + static let defaultPresentList = "defaultPresentList" static let alert = "alert" static let sound = "sound" static let badge = "badge" @@ -76,11 +80,9 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot var channel: FlutterMethodChannel var initialized = false - var defaultPresentAlert = false - var defaultPresentSound = false - var defaultPresentBadge = false var launchNotificationResponseDict: [String: Any?]? var launchingAppFromNotification = false + let presentationOptionsUserDefaults = "flutter_local_notifications_presentation_options" // MARK: - FlutterPlugin initialization and registration @@ -112,8 +114,19 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot let presentAlert = notification.request.content.userInfo[MethodCallArguments.presentAlert] as! Bool let presentSound = notification.request.content.userInfo[MethodCallArguments.presentSound] as! Bool let presentBadge = notification.request.content.userInfo[MethodCallArguments.presentBadge] as! Bool - if presentAlert { - options.insert(.alert) + if #available(macOS 11.0, *) { + let presentBanner = notification.request.content.userInfo[MethodCallArguments.presentBanner] as! Bool + let presentList = notification.request.content.userInfo[MethodCallArguments.presentList] as! Bool + if presentBanner { + options.insert(.banner) + } + if presentList { + options.insert(.list) + } + } else { + if presentAlert { + options.insert(.alert) + } } if presentSound { options.insert(.sound) @@ -201,9 +214,12 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot func initialize(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let arguments = call.arguments as! [String: AnyObject] - defaultPresentAlert = arguments[MethodCallArguments.defaultPresentAlert] as! Bool - defaultPresentSound = arguments[MethodCallArguments.defaultPresentSound] as! Bool - defaultPresentBadge = arguments[MethodCallArguments.defaultPresentBadge] as! Bool + let defaultPresentAlert = arguments[MethodCallArguments.defaultPresentAlert] as! Bool + let defaultPresentSound = arguments[MethodCallArguments.defaultPresentSound] as! Bool + let defaultPresentBadge = arguments[MethodCallArguments.defaultPresentBadge] as! Bool + let defaultPresentBanner = arguments[MethodCallArguments.defaultPresentBanner] as! Bool + let defaultPresentList = arguments[MethodCallArguments.defaultPresentList] as! Bool + UserDefaults.standard.set([MethodCallArguments.presentAlert: defaultPresentAlert, MethodCallArguments.presentBadge: defaultPresentBadge, MethodCallArguments.presentSound: defaultPresentSound, MethodCallArguments.presentBanner: defaultPresentBanner, MethodCallArguments.presentList: defaultPresentList], forKey: presentationOptionsUserDefaults) if #available(macOS 10.14, *) { let requestedAlertPermission = arguments[MethodCallArguments.requestAlertPermission] as! Bool let requestedSoundPermission = arguments[MethodCallArguments.requestSoundPermission] as! Bool @@ -494,9 +510,12 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot if let body = arguments[MethodCallArguments.body] as? String { content.body = body } - var presentSound = defaultPresentSound - var presentBadge = defaultPresentBadge - var presentAlert = defaultPresentAlert + let persistedPresentationOptions = UserDefaults.standard.dictionary(forKey: presentationOptionsUserDefaults)! + var presentSound = persistedPresentationOptions[MethodCallArguments.presentSound] as! Bool + var presentBadge = persistedPresentationOptions[MethodCallArguments.presentBadge] as! Bool + var presentAlert = persistedPresentationOptions[MethodCallArguments.presentAlert] as! Bool + var presentBanner = persistedPresentationOptions[MethodCallArguments.presentBanner] as! Bool + var presentList = persistedPresentationOptions[MethodCallArguments.presentList] as! Bool if let platformSpecifics = arguments[MethodCallArguments.platformSpecifics] as? [String: AnyObject] { if let sound = platformSpecifics[MethodCallArguments.sound] as? String { content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init(sound)) @@ -513,6 +532,12 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot if !(platformSpecifics[MethodCallArguments.presentBadge] is NSNull) && platformSpecifics[MethodCallArguments.presentBadge] != nil { presentBadge = platformSpecifics[MethodCallArguments.presentBadge] as! Bool } + if !(platformSpecifics[MethodCallArguments.presentBanner] is NSNull) && platformSpecifics[MethodCallArguments.presentBanner] != nil { + presentBanner = platformSpecifics[MethodCallArguments.presentBanner] as! Bool + } + if !(platformSpecifics[MethodCallArguments.presentList] is NSNull) && platformSpecifics[MethodCallArguments.presentList] != nil { + presentList = platformSpecifics[MethodCallArguments.presentList] as! Bool + } if let threadIdentifier = platformSpecifics[MethodCallArguments.threadIdentifier] as? String { content.threadIdentifier = threadIdentifier } @@ -546,7 +571,9 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot } } } - content.userInfo = [MethodCallArguments.payload: arguments[MethodCallArguments.payload] as Any, MethodCallArguments.presentSound: presentSound, MethodCallArguments.presentBadge: presentBadge, MethodCallArguments.presentAlert: presentAlert] + content.userInfo = [MethodCallArguments.payload: arguments[MethodCallArguments.payload] as Any, MethodCallArguments.presentSound: presentSound, MethodCallArguments.presentBadge: presentBadge, MethodCallArguments.presentAlert: presentAlert, + MethodCallArguments.presentBanner: presentBanner, + MethodCallArguments.presentList: presentList] if presentSound && content.sound == nil { content.sound = UNNotificationSound.default } @@ -643,7 +670,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot if let body = arguments[MethodCallArguments.body] as? String { notification.informativeText = body } - var presentSound = defaultPresentSound + var presentSound = false if let platformSpecifics = arguments[MethodCallArguments.platformSpecifics] as? [String: AnyObject] { if let sound = platformSpecifics[MethodCallArguments.sound] as? String { notification.soundName = sound diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index dc4d34664..ef6c1aec1 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: 14.1.1 +version: 15.0.0-dev.1 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications issue_tracker: https://github.com/MaikuB/flutter_local_notifications/issues diff --git a/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart b/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart index 4fa354ce7..9149d572e 100644 --- a/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart +++ b/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart @@ -48,10 +48,12 @@ void main() { 'requestAlertPermission': true, 'requestSoundPermission': true, 'requestBadgePermission': true, - 'requestCriticalPermission': false, 'defaultPresentAlert': true, 'defaultPresentSound': true, 'defaultPresentBadge': true, + 'defaultPresentBanner': true, + 'defaultPresentList': true, + 'requestCriticalPermission': false, 'notificationCategories': [], }) ]); @@ -108,6 +110,8 @@ void main() { 'defaultPresentAlert': true, 'defaultPresentSound': true, 'defaultPresentBadge': true, + 'defaultPresentBanner': true, + 'defaultPresentList': true, 'notificationCategories': >[ { 'identifier': 'category1', @@ -160,12 +164,15 @@ void main() { test('initialize with all settings off', () async { const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings( - requestAlertPermission: false, - requestBadgePermission: false, - requestSoundPermission: false, - defaultPresentAlert: false, - defaultPresentBadge: false, - defaultPresentSound: false); + requestAlertPermission: false, + requestBadgePermission: false, + requestSoundPermission: false, + defaultPresentAlert: false, + defaultPresentBadge: false, + defaultPresentSound: false, + defaultPresentBanner: false, + defaultPresentList: false, + ); const InitializationSettings initializationSettings = InitializationSettings(iOS: iosInitializationSettings); await flutterLocalNotificationsPlugin.initialize(initializationSettings); @@ -178,6 +185,8 @@ void main() { 'defaultPresentAlert': false, 'defaultPresentSound': false, 'defaultPresentBadge': false, + 'defaultPresentBanner': false, + 'defaultPresentList': false, 'notificationCategories': [], }) ]); @@ -213,6 +222,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, subtitle: 'a subtitle', sound: 'sound.mp3', badgeNumber: 1, @@ -238,6 +249,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': 'a subtitle', 'sound': 'sound.mp3', 'badgeNumber': 1, @@ -273,6 +286,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -307,6 +322,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': null, 'sound': 'sound.mp3', 'badgeNumber': 1, @@ -347,6 +364,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -380,6 +399,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': null, 'sound': 'sound.mp3', 'badgeNumber': 1, @@ -414,6 +435,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -449,6 +472,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': null, 'sound': 'sound.mp3', 'badgeNumber': 1, @@ -483,6 +508,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -519,6 +546,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': null, 'sound': 'sound.mp3', 'badgeNumber': 1, diff --git a/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart b/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart index 5caa9a095..7bb6caadd 100644 --- a/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart +++ b/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart @@ -51,6 +51,8 @@ void main() { 'defaultPresentAlert': true, 'defaultPresentSound': true, 'defaultPresentBadge': true, + 'defaultPresentBanner': true, + 'defaultPresentList': true, 'notificationCategories': >[], }) ]); @@ -65,6 +67,8 @@ void main() { defaultPresentAlert: false, defaultPresentBadge: false, defaultPresentSound: false, + defaultPresentBanner: false, + defaultPresentList: false, ); const InitializationSettings initializationSettings = InitializationSettings(macOS: macOSInitializationSettings); @@ -78,6 +82,8 @@ void main() { 'defaultPresentAlert': false, 'defaultPresentSound': false, 'defaultPresentBadge': false, + 'defaultPresentBanner': false, + 'defaultPresentList': false, 'notificationCategories': >[], }) ]); @@ -114,6 +120,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, threadIdentifier: 'thread', @@ -149,6 +157,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'sound': 'sound.mp3', 'badgeNumber': 1, 'threadIdentifier': 'thread', @@ -185,6 +195,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -217,6 +229,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'subtitle': null, 'sound': 'sound.mp3', 'badgeNumber': 1, @@ -255,6 +269,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -290,6 +306,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'sound': 'sound.mp3', 'badgeNumber': 1, 'threadIdentifier': null, @@ -323,6 +341,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -362,6 +382,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'sound': 'sound.mp3', 'badgeNumber': 1, 'threadIdentifier': null, @@ -397,6 +419,8 @@ void main() { presentAlert: true, presentBadge: true, presentSound: true, + presentBanner: true, + presentList: true, sound: 'sound.mp3', badgeNumber: 1, attachments: [ @@ -437,6 +461,8 @@ void main() { 'presentAlert': true, 'presentBadge': true, 'presentSound': true, + 'presentBanner': true, + 'presentList': true, 'sound': 'sound.mp3', 'badgeNumber': 1, 'threadIdentifier': null,