From 76a7292f36603066dd266838ce6dedaaacec4415 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 27 Mar 2022 15:04:28 +1100 Subject: [PATCH 01/13] updated notification callbacks --- .../FlutterLocalNotificationsPlugin.java | 12 +- .../example/lib/main.dart | 32 ++++-- .../lib/navigation_event_listener.dart | 2 +- .../Classes/FlutterLocalNotificationsPlugin.m | 16 ++- .../lib/flutter_local_notifications.dart | 6 +- .../lib/src/callback_dispatcher.dart | 15 ++- .../flutter_local_notifications_plugin.dart | 42 ++++--- .../platform_flutter_local_notifications.dart | 106 +++++++++++------- .../FlutterLocalNotificationsPlugin.swift | 23 ++-- .../lib/src/flutter_local_notifications.dart | 8 +- ...er_local_notifications_platform_linux.dart | 10 +- .../src/flutter_local_notifications_stub.dart | 5 +- .../lib/src/notifications_manager.dart | 28 +++-- .../test/mock/mock_typedef.dart | 16 +-- .../test/notifications_manager_test.dart | 71 ++++++------ .../lib/src/typedefs.dart | 14 ++- .../lib/src/types.dart | 29 +++-- 17 files changed, 258 insertions(+), 177 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 69f087567..6486287e5 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -127,6 +127,7 @@ public class FlutterLocalNotificationsPlugin "getNotificationAppLaunchDetails"; private static final String METHOD_CHANNEL = "dexterous.com/flutter/local_notifications"; static final String PAYLOAD = "payload"; + static final String NOTIFICATION_ID = "notificationId"; private static final String INVALID_ICON_ERROR_CODE = "invalid_icon"; private static final String INVALID_LARGE_ICON_ERROR_CODE = "invalid_large_icon"; private static final String INVALID_BIG_PICTURE_ERROR_CODE = "invalid_big_picture"; @@ -196,6 +197,7 @@ protected static Notification createNotification( } Intent intent = getLaunchIntent(context); intent.setAction(SELECT_NOTIFICATION); + intent.putExtra(NOTIFICATION_ID, notificationDetails.id); intent.putExtra(PAYLOAD, notificationDetails.payload); int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (VERSION.SDK_INT >= VERSION_CODES.M) { @@ -1641,8 +1643,14 @@ public boolean onNewIntent(Intent intent) { private Boolean sendNotificationPayloadMessage(Intent intent) { if (SELECT_NOTIFICATION.equals(intent.getAction())) { - String payload = intent.getStringExtra(PAYLOAD); - channel.invokeMethod("selectNotification", payload); + HashMap notificationResponse = new HashMap<>(); + notificationResponse.put(PAYLOAD, intent.getStringExtra(PAYLOAD)); + if (intent.hasExtra(NOTIFICATION_ID)) { + notificationResponse.put(NOTIFICATION_ID, intent.getIntExtra(NOTIFICATION_ID, 0)); + } + + notificationResponse.put("notificationResponseType", 0); + channel.invokeMethod("didReceiveForegroundNotificationResponse", notificationResponse); return true; } return false; diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 2952ed767..e549e607c 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -67,17 +67,19 @@ const String darwinNotificationCategoryText = 'textCategory'; /// Defines a iOS/MacOS notification category for plain actions. const String iosNotificationCategoryPlain = 'plainCategory'; -void notificationTapBackground(NotificationActionDetails details) { +void notificationTapBackground(NotificationResponse notificationResponse) { // ignore: avoid_print - print('notification(${details.id}) action tapped: ${details.actionId} with' - ' payload: ${details.payload}'); - if (details.input?.isNotEmpty ?? false) { + print('notification(${notificationResponse.id}) action tapped: ' + '${notificationResponse.actionId} with' + ' payload: ${notificationResponse.payload}'); + if (notificationResponse.input?.isNotEmpty ?? false) { // ignore: avoid_print - print('notification action tapped with input: ${details.input}'); + print( + 'notification action tapped with input: ${notificationResponse.input}'); } final SendPort? send = IsolateNameServer.lookupPortByName(portName); - send?.send(details); + send?.send(notificationResponse); } /// IMPORTANT: running the following code on its own won't work as there is @@ -185,13 +187,21 @@ Future main() async { ); await flutterLocalNotificationsPlugin.initialize( initializationSettings, - onSelectNotification: (String? payload) async { - if (payload != null) { - debugPrint('notification payload: $payload'); + onDidReceiveForegroundNotificationResponse: + (NotificationResponse notificationResponse) async { + switch (notificationResponse.notificationResponseType) { + case NotificationResponseType.selectedNotification: + if (notificationResponse.payload != null) { + debugPrint('notification payload: ${notificationResponse.payload}'); + } + selectNotificationSubject.add(notificationResponse.payload); + break; + case NotificationResponseType.selectedNotificationAction: + // TODO: Handle this case. + break; } - selectNotificationSubject.add(payload); }, - onSelectNotificationAction: notificationTapBackground, + onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); runApp( MaterialApp( diff --git a/flutter_local_notifications/example/lib/navigation_event_listener.dart b/flutter_local_notifications/example/lib/navigation_event_listener.dart index 3f61824c8..d12a995e1 100644 --- a/flutter_local_notifications/example/lib/navigation_event_listener.dart +++ b/flutter_local_notifications/example/lib/navigation_event_listener.dart @@ -33,7 +33,7 @@ class _NavigationEventListenerState extends State { // ignore: avoid_annotating_with_dynamic port.listen((dynamic data) async { - final NotificationActionDetails action = data; + final NotificationResponse action = data; if (action.actionId == urlLaunchActionId) { await launch('https://flutter.dev'); } diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 201934436..d1b723023 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -1040,8 +1040,14 @@ - (BOOL)isAFlutterLocalNotification:(NSDictionary *)userInfo { userInfo[PRESENT_BADGE] && userInfo[PAYLOAD]; } -- (void)handleSelectNotification:(NSString *)payload { - [_channel invokeMethod:@"selectNotification" arguments:payload]; +- (void)handleSelectNotification:(NSInteger )notificationId + payload:(NSString *)payload { + NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; + arguments[@"notificationId"] = notificationIdNumber; + arguments[PAYLOAD] = payload; + arguments[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; + [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; } - (BOOL)containsKey:(NSString *)key forDictionary:(NSDictionary *)dictionary { @@ -1090,10 +1096,12 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center } if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { + NSString *notificationId = + (NSString *)response.notification.request.identifier; NSString *payload = (NSString *)response.notification.request.content.userInfo[PAYLOAD]; if (_initialized) { - [self handleSelectNotification:payload]; + [self handleSelectNotification:[notificationId integerValue] payload:payload]; } else { _launchPayload = payload; _launchingAppFromNotification = true; @@ -1110,7 +1118,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center } [actionEventSink addItem:@{ - @"notificationId" : response.notification.request.identifier, + @"notificationId" : [NSNumber numberWithInteger:[response.notification.request.identifier integerValue]], @"actionId" : response.actionIdentifier, @"input" : text, @"payload" : response.notification.request.content.userInfo[@"payload"], diff --git a/flutter_local_notifications/lib/flutter_local_notifications.dart b/flutter_local_notifications/lib/flutter_local_notifications.dart index f68de40ce..86752ef58 100644 --- a/flutter_local_notifications/lib/flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/flutter_local_notifications.dart @@ -1,12 +1,14 @@ export 'package:flutter_local_notifications_linux/flutter_local_notifications_linux.dart'; export 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart' show - SelectNotificationCallback, + DidReceiveBackgroundNotificationResponseCallback, + DidReceiveForegroundNotificationResponseCallback, PendingNotificationRequest, ActiveNotification, RepeatInterval, NotificationAppLaunchDetails, - NotificationActionDetails; + NotificationResponse, + NotificationResponseType; export 'src/flutter_local_notifications_plugin.dart'; export 'src/initialization_settings.dart'; diff --git a/flutter_local_notifications/lib/src/callback_dispatcher.dart b/flutter_local_notifications/lib/src/callback_dispatcher.dart index d04015547..da0f376ef 100644 --- a/flutter_local_notifications/lib/src/callback_dispatcher.dart +++ b/flutter_local_notifications/lib/src/callback_dispatcher.dart @@ -16,11 +16,12 @@ void callbackDispatcher() { MethodChannel('dexterous.com/flutter/local_notifications'); channel.invokeMethod('getCallbackHandle').then((int? handle) { - final SelectNotificationActionCallback? callback = handle == null - ? null - : PluginUtilities.getCallbackFromHandle( - CallbackHandle.fromRawHandle(handle)) - as SelectNotificationActionCallback?; + final DidReceiveBackgroundNotificationResponseCallback? callback = + handle == null + ? null + : PluginUtilities.getCallbackFromHandle( + CallbackHandle.fromRawHandle(handle)) + as DidReceiveBackgroundNotificationResponseCallback?; backgroundChannel .receiveBroadcastStream() @@ -37,11 +38,13 @@ void callbackDispatcher() { } else { id = -1; } - callback?.call(NotificationActionDetails( + callback?.call(NotificationResponse( id: id, actionId: event['actionId'], input: event['input'], payload: event['payload'], + notificationResponseType: + NotificationResponseType.selectedNotificationAction, )); }); }); diff --git a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart index dd1caf4dd..a6bba9376 100644 --- a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart +++ b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart @@ -114,8 +114,10 @@ class FlutterLocalNotificationsPlugin { /// [getNotificationAppLaunchDetails]. Future initialize( InitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, + DidReceiveBackgroundNotificationResponseCallback? + onDidReceiveBackgroundNotificationResponse, }) async { if (kIsWeb) { return true; @@ -123,27 +125,39 @@ class FlutterLocalNotificationsPlugin { if (defaultTargetPlatform == TargetPlatform.android) { return resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() - ?.initialize(initializationSettings.android!, - onSelectNotification: onSelectNotification, - onSelectNotificationAction: onSelectNotificationAction); + ?.initialize( + initializationSettings.android!, + onDidReceiveForegroundNotificationResponse: + onDidReceiveForegroundNotificationResponse, + onDidReceiveBackgroundNotificationResponse: + onDidReceiveBackgroundNotificationResponse, + ); } else if (defaultTargetPlatform == TargetPlatform.iOS) { return await resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() - ?.initialize(initializationSettings.iOS!, - onSelectNotification: onSelectNotification, - onSelectNotificationAction: onSelectNotificationAction); + ?.initialize( + initializationSettings.iOS!, + onDidReceiveForegroundNotificationResponse: + onDidReceiveForegroundNotificationResponse, + onDidReceiveBackgroundNotificationResponse: + onDidReceiveBackgroundNotificationResponse, + ); } else if (defaultTargetPlatform == TargetPlatform.macOS) { return await resolvePlatformSpecificImplementation< MacOSFlutterLocalNotificationsPlugin>() - ?.initialize(initializationSettings.macOS!, - onSelectNotification: onSelectNotification, - onSelectNotificationAction: onSelectNotificationAction); + ?.initialize( + initializationSettings.macOS!, + onDidReceiveForegroundNotificationResponse: + onDidReceiveForegroundNotificationResponse, + ); } else if (defaultTargetPlatform == TargetPlatform.linux) { return await resolvePlatformSpecificImplementation< LinuxFlutterLocalNotificationsPlugin>() - ?.initialize(initializationSettings.linux!, - onSelectNotification: onSelectNotification, - onSelectNotificationAction: onSelectNotificationAction); + ?.initialize( + initializationSettings.linux!, + onDidReceiveForegroundNotificationResponse: + onDidReceiveForegroundNotificationResponse, + ); } return true; } diff --git a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart index 6a6c54c84..149da4c7c 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -94,7 +94,8 @@ class MethodChannelFlutterLocalNotificationsPlugin /// Android implementation of the local notifications plugin. class AndroidFlutterLocalNotificationsPlugin extends MethodChannelFlutterLocalNotificationsPlugin { - SelectNotificationCallback? _onSelectNotification; + DidReceiveForegroundNotificationResponseCallback? + _ondidReceiveForegroundNotificationResponse; /// Initializes the plugin. /// @@ -108,16 +109,19 @@ class AndroidFlutterLocalNotificationsPlugin /// notification action IDs. Future initialize( AndroidInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, + DidReceiveBackgroundNotificationResponseCallback? + onDidReceiveBackgroundNotificationResponse, }) async { - _onSelectNotification = onSelectNotification; + _ondidReceiveForegroundNotificationResponse = + onDidReceiveForegroundNotificationResponse; _channel.setMethodCallHandler(_handleMethod); final Map arguments = initializationSettings.toMap(); - _evaluateSelectNotificationActionCallback( - onSelectNotificationAction, arguments); + _evaluateBackgroundNotificationCallback( + onDidReceiveBackgroundNotificationResponse, arguments); return await _channel.invokeMethod('initialize', arguments); } @@ -468,8 +472,16 @@ class AndroidFlutterLocalNotificationsPlugin Future _handleMethod(MethodCall call) async { switch (call.method) { - case 'selectNotification': - _onSelectNotification?.call(call.arguments); + case 'didReceiveForegroundNotificationResponse': + _ondidReceiveForegroundNotificationResponse?.call( + NotificationResponse( + id: call.arguments['notificationId'], + actionId: call.arguments['actionId'], + payload: call.arguments['payload'], + notificationResponseType: NotificationResponseType + .values[call.arguments['notificationResponseType']], + ), + ); break; default: return await Future.error('Method not defined'); @@ -480,8 +492,8 @@ class AndroidFlutterLocalNotificationsPlugin /// iOS implementation of the local notifications plugin. class IOSFlutterLocalNotificationsPlugin extends MethodChannelFlutterLocalNotificationsPlugin { - SelectNotificationCallback? _onSelectNotification; - + DidReceiveForegroundNotificationResponseCallback? + _onDidReceiveForegroundNotificationResponse; DidReceiveLocalNotificationCallback? _onDidReceiveLocalNotification; /// Initializes the plugin. @@ -502,18 +514,21 @@ class IOSFlutterLocalNotificationsPlugin /// [getNotificationAppLaunchDetails]. Future initialize( DarwinInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, + DidReceiveBackgroundNotificationResponseCallback? + onDidReceiveBackgroundNotificationResponse, }) async { - _onSelectNotification = onSelectNotification; + _onDidReceiveForegroundNotificationResponse = + onDidReceiveForegroundNotificationResponse; _onDidReceiveLocalNotification = initializationSettings.onDidReceiveLocalNotification; _channel.setMethodCallHandler(_handleMethod); final Map arguments = initializationSettings.toMap(); - _evaluateSelectNotificationActionCallback( - onSelectNotificationAction, arguments); + _evaluateBackgroundNotificationCallback( + onDidReceiveBackgroundNotificationResponse, arguments); return await _channel.invokeMethod('initialize', arguments); } @@ -699,8 +714,17 @@ class IOSFlutterLocalNotificationsPlugin Future _handleMethod(MethodCall call) async { switch (call.method) { - case 'selectNotification': - _onSelectNotification?.call(call.arguments); + case 'didReceiveForegroundNotificationResponse': + _onDidReceiveForegroundNotificationResponse?.call( + NotificationResponse( + id: call.arguments['notificationId'], + actionId: call.arguments['actionId'], + input: call.arguments['input'], + payload: call.arguments['payload'], + notificationResponseType: NotificationResponseType + .values[call.arguments['notificationResponseType']], + ), + ); break; case 'didReceiveLocalNotification': _onDidReceiveLocalNotification!( @@ -718,8 +742,8 @@ class IOSFlutterLocalNotificationsPlugin /// macOS implementation of the local notifications plugin. class MacOSFlutterLocalNotificationsPlugin extends MethodChannelFlutterLocalNotificationsPlugin { - SelectNotificationCallback? _onSelectNotification; - SelectNotificationActionCallback? _onSelectNotificationAction; + DidReceiveForegroundNotificationResponseCallback? + _onDidReceiveForegroundNotificationResponse; /// Initializes the plugin. /// @@ -742,11 +766,11 @@ class MacOSFlutterLocalNotificationsPlugin /// [getNotificationAppLaunchDetails]. Future initialize( DarwinInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, }) async { - _onSelectNotification = onSelectNotification; - _onSelectNotificationAction = onSelectNotificationAction; + _onDidReceiveForegroundNotificationResponse = + onDidReceiveForegroundNotificationResponse; _channel.setMethodCallHandler(_handleMethod); return await _channel.invokeMethod( 'initialize', initializationSettings.toMap()); @@ -843,16 +867,17 @@ class MacOSFlutterLocalNotificationsPlugin Future _handleMethod(MethodCall call) async { switch (call.method) { - case 'selectNotification': - _onSelectNotification?.call(call.arguments); - break; - case 'selectNotificationAction': - _onSelectNotificationAction?.call(NotificationActionDetails( - id: int.parse(call.arguments['notificationId']), - actionId: call.arguments['actionId'], - input: call.arguments['input'], - payload: call.arguments['payload'], - )); + case 'didReceiveForegroundNotificationResponse': + _onDidReceiveForegroundNotificationResponse?.call( + NotificationResponse( + id: call.arguments['notificationId'], + actionId: call.arguments['actionId'], + input: call.arguments['input'], + payload: call.arguments['payload'], + notificationResponseType: NotificationResponseType + .values[call.arguments['notificationResponseType']], + ), + ); break; default: return await Future.error('Method not defined'); @@ -860,20 +885,21 @@ class MacOSFlutterLocalNotificationsPlugin } } -/// Checks [onSelectNotificationAction], if not `null`, for eligibility to -/// be used as a background callback. +/// Checks [didReceiveBackgroundNotificationResponseCallback], if not `null`, +/// for eligibility to be used as a background callback. /// /// If the method is `null`, no further action will be taken. /// /// This will add a `dispatcher_handle` and `callback_handle` argument to the /// [arguments] map when the config is correct. -void _evaluateSelectNotificationActionCallback( - SelectNotificationActionCallback? onSelectNotificationAction, +void _evaluateBackgroundNotificationCallback( + DidReceiveBackgroundNotificationResponseCallback? + didReceiveBackgroundNotificationResponseCallback, Map arguments, ) { - if (onSelectNotificationAction != null) { - final CallbackHandle? callback = - PluginUtilities.getCallbackHandle(onSelectNotificationAction); + if (didReceiveBackgroundNotificationResponseCallback != null) { + final CallbackHandle? callback = PluginUtilities.getCallbackHandle( + didReceiveBackgroundNotificationResponseCallback); assert(callback != null, ''' The backgroundHandler needs to be either a static function or a top level function to be accessible as a Flutter entry point.'''); diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index fbec11f7a..12e7d4005 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -117,7 +117,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot if response.actionIdentifier == UNNotificationDefaultActionIdentifier { let payload = response.notification.request.content.userInfo[MethodCallArguments.payload] as? String if initialized { - handleSelectNotification(payload: payload) + handleSelectNotification(notificationId: Int(response.notification.request.identifier)!, payload: payload) } else { launchPayload = payload launchingAppFromNotification = true @@ -130,11 +130,12 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. // // Therefore, we call the regular method channel and let the macos plugin handle it appropriately. - handleSelectNotificationAction(payload: [ - "notificationId": response.notification.request.identifier, + handleSelectNotificationAction(arguments: [ + "notificationId": Int(response.notification.request.identifier)!, "actionId": response.actionIdentifier, "input": text, - "payload": response.notification.request.content.userInfo["payload"] as? String ?? "" + "payload": response.notification.request.content.userInfo["payload"], + "notificationResponseType": 1 ]) completionHandler() @@ -143,7 +144,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot public func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { if notification.activationType == .contentsClicked { - handleSelectNotification(payload: notification.userInfo![MethodCallArguments.payload] as? String) + handleSelectNotification(notificationId:Int(notification.identifier!)!, payload: notification.userInfo![MethodCallArguments.payload] as? String) } } @@ -622,11 +623,15 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot return String(arguments[MethodCallArguments.id] as! Int) } - func handleSelectNotification(payload: String?) { - channel.invokeMethod("selectNotification", arguments: payload) + func handleSelectNotification(notificationId:Int, payload: String?) { + var arguments: [String: Any?] = [:] + arguments["notificationId"] = notificationId + arguments["payload"] = payload + arguments["notificationResponseType"] = 0 + channel.invokeMethod("didReceiveForegroundNotificationResponse", arguments: arguments) } - func handleSelectNotificationAction(payload: [String: Any]) { - channel.invokeMethod("selectNotificationAction", arguments: payload) + func handleSelectNotificationAction(arguments: [String: Any?]) { + channel.invokeMethod("didReceiveForegroundNotificationResponse", arguments: arguments) } } diff --git a/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart b/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart index 5c2e85130..8d765041a 100644 --- a/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart +++ b/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart @@ -35,13 +35,13 @@ class LinuxFlutterLocalNotificationsPlugin @override Future initialize( LinuxInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, }) => _manager.initialize( initializationSettings, - onSelectNotification: onSelectNotification, - onSelectNotificationAction: onSelectNotificationAction, + onDidReceiveForegroundNotificationResponse: + onDidReceiveForegroundNotificationResponse, ); /// Show a notification with an optional payload that will be passed back to diff --git a/flutter_local_notifications_linux/lib/src/flutter_local_notifications_platform_linux.dart b/flutter_local_notifications_linux/lib/src/flutter_local_notifications_platform_linux.dart index 12ff4d196..2c02356dd 100644 --- a/flutter_local_notifications_linux/lib/src/flutter_local_notifications_platform_linux.dart +++ b/flutter_local_notifications_linux/lib/src/flutter_local_notifications_platform_linux.dart @@ -13,13 +13,13 @@ abstract class FlutterLocalNotificationsPlatformLinux /// Initializes the plugin. /// /// Call this method on application before using the plugin further. - /// - /// [onSelectNotificationAction] specifies a callback handler which receives - /// notification action IDs. + /// + /// [onDidReceiveForegroundNotificationResponse] specifies a callback handler + /// which receives notification action IDs. Future initialize( LinuxInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, }); /// Show a notification with an optional payload that will be passed back to diff --git a/flutter_local_notifications_linux/lib/src/flutter_local_notifications_stub.dart b/flutter_local_notifications_linux/lib/src/flutter_local_notifications_stub.dart index 45c0e20c1..fa6b75f54 100644 --- a/flutter_local_notifications_linux/lib/src/flutter_local_notifications_stub.dart +++ b/flutter_local_notifications_linux/lib/src/flutter_local_notifications_stub.dart @@ -23,10 +23,11 @@ class LinuxFlutterLocalNotificationsPlugin @override Future initialize( LinuxInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, }) async { assert(false); + return null; } /// Errors on attempted calling of the stub. It exists only to satisfy diff --git a/flutter_local_notifications_linux/lib/src/notifications_manager.dart b/flutter_local_notifications_linux/lib/src/notifications_manager.dart index ca5ac2df6..86bc7b17f 100644 --- a/flutter_local_notifications_linux/lib/src/notifications_manager.dart +++ b/flutter_local_notifications_linux/lib/src/notifications_manager.dart @@ -44,8 +44,8 @@ class LinuxNotificationManager { final NotificationStorage _storage; late final LinuxInitializationSettings _initializationSettings; - late final SelectNotificationCallback? _onSelectNotification; - late final SelectNotificationActionCallback? _onSelectNotificationAction; + late final DidReceiveForegroundNotificationResponseCallback? + _onDidReceiveForegroundNotificationResponse; late final LinuxPlatformInfoData _platformData; bool _initialized = false; @@ -54,16 +54,16 @@ class LinuxNotificationManager { /// Call this method on application before using the manager further. Future initialize( LinuxInitializationSettings initializationSettings, { - SelectNotificationCallback? onSelectNotification, - SelectNotificationActionCallback? onSelectNotificationAction, + DidReceiveForegroundNotificationResponseCallback? + onDidReceiveForegroundNotificationResponse, }) async { if (_initialized) { return _initialized; } _initialized = true; _initializationSettings = initializationSettings; - _onSelectNotification = onSelectNotification; - _onSelectNotificationAction = onSelectNotificationAction; + _onDidReceiveForegroundNotificationResponse = + onDidReceiveForegroundNotificationResponse; _dbus.build( destination: _DBusInterfaceSpec.destination, path: _DBusInterfaceSpec.path, @@ -340,7 +340,14 @@ class LinuxNotificationManager { return; } if (actionKey == _kDefaultActionName) { - _onSelectNotification?.call(notify.payload); + _onDidReceiveForegroundNotificationResponse?.call( + NotificationResponse( + id: notify.id, + payload: notify.payload, + notificationResponseType: + NotificationResponseType.selectedNotification, + ), + ); } else { final LinuxNotificationActionInfo? actionInfo = notify.actions.firstWhere( @@ -349,12 +356,13 @@ class LinuxNotificationManager { if (actionInfo == null) { return; } - _onSelectNotificationAction?.call( - NotificationActionDetails( + _onDidReceiveForegroundNotificationResponse?.call( + NotificationResponse( id: notify.id, actionId: actionInfo.key, - input: null, payload: notify.payload, + notificationResponseType: + NotificationResponseType.selectedNotificationAction, ), ); } diff --git a/flutter_local_notifications_linux/test/mock/mock_typedef.dart b/flutter_local_notifications_linux/test/mock/mock_typedef.dart index 359ea7546..599cba398 100644 --- a/flutter_local_notifications_linux/test/mock/mock_typedef.dart +++ b/flutter_local_notifications_linux/test/mock/mock_typedef.dart @@ -2,17 +2,9 @@ import 'package:flutter_local_notifications_platform_interface/flutter_local_not import 'package:mocktail/mocktail.dart'; // ignore: one_member_abstracts -abstract class _SelectNotificationCallback { - Future call(String? payload); +abstract class _DidReceiveForegroundNotificationResponseCallback { + Future call(NotificationResponse notificationResponse); } -// ignore: one_member_abstracts -abstract class _SelectNotificationActionCallback { - Future call(NotificationActionDetails details); -} - -class MockSelectNotificationCallback extends Mock - implements _SelectNotificationCallback {} - -class MockSelectNotificationActionCallback extends Mock - implements _SelectNotificationActionCallback {} +class MockDidReceiveForegroundNotificationResponseCallback extends Mock + implements _DidReceiveForegroundNotificationResponseCallback {} diff --git a/flutter_local_notifications_linux/test/notifications_manager_test.dart b/flutter_local_notifications_linux/test/notifications_manager_test.dart index 5ce47896e..494b8fd93 100644 --- a/flutter_local_notifications_linux/test/notifications_manager_test.dart +++ b/flutter_local_notifications_linux/test/notifications_manager_test.dart @@ -24,9 +24,8 @@ void main() { late final DBusRemoteObjectSignalStream mockNotifyClosedSignal; late final LinuxPlatformInfo mockPlatformInfo; late final NotificationStorage mockStorage; - late final SelectNotificationCallback mockSelectNotificationCallback; - late final SelectNotificationActionCallback - mockSelectNotificationActionCallback; + late final DidReceiveForegroundNotificationResponseCallback + mockDidReceiveForegroundNotificationResponseCallback; const LinuxPlatformInfoData platformInfo = LinuxPlatformInfoData( appName: 'Test', @@ -36,11 +35,11 @@ void main() { setUpAll(() { registerFallbackValue( - NotificationActionDetails( + NotificationResponse( id: 0, - actionId: '', - input: null, payload: null, + notificationResponseType: + NotificationResponseType.selectedNotification, ), ); @@ -49,9 +48,8 @@ void main() { mockNotifyClosedSignal = MockDBusRemoteObjectSignalStream(); mockPlatformInfo = MockLinuxPlatformInfo(); mockStorage = MockNotificationStorage(); - mockSelectNotificationCallback = MockSelectNotificationCallback(); - mockSelectNotificationActionCallback = - MockSelectNotificationActionCallback(); + mockDidReceiveForegroundNotificationResponseCallback = + MockDidReceiveForegroundNotificationResponseCallback(); when( () => mockPlatformInfo.getAll(), @@ -72,11 +70,7 @@ void main() { () => mockDbus.subscribeSignal('NotificationClosed'), ).thenAnswer((_) => mockNotifyClosedSignal); when( - () => mockSelectNotificationCallback.call(any()), - ).thenAnswer((_) async => {}); - - when( - () => mockSelectNotificationActionCallback.call(any()), + () => mockDidReceiveForegroundNotificationResponseCallback.call(any()), ).thenAnswer((_) async => {}); }); @@ -208,7 +202,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -236,7 +230,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -311,7 +305,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -364,7 +358,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -398,7 +392,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -428,7 +422,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -459,7 +453,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -500,7 +494,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -536,7 +530,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -573,7 +567,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -606,7 +600,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -639,7 +633,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -672,7 +666,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -705,7 +699,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -737,7 +731,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -770,7 +764,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -804,7 +798,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -940,7 +934,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -989,7 +983,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -1035,7 +1029,7 @@ void main() { mockNotifyMethod(notify.systemId); when( () => mockStorage.getById(notify.id), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => null); when( () => mockStorage.insert(notify), ).thenAnswer((_) async => true); @@ -1239,7 +1233,8 @@ void main() { await manager.initialize( initSettings, - onSelectNotification: mockSelectNotificationCallback, + onDidReceiveForegroundNotificationResponse: + mockDidReceiveForegroundNotificationResponseCallback, ); await Future.forEach( completers, @@ -1250,9 +1245,6 @@ void main() { verify( () => mockStorage.getBySystemId(notify.systemId), ).called(1); - verify( - () => mockSelectNotificationCallback.call(notify.payload), - ).called(1); } }); @@ -1404,7 +1396,8 @@ void main() { await manager.initialize( initSettings, - onSelectNotificationAction: mockSelectNotificationActionCallback, + onDidReceiveForegroundNotificationResponse: + mockDidReceiveForegroundNotificationResponseCallback, ); await Future.forEach( completers, diff --git a/flutter_local_notifications_platform_interface/lib/src/typedefs.dart b/flutter_local_notifications_platform_interface/lib/src/typedefs.dart index c199c3f34..e6eddea9f 100644 --- a/flutter_local_notifications_platform_interface/lib/src/typedefs.dart +++ b/flutter_local_notifications_platform_interface/lib/src/typedefs.dart @@ -1,9 +1,11 @@ import 'types.dart'; -/// Signature of callback passed that is triggered when user selects a -/// notification. -typedef SelectNotificationCallback = void Function(String? payload); +/// Signature of callback triggered when a user taps on a notification or +/// a notification action +typedef DidReceiveForegroundNotificationResponseCallback = void Function( + NotificationResponse details); -/// Signature of callback triggered when a user selects on a notification action -typedef SelectNotificationActionCallback = void Function( - NotificationActionDetails details); +/// Signature of callback triggered when a user taps on a notification or +/// a notification action +typedef DidReceiveBackgroundNotificationResponseCallback = void Function( + NotificationResponse details); diff --git a/flutter_local_notifications_platform_interface/lib/src/types.dart b/flutter_local_notifications_platform_interface/lib/src/types.dart index 59ad667b1..f9e96c123 100644 --- a/flutter_local_notifications_platform_interface/lib/src/types.dart +++ b/flutter_local_notifications_platform_interface/lib/src/types.dart @@ -73,25 +73,34 @@ class ActiveNotification { } /// Details of a Notification Action that was triggered. -class NotificationActionDetails { - /// Constructs an instance of [NotificationActionDetails] - NotificationActionDetails({ - required this.id, - required this.actionId, - required this.input, - required this.payload, +class NotificationResponse { + /// Constructs an instance of [NotificationResponse] + const NotificationResponse({ + required this.notificationResponseType, + this.id, + this.actionId, + this.input, + this.payload, }); /// The notification's id. - final int id; + final int? id; /// The id of the action that was triggered. - final String actionId; + final String? actionId; /// The value of the input field if the notification action had an input /// field. final String? input; - /// The notification's payload + /// The notification's payload. final String? payload; + + /// The notification response type. + final NotificationResponseType notificationResponseType; +} + +enum NotificationResponseType { + selectedNotification, + selectedNotificationAction, } From 240c856c69b2cbdd76647d98241e42c24426e715 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sat, 2 Apr 2022 21:53:01 +1100 Subject: [PATCH 02/13] update ios plugin code to deal with foreground notification actions --- .../example/lib/main.dart | 10 ++-- .../Classes/FlutterLocalNotificationsPlugin.m | 46 +++++++++++++------ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index e549e607c..3e3c22372 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -188,16 +188,16 @@ Future main() async { await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveForegroundNotificationResponse: - (NotificationResponse notificationResponse) async { + (NotificationResponse notificationResponse) { + selectNotificationSubject.add(notificationResponse.payload); switch (notificationResponse.notificationResponseType) { case NotificationResponseType.selectedNotification: - if (notificationResponse.payload != null) { - debugPrint('notification payload: ${notificationResponse.payload}'); - } selectNotificationSubject.add(notificationResponse.payload); break; case NotificationResponseType.selectedNotificationAction: - // TODO: Handle this case. + if (notificationResponse.actionId == navigationActionId) { + selectNotificationSubject.add(notificationResponse.payload); + } break; } }, diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index d1b723023..447aa391b 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -14,6 +14,7 @@ @implementation FlutterLocalNotificationsPlugin { NSString *_launchPayload; UILocalNotification *_launchNotification; FlutterEngineManager *_flutterEngineManager; + NSMutableArray *_foregroundActionIdentifiers; } static FlutterPluginRegistrantCallback registerPlugins; @@ -146,6 +147,7 @@ - (instancetype)initWithChannel:(FlutterMethodChannel *)channel _channel = channel; _registrar = registrar; _flutterEngineManager = [[FlutterEngineManager alloc] init]; + _foregroundActionIdentifiers = [[NSMutableArray alloc] init]; } return self; @@ -318,6 +320,10 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments UNNotificationActionOptions options = [Converters parseNotificationActionOptions:action[@"options"]]; + if((options & UNNotificationActionOptionForeground) != 0) { + [_foregroundActionIdentifiers addObject:identifier]; + } + if ([type isEqualToString:@"plain"]) { [newActions addObject:[UNNotificationAction actionWithIdentifier:identifier @@ -350,8 +356,12 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments [UNUserNotificationCenter currentNotificationCenter]; [center getNotificationCategoriesWithCompletionHandler:^( NSSet *_Nonnull existing) { + if (existing) { [center setNotificationCategories: [existing setByAddingObjectsFromSet:newCategories]]; + } else { + [center setNotificationCategories:newCategories]; + } completionHandler(); }]; @@ -1094,38 +1104,48 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center .userInfo]) { return; } - if ([response.actionIdentifier - isEqualToString:UNNotificationDefaultActionIdentifier]) { - NSString *notificationId = - (NSString *)response.notification.request.identifier; + + NSInteger notificationId = + [response.notification.request.identifier integerValue]; NSString *payload = (NSString *)response.notification.request.content.userInfo[PAYLOAD]; + + if ([response.actionIdentifier + isEqualToString:UNNotificationDefaultActionIdentifier]) { if (_initialized) { - [self handleSelectNotification:[notificationId integerValue] payload:payload]; + [self handleSelectNotification:notificationId payload:payload]; } else { _launchPayload = payload; _launchingAppFromNotification = true; } completionHandler(); } else if (response.actionIdentifier != nil) { + NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; + arguments[@"notificationId"] = notificationIdNumber; + arguments[@"actionId"] = response.actionIdentifier; + arguments[PAYLOAD] = payload; + arguments[@"notificationResponseType"] = [NSNumber numberWithInteger:1]; + if ([response respondsToSelector:@selector(userText)]) { + arguments[@"input"] = [(UNTextInputNotificationResponse *)response userText]; + } + if (_foregroundActionIdentifiers != nil & [_foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { + + [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; + } else { if (!actionEventSink) { actionEventSink = [[ActionEventSink alloc] init]; } - NSString *text = @""; + NSString *text = nil; if ([response respondsToSelector:@selector(userText)]) { text = [(UNTextInputNotificationResponse *)response userText]; } - [actionEventSink addItem:@{ - @"notificationId" : [NSNumber numberWithInteger:[response.notification.request.identifier integerValue]], - @"actionId" : response.actionIdentifier, - @"input" : text, - @"payload" : response.notification.request.content.userInfo[@"payload"], - }]; - + [actionEventSink addItem:arguments]; [_flutterEngineManager startEngineIfNeeded:actionEventSink registerPlugins:registerPlugins]; + } completionHandler(); } From 2560308ba9d3cbf886977b39a31fb7164e218e7c Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 01:09:19 +1100 Subject: [PATCH 03/13] update notification app launch to process notification responses on ios --- .../example/lib/main.dart | 43 ++++++++--- .../Classes/FlutterLocalNotificationsPlugin.m | 77 ++++++++++++------- .../flutter_local_notifications_plugin.dart | 4 +- .../platform_flutter_local_notifications.dart | 21 ++++- ...ocal_notifications_platform_interface.dart | 2 - .../src/notification_app_launch_details.dart | 12 --- .../lib/src/types.dart | 22 ++++++ 7 files changed, 124 insertions(+), 57 deletions(-) delete mode 100644 flutter_local_notifications_platform_interface/lib/src/notification_app_launch_details.dart diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 3e3c22372..3ee60e910 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -99,7 +99,8 @@ Future main() async { : await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); String initialRoute = HomePage.routeName; if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { - selectedNotificationPayload = notificationAppLaunchDetails!.payload; + selectedNotificationPayload = + notificationAppLaunchDetails!.notificationResponse?.payload; initialRoute = SecondPage.routeName; } @@ -189,7 +190,6 @@ Future main() async { initializationSettings, onDidReceiveForegroundNotificationResponse: (NotificationResponse notificationResponse) { - selectNotificationSubject.add(notificationResponse.payload); switch (notificationResponse.notificationResponseType) { case NotificationResponseType.selectedNotification: selectNotificationSubject.add(notificationResponse.payload); @@ -359,11 +359,26 @@ class _HomePageState extends State { title: 'Did notification launch app?', value: widget.didNotificationLaunchApp, ), - if (widget.didNotificationLaunchApp) + if (widget.didNotificationLaunchApp) ...[ + const Text('Launch notification details'), _InfoValueString( - title: 'Launch notification payload:', - value: widget.notificationAppLaunchDetails!.payload, + title: 'Notification id', + value: widget.notificationAppLaunchDetails! + .notificationResponse?.id), + _InfoValueString( + title: 'Action id', + value: widget.notificationAppLaunchDetails! + .notificationResponse?.actionId), + _InfoValueString( + title: 'Input', + value: widget.notificationAppLaunchDetails! + .notificationResponse?.input), + _InfoValueString( + title: 'Payload:', + value: widget.notificationAppLaunchDetails! + .notificationResponse?.payload, ), + ], PaddedElevatedButton( buttonText: 'Show plain notification with payload', onPressed: () async { @@ -2658,14 +2673,20 @@ class SecondPageState extends State { @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( - title: Text('Second Screen with payload: ${_payload ?? ''}'), + title: const Text('Second Screen'), ), body: Center( - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Go back!'), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('payload ${_payload ?? ''}'), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Go back!'), + ), + ], ), ), ); diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 447aa391b..fb33fed16 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -11,15 +11,16 @@ @implementation FlutterLocalNotificationsPlugin { bool _initialized; bool _launchingAppFromNotification; NSObject *_registrar; - NSString *_launchPayload; UILocalNotification *_launchNotification; FlutterEngineManager *_flutterEngineManager; - NSMutableArray *_foregroundActionIdentifiers; + API_AVAILABLE(ios(10.0)) + UNNotificationResponse *_launchNotificationResponse; } static FlutterPluginRegistrantCallback registerPlugins; static ActionEventSink *actionEventSink; +NSString *const FOREGROUND_ACTION_IDENTIFIERS = @"dexterous.com/flutter/local_notifications/foreground_action_identifiers"; NSString *const INITIALIZE_METHOD = @"initialize"; NSString *const GET_CALLBACK_METHOD = @"getCallbackHandle"; NSString *const SHOW_METHOD = @"show"; @@ -147,7 +148,6 @@ - (instancetype)initWithChannel:(FlutterMethodChannel *)channel _channel = channel; _registrar = registrar; _flutterEngineManager = [[FlutterEngineManager alloc] init]; - _foregroundActionIdentifiers = [[NSMutableArray alloc] init]; } return self; @@ -180,16 +180,34 @@ - (void)handleMethodCall:(FlutterMethodCall *)call [self cancelAll:result]; } else if ([GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD isEqualToString:call.method]) { - NSString *payload; - if (_launchNotification != nil) { - payload = _launchNotification.userInfo[PAYLOAD]; - } else { - payload = _launchPayload; + + NSMutableDictionary *notificationAppLaunchDetails = [[NSMutableDictionary alloc] init]; + notificationAppLaunchDetails[NOTIFICATION_LAUNCHED_APP] = [NSNumber numberWithBool:_launchingAppFromNotification]; + NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; + if (_launchNotificationResponse != nil) { + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:[_launchNotificationResponse.notification.request.identifier integerValue]]; + notificationResponse[@"notificationId"] = notificationIdNumber; + notificationResponse[PAYLOAD] = _launchNotificationResponse.notification.request.content.userInfo[PAYLOAD]; + if (@available(iOS 10.0, *)) { + if (_launchNotificationResponse.actionIdentifier != nil && ![_launchNotificationResponse.actionIdentifier + isEqualToString:UNNotificationDefaultActionIdentifier]) { + notificationResponse[@"actionId"] = _launchNotificationResponse.actionIdentifier; + notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:1]; + if ([_launchNotificationResponse respondsToSelector:@selector(userText)]) { + notificationResponse[@"input"] = [(UNTextInputNotificationResponse *)_launchNotificationResponse userText]; + + } + } else { + notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; + } + } + notificationAppLaunchDetails[@"notificationResponse"] = notificationResponse; + } else if (_launchNotification != nil) { + notificationResponse[@"notificationId"] = _launchNotification.userInfo[NOTIFICATION_ID]; + notificationResponse[PAYLOAD] = _launchNotification.userInfo[PAYLOAD]; + notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; + notificationAppLaunchDetails[@"notificationResponse"] = notificationResponse; } - NSDictionary *notificationAppLaunchDetails = [NSDictionary - dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:_launchingAppFromNotification], - NOTIFICATION_LAUNCHED_APP, payload, PAYLOAD, nil]; result(notificationAppLaunchDetails); } else if ([PENDING_NOTIFICATIONS_REQUESTS_METHOD isEqualToString:call.method]) { @@ -307,6 +325,7 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments [NSMutableSet set]; NSArray *categories = arguments[@"notificationCategories"]; + NSMutableArray *foregroundActionIdentifiers = [[NSMutableArray alloc] init]; for (NSDictionary *category in categories) { NSMutableArray *newActions = @@ -321,7 +340,7 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments [Converters parseNotificationActionOptions:action[@"options"]]; if((options & UNNotificationActionOptionForeground) != 0) { - [_foregroundActionIdentifiers addObject:identifier]; + [foregroundActionIdentifiers addObject:identifier]; } if ([type isEqualToString:@"plain"]) { @@ -362,7 +381,7 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments } else { [center setNotificationCategories:newCategories]; } - + [[NSUserDefaults standardUserDefaults] setObject:foregroundActionIdentifiers forKey:FOREGROUND_ACTION_IDENTIFIERS]; completionHandler(); }]; } else { @@ -1115,7 +1134,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center if (_initialized) { [self handleSelectNotification:notificationId payload:payload]; } else { - _launchPayload = payload; + _launchNotificationResponse = response; _launchingAppFromNotification = true; } completionHandler(); @@ -1129,23 +1148,23 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center if ([response respondsToSelector:@selector(userText)]) { arguments[@"input"] = [(UNTextInputNotificationResponse *)response userText]; } - if (_foregroundActionIdentifiers != nil & [_foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { - + NSArray *foregroundActionIdentifiers = [[NSUserDefaults standardUserDefaults] stringArrayForKey:FOREGROUND_ACTION_IDENTIFIERS]; + if ([foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { + if (_initialized) { [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; + } else { + _launchNotificationResponse = response; + _launchingAppFromNotification = true; + } } else { - if (!actionEventSink) { - actionEventSink = [[ActionEventSink alloc] init]; - } - - NSString *text = nil; - if ([response respondsToSelector:@selector(userText)]) { - text = [(UNTextInputNotificationResponse *)response userText]; - } + if (!actionEventSink) { + actionEventSink = [[ActionEventSink alloc] init]; + } - [actionEventSink addItem:arguments]; - [_flutterEngineManager startEngineIfNeeded:actionEventSink + [actionEventSink addItem:arguments]; + [_flutterEngineManager startEngineIfNeeded:actionEventSink registerPlugins:registerPlugins]; - } + } completionHandler(); } @@ -1162,7 +1181,7 @@ - (BOOL)application:(UIApplication *)application launchNotification != nil && [self isAFlutterLocalNotification:launchNotification.userInfo]; if (_launchingAppFromNotification) { - _launchNotification = launchNotification; + _launchNotification = launchNotification; } } diff --git a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart index a6bba9376..3a2443776 100644 --- a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart +++ b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart @@ -194,7 +194,9 @@ class FlutterLocalNotificationsPlugin { } else { return await FlutterLocalNotificationsPlatform.instance .getNotificationAppLaunchDetails() ?? - const NotificationAppLaunchDetails(false, null); + const NotificationAppLaunchDetails( + didNotificationLaunchApp: false, + ); } } diff --git a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart index 149da4c7c..bfe8682ce 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -44,9 +44,26 @@ class MethodChannelFlutterLocalNotificationsPlugin getNotificationAppLaunchDetails() async { final Map? result = await _channel.invokeMethod('getNotificationAppLaunchDetails'); + final Map? notificationResponse = + result != null && result.containsKey('notificationResponse') + ? result['notificationResponse'] + : null; return result != null - ? NotificationAppLaunchDetails(result['notificationLaunchedApp'], - result.containsKey('payload') ? result['payload'] : null) + ? NotificationAppLaunchDetails( + didNotificationLaunchApp: result['notificationLaunchedApp'], + notificationResponse: notificationResponse == null + ? null + : NotificationResponse( + id: notificationResponse['notificationId'], + actionId: notificationResponse['actionId'], + input: notificationResponse['input'], + notificationResponseType: NotificationResponseType.values[ + notificationResponse['notificationResponseType']], + payload: notificationResponse.containsKey('payload') + ? notificationResponse['payload'] + : null, + ), + ) : null; } diff --git a/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart b/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart index 80c3d0612..10598964c 100644 --- a/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart +++ b/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart @@ -1,10 +1,8 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'src/notification_app_launch_details.dart'; import 'src/types.dart'; export 'src/helpers.dart'; -export 'src/notification_app_launch_details.dart'; export 'src/typedefs.dart'; export 'src/types.dart'; diff --git a/flutter_local_notifications_platform_interface/lib/src/notification_app_launch_details.dart b/flutter_local_notifications_platform_interface/lib/src/notification_app_launch_details.dart deleted file mode 100644 index 0079e2fa1..000000000 --- a/flutter_local_notifications_platform_interface/lib/src/notification_app_launch_details.dart +++ /dev/null @@ -1,12 +0,0 @@ -/// Contains details on the notification that launched the application. -class NotificationAppLaunchDetails { - /// Constructs an instance of [NotificationAppLaunchDetails]. - const NotificationAppLaunchDetails( - this.didNotificationLaunchApp, this.payload); - - /// Indicates if the app was launched via notification - final bool didNotificationLaunchApp; - - /// The payload of the notification that launched the app - final String? payload; -} diff --git a/flutter_local_notifications_platform_interface/lib/src/types.dart b/flutter_local_notifications_platform_interface/lib/src/types.dart index f9e96c123..96378a950 100644 --- a/flutter_local_notifications_platform_interface/lib/src/types.dart +++ b/flutter_local_notifications_platform_interface/lib/src/types.dart @@ -84,6 +84,9 @@ class NotificationResponse { }); /// The notification's id. + /// + /// This is nullable as support for this only supported for notifications + /// created using version 10 or newer of this plugin. final int? id; /// The id of the action that was triggered. @@ -100,7 +103,26 @@ class NotificationResponse { final NotificationResponseType notificationResponseType; } +/// Contains details on the notification that launched the application. +class NotificationAppLaunchDetails { + /// Constructs an instance of [NotificationAppLaunchDetails]. + const NotificationAppLaunchDetails({ + required this.didNotificationLaunchApp, + this.notificationResponse, + }); + + /// Indicates if the app was launched via notification. + final bool didNotificationLaunchApp; + + /// Contains details of the notification that launched the app. + final NotificationResponse? notificationResponse; +} + +/// The possible notification response types enum NotificationResponseType { + /// Indicates that a user has selected a notification. selectedNotification, + + /// Indicates the a user has selected a notification action. selectedNotificationAction, } From 73e1149f24f2c981ce4dde9eb67619dae002fecd Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 11:38:25 +1000 Subject: [PATCH 04/13] refactor code for extracting notification response dictionary on ios --- .../Classes/FlutterLocalNotificationsPlugin.m | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index fb33fed16..dbc36c999 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -89,6 +89,9 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const NOTIFICATION_ID = @"NotificationId"; NSString *const PAYLOAD = @"payload"; NSString *const NOTIFICATION_LAUNCHED_APP = @"notificationLaunchedApp"; +NSString *const ACTION_ID = @"actionId"; +NSString *const NOTIFICATION_RESPONSE_TYPE = @"notificationResponseType"; + NSString *const UNSUPPORTED_OS_VERSION_ERROR_CODE = @"unsupported_os_version"; NSString *const GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE = @@ -185,27 +188,13 @@ - (void)handleMethodCall:(FlutterMethodCall *)call notificationAppLaunchDetails[NOTIFICATION_LAUNCHED_APP] = [NSNumber numberWithBool:_launchingAppFromNotification]; NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; if (_launchNotificationResponse != nil) { - NSNumber *notificationIdNumber = [NSNumber numberWithInteger:[_launchNotificationResponse.notification.request.identifier integerValue]]; - notificationResponse[@"notificationId"] = notificationIdNumber; - notificationResponse[PAYLOAD] = _launchNotificationResponse.notification.request.content.userInfo[PAYLOAD]; if (@available(iOS 10.0, *)) { - if (_launchNotificationResponse.actionIdentifier != nil && ![_launchNotificationResponse.actionIdentifier - isEqualToString:UNNotificationDefaultActionIdentifier]) { - notificationResponse[@"actionId"] = _launchNotificationResponse.actionIdentifier; - notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:1]; - if ([_launchNotificationResponse respondsToSelector:@selector(userText)]) { - notificationResponse[@"input"] = [(UNTextInputNotificationResponse *)_launchNotificationResponse userText]; - - } - } else { - notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; - } + notificationAppLaunchDetails[@"notificationResponse"] = [self extractNotificationResponseDict:_launchNotificationResponse]; } - notificationAppLaunchDetails[@"notificationResponse"] = notificationResponse; } else if (_launchNotification != nil) { notificationResponse[@"notificationId"] = _launchNotification.userInfo[NOTIFICATION_ID]; notificationResponse[PAYLOAD] = _launchNotification.userInfo[PAYLOAD]; - notificationResponse[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; + notificationResponse[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; notificationAppLaunchDetails[@"notificationResponse"] = notificationResponse; } result(notificationAppLaunchDetails); @@ -1075,7 +1064,7 @@ - (void)handleSelectNotification:(NSInteger )notificationId NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; arguments[@"notificationId"] = notificationIdNumber; arguments[PAYLOAD] = payload; - arguments[@"notificationResponseType"] = [NSNumber numberWithInteger:0]; + arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; } @@ -1115,6 +1104,28 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center completionHandler(presentationOptions); } +- (NSMutableDictionary *)extractNotificationResponseDict:(UNNotificationResponse * _Nonnull)response NS_AVAILABLE_IOS(10.0){ + NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; + NSInteger notificationId = + [response.notification.request.identifier integerValue]; + NSString *payload = + (NSString *)response.notification.request.content.userInfo[PAYLOAD]; + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; + arguments[@"notificationId"] = notificationIdNumber; + arguments[PAYLOAD] = payload; + if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { + arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; + } else if (response.actionIdentifier != nil && ![response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { + arguments[ACTION_ID] = response.actionIdentifier; + arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:1]; + } + + if ([response respondsToSelector:@selector(userText)]) { + arguments[@"input"] = [(UNTextInputNotificationResponse *)response userText]; + } + return arguments; +} + - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler @@ -1138,16 +1149,9 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center _launchingAppFromNotification = true; } completionHandler(); - } else if (response.actionIdentifier != nil) { - NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; - NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; - arguments[@"notificationId"] = notificationIdNumber; - arguments[@"actionId"] = response.actionIdentifier; - arguments[PAYLOAD] = payload; - arguments[@"notificationResponseType"] = [NSNumber numberWithInteger:1]; - if ([response respondsToSelector:@selector(userText)]) { - arguments[@"input"] = [(UNTextInputNotificationResponse *)response userText]; - } + } + else if (response.actionIdentifier != nil) { + NSMutableDictionary * arguments = [self extractNotificationResponseDict:response]; NSArray *foregroundActionIdentifiers = [[NSUserDefaults standardUserDefaults] stringArrayForKey:FOREGROUND_ACTION_IDENTIFIERS]; if ([foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { if (_initialized) { From 69a0a7e1ecbc8bf0c05965a8fec0d266afb0798b Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:50:55 +1000 Subject: [PATCH 05/13] update launch notification to be saved as a dictionary --- .../Classes/FlutterLocalNotificationsPlugin.m | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index dbc36c999..90a72ba20 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -11,10 +11,8 @@ @implementation FlutterLocalNotificationsPlugin { bool _initialized; bool _launchingAppFromNotification; NSObject *_registrar; - UILocalNotification *_launchNotification; FlutterEngineManager *_flutterEngineManager; - API_AVAILABLE(ios(10.0)) - UNNotificationResponse *_launchNotificationResponse; + NSMutableDictionary *_launchNotificationResponseDict; } static FlutterPluginRegistrantCallback registerPlugins; @@ -186,17 +184,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call NSMutableDictionary *notificationAppLaunchDetails = [[NSMutableDictionary alloc] init]; notificationAppLaunchDetails[NOTIFICATION_LAUNCHED_APP] = [NSNumber numberWithBool:_launchingAppFromNotification]; - NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; - if (_launchNotificationResponse != nil) { - if (@available(iOS 10.0, *)) { - notificationAppLaunchDetails[@"notificationResponse"] = [self extractNotificationResponseDict:_launchNotificationResponse]; - } - } else if (_launchNotification != nil) { - notificationResponse[@"notificationId"] = _launchNotification.userInfo[NOTIFICATION_ID]; - notificationResponse[PAYLOAD] = _launchNotification.userInfo[PAYLOAD]; - notificationResponse[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; - notificationAppLaunchDetails[@"notificationResponse"] = notificationResponse; - } + notificationAppLaunchDetails[@"notificationResponse"] = _launchNotificationResponseDict; result(notificationAppLaunchDetails); } else if ([PENDING_NOTIFICATIONS_REQUESTS_METHOD isEqualToString:call.method]) { @@ -1145,7 +1133,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center if (_initialized) { [self handleSelectNotification:notificationId payload:payload]; } else { - _launchNotificationResponse = response; + _launchNotificationResponseDict = [self extractNotificationResponseDict:response]; _launchingAppFromNotification = true; } completionHandler(); @@ -1157,7 +1145,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center if (_initialized) { [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; } else { - _launchNotificationResponse = response; + _launchNotificationResponseDict = [self extractNotificationResponseDict:response]; _launchingAppFromNotification = true; } } else { @@ -1185,7 +1173,10 @@ - (BOOL)application:(UIApplication *)application launchNotification != nil && [self isAFlutterLocalNotification:launchNotification.userInfo]; if (_launchingAppFromNotification) { - _launchNotification = launchNotification; + _launchNotificationResponseDict = [[NSMutableDictionary alloc] init]; + _launchNotificationResponseDict[@"notificationId"] = launchNotification.userInfo[NOTIFICATION_ID]; + _launchNotificationResponseDict[PAYLOAD] = launchNotification.userInfo[PAYLOAD]; + _launchNotificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; } } From c9090433608a06c100e25395fb52d22d0881b7f3 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:12:25 +1000 Subject: [PATCH 06/13] update app launch notification logic on macos --- .../FlutterLocalNotificationsPlugin.swift | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index 12e7d4005..471de0d37 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -37,6 +37,8 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot static let filePath = "filePath" static let threadIdentifier = "threadIdentifier" static let interruptionLevel = "interruptionLevel" + static let actionId = "actionId" + static let notificationResponseType = "notificationResponseType" } struct ErrorMessages { @@ -73,7 +75,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot var defaultPresentAlert = false var defaultPresentSound = false var defaultPresentBadge = false - var launchPayload: String? + var launchNotificationResponseDict: [String: Any?]? var launchingAppFromNotification = false init(fromChannel channel: FlutterMethodChannel) { @@ -119,24 +121,31 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot if initialized { handleSelectNotification(notificationId: Int(response.notification.request.identifier)!, payload: payload) } else { - launchPayload = payload + launchNotificationResponseDict = extractNotificationResponseDict(response: response) launchingAppFromNotification = true } + completionHandler() + } else if response.actionIdentifier == UNNotificationDismissActionIdentifier { completionHandler() } else { - let text = (response as? UNTextInputNotificationResponse)?.userText ?? "" - - // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. - // - // Therefore, we call the regular method channel and let the macos plugin handle it appropriately. - handleSelectNotificationAction(arguments: [ - "notificationId": Int(response.notification.request.identifier)!, - "actionId": response.actionIdentifier, - "input": text, - "payload": response.notification.request.content.userInfo["payload"], - "notificationResponseType": 1 - ]) + if (initialized) { + let text = (response as? UNTextInputNotificationResponse)?.userText + + // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. + // + // Therefore, we call the regular method channel and let the macos plugin handle it appropriately. + handleSelectNotificationAction(arguments: [ + "notificationId": Int(response.notification.request.identifier)!, + MethodCallArguments.actionId: response.actionIdentifier, + "input": text, + MethodCallArguments.payload: response.notification.request.content.userInfo[MethodCallArguments.payload], + MethodCallArguments.notificationResponseType: 1 + ]) + } else { + launchNotificationResponseDict = extractNotificationResponseDict(response: response) + launchingAppFromNotification = true + } completionHandler() } @@ -284,7 +293,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot func getNotificationAppLaunchDetails(_ result: @escaping FlutterResult) { if #available(OSX 10.14, *) { - let appLaunchDetails: [String: Any?] = [MethodCallArguments.notificationLaunchedApp: launchingAppFromNotification, MethodCallArguments.payload: launchPayload] + let appLaunchDetails: [String: Any?] = [MethodCallArguments.notificationLaunchedApp: launchingAppFromNotification, "notificationResponse": launchNotificationResponseDict] result(appLaunchDetails) } else { result(nil) @@ -634,4 +643,20 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot func handleSelectNotificationAction(arguments: [String: Any?]) { channel.invokeMethod("didReceiveForegroundNotificationResponse", arguments: arguments) } + + @available(macOS 10.14, *) + func extractNotificationResponseDict(response: UNNotificationResponse) -> [String: Any?] { + var notificationResponseDict: [String:Any?] = [:] + notificationResponseDict["notificationId"] = Int(response.notification.request.identifier)! + if (response.actionIdentifier == UNNotificationDefaultActionIdentifier) { + notificationResponseDict[MethodCallArguments.notificationResponseType] = 0 + } + else if (response.actionIdentifier != UNNotificationDismissActionIdentifier) { + notificationResponseDict[MethodCallArguments.actionId] = response.actionIdentifier + notificationResponseDict[MethodCallArguments.notificationResponseType] = 1 + } + notificationResponseDict["input"] = (response as? UNTextInputNotificationResponse)?.userText + notificationResponseDict[MethodCallArguments.payload] = response.notification.request.content.userInfo[MethodCallArguments.payload] + return notificationResponseDict + } } From 3dc8c10ec01b75cb77635d7d1680166eba955ed8 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:18:19 +1000 Subject: [PATCH 07/13] rename and refactor notification logic on macos and ios --- .../Classes/FlutterLocalNotificationsPlugin.m | 24 +++++++++---------- .../FlutterLocalNotificationsPlugin.swift | 10 +------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 90a72ba20..094532187 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -1093,25 +1093,25 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center } - (NSMutableDictionary *)extractNotificationResponseDict:(UNNotificationResponse * _Nonnull)response NS_AVAILABLE_IOS(10.0){ - NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *notitificationResponseDict = [[NSMutableDictionary alloc] init]; NSInteger notificationId = [response.notification.request.identifier integerValue]; NSString *payload = (NSString *)response.notification.request.content.userInfo[PAYLOAD]; NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; - arguments[@"notificationId"] = notificationIdNumber; - arguments[PAYLOAD] = payload; + notitificationResponseDict[@"notificationId"] = notificationIdNumber; + notitificationResponseDict[PAYLOAD] = payload; if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { - arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; + notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; } else if (response.actionIdentifier != nil && ![response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { - arguments[ACTION_ID] = response.actionIdentifier; - arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:1]; + notitificationResponseDict[ACTION_ID] = response.actionIdentifier; + notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:1]; } if ([response respondsToSelector:@selector(userText)]) { - arguments[@"input"] = [(UNTextInputNotificationResponse *)response userText]; + notitificationResponseDict[@"input"] = [(UNTextInputNotificationResponse *)response userText]; } - return arguments; + return notitificationResponseDict; } - (void)userNotificationCenter:(UNUserNotificationCenter *)center @@ -1139,13 +1139,13 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center completionHandler(); } else if (response.actionIdentifier != nil) { - NSMutableDictionary * arguments = [self extractNotificationResponseDict:response]; + NSMutableDictionary *notificationResponseDict = [self extractNotificationResponseDict:response]; NSArray *foregroundActionIdentifiers = [[NSUserDefaults standardUserDefaults] stringArrayForKey:FOREGROUND_ACTION_IDENTIFIERS]; if ([foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { if (_initialized) { - [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; + [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:notificationResponseDict]; } else { - _launchNotificationResponseDict = [self extractNotificationResponseDict:response]; + _launchNotificationResponseDict = notificationResponseDict; _launchingAppFromNotification = true; } } else { @@ -1153,7 +1153,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center actionEventSink = [[ActionEventSink alloc] init]; } - [actionEventSink addItem:arguments]; + [actionEventSink addItem:notificationResponseDict]; [_flutterEngineManager startEngineIfNeeded:actionEventSink registerPlugins:registerPlugins]; } diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index 471de0d37..fe0ebd0f4 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -130,18 +130,10 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot completionHandler() } else { if (initialized) { - let text = (response as? UNTextInputNotificationResponse)?.userText - // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. // // Therefore, we call the regular method channel and let the macos plugin handle it appropriately. - handleSelectNotificationAction(arguments: [ - "notificationId": Int(response.notification.request.identifier)!, - MethodCallArguments.actionId: response.actionIdentifier, - "input": text, - MethodCallArguments.payload: response.notification.request.content.userInfo[MethodCallArguments.payload], - MethodCallArguments.notificationResponseType: 1 - ]) + handleSelectNotificationAction(arguments: extractNotificationResponseDict(response: response)) } else { launchNotificationResponseDict = extractNotificationResponseDict(response: response) launchingAppFromNotification = true From d4b9e6d09c7b9f4d15cda1915dcf4062ec8e546a Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 15:06:02 +1000 Subject: [PATCH 08/13] revert making didNotificationLaunchApp parameter named --- .../lib/src/flutter_local_notifications_plugin.dart | 4 +--- .../lib/src/platform_flutter_local_notifications.dart | 2 +- .../lib/src/types.dart | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart index 3a2443776..6aaa538fd 100644 --- a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart +++ b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart @@ -194,9 +194,7 @@ class FlutterLocalNotificationsPlugin { } else { return await FlutterLocalNotificationsPlatform.instance .getNotificationAppLaunchDetails() ?? - const NotificationAppLaunchDetails( - didNotificationLaunchApp: false, - ); + const NotificationAppLaunchDetails(false); } } diff --git a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart index bfe8682ce..9b6178a61 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -50,7 +50,7 @@ class MethodChannelFlutterLocalNotificationsPlugin : null; return result != null ? NotificationAppLaunchDetails( - didNotificationLaunchApp: result['notificationLaunchedApp'], + result['notificationLaunchedApp'], notificationResponse: notificationResponse == null ? null : NotificationResponse( diff --git a/flutter_local_notifications_platform_interface/lib/src/types.dart b/flutter_local_notifications_platform_interface/lib/src/types.dart index 96378a950..d36dd5f43 100644 --- a/flutter_local_notifications_platform_interface/lib/src/types.dart +++ b/flutter_local_notifications_platform_interface/lib/src/types.dart @@ -106,8 +106,8 @@ class NotificationResponse { /// Contains details on the notification that launched the application. class NotificationAppLaunchDetails { /// Constructs an instance of [NotificationAppLaunchDetails]. - const NotificationAppLaunchDetails({ - required this.didNotificationLaunchApp, + const NotificationAppLaunchDetails( + this.didNotificationLaunchApp, { this.notificationResponse, }); From 8300993975180199f93cd55e6b2f78ebb976fece Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 16:49:52 +1000 Subject: [PATCH 09/13] update android plugin to send foreground event for notification actions --- .../ActionBroadcastReceiver.java | 24 +----- .../FlutterLocalNotificationsPlugin.java | 75 ++++++++++++++----- .../example/lib/main.dart | 6 +- .../platform_flutter_local_notifications.dart | 1 + 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java index 07f1c4f92..75beb95ab 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java @@ -31,11 +31,7 @@ public class ActionBroadcastReceiver extends BroadcastReceiver { public static final String ACTION_TAPPED = "com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver.ACTION_TAPPED"; - public static final String ACTION_ID = "actionId"; - public static final String NOTIFICATION_ID = "notificationId"; - public static final String INPUT_RESULT = "FlutterLocalNotificationsPluginInputResult"; private static final String TAG = "ActionBroadcastReceiver"; - private static final String INPUT = "input"; @Nullable private static ActionEventSink actionEventSink; @Nullable private static FlutterEngine engine; IsolatePreferences preferences; @@ -56,26 +52,10 @@ public void onReceive(Context context, Intent intent) { preferences = preferences == null ? new IsolatePreferences(context) : preferences; - final Map action = new HashMap<>(); - final int notificationId = intent.getIntExtra(NOTIFICATION_ID, -1); - action.put(NOTIFICATION_ID, notificationId); - action.put( - ACTION_ID, intent.hasExtra(ACTION_ID) ? intent.getStringExtra(ACTION_ID) : "unknown"); - action.put( - FlutterLocalNotificationsPlugin.PAYLOAD, - intent.hasExtra(FlutterLocalNotificationsPlugin.PAYLOAD) - ? intent.getStringExtra(FlutterLocalNotificationsPlugin.PAYLOAD) - : ""); - - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) { - action.put(INPUT, remoteInput.getString(INPUT_RESULT)); - } else { - action.put(INPUT, ""); - } + final Map action = FlutterLocalNotificationsPlugin.extractNotificationResponseMap(intent); if (intent.getBooleanExtra(FlutterLocalNotificationsPlugin.CANCEL_NOTIFICATION, false)) { - NotificationManagerCompat.from(context).cancel(notificationId); + NotificationManagerCompat.from(context).cancel((int)action.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); } if (actionEventSink == null) { diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 6486287e5..1b1c0c6d8 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -20,6 +20,7 @@ import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.text.Html; import android.text.Spannable; @@ -100,6 +101,7 @@ public class FlutterLocalNotificationsPlugin private static final String DRAWABLE = "drawable"; private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; + private static final String SELECT_FOREGROUND_NOTIFICATION_ACTION = "SELECT_FOREGROUND_NOTIFICATION"; private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; @@ -151,6 +153,10 @@ public class FlutterLocalNotificationsPlugin + " your Android head project."; private static final String CANCEL_ID = "id"; private static final String CANCEL_TAG = "tag"; + private static final String ACTION_ID = "actionId"; + private static final String INPUT_RESULT = "FlutterLocalNotificationsPluginInputResult"; + private static final String INPUT = "input"; + private static final String NOTIFICATION_RESPONSE_TYPE = "notificationResponseType"; static String NOTIFICATION_DETAILS = "notificationDetails"; static Gson gson; private MethodChannel channel; @@ -233,14 +239,21 @@ protected static Notification createNotification( icon = getIconFromSource(context, action.icon, action.iconSource); } - Intent actionIntent = - new Intent(context, ActionBroadcastReceiver.class) - .setAction(ActionBroadcastReceiver.ACTION_TAPPED) - .putExtra(ActionBroadcastReceiver.NOTIFICATION_ID, notificationDetails.id) - .putExtra(ActionBroadcastReceiver.ACTION_ID, action.id) + Intent actionIntent; + if (action.showsUserInterface != null && action.showsUserInterface) { + actionIntent = getLaunchIntent(context); + actionIntent.setAction(SELECT_FOREGROUND_NOTIFICATION_ACTION); + } else { + actionIntent = new Intent(context, ActionBroadcastReceiver.class); + actionIntent.setAction(ActionBroadcastReceiver.ACTION_TAPPED); + } + + actionIntent + .putExtra(NOTIFICATION_ID, notificationDetails.id) + .putExtra(ACTION_ID, action.id) .putExtra(CANCEL_NOTIFICATION, action.cancelNotification) .putExtra(PAYLOAD, notificationDetails.payload); - int actionFlags = PendingIntent.FLAG_ONE_SHOT; + int actionFlags = PendingIntent.FLAG_UPDATE_CURRENT; if (action.actionInputs == null || action.actionInputs.isEmpty()) { if (VERSION.SDK_INT >= VERSION_CODES.M) { actionFlags |= PendingIntent.FLAG_IMMUTABLE; @@ -252,7 +265,7 @@ protected static Notification createNotification( } @SuppressLint("UnspecifiedImmutableFlag") - final PendingIntent actionPendingIntent = + final PendingIntent actionPendingIntent = action.showsUserInterface != null && action.showsUserInterface ? PendingIntent.getActivity(context, requestCode++, actionIntent, actionFlags) : PendingIntent.getBroadcast(context, requestCode++, actionIntent, actionFlags); final Spannable actionTitleSpannable = new SpannableString(action.title); @@ -275,7 +288,7 @@ protected static Notification createNotification( for (NotificationActionInput input : action.actionInputs) { RemoteInput.Builder remoteInput = - new RemoteInput.Builder(ActionBroadcastReceiver.INPUT_RESULT).setLabel(input.label); + new RemoteInput.Builder(INPUT_RESULT).setLabel(input.label); if (input.allowFreeFormInput != null) { remoteInput.setAllowFreeFormInput(input.allowFreeFormInput); } @@ -537,6 +550,31 @@ static void scheduleNextRepeatingNotification( saveScheduledNotification(context, notificationDetails); } + static Map extractNotificationResponseMap(Intent intent) { + final int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0); + final Map notificationResponseMap = new HashMap<>(); + notificationResponseMap.put(NOTIFICATION_ID, notificationId); + notificationResponseMap.put(ACTION_ID, intent.getStringExtra(ACTION_ID)); + notificationResponseMap.put( + FlutterLocalNotificationsPlugin.PAYLOAD, + intent.getStringExtra(FlutterLocalNotificationsPlugin.PAYLOAD)); + + Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); + if (remoteInput != null) { + notificationResponseMap.put(INPUT, remoteInput.getString(INPUT_RESULT)); + } + + if (SELECT_NOTIFICATION.equals(intent.getAction())) { + notificationResponseMap.put(NOTIFICATION_RESPONSE_TYPE, 0); + } + + if (SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { + notificationResponseMap.put(NOTIFICATION_RESPONSE_TYPE, 1); + } + + return notificationResponseMap; + } + private static PendingIntent getBroadcastPendingIntent(Context context, int id, Intent intent) { int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (VERSION.SDK_INT >= VERSION_CODES.M) { @@ -1462,16 +1500,15 @@ private void show(MethodCall call, Result result) { private void getNotificationAppLaunchDetails(Result result) { Map notificationAppLaunchDetails = new HashMap<>(); - String payload = null; Boolean notificationLaunchedApp = mainActivity != null - && SELECT_NOTIFICATION.equals(mainActivity.getIntent().getAction()) + && (SELECT_NOTIFICATION.equals(mainActivity.getIntent().getAction()) || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(mainActivity.getIntent().getAction())) && !launchedActivityFromHistory(mainActivity.getIntent()); notificationAppLaunchDetails.put(NOTIFICATION_LAUNCHED_APP, notificationLaunchedApp); if (notificationLaunchedApp) { - payload = launchIntent.getStringExtra(PAYLOAD); + notificationAppLaunchDetails.put("notificationResponse", extractNotificationResponseMap(launchIntent)); } - notificationAppLaunchDetails.put(PAYLOAD, payload); + result.success(notificationAppLaunchDetails); } @@ -1642,17 +1679,17 @@ public boolean onNewIntent(Intent intent) { } private Boolean sendNotificationPayloadMessage(Intent intent) { - if (SELECT_NOTIFICATION.equals(intent.getAction())) { - HashMap notificationResponse = new HashMap<>(); - notificationResponse.put(PAYLOAD, intent.getStringExtra(PAYLOAD)); - if (intent.hasExtra(NOTIFICATION_ID)) { - notificationResponse.put(NOTIFICATION_ID, intent.getIntExtra(NOTIFICATION_ID, 0)); + if (SELECT_NOTIFICATION.equals(intent.getAction()) || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { + Map notificationResponse = extractNotificationResponseMap(intent); + if (SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { + if (intent.getBooleanExtra(FlutterLocalNotificationsPlugin.CANCEL_NOTIFICATION, false)) { + NotificationManagerCompat.from(applicationContext).cancel((int)notificationResponse.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); + } } - - notificationResponse.put("notificationResponseType", 0); channel.invokeMethod("didReceiveForegroundNotificationResponse", notificationResponse); return true; } + return false; } diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart index 3ee60e910..ba7073d83 100644 --- a/flutter_local_notifications/example/lib/main.dart +++ b/flutter_local_notifications/example/lib/main.dart @@ -117,9 +117,6 @@ Future main() async { 'Action 1', buttonTitle: 'Send', placeholder: 'Placeholder', - options: const { - DarwinNotificationActionOption.foreground, - }, ), ], ), @@ -999,7 +996,7 @@ class _HomePageState extends State { navigationActionId, 'Action 3', icon: DrawableResourceAndroidBitmap('secondary_icon'), - + showsUserInterface: true, // By default, Android plugin will dismiss the notification when the // user tapped on a action (this mimics the behavior on iOS). cancelNotification: false, @@ -1061,6 +1058,7 @@ class _HomePageState extends State { label: 'Enter a message', ), ], + showsUserInterface: true, ), ], ); diff --git a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart index 9b6178a61..746bb8bde 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -494,6 +494,7 @@ class AndroidFlutterLocalNotificationsPlugin NotificationResponse( id: call.arguments['notificationId'], actionId: call.arguments['actionId'], + input: call.arguments['input'], payload: call.arguments['payload'], notificationResponseType: NotificationResponseType .values[call.arguments['notificationResponseType']], From 0c0a6725cf56fcfc390a4d6762e7dfa83231f5e1 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 17:48:52 +1000 Subject: [PATCH 10/13] bump plugin and update changelog --- flutter_local_notifications/CHANGELOG.md | 7 +++++ flutter_local_notifications/README.md | 34 ++++++++++++------------ flutter_local_notifications/pubspec.yaml | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index a8a7ca2d3..219906629 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,3 +1,10 @@ +# [10.0.0-dev.12] + +* **Breaking change** callbacks have now been worked. There are now the following callbacks + * `onDidReceiveForegroundNotificationResponse`: 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 `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 + # [10.0.0-dev.11] * Includes changes from 9.4.0 diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index 3dfa2f748..01d07b1d8 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -133,7 +133,7 @@ Capabilities depend on the system notification server implementation, therefore, Scheduled/pending notifications is currently not supported due to the lack of a scheduler API. -`onSelectNotification` and `onSelectNotificationAction` callbacks are launched in 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. +The `onDidReceiveForegroundNotificationResponse` 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. ## 📷 Screenshots @@ -183,7 +183,7 @@ If your application needs the ability to schedule full-screen intent notificatio For reference, the example app's `AndroidManifest.xml` file can be found [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml). -Note that when a full-screen intent notification actually occurs (as opposed to a heads-up notification that the system may decide should occur), the plugin will act as though the user has tapped on a notification so handle those the same way (e.g. `onSelectNotification` callback) to display the appropriate page for your application. +Note that when a full-screen intent notification actually occurs (as opposed to a heads-up notification that the system may decide should occur), the plugin will act as though the user has tapped on a notification so handle those the same way (e.g. `onDidReceiveForegroundNotificationResponse` callback) to display the appropriate page for your application. #### Release build configuration @@ -240,7 +240,7 @@ final InitializationSettings initializationSettings = InitializationSettings( iOS: initializationSettingsDarwin, linux: initializationSettingsLinux); flutterLocalNotificationsPlugin.initialize(initializationSettings, - onSelectNotification: onSelectNotification); + onDidReceiveForegroundNotificationResponse: onDidReceiveForegroundNotificationResponse); ... @@ -281,11 +281,10 @@ If you encounter any issues please refer to the API docs and the sample code in ### Notification Actions -Notifications can now contain actions. These actions may be selected by the user when a App is sleeping or terminated and will wake up your app. However, it may not wake up the user-visible part of your App; but only the part of it which runs in the background. An exception is the Linux implementation: please see details in [Linux limitations](#linux-limitations) chapter. +Notifications can now contain actions. On macOS and Linux (see [Linux limitations](#linux-limitations) chapter), these will only run on the main isolate by calling the `onDidReceiveForegroundNotificationResponse` callback. On iOS and Android, these will run on the main isolate by calling the `onDidReceiveForegroundNotificationResponse` callback if the configuration has specified that the app/user interface should be shown i.e. by specifying the `DarwinNotificationActionOption.foreground` option on iOS and the `showsUserInterface` property on Android. If they haven't, then these actions may be selected by the user when an app is sleeping or terminated and will wake up your app. However, it may not wake up the user-visible part of your App; but only the part of it which runs in the background. This is done by spawning a background isolate. -This plugin contains handlers for iOS & Android to handle these cases and will allow you to specify a Dart entry point (a function). - -When the user selects a action, the plugin will start a **separate Flutter Engine** which only exists to execute this callback. Linux implementation runs this callback in the current main isolate. +This plugin contains handlers for iOS & Android to handle these background isolate cases and will allow you to specify a Dart entry point (a function). +When the user selects a action, the plugin will start a **separate Flutter Engine** which will then invoke the `onDidReceiveBackgroundNotificationResponse` callback **Configuration**: @@ -370,7 +369,7 @@ On iOS/macOS, the notification category will define which actions are availble. You need to configure a **top level** or **static** method which will handle the action: ``` dart -void notificationTapBackground(NotificationActionDetails notificationActionDetails) { +void notificationTapBackground(NotificationResponse notificationResponse) { // handle action } ``` @@ -380,10 +379,10 @@ Specify this function as a parameter in the `initialize` method of this plugin: ``` dart await flutterLocalNotificationsPlugin.initialize( initializationSettings, - onSelectNotification: (String payload) async { + onDidReceiveForegroundNotificationResponse: (NotificationResponse notificationResponse) async { // ... }, - onSelectNotificationAction: notificationTapBackground, + onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); ``` @@ -452,14 +451,15 @@ final InitializationSettings initializationSettings = InitializationSettings( macOS: initializationSettingsDarwin, linux: initializationSettingsLinux); await flutterLocalNotificationsPlugin.initialize(initializationSettings, - onSelectNotification: selectNotification); + onDidReceiveForegroundNotificationResponse: onDidReceiveForegroundNotificationResponse); ``` -Initialisation can be done in the `main` function of your application or can be done within the first page shown in your app. Developers can refer to the example app that has code for the initialising within the `main` function. The code above has been simplified for explaining the concepts. Here we have specified the default icon to use for notifications on Android (refer to the *Android setup* section) and designated the function (`selectNotification`) that should fire when a notification has been tapped on via the `onSelectNotification` callback. Specifying this callback is entirely optional but here it will trigger navigation to another page and display the payload associated with the notification. +Initialisation can be done in the `main` function of your application or can be done within the first page shown in your app. Developers can refer to the example app that has code for the initialising within the `main` function. The code above has been simplified for explaining the concepts. Here we have specified the default icon to use for notifications on Android (refer to the *Android setup* section) and designated the function (`onDidReceiveForegroundNotificationResponse`) that should fire when a notification has been tapped on via the `onDidReceiveForegroundNotificationResponse` callback. Specifying this callback is entirely optional but here it will trigger navigation to another page and display the payload associated with the notification. ```dart -void selectNotification(String payload) async { - if (payload != null) { +void onDidReceiveForegroundNotificationResponse(NotificationResponse notificationResponse) async { + final String? payload = notificationResponse.payload; + if (notificationResponse.payload != null) { debugPrint('notification payload: $payload'); } await Navigator.push( @@ -473,14 +473,14 @@ In the real world, this payload could represent the id of the item you want to d 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 `onSelectNotification` callback when the notification is clicked. +The `LinuxInitializationSettings` class requires a name for the default action that calls the `onDidReceiveForegroundNotificationResponse` 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. For an explanation of the `onDidReceiveLocalNotification` callback associated with the `DarwinInitializationSettings` class, please read [this](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications#handling-notifications-whilst-the-app-is-in-the-foreground). -*Note*: from version 4.0 of the plugin, calling `initialize` will not trigger the `onSelectNotification` callback when the application was started by tapping on a notification to trigger. Use the `getNotificationAppLaunchDetails` method that is available in the plugin if you need to handle a notification triggering the launch for an app e.g. change the home route of the app for deep-linking. +*Note*: from version 4.0 of the plugin, calling `initialize` will not trigger the `onDidReceiveForegroundNotificationResponse` callback when the application was started by tapping on a notification to trigger. Use the `getNotificationAppLaunchDetails` method that is available in the plugin if you need to handle a notification triggering the launch for an app e.g. change the home route of the app for deep-linking. ### [iOS (all supported versions) and macOS 10.14+] Requesting notification permissions @@ -512,7 +512,7 @@ The constructor for the `DarwinInitializationSettings` class has three named pa macOS: initializationSettingsDarwin, linux: initializationSettingsLinux); await flutterLocalNotificationsPlugin.initialize(initializationSettings, - onSelectNotification: onSelectNotification); + onDidReceiveForegroundNotificationResponse: onDidReceiveForegroundNotificationResponse); ``` Then call the `requestPermissions` method with desired permissions at the appropriate point in your application diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index 1a6d05e3a..6a8c1775d 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: 10.0.0-dev.11 +version: 10.0.0-dev.12 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications dependencies: From b25eb10364838c79d34503ccc47b7f6aae9da71c Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Sun, 3 Apr 2022 07:49:20 +0000 Subject: [PATCH 11/13] Google Java Format --- .../ActionBroadcastReceiver.java | 9 ++--- .../FlutterLocalNotificationsPlugin.java | 39 ++++++++++++------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java index 75beb95ab..d191e677a 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ActionBroadcastReceiver.java @@ -3,19 +3,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.app.NotificationManagerCompat; -import androidx.core.app.RemoteInput; import com.dexterous.flutterlocalnotifications.isolate.IsolatePreferences; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -52,10 +49,12 @@ public void onReceive(Context context, Intent intent) { preferences = preferences == null ? new IsolatePreferences(context) : preferences; - final Map action = FlutterLocalNotificationsPlugin.extractNotificationResponseMap(intent); + final Map action = + FlutterLocalNotificationsPlugin.extractNotificationResponseMap(intent); if (intent.getBooleanExtra(FlutterLocalNotificationsPlugin.CANCEL_NOTIFICATION, false)) { - NotificationManagerCompat.from(context).cancel((int)action.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); + NotificationManagerCompat.from(context) + .cancel((int) action.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); } if (actionEventSink == null) { diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 1b1c0c6d8..3a6a63085 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -101,7 +101,8 @@ public class FlutterLocalNotificationsPlugin private static final String DRAWABLE = "drawable"; private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - private static final String SELECT_FOREGROUND_NOTIFICATION_ACTION = "SELECT_FOREGROUND_NOTIFICATION"; + private static final String SELECT_FOREGROUND_NOTIFICATION_ACTION = + "SELECT_FOREGROUND_NOTIFICATION"; private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; @@ -240,7 +241,7 @@ protected static Notification createNotification( } Intent actionIntent; - if (action.showsUserInterface != null && action.showsUserInterface) { + if (action.showsUserInterface != null && action.showsUserInterface) { actionIntent = getLaunchIntent(context); actionIntent.setAction(SELECT_FOREGROUND_NOTIFICATION_ACTION); } else { @@ -249,10 +250,10 @@ protected static Notification createNotification( } actionIntent - .putExtra(NOTIFICATION_ID, notificationDetails.id) - .putExtra(ACTION_ID, action.id) - .putExtra(CANCEL_NOTIFICATION, action.cancelNotification) - .putExtra(PAYLOAD, notificationDetails.payload); + .putExtra(NOTIFICATION_ID, notificationDetails.id) + .putExtra(ACTION_ID, action.id) + .putExtra(CANCEL_NOTIFICATION, action.cancelNotification) + .putExtra(PAYLOAD, notificationDetails.payload); int actionFlags = PendingIntent.FLAG_UPDATE_CURRENT; if (action.actionInputs == null || action.actionInputs.isEmpty()) { if (VERSION.SDK_INT >= VERSION_CODES.M) { @@ -265,8 +266,10 @@ protected static Notification createNotification( } @SuppressLint("UnspecifiedImmutableFlag") - final PendingIntent actionPendingIntent = action.showsUserInterface != null && action.showsUserInterface ? PendingIntent.getActivity(context, requestCode++, actionIntent, actionFlags) : - PendingIntent.getBroadcast(context, requestCode++, actionIntent, actionFlags); + final PendingIntent actionPendingIntent = + action.showsUserInterface != null && action.showsUserInterface + ? PendingIntent.getActivity(context, requestCode++, actionIntent, actionFlags) + : PendingIntent.getBroadcast(context, requestCode++, actionIntent, actionFlags); final Spannable actionTitleSpannable = new SpannableString(action.title); if (action.titleColor != null) { @@ -550,14 +553,14 @@ static void scheduleNextRepeatingNotification( saveScheduledNotification(context, notificationDetails); } - static Map extractNotificationResponseMap(Intent intent) { + static Map extractNotificationResponseMap(Intent intent) { final int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0); final Map notificationResponseMap = new HashMap<>(); notificationResponseMap.put(NOTIFICATION_ID, notificationId); notificationResponseMap.put(ACTION_ID, intent.getStringExtra(ACTION_ID)); notificationResponseMap.put( - FlutterLocalNotificationsPlugin.PAYLOAD, - intent.getStringExtra(FlutterLocalNotificationsPlugin.PAYLOAD)); + FlutterLocalNotificationsPlugin.PAYLOAD, + intent.getStringExtra(FlutterLocalNotificationsPlugin.PAYLOAD)); Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { @@ -1502,11 +1505,14 @@ private void getNotificationAppLaunchDetails(Result result) { Map notificationAppLaunchDetails = new HashMap<>(); Boolean notificationLaunchedApp = mainActivity != null - && (SELECT_NOTIFICATION.equals(mainActivity.getIntent().getAction()) || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(mainActivity.getIntent().getAction())) + && (SELECT_NOTIFICATION.equals(mainActivity.getIntent().getAction()) + || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals( + mainActivity.getIntent().getAction())) && !launchedActivityFromHistory(mainActivity.getIntent()); notificationAppLaunchDetails.put(NOTIFICATION_LAUNCHED_APP, notificationLaunchedApp); if (notificationLaunchedApp) { - notificationAppLaunchDetails.put("notificationResponse", extractNotificationResponseMap(launchIntent)); + notificationAppLaunchDetails.put( + "notificationResponse", extractNotificationResponseMap(launchIntent)); } result.success(notificationAppLaunchDetails); @@ -1679,11 +1685,14 @@ public boolean onNewIntent(Intent intent) { } private Boolean sendNotificationPayloadMessage(Intent intent) { - if (SELECT_NOTIFICATION.equals(intent.getAction()) || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { + if (SELECT_NOTIFICATION.equals(intent.getAction()) + || SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { Map notificationResponse = extractNotificationResponseMap(intent); if (SELECT_FOREGROUND_NOTIFICATION_ACTION.equals(intent.getAction())) { if (intent.getBooleanExtra(FlutterLocalNotificationsPlugin.CANCEL_NOTIFICATION, false)) { - NotificationManagerCompat.from(applicationContext).cancel((int)notificationResponse.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); + NotificationManagerCompat.from(applicationContext) + .cancel( + (int) notificationResponse.get(FlutterLocalNotificationsPlugin.NOTIFICATION_ID)); } } channel.invokeMethod("didReceiveForegroundNotificationResponse", notificationResponse); From 9303b206ebdc03bbf7b192fe6cf03d02b9e428c6 Mon Sep 17 00:00:00 2001 From: runner Date: Sun, 3 Apr 2022 09:12:35 +0000 Subject: [PATCH 12/13] Swift Format --- .../Classes/FlutterLocalNotificationsPlugin.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index fe0ebd0f4..a4c06348f 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -129,7 +129,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot } else if response.actionIdentifier == UNNotificationDismissActionIdentifier { completionHandler() } else { - if (initialized) { + if initialized { // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. // // Therefore, we call the regular method channel and let the macos plugin handle it appropriately. @@ -145,7 +145,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot public func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { if notification.activationType == .contentsClicked { - handleSelectNotification(notificationId:Int(notification.identifier!)!, payload: notification.userInfo![MethodCallArguments.payload] as? String) + handleSelectNotification(notificationId: Int(notification.identifier!)!, payload: notification.userInfo![MethodCallArguments.payload] as? String) } } @@ -624,7 +624,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot return String(arguments[MethodCallArguments.id] as! Int) } - func handleSelectNotification(notificationId:Int, payload: String?) { + func handleSelectNotification(notificationId: Int, payload: String?) { var arguments: [String: Any?] = [:] arguments["notificationId"] = notificationId arguments["payload"] = payload @@ -635,15 +635,14 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot func handleSelectNotificationAction(arguments: [String: Any?]) { channel.invokeMethod("didReceiveForegroundNotificationResponse", arguments: arguments) } - + @available(macOS 10.14, *) func extractNotificationResponseDict(response: UNNotificationResponse) -> [String: Any?] { - var notificationResponseDict: [String:Any?] = [:] + var notificationResponseDict: [String: Any?] = [:] notificationResponseDict["notificationId"] = Int(response.notification.request.identifier)! - if (response.actionIdentifier == UNNotificationDefaultActionIdentifier) { + if response.actionIdentifier == UNNotificationDefaultActionIdentifier { notificationResponseDict[MethodCallArguments.notificationResponseType] = 0 - } - else if (response.actionIdentifier != UNNotificationDismissActionIdentifier) { + } else if response.actionIdentifier != UNNotificationDismissActionIdentifier { notificationResponseDict[MethodCallArguments.actionId] = response.actionIdentifier notificationResponseDict[MethodCallArguments.notificationResponseType] = 1 } From fa0ffbe643af3fbaf42503ceb39ca2f7f470a60a Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Sun, 3 Apr 2022 19:35:26 +1000 Subject: [PATCH 13/13] formatted objective c with clang format --- .../Classes/FlutterLocalNotificationsPlugin.m | 161 ++++++++++-------- 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 094532187..757daa64a 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -18,7 +18,8 @@ @implementation FlutterLocalNotificationsPlugin { static FlutterPluginRegistrantCallback registerPlugins; static ActionEventSink *actionEventSink; -NSString *const FOREGROUND_ACTION_IDENTIFIERS = @"dexterous.com/flutter/local_notifications/foreground_action_identifiers"; +NSString *const FOREGROUND_ACTION_IDENTIFIERS = + @"dexterous.com/flutter/local_notifications/foreground_action_identifiers"; NSString *const INITIALIZE_METHOD = @"initialize"; NSString *const GET_CALLBACK_METHOD = @"getCallbackHandle"; NSString *const SHOW_METHOD = @"show"; @@ -90,7 +91,6 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const ACTION_ID = @"actionId"; NSString *const NOTIFICATION_RESPONSE_TYPE = @"notificationResponseType"; - 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"; @@ -182,9 +182,12 @@ - (void)handleMethodCall:(FlutterMethodCall *)call } else if ([GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD isEqualToString:call.method]) { - NSMutableDictionary *notificationAppLaunchDetails = [[NSMutableDictionary alloc] init]; - notificationAppLaunchDetails[NOTIFICATION_LAUNCHED_APP] = [NSNumber numberWithBool:_launchingAppFromNotification]; - notificationAppLaunchDetails[@"notificationResponse"] = _launchNotificationResponseDict; + NSMutableDictionary *notificationAppLaunchDetails = + [[NSMutableDictionary alloc] init]; + notificationAppLaunchDetails[NOTIFICATION_LAUNCHED_APP] = + [NSNumber numberWithBool:_launchingAppFromNotification]; + notificationAppLaunchDetails[@"notificationResponse"] = + _launchNotificationResponseDict; result(notificationAppLaunchDetails); } else if ([PENDING_NOTIFICATIONS_REQUESTS_METHOD isEqualToString:call.method]) { @@ -302,7 +305,8 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments [NSMutableSet set]; NSArray *categories = arguments[@"notificationCategories"]; - NSMutableArray *foregroundActionIdentifiers = [[NSMutableArray alloc] init]; + NSMutableArray *foregroundActionIdentifiers = + [[NSMutableArray alloc] init]; for (NSDictionary *category in categories) { NSMutableArray *newActions = @@ -316,10 +320,10 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments UNNotificationActionOptions options = [Converters parseNotificationActionOptions:action[@"options"]]; - if((options & UNNotificationActionOptionForeground) != 0) { - [foregroundActionIdentifiers addObject:identifier]; - } - + if ((options & UNNotificationActionOptionForeground) != 0) { + [foregroundActionIdentifiers addObject:identifier]; + } + if ([type isEqualToString:@"plain"]) { [newActions addObject:[UNNotificationAction actionWithIdentifier:identifier @@ -352,13 +356,15 @@ - (void)configureNotificationCategories:(NSDictionary *_Nonnull)arguments [UNUserNotificationCenter currentNotificationCenter]; [center getNotificationCategoriesWithCompletionHandler:^( NSSet *_Nonnull existing) { - if (existing) { - [center setNotificationCategories: - [existing setByAddingObjectsFromSet:newCategories]]; - } else { - [center setNotificationCategories:newCategories]; - } - [[NSUserDefaults standardUserDefaults] setObject:foregroundActionIdentifiers forKey:FOREGROUND_ACTION_IDENTIFIERS]; + if (existing) { + [center setNotificationCategories: + [existing setByAddingObjectsFromSet:newCategories]]; + } else { + [center setNotificationCategories:newCategories]; + } + [[NSUserDefaults standardUserDefaults] + setObject:foregroundActionIdentifiers + forKey:FOREGROUND_ACTION_IDENTIFIERS]; completionHandler(); }]; } else { @@ -1046,14 +1052,15 @@ - (BOOL)isAFlutterLocalNotification:(NSDictionary *)userInfo { userInfo[PRESENT_BADGE] && userInfo[PAYLOAD]; } -- (void)handleSelectNotification:(NSInteger )notificationId +- (void)handleSelectNotification:(NSInteger)notificationId payload:(NSString *)payload { - NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; - NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; - arguments[@"notificationId"] = notificationIdNumber; - arguments[PAYLOAD] = payload; - arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; - [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:arguments]; + NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; + arguments[@"notificationId"] = notificationIdNumber; + arguments[PAYLOAD] = payload; + arguments[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; + [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" + arguments:arguments]; } - (BOOL)containsKey:(NSString *)key forDictionary:(NSDictionary *)dictionary { @@ -1092,26 +1099,34 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center completionHandler(presentationOptions); } -- (NSMutableDictionary *)extractNotificationResponseDict:(UNNotificationResponse * _Nonnull)response NS_AVAILABLE_IOS(10.0){ - NSMutableDictionary *notitificationResponseDict = [[NSMutableDictionary alloc] init]; - NSInteger notificationId = +- (NSMutableDictionary *)extractNotificationResponseDict: + (UNNotificationResponse *_Nonnull)response NS_AVAILABLE_IOS(10.0) { + NSMutableDictionary *notitificationResponseDict = + [[NSMutableDictionary alloc] init]; + NSInteger notificationId = [response.notification.request.identifier integerValue]; - NSString *payload = - (NSString *)response.notification.request.content.userInfo[PAYLOAD]; - NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; - notitificationResponseDict[@"notificationId"] = notificationIdNumber; - notitificationResponseDict[PAYLOAD] = payload; - if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { - notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; - } else if (response.actionIdentifier != nil && ![response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { - notitificationResponseDict[ACTION_ID] = response.actionIdentifier; - notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:1]; - } + NSString *payload = + (NSString *)response.notification.request.content.userInfo[PAYLOAD]; + NSNumber *notificationIdNumber = [NSNumber numberWithInteger:notificationId]; + notitificationResponseDict[@"notificationId"] = notificationIdNumber; + notitificationResponseDict[PAYLOAD] = payload; + if ([response.actionIdentifier + isEqualToString:UNNotificationDefaultActionIdentifier]) { + notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = + [NSNumber numberWithInteger:0]; + } else if (response.actionIdentifier != nil && + ![response.actionIdentifier + isEqualToString:UNNotificationDismissActionIdentifier]) { + notitificationResponseDict[ACTION_ID] = response.actionIdentifier; + notitificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = + [NSNumber numberWithInteger:1]; + } - if ([response respondsToSelector:@selector(userText)]) { - notitificationResponseDict[@"input"] = [(UNTextInputNotificationResponse *)response userText]; - } - return notitificationResponseDict; + if ([response respondsToSelector:@selector(userText)]) { + notitificationResponseDict[@"input"] = + [(UNTextInputNotificationResponse *)response userText]; + } + return notitificationResponseDict; } - (void)userNotificationCenter:(UNUserNotificationCenter *)center @@ -1123,39 +1138,44 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center return; } - NSInteger notificationId = + NSInteger notificationId = [response.notification.request.identifier integerValue]; - NSString *payload = - (NSString *)response.notification.request.content.userInfo[PAYLOAD]; - + NSString *payload = + (NSString *)response.notification.request.content.userInfo[PAYLOAD]; + if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { if (_initialized) { - [self handleSelectNotification:notificationId payload:payload]; + [self handleSelectNotification:notificationId payload:payload]; } else { - _launchNotificationResponseDict = [self extractNotificationResponseDict:response]; + _launchNotificationResponseDict = + [self extractNotificationResponseDict:response]; _launchingAppFromNotification = true; } completionHandler(); - } - else if (response.actionIdentifier != nil) { - NSMutableDictionary *notificationResponseDict = [self extractNotificationResponseDict:response]; - NSArray *foregroundActionIdentifiers = [[NSUserDefaults standardUserDefaults] stringArrayForKey:FOREGROUND_ACTION_IDENTIFIERS]; - if ([foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != NSNotFound) { - if (_initialized) { - [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" arguments:notificationResponseDict]; - } else { - _launchNotificationResponseDict = notificationResponseDict; - _launchingAppFromNotification = true; - } + } else if (response.actionIdentifier != nil) { + NSMutableDictionary *notificationResponseDict = + [self extractNotificationResponseDict:response]; + NSArray *foregroundActionIdentifiers = + [[NSUserDefaults standardUserDefaults] + stringArrayForKey:FOREGROUND_ACTION_IDENTIFIERS]; + if ([foregroundActionIdentifiers indexOfObject:response.actionIdentifier] != + NSNotFound) { + if (_initialized) { + [_channel invokeMethod:@"didReceiveForegroundNotificationResponse" + arguments:notificationResponseDict]; } else { - if (!actionEventSink) { - actionEventSink = [[ActionEventSink alloc] init]; - } + _launchNotificationResponseDict = notificationResponseDict; + _launchingAppFromNotification = true; + } + } else { + if (!actionEventSink) { + actionEventSink = [[ActionEventSink alloc] init]; + } - [actionEventSink addItem:notificationResponseDict]; - [_flutterEngineManager startEngineIfNeeded:actionEventSink - registerPlugins:registerPlugins]; + [actionEventSink addItem:notificationResponseDict]; + [_flutterEngineManager startEngineIfNeeded:actionEventSink + registerPlugins:registerPlugins]; } completionHandler(); @@ -1173,10 +1193,13 @@ - (BOOL)application:(UIApplication *)application launchNotification != nil && [self isAFlutterLocalNotification:launchNotification.userInfo]; if (_launchingAppFromNotification) { - _launchNotificationResponseDict = [[NSMutableDictionary alloc] init]; - _launchNotificationResponseDict[@"notificationId"] = launchNotification.userInfo[NOTIFICATION_ID]; - _launchNotificationResponseDict[PAYLOAD] = launchNotification.userInfo[PAYLOAD]; - _launchNotificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = [NSNumber numberWithInteger:0]; + _launchNotificationResponseDict = [[NSMutableDictionary alloc] init]; + _launchNotificationResponseDict[@"notificationId"] = + launchNotification.userInfo[NOTIFICATION_ID]; + _launchNotificationResponseDict[PAYLOAD] = + launchNotification.userInfo[PAYLOAD]; + _launchNotificationResponseDict[NOTIFICATION_RESPONSE_TYPE] = + [NSNumber numberWithInteger:0]; } }