diff --git a/src/Shiny.Push/PushAccessState.cs b/src/Shiny.Push/Models.cs similarity index 60% rename from src/Shiny.Push/PushAccessState.cs rename to src/Shiny.Push/Models.cs index 3376195ef..676a029dd 100644 --- a/src/Shiny.Push/PushAccessState.cs +++ b/src/Shiny.Push/Models.cs @@ -1,4 +1,6 @@ -namespace Shiny.Push; +using System.Collections.Generic; + +namespace Shiny.Push; public record PushAccessState( AccessState Status, @@ -13,3 +15,13 @@ public void Assert() throw new PermissionException("Push registration fail", this.Status); } } + +public record PushNotification( + IDictionary Data, + Notification? Notification +); + +public record Notification( + string? Title, + string? Message +); \ No newline at end of file diff --git a/src/Shiny.Push/Notification.cs b/src/Shiny.Push/Notification.cs deleted file mode 100644 index c45c2edf5..000000000 --- a/src/Shiny.Push/Notification.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Shiny.Push; - -public record Notification( - string? Title, - string? Message -); \ No newline at end of file diff --git a/src/Shiny.Push/Platforms/Android/PushManager.cs b/src/Shiny.Push/Platforms/Android/PushManager.cs index e5563586d..a8f4e02f6 100644 --- a/src/Shiny.Push/Platforms/Android/PushManager.cs +++ b/src/Shiny.Push/Platforms/Android/PushManager.cs @@ -17,37 +17,25 @@ namespace Shiny.Push; -public class PushManager : NotifyPropertyChanged, - IPushManager, - IShinyStartupTask, - IAndroidLifecycle.IOnActivityOnCreate, - IAndroidLifecycle.IOnActivityNewIntent +public class PushManager( + AndroidPlatform platform, + FirebaseConfig config, + IServiceProvider services, + ILogger logger, + IPushProvider? provider = null +) : NotifyPropertyChanged, + IPushManager, + IShinyStartupTask, + IAndroidLifecycle.IOnActivityOnCreate, + IAndroidLifecycle.IOnActivityNewIntent { - readonly AndroidPlatform platform; - readonly IServiceProvider services; - readonly FirebaseConfig config; - readonly ILogger logger; - readonly IPushProvider provider; + IPushProvider useProvider = null!; bool registrationRequest = false; - public PushManager( - AndroidPlatform platform, - FirebaseConfig config, - IServiceProvider services, - ILogger logger, - IPushProvider? provider = null - ) - { - this.platform = platform; - this.config = config; - this.services = services; - this.logger = logger; - this.provider = provider ?? new FirebasePushProvider(); - } - public async void Start() { + this.useProvider = provider ?? new FirebasePushProvider(); this.TryCreateConfiguredChannel(); if (this.RegistrationToken.IsEmpty()) return; @@ -55,41 +43,41 @@ public async void Start() try { this.NativeRegistrationToken = await this.RequestNativeToken(); - var regToken = await this.provider.Register(this.NativeRegistrationToken); // never null on firebase + var regToken = await this.useProvider.Register(this.NativeRegistrationToken); // never null on firebase if (regToken != this.RegistrationToken) { this.RegistrationToken = regToken; - await this.services + await services .RunDelegates( x => x.OnNewToken(regToken), - this.logger + logger ) .ConfigureAwait(false); } } catch (Exception ex) { - this.logger.LogWarning(ex, "There was an error restarting push services"); + logger.LogWarning(ex, "There was an error restarting push services"); } } void TryCreateConfiguredChannel() { - if (this.config.DefaultChannel == null) + if (config.DefaultChannel == null) return; - using var nativeManager = this.platform.GetSystemService(Context.NotificationService); - var channel = nativeManager.GetNotificationChannel(this.config.DefaultChannel.Id); + using var nativeManager = platform.GetSystemService(Context.NotificationService); + var channel = nativeManager.GetNotificationChannel(config.DefaultChannel.Id); if (channel != null) nativeManager.DeleteNotificationChannel(channel.Id); - nativeManager.CreateNotificationChannel(this.config.DefaultChannel); + nativeManager.CreateNotificationChannel(config.DefaultChannel); } - public IPushTagSupport? Tags => (this.provider as IPushTagSupport); + public IPushTagSupport? Tags => this.useProvider as IPushTagSupport; string? regToken; public string? RegistrationToken @@ -115,7 +103,7 @@ public async Task RequestAccess(CancellationToken cancelToken = // TODO: verify google signed in if (OperatingSystem.IsAndroidVersionAtLeast(33)) { - var access = await this.platform + var access = await platform .RequestAccess(Manifest.Permission.PostNotifications) .ToTask(cancelToken); @@ -124,14 +112,14 @@ public async Task RequestAccess(CancellationToken cancelToken = } var nativeToken = await this.RequestNativeToken(); - var regToken = await this.provider.Register(nativeToken); // never null on firebase + var regToken = await this.useProvider.Register(nativeToken); // never null on firebase if (regToken != null && this.RegistrationToken != regToken) { - await this.services + await services .RunDelegates( x => x.OnNewToken(regToken), - this.logger + logger ) .ConfigureAwait(false); } @@ -152,7 +140,7 @@ public async Task UnRegister() if (this.RegistrationToken == null) return; - await this.provider + await this.useProvider .UnRegister() .ConfigureAwait(false); @@ -162,10 +150,10 @@ await FirebaseMessaging .AsAsync() .ConfigureAwait(false); - await this.services + await services .RunDelegates( x => x.OnUnRegistered(this.RegistrationToken!), - this.logger + logger ) .ConfigureAwait(false); @@ -179,12 +167,12 @@ public void ActivityOnCreate(Activity activity, Bundle? savedInstanceState) public void Handle(Activity activity, Intent? intent) { - var intentAction = this.config.IntentAction ?? ShinyPushIntents.NotificationClickAction; + var intentAction = config.IntentAction ?? ShinyPushIntents.NotificationClickAction; var clickAction = intent?.Action?.Equals(intentAction, StringComparison.InvariantCultureIgnoreCase) ?? false; if (!clickAction) return; - this.logger.LogDebug("Detected incoming remote notification intent"); + logger.LogDebug("Detected incoming remote notification intent"); var dict = new Dictionary(); if (intent!.Extras != null) @@ -198,13 +186,13 @@ public void Handle(Activity activity, Intent? intent) } // can I extract the notification here? var data = new PushNotification(dict, null); - this.services + services .RunDelegates( x => x.OnEntry(data), - this.logger + logger ) .ContinueWith(x => - this.logger.LogInformation("Finished executing push delegates") + logger.LogInformation("Finished executing push delegates") ); } @@ -227,22 +215,22 @@ void DoInit() if (!this.IsFirebaseAappAlreadyInitialized()) { - if (this.config.UseEmbeddedConfiguration) + if (config.UseEmbeddedConfiguration) { - FirebaseApp.InitializeApp(this.platform.AppContext); + FirebaseApp.InitializeApp(platform.AppContext); if (FirebaseApp.Instance == null) throw new InvalidOperationException("Firebase did not initialize. Ensure your google.services.json is property setup. Install the nuget package `Xamarin.GooglePlayServices.Tasks` into your Android head project, restart visual studio, and then set your google-services.json to GoogleServicesJson"); } else { var options = new FirebaseOptions.Builder() - .SetApplicationId(this.config.AppId) - .SetProjectId(this.config.ProjectId) - .SetApiKey(this.config.ApiKey) - .SetGcmSenderId(this.config.SenderId) - .Build(); + .SetApplicationId(config.AppId) + .SetProjectId(config.ProjectId) + .SetApiKey(config.ApiKey) + .SetGcmSenderId(config.SenderId) + .Build(); - FirebaseApp.InitializeApp(this.platform.AppContext, options); + FirebaseApp.InitializeApp(platform.AppContext, options); } } @@ -252,12 +240,12 @@ void DoInit() return; this.NativeRegistrationToken = token; - this.RegistrationToken = await this.provider.Register(token); + this.RegistrationToken = await provider.Register(token); - await this.services + await services .RunDelegates( x => x.OnNewToken(this.RegistrationToken), - this.logger + logger ) .ConfigureAwait(false); }; @@ -279,19 +267,19 @@ await this.services var push = new AndroidPushNotification( notification, msg, - this.config, - this.platform + config, + platform ); - await this.services + await services .RunDelegates( x => x.OnReceived(push), - this.logger + logger ) .ConfigureAwait(false); } catch (Exception ex) { - this.logger.LogError(ex, "Failed to receive firebase message"); + logger.LogError(ex, "Failed to receive firebase message"); } }; @@ -302,7 +290,7 @@ await this.services bool IsFirebaseAappAlreadyInitialized() { var isAppInitialized = false; - var firebaseApps = FirebaseApp.GetApps(this.platform.AppContext); + var firebaseApps = FirebaseApp.GetApps(platform.AppContext); foreach (var app in firebaseApps) { if (string.Equals(app.Name, FirebaseApp.DefaultAppName)) diff --git a/src/Shiny.Push/Platforms/Apple/PushManager.cs b/src/Shiny.Push/Platforms/Apple/PushManager.cs index 0c190ded9..1ba4fb5b5 100644 --- a/src/Shiny.Push/Platforms/Apple/PushManager.cs +++ b/src/Shiny.Push/Platforms/Apple/PushManager.cs @@ -13,38 +13,24 @@ namespace Shiny.Push; -public class PushManager : NotifyPropertyChanged, - IApplePushManager, - IIosLifecycle.IOnFinishedLaunching, - IIosLifecycle.IRemoteNotifications, - IIosLifecycle.INotificationHandler +public class PushManager( + IServiceProvider services, + IPlatform platform, + ILogger logger, + IPushProvider? provider = null +) : NotifyPropertyChanged, + IApplePushManager, + IIosLifecycle.IOnFinishedLaunching, + IIosLifecycle.IRemoteNotifications, + IIosLifecycle.INotificationHandler { static readonly NSString apsKey = new NSString("aps"); static readonly NSString alertKey = new NSString("alert"); - readonly IServiceProvider services; - readonly IPlatform platform; - readonly ILogger logger; - readonly IPushProvider? provider; - TaskCompletionSource? tokenSource; - public PushManager( - IServiceProvider services, - IPlatform platform, - ILogger logger, - IPushProvider? provider = null - ) - { - this.services = services; - this.platform = platform; - this.logger = logger; - this.provider = provider; - } - - - public IPushTagSupport? Tags => (this.provider as IPushTagSupport); + public IPushTagSupport? Tags => provider as IPushTagSupport; string? registrationToken; @@ -89,16 +75,16 @@ public void Start() { if (x.Exception != null) { - this.logger.LogWarning(x.Exception, "Failed to auto start push"); + logger.LogWarning(x.Exception, "Failed to auto start push"); } else if (x.Result.Status != AccessState.Available) { // TODO: unregister delegate - this.logger.LogInformation("User has removed push notification access - " + x.Result.Status); + logger.LogInformation("User has removed push notification access - " + x.Result.Status); } else { - this.logger.LogInformation("PushManager still has user permissions"); + logger.LogInformation("PushManager still has user permissions"); } }); } @@ -117,15 +103,15 @@ public async Task RequestAccess(UNAuthorizationOptions options, var nativeToken = deviceToken.ToPushTokenString(); var regToken = nativeToken; - if (this.provider != null) - regToken = await this.provider.Register(deviceToken); + if (provider != null) + regToken = await provider.Register(deviceToken); if (regToken != null && this.RegistrationToken != regToken) { - await this.services + await services .RunDelegates( x => x.OnNewToken(regToken), - this.logger + logger ) .ConfigureAwait(false); } @@ -143,20 +129,20 @@ public Task RequestAccess(CancellationToken cancelToken = defau public async Task UnRegister() { - await this.platform + await platform .InvokeOnMainThreadAsync(UIApplication .SharedApplication .UnregisterForRemoteNotifications ) .ConfigureAwait(false); - if (this.provider != null) - await this.provider.UnRegister().ConfigureAwait(false); + if (provider != null) + await provider.UnRegister().ConfigureAwait(false); - await this.services + await services .RunDelegates( x => x.OnUnRegistered(this.RegistrationToken!), - this.logger + logger ) .ConfigureAwait(false); @@ -170,7 +156,7 @@ protected async Task RequestRawToken(CancellationToken cancelToken) this.tokenSource = new(); using var cancelSrc = cancelToken.Register(() => this.tokenSource.TrySetCanceled()); - await this.platform + await platform .InvokeOnMainThreadAsync( () => UIApplication .SharedApplication @@ -191,7 +177,7 @@ public void OnWillPresentNotification(UNNotification notification, Action { - var options = this.services + var options = services .GetServices() .Select(x => { @@ -201,13 +187,13 @@ public void OnWillPresentNotification(UNNotification notification, Action x != null); - this.platform.InvokeOnMainThread(() => + platform.InvokeOnMainThread(() => completionHandler.Invoke( options ?? UNNotificationPresentationOptions.List | @@ -226,19 +212,19 @@ public void OnDidReceiveNotificationResponse(UNNotificationResponse response, Ac if (response?.Notification?.Request?.Trigger is not UNPushNotificationTrigger push) return; - this.logger.LogDebug("OnDidReceiveNotificationResponse - Background remote notification entry detected"); + logger.LogDebug("OnDidReceiveNotificationResponse - Background remote notification entry detected"); var data = this.ToPushNotification(response.Notification); - this.services + services .RunDelegates( x => x.OnEntry(data), - this.logger + logger ) .ContinueWith(_ => { // This needs be invoked on MainThread, // otherwise iOS app crashes if we tap on push notification alert // from notification center, while App in Active state. - this.platform.InvokeOnMainThread(() => completionHandler.Invoke()); + platform.InvokeOnMainThread(() => completionHandler.Invoke()); }); } @@ -247,18 +233,18 @@ public void OnDidReceiveNotificationResponse(UNNotificationResponse response, Ac public void OnFailedToRegister(NSError error) => this.tokenSource?.TrySetException(new Exception(error.LocalizedDescription)); public void OnDidReceive(NSDictionary userInfo, Action completionHandler) { - this.logger.LogDebug("Incoming Background remote notification"); + logger.LogDebug("Incoming Background remote notification"); var dict = userInfo.FromNsDictionary(); var data = new PushNotification(dict, null); - this.services + services .RunDelegates( x => x.OnReceived(data), - this.logger + logger ) .ContinueWith(x => { - var fetchResult = this.services + var fetchResult = services .GetServices() .Select(x => { @@ -268,13 +254,13 @@ public void OnDidReceive(NSDictionary userInfo, Action } catch (Exception ex) { - this.logger.LogError(ex, $"Error executing ApplePushDelegate {x.GetType().FullName}.GetFetchResult"); + logger.LogError(ex, $"Error executing ApplePushDelegate {x.GetType().FullName}.GetFetchResult"); return null; } }) .FirstOrDefault(x => x != null); - this.platform.InvokeOnMainThread( + platform.InvokeOnMainThread( () => completionHandler.Invoke(fetchResult ?? UIBackgroundFetchResult.NewData) ); }); @@ -287,15 +273,15 @@ public void Handle(UIApplicationLaunchEventArgs args) if (args.RemoteNotifications == null) return; - this.logger.LogDebug("App entry remote notification detected"); + logger.LogDebug("App entry remote notification detected"); var notification = this.ToNotification(args.RemoteNotifications); var dict = args.RemoteNotifications.FromNsDictionary(); dict.Remove("aps"); var push = new PushNotification(dict ?? new Dictionary(0), notification); - this.services.RunDelegates( + services.RunDelegates( x => x.OnEntry(push), - this.logger + logger ); } @@ -306,13 +292,13 @@ protected virtual void TryProcessIncomingNotification(UNNotification? notificati if (notification?.Request?.Trigger is not UNPushNotificationTrigger push) return; - this.logger.LogDebug(logMessage); + logger.LogDebug(logMessage); var data = this.ToPushNotification(notification); - this.services + services .RunDelegates( x => x.OnReceived(data), - this.logger + logger ) .ContinueWith(_ => completionHandler.Invoke(data)); } diff --git a/src/Shiny.Push/PushNotification.cs b/src/Shiny.Push/PushNotification.cs deleted file mode 100644 index bc8d492a1..000000000 --- a/src/Shiny.Push/PushNotification.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Shiny.Push; - - -public record PushNotification( - IDictionary Data, - Notification? Notification -); \ No newline at end of file