Skip to content

Commit

Permalink
Merge pull request #51 from thomasgalliker/subscribe-topic-after-restart
Browse files Browse the repository at this point in the history
Merge subscribe-topic-after-restart into develop
  • Loading branch information
thomasgalliker authored Aug 21, 2024
2 parents a5ae4ba + d891233 commit 2b775b1
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 115 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Foundation;

namespace Plugin.FirebasePushNotifications.Extensions
{
internal static class NSDictionaryExtensions
{
internal static IDictionary<string, object> GetParameters(this NSDictionary data)
{
var parameters = new Dictionary<string, object>();

var keyAps = new NSString("aps");
var keyAlert = new NSString("alert");

foreach (var val in data)
{
if (val.Key.Equals(keyAps))
{
if (data.ValueForKey(keyAps) is NSDictionary aps)
{
foreach (var apsVal in aps)
{
if (apsVal.Value is NSDictionary dictionaryValue)
{
if (apsVal.Key.Equals(keyAlert))
{
foreach (var alertVal in dictionaryValue)
{
parameters.Add($"aps.alert.{alertVal.Key}", $"{alertVal.Value}");
}
}
}
else
{
parameters.Add($"aps.{apsVal.Key}", $"{apsVal.Value}");
}

}
}
}
else
{
parameters.Add($"{val.Key}", $"{val.Value}");
}
}

return parameters;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Foundation;
using Microsoft.Extensions.Logging;
using Plugin.FirebasePushNotifications.Extensions;
using UIKit;
using UserNotifications;

Expand All @@ -12,8 +13,10 @@ namespace Plugin.FirebasePushNotifications.Platforms
[Preserve(AllMembers = true)]
public class FirebasePushNotificationManager : FirebasePushNotificationManagerBase, IFirebasePushNotification
{
private const UNAuthorizationOptions AuthorizationOptions =
UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;

private readonly Queue<(string Topic, bool Subscribe)> pendingTopics = new Queue<(string, bool)>();
private bool hasToken = false;
private bool disposed;

internal FirebasePushNotificationManager()
Expand All @@ -25,7 +28,7 @@ public string Token
{
get
{
var fcmToken = Firebase.CloudMessaging.Messaging.SharedInstance?.FcmToken;
var fcmToken = Firebase.CloudMessaging.Messaging.SharedInstance.FcmToken;
if (!string.IsNullOrEmpty(fcmToken))
{
return fcmToken;
Expand Down Expand Up @@ -141,19 +144,13 @@ public async Task RegisterForPushNotificationsAsync()

Firebase.CloudMessaging.Messaging.SharedInstance.AutoInitEnabled = true;


var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
var (granted, error) = await UNUserNotificationCenter.Current.RequestAuthorizationAsync(authOptions);
var (granted, error) = await UNUserNotificationCenter.Current.RequestAuthorizationAsync(AuthorizationOptions);
if (granted)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
UIApplication.SharedApplication.RegisterForRemoteNotifications();
});

// this.DidReceiveRegistrationToken(
// Firebase.CloudMessaging.Messaging.SharedInstance,
// Firebase.CloudMessaging.Messaging.SharedInstance.FcmToken);
}
else
{
Expand All @@ -168,15 +165,16 @@ await MainThread.InvokeOnMainThreadAsync(() =>
}
}

private static bool HasApnsToken => Firebase.CloudMessaging.Messaging.SharedInstance.ApnsToken != null;

/// <inheritdoc />
public async Task UnregisterForPushNotificationsAsync()
{
this.logger.LogDebug("UnregisterForPushNotificationsAsync");

if (this.hasToken)
if (HasApnsToken)
{
this.UnsubscribeAllTopics();
this.hasToken = false;
}

Firebase.CloudMessaging.Messaging.SharedInstance.AutoInitEnabled = false;
Expand Down Expand Up @@ -236,13 +234,13 @@ public void DidReceiveRemoteNotification(UIApplication application, NSDictionary
private void DidReceiveRemoteNotificationInternal(NSDictionary userInfo)
{
Firebase.CloudMessaging.Messaging.SharedInstance.AppDidReceiveMessage(userInfo);
var data = GetParameters(userInfo);
var data = userInfo.GetParameters();
this.HandleNotificationReceived(data);
}

private void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
var data = GetParameters(notification.Request.Content.UserInfo);
var data = notification.Request.Content.UserInfo.GetParameters();
var notificationPresentationOptions = GetNotificationPresentationOptions(data);
this.logger.LogDebug($"WillPresentNotification: UNNotificationPresentationOptions={notificationPresentationOptions}");

Expand Down Expand Up @@ -296,48 +294,6 @@ private static UNNotificationPresentationOptions GetNotificationPresentationOpti
return notificationPresentationOptions;
}

private static IDictionary<string, object> GetParameters(NSDictionary data)
{
var parameters = new Dictionary<string, object>();

var keyAps = new NSString("aps");
var keyAlert = new NSString("alert");

foreach (var val in data)
{
if (val.Key.Equals(keyAps))
{
if (data.ValueForKey(keyAps) is NSDictionary aps)
{
foreach (var apsVal in aps)
{
if (apsVal.Value is NSDictionary dictionaryValue)
{
if (apsVal.Key.Equals(keyAlert))
{
foreach (var alertVal in dictionaryValue)
{
parameters.Add($"aps.alert.{alertVal.Key}", $"{alertVal.Value}");
}
}
}
else
{
parameters.Add($"aps.{apsVal.Key}", $"{apsVal.Value}");
}

}
}
}
else
{
parameters.Add($"{val.Key}", $"{val.Value}");
}
}

return parameters;
}

/// <inheritdoc />
public void SubscribeTopics(string[] topics)
{
Expand All @@ -360,7 +316,7 @@ public void SubscribeTopic(string topic)
throw new ArgumentException("Topic must not be empty", nameof(topic));
}

if (!this.hasToken)
if (!HasApnsToken)
{
this.pendingTopics.Enqueue((topic, true));
return;
Expand All @@ -374,7 +330,6 @@ public void SubscribeTopic(string topic)
Firebase.CloudMessaging.Messaging.SharedInstance.Subscribe(topic);
subscribedTopics.Add(topic);

// TODO: Improve write performance here; don't loop all topics one by one
this.SubscribedTopics = subscribedTopics.ToArray();
}
else
Expand All @@ -386,8 +341,12 @@ public void SubscribeTopic(string topic)
/// <inheritdoc />
public void UnsubscribeAllTopics()
{
foreach (var topic in this.SubscribedTopics)
var topics = this.SubscribedTopics.ToArray();
this.logger.LogDebug($"UnsubscribeAllTopics: topics=[{string.Join(',', topics)}]");

foreach (var topic in topics)
{
this.logger.LogDebug($"Unsubscribe: topic=\"{topic}\"");
Firebase.CloudMessaging.Messaging.SharedInstance.Unsubscribe(topic);
}

Expand Down Expand Up @@ -422,7 +381,7 @@ public void UnsubscribeTopic(string topic)
throw new ArgumentException("Topic must not be empty", nameof(topic));
}

if (!this.hasToken)
if (!HasApnsToken)
{
this.pendingTopics.Enqueue((topic, false));
return;
Expand All @@ -436,7 +395,6 @@ public void UnsubscribeTopic(string topic)
Firebase.CloudMessaging.Messaging.SharedInstance.Unsubscribe(topic);
subscribedTopics.Remove(topic);

// TODO: Improve write performance here; don't loop all topics one by one
this.SubscribedTopics = subscribedTopics.ToArray();
}
else
Expand All @@ -449,7 +407,7 @@ private void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNN
{
this.logger.LogDebug("DidReceiveNotificationResponse");

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

NotificationCategoryType notificationCategoryType;

Expand Down Expand Up @@ -498,7 +456,15 @@ private void DidReceiveRegistrationToken(string fcmToken)

this.HandleTokenRefresh(fcmToken);

this.hasToken = true;
this.TryDequeuePendingTopics();
}

private void TryDequeuePendingTopics()
{
if (!HasApnsToken)
{
return;
}

while (this.pendingTopics.TryDequeue(out var pendingTopic))
{
Expand Down Expand Up @@ -530,19 +496,14 @@ public void RemoveNotification(string tag, int id)
public async void RemoveNotification(int id)
{
const string notificationIdKey = "id";
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
var deliveredNotifications = await UNUserNotificationCenter.Current.GetDeliveredNotificationsAsync();
var deliveredNotificationsMatches = deliveredNotifications.Where(u => $"{u.Request.Content.UserInfo[notificationIdKey]}".Equals($"{id}")).Select(s => s.Request.Identifier).ToArray();
if (deliveredNotificationsMatches.Length > 0)
{
UNUserNotificationCenter.Current.RemoveDeliveredNotifications(deliveredNotificationsMatches);

}
}
else
var deliveredNotifications = await UNUserNotificationCenter.Current.GetDeliveredNotificationsAsync();
var deliveredNotificationsMatches = deliveredNotifications
.Where(u => $"{u.Request.Content.UserInfo[notificationIdKey]}".Equals($"{id}"))
.Select(s => s.Request.Identifier)
.ToArray();
if (deliveredNotificationsMatches.Length > 0)
{
throw new NotSupportedException();
UNUserNotificationCenter.Current.RemoveDeliveredNotifications(deliveredNotificationsMatches);
}
}

Expand All @@ -568,4 +529,4 @@ protected override void Dispose(bool disposing)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
using Foundation;
using Microsoft.Extensions.Logging;
using Plugin.FirebasePushNotifications.Model;
using UserNotifications;

namespace Plugin.FirebasePushNotifications.Platforms
{
public class NotificationPermissions : INotificationPermissions
{
public NotificationPermissions()
private const UNAuthorizationOptions AuthorizationOptions =
UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;

private readonly ILogger logger;

public NotificationPermissions(ILogger<NotificationPermissions> logger)
{
this.logger = logger;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -35,14 +43,17 @@ public async Task<AuthorizationStatus> GetAuthorizationStatusAsync(string[] grou
}

/// <inheritdoc/>
public Task<bool> RequestPermissionAsync()
public async Task<bool> RequestPermissionAsync()
{
var tcs = new TaskCompletionSource<bool>();
UNUserNotificationCenter.Current.RequestAuthorization(
UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound,
(approved, error) => tcs.TrySetResult(approved)
);
return tcs.Task;
var (granted, error) = await UNUserNotificationCenter.Current.RequestAuthorizationAsync(AuthorizationOptions);
if (error != null)
{
var exception = new Exception("RequestPermissionAsync failed with exception", new NSErrorException(error));
this.logger.LogError(exception, exception.Message);
throw exception;
}

return granted;
}
}
}
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,4 @@ Contributors welcome! If you find a bug or you want to propose a new feature, fe
- FCM messages, data format, concepts and options: https://firebase.google.com/docs/cloud-messaging/concept-options
- Set up a Firebase Cloud Messaging client app on Apple platforms: https://firebase.google.com/docs/cloud-messaging/ios/client
- Set up a Firebase Cloud Messaging client app on Android: https://firebase.google.com/docs/cloud-messaging/android/client
- Expandable notification on Android: https://developer.android.com/develop/ui/views/notifications/expanded
-
- Expandable notification on Android: https://developer.android.com/develop/ui/views/notifications/expanded
2 changes: 1 addition & 1 deletion Samples/MauiSampleApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static MauiApp CreateMauiApp()
#if ANDROID
// o.Android.NotificationActivityType = typeof(MainActivity);
// o.Android.NotificationChannels = NotificationChannelSamples.GetAll().ToArray();
//o.Android.NotificationCategories = NotificationCategorySamples.GetAll().ToArray(); // TODO:
// o.Android.NotificationCategories = NotificationCategorySamples.GetAll().ToArray();
#endif
})
.ConfigureFonts(fonts =>
Expand Down
17 changes: 1 addition & 16 deletions Samples/MauiSampleApp/Resources/Styles/Styles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -407,23 +407,8 @@
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>

<Style
ApplyToDerivedTypes="True"
TargetType="Shell">
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.ForegroundColor" Value="{OnPlatform WinUI={StaticResource Primary}, Default={StaticResource White}}" />
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
<Setter Property="Shell.NavBarHasShadow" Value="False" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>

<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
</Style>
Expand Down
Loading

0 comments on commit 2b775b1

Please sign in to comment.