From 038375168124d531b6cdf8892e820bbedec4b668 Mon Sep 17 00:00:00 2001 From: Jakub Florkowski Date: Sun, 15 Mar 2026 19:48:47 +0100 Subject: [PATCH] Add PostNotifications iOS/MacCatalyst implementation - Implement CheckStatusAsync using UNUserNotificationCenter.GetNotificationSettings - Implement RequestAsync using UNUserNotificationCenter.RequestAuthorizationAsync - Handle Provisional and Ephemeral authorization statuses as Granted - Check status before requesting to avoid unnecessary prompts - Add PublicAPI.Unshipped.txt entries for net-ios and net-maccatalyst - Remove #pragma warning disable RS0016 per review feedback - Add POST_NOTIFICATIONS permission to Android sample manifest - Add UIBackgroundModes to iOS sample Info.plist - Add device test for PostNotifications.CheckStatusAsync - Fix indentation consistency in sample files Fixes #23962 Co-authored-by: Ieuan Walker Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Platforms/Android/AndroidManifest.xml | 1 + .../samples/Samples/Platforms/iOS/Info.plist | 4 ++ .../src/Permissions/Permissions.ios.cs | 46 +++++++++++++++++++ .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 2 + .../net-maccatalyst/PublicAPI.Unshipped.txt | 2 + .../DeviceTests/Tests/Permissions_Tests.cs | 11 +++++ 6 files changed, 66 insertions(+) diff --git a/src/Essentials/samples/Samples/Platforms/Android/AndroidManifest.xml b/src/Essentials/samples/Samples/Platforms/Android/AndroidManifest.xml index 75eb405387d7..78eaefb7827c 100644 --- a/src/Essentials/samples/Samples/Platforms/Android/AndroidManifest.xml +++ b/src/Essentials/samples/Samples/Platforms/Android/AndroidManifest.xml @@ -11,6 +11,7 @@ + diff --git a/src/Essentials/samples/Samples/Platforms/iOS/Info.plist b/src/Essentials/samples/Samples/Platforms/iOS/Info.plist index 83bae2a68f1f..8baa6df9ac79 100644 --- a/src/Essentials/samples/Samples/Platforms/iOS/Info.plist +++ b/src/Essentials/samples/Samples/Platforms/iOS/Info.plist @@ -64,5 +64,9 @@ Just writing events to the Calendar, nothing to see here. NSRemindersFullAccessUsageDescription Just so you will remember + UIBackgroundModes + + remote-notification + diff --git a/src/Essentials/src/Permissions/Permissions.ios.cs b/src/Essentials/src/Permissions/Permissions.ios.cs index 2617b98a5263..09ea3dbe42ad 100644 --- a/src/Essentials/src/Permissions/Permissions.ios.cs +++ b/src/Essentials/src/Permissions/Permissions.ios.cs @@ -7,6 +7,7 @@ using CoreBluetooth; using MediaPlayer; using Speech; +using UserNotifications; namespace Microsoft.Maui.ApplicationModel { @@ -293,6 +294,51 @@ public override Task RequestAsync() } } + public partial class PostNotifications : BasePlatformPermission + { + /// + public override Task CheckStatusAsync() + { + var tcs = new TaskCompletionSource(); + + UNUserNotificationCenter.Current.GetNotificationSettings(settings => + { + switch (settings.AuthorizationStatus) + { + case UNAuthorizationStatus.Authorized: + case UNAuthorizationStatus.Provisional: + case UNAuthorizationStatus.Ephemeral: + tcs.TrySetResult(PermissionStatus.Granted); + break; + case UNAuthorizationStatus.Denied: + tcs.TrySetResult(PermissionStatus.Denied); + break; + default: + tcs.TrySetResult(PermissionStatus.Unknown); + break; + } + }); + + return tcs.Task; + } + + /// + public override async Task RequestAsync() + { + var status = await CheckStatusAsync(); + if (status == PermissionStatus.Granted) + return status; + + var (granted, error) = await UNUserNotificationCenter.Current.RequestAuthorizationAsync( + UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound); + + if (error is not null) + return PermissionStatus.Unknown; + + return granted ? PermissionStatus.Granted : PermissionStatus.Denied; + } + } + public partial class Speech : BasePlatformPermission { /// diff --git a/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index a6ea2f543be2..d694af4cdb7a 100644 --- a/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(Sys override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object? override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object? +~override Microsoft.Maui.ApplicationModel.Permissions.PostNotifications.CheckStatusAsync() -> System.Threading.Tasks.Task +~override Microsoft.Maui.ApplicationModel.Permissions.PostNotifications.RequestAsync() -> System.Threading.Tasks.Task diff --git a/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index a6ea2f543be2..d694af4cdb7a 100644 --- a/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(Sys override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object? override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object? +~override Microsoft.Maui.ApplicationModel.Permissions.PostNotifications.CheckStatusAsync() -> System.Threading.Tasks.Task +~override Microsoft.Maui.ApplicationModel.Permissions.PostNotifications.RequestAsync() -> System.Threading.Tasks.Task diff --git a/src/Essentials/test/DeviceTests/Tests/Permissions_Tests.cs b/src/Essentials/test/DeviceTests/Tests/Permissions_Tests.cs index da41c2eb4367..e01ab462722a 100644 --- a/src/Essentials/test/DeviceTests/Tests/Permissions_Tests.cs +++ b/src/Essentials/test/DeviceTests/Tests/Permissions_Tests.cs @@ -103,5 +103,16 @@ public async Task StorageAndroid13AlwaysGranted() Assert.Equal(PermissionStatus.Denied, status); } } + + [Fact] + public async Task PostNotifications_CheckStatus_ReturnsValidStatus() + { + var status = await Permissions.CheckStatusAsync(); + Assert.True( + status == PermissionStatus.Unknown || + status == PermissionStatus.Granted || + status == PermissionStatus.Denied, + $"Unexpected PostNotifications status: {status}"); + } } }