TobiasBuchholz / Plugin.Firebase

Wrapper around the native Android and iOS Firebase Xamarin SDKs
MIT License
211 stars 49 forks source link

UserNotificationActions #191

Open Gekidoku opened 1 year ago

Gekidoku commented 1 year ago

Previously i used https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/Plugin.FirebasePushNotification This had a feature that allowed you to add NotificationActions to your notifications. Like a Yes / No Button on the notification that did different things when you clicked on them.

Would it be possible to add that to this library?

What you did is you setup NotificationUserCategories in your MainApplication.

FirebasePushNotificationManager.Initialize(Xamarin.Essentials.Platform.AppContext, new NotificationUserCategory[] 
            { 
                new NotificationUserCategory("StillAlive", new List<NotificationUserAction>
                {
                    new NotificationUserAction("Yes","Yes", NotificationActionType.Foreground),
                    new NotificationUserAction("No","No", NotificationActionType.Foreground)

                })
            },false,false,true);

and then added a key 'click_action' to your notifications with the value being what you setup. In this case StillAlive.

Then they have the android and ios handlers add actions Android example. the ios one is in the github i linked

if (parameters.TryGetValue(ActionKey, out var actionContent))
            {
                category = actionContent.ToString();
            }

            var notificationCategories = CrossFirebasePushNotification.Current?.GetUserNotificationCategories();
            if (notificationCategories != null && notificationCategories.Length > 0)
            {
                foreach (var userCat in notificationCategories)
                {
                    if (userCat != null && userCat.Actions != null && userCat.Actions.Count > 0)
                    {
                        foreach (var action in userCat.Actions)
                        {
                            var aRequestCode = Guid.NewGuid().GetHashCode();

                            if (userCat.Category.Equals(category, StringComparison.CurrentCultureIgnoreCase))
                            {
                                Intent actionIntent = null;
                                PendingIntent pendingActionIntent = null;

                                if (action.Type == NotificationActionType.Foreground)
                                {
                                    actionIntent = typeof(Activity).IsAssignableFrom(FirebasePushNotificationManager.NotificationActivityType) ? new Intent(Application.Context, FirebasePushNotificationManager.NotificationActivityType) : (FirebasePushNotificationManager.DefaultNotificationActivityType == null ? context.PackageManager.GetLaunchIntentForPackage(context.PackageName) : new Intent(Application.Context, FirebasePushNotificationManager.DefaultNotificationActivityType));

                                    if (FirebasePushNotificationManager.NotificationActivityFlags != null)
                                    {
                                        actionIntent.SetFlags(FirebasePushNotificationManager.NotificationActivityFlags.Value);
                                    }

                                    extras.PutString(ActionIdentifierKey, action.Id);
                                    actionIntent.PutExtras(extras);
                                    pendingActionIntent = PendingIntent.GetActivity(context, aRequestCode, actionIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

                                }
                                else
                                {
                                    actionIntent = new Intent(context, typeof(PushNotificationActionReceiver));
                                    extras.PutString(ActionIdentifierKey, action.Id);
                                    actionIntent.PutExtras(extras);
                                    pendingActionIntent = PendingIntent.GetBroadcast(context, aRequestCode, actionIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

                                }

                                notificationBuilder.AddAction(new NotificationCompat.Action.Builder(context.Resources.GetIdentifier(action.Icon, "drawable", Application.Context.PackageName), action.Title, pendingActionIntent).Build());
                            }

                        }
                    }
                }

            }

They also had an event called OnNotificationAction. And while processing the intent they would check for these actions als extra's in the intent

public static void ProcessIntent(Activity activity, Intent intent, bool enableDelayedResponse = true)
        {
            DefaultNotificationActivityType = activity.GetType();
            var extras = intent?.Extras;
            if (extras != null && !extras.IsEmpty)
            {
                var parameters = new Dictionary<string, object>();
                foreach (var key in extras.KeySet())
                {
                    if (!parameters.ContainsKey(key) && extras.Get(key) != null)
                    {
                        parameters.Add(key, $"{extras.Get(key)}");
                    }
                }

                if (parameters.Count > 0)
                {
                    var manager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;
                    var notificationId = extras.GetInt(DefaultPushNotificationHandler.ActionNotificationIdKey, -1);
                    if (notificationId != -1)
                    {
                        var notificationTag = extras.GetString(DefaultPushNotificationHandler.ActionNotificationTagKey, string.Empty);
                        if (notificationTag == null)
                        {
                            manager.Cancel(notificationId);
                        }
                        else
                        {
                            manager.Cancel(notificationTag, notificationId);
                        }
                    }

                    var response = new NotificationResponse(parameters, extras.GetString(DefaultPushNotificationHandler.ActionIdentifierKey, string.Empty));

                    if (string.IsNullOrEmpty(response.Identifier))
                    {
                        if (_onNotificationOpened == null && enableDelayedResponse)
                        {
                            delayedNotificationResponse = response;
                        }
                        else
                        {
                            _onNotificationOpened?.Invoke(CrossFirebasePushNotification.Current, new FirebasePushNotificationResponseEventArgs(response.Data, response.Identifier, response.Type));
                        }
                    }
                    else
                    {
                        if (_onNotificationAction == null && enableDelayedResponse)
                        {
                            delayedNotificationResponse = response;
                        }
                        else
                        {
                            _onNotificationAction?.Invoke(CrossFirebasePushNotification.Current, new FirebasePushNotificationResponseEventArgs(response.Data, response.Identifier, response.Type));
                        }
                    }

                    CrossFirebasePushNotification.Current.NotificationHandler?.OnOpened(response);
                }

            }
        }

and launch OnNotificationAction in addition to onopened (tapped in your case) so you could trigger code based on what you clicked.

CrossFirebaseCloudMessaging.Current.OnNotificationAction += async (s, p) =>
            {
                if (!string.IsNullOrEmpty(p.Identifier))
                {
                    if (p.Identifier == "Yes")
                    {
                        //DoYes

                    }
                    else if (p.Identifier == "No")
                    {
                        //DoNo
                   }
            }
}

Think this would make a great addition.

Gekidoku commented 1 year ago

Think this could be added to HandleShowLocalNotification. Would need to add NotificationUserCategory as a class. add a check for click_action. then check if you have the NotificationUserCategory. and add them as extra's on the android version.

Seems that on the ios version they do it in their version of RegisterForRemoteNotifications. by doing this.

public static void RegisterUserNotificationCategories(NotificationUserCategory[] userCategories)
        {
            if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
            {
                if (userCategories != null && userCategories.Length > 0)
                {
                    usernNotificationCategories.Clear();
                    IList<UNNotificationCategory> categories = new List<UNNotificationCategory>();
                    foreach (var userCat in userCategories)
                    {
                        IList<UNNotificationAction> actions = new List<UNNotificationAction>();

                        foreach (var action in userCat.Actions)
                        {

                            // Create action
                            var actionID = action.Id;
                            var title = action.Title;
                            var notificationActionType = UNNotificationActionOptions.None;
                            switch (action.Type)
                            {
                                case NotificationActionType.AuthenticationRequired:
                                    notificationActionType = UNNotificationActionOptions.AuthenticationRequired;
                                    break;
                                case NotificationActionType.Destructive:
                                    notificationActionType = UNNotificationActionOptions.Destructive;
                                    break;
                                case NotificationActionType.Foreground:
                                    notificationActionType = UNNotificationActionOptions.Foreground;
                                    break;

                            }

                            var notificationAction = UNNotificationAction.FromIdentifier(actionID, title, notificationActionType);

                            actions.Add(notificationAction);

                        }

                        // Create category
                        var categoryID = userCat.Category;
                        var notificationActions = actions.ToArray() ?? new UNNotificationAction[] { };
                        var intentIDs = new string[] { };
                        var categoryOptions = new UNNotificationCategoryOptions[] { };

                        var category = UNNotificationCategory.FromIdentifier(categoryID, notificationActions, intentIDs, userCat.Type == NotificationCategoryType.Dismiss ? UNNotificationCategoryOptions.CustomDismissAction : UNNotificationCategoryOptions.None);

                        categories.Add(category);

                        usernNotificationCategories.Add(userCat);

                    }

                    // Register categories
                    UNUserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(categories.ToArray()));

                }
            }

        }

and as far as i can see doing this is all that is needed for the ios part.

Gekidoku commented 1 year ago

My bad they also had this on ios in their manager

public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
        {

            var parameters = GetParameters(response.Notification.Request.Content.UserInfo);

            var catType = NotificationCategoryType.Default;
            if (response.IsCustomAction)
            {
                catType = NotificationCategoryType.Custom;
            }
            else if (response.IsDismissAction)
            {
                catType = NotificationCategoryType.Dismiss;
            }

            var ident = $"{response.ActionIdentifier}".Equals("com.apple.UNNotificationDefaultActionIdentifier", StringComparison.CurrentCultureIgnoreCase) ? string.Empty : $"{response.ActionIdentifier}";
            var notificationResponse = new NotificationResponse(parameters, ident, catType);

            if (string.IsNullOrEmpty(ident))
            {
                _onNotificationOpened?.Invoke(this, new FirebasePushNotificationResponseEventArgs(notificationResponse.Data, notificationResponse.Identifier, notificationResponse.Type));

                CrossFirebasePushNotification.Current.NotificationHandler?.OnOpened(notificationResponse);
            }
            else
            {
                _onNotificationAction?.Invoke(this, new FirebasePushNotificationResponseEventArgs(notificationResponse.Data, notificationResponse.Identifier, notificationResponse.Type));

                // CrossFirebasePushNotification.Current.NotificationHandler?.OnOpened(notificationResponse);
            }

            // Inform caller it has been handled
            completionHandler();
        }
TobiasBuchholz commented 1 year ago

Hi @Gekidoku, sounds good! You are very welcome to create a pull request for that feature :)

Gekidoku commented 1 year ago

Started on this. So far the first "major" alteration to the setup is that OnNewIntent needs to recieve the MainActivity as an Activity. since the action needs to refer to the MainActivity. So setup for the onnewIntent will be FirebaseCloudMessagingImplementation.OnNewIntent(this, intent); in your MainActivity.cs

Gekidoku commented 1 year ago

After working on it a bit i couldn't figure out how to connect the plugin to my own app to test the code. Tried copying how you did it with the playground but it didn't work.

So i decided to start sort of from scratch since i only need firebase for the cloud messaging. So i pulled a fork off https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/Plugin.FirebasePushNotification

and started my journey Its now here https://github.com/Gekidoku/BetterFireBaseNotificationsPlugin So far i've tested:

still to test:

angelru commented 1 year ago

Interested in this functionality to be able to open links with a button from a push notification

andyzukunft commented 11 months ago

@Gekidoku I don't follow you. You created a new plugin because you weren't able to test? Is that correct? In my opinion it would be best to integrate the feature into this one, so most users can benefit from your feature. Maybe we can figure out how this can be done?

Gekidoku commented 11 months ago

That was partly the reason. The other reason was that I didn't need all the other firebase stuff. You're free to see how I added usercategories in my code and add it here. Im currently pretty swamped so dont have time for that myself