mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 19:08:32 +03:00
[SG-702] Tapping Push Notification does not open the account the request is for (#2112)
* [SG-702] Tap notification now switches accounts if it is a passwordless notification. * [SG-702] Fix compilation errors * [SG-702] Fixed iOS notification tap fix * [SG-702] Notification data model * [SG-702] Change method signature with object containing properties. PR fixes.
This commit is contained in:
parent
1e5eab0574
commit
abada481b7
11 changed files with 128 additions and 12 deletions
|
@ -20,6 +20,8 @@ using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using ZXing.Net.Mobile.Android;
|
using ZXing.Net.Mobile.Android;
|
||||||
using FileProvider = AndroidX.Core.Content.FileProvider;
|
using FileProvider = AndroidX.Core.Content.FileProvider;
|
||||||
|
@ -39,6 +41,7 @@ namespace Bit.Droid
|
||||||
private IStateService _stateService;
|
private IStateService _stateService;
|
||||||
private IAppIdService _appIdService;
|
private IAppIdService _appIdService;
|
||||||
private IEventService _eventService;
|
private IEventService _eventService;
|
||||||
|
private IPushNotificationListenerService _pushNotificationListenerService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private PendingIntent _eventUploadPendingIntent;
|
private PendingIntent _eventUploadPendingIntent;
|
||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
|
@ -61,6 +64,7 @@ namespace Bit.Droid
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
|
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
TabLayoutResource = Resource.Layout.Tabbar;
|
TabLayoutResource = Resource.Layout.Tabbar;
|
||||||
|
@ -145,6 +149,15 @@ namespace Bit.Droid
|
||||||
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
|
if (Intent?.GetStringExtra(Constants.NotificationData) is string notificationDataJson)
|
||||||
|
{
|
||||||
|
var notificationType = JToken.Parse(notificationDataJson).SelectToken(Constants.NotificationDataType);
|
||||||
|
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
|
||||||
|
{
|
||||||
|
_pushNotificationListenerService.OnNotificationTapped(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using AndroidX.Core.App;
|
using AndroidX.Core.App;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
|
@ -67,28 +70,34 @@ namespace Bit.Droid.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendLocalNotification(string title, string message, string notificationId)
|
public void SendLocalNotification(string title, string message, BaseNotificationData data)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(notificationId))
|
if (string.IsNullOrEmpty(data.Id))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = Android.App.Application.Context;
|
var context = Android.App.Application.Context;
|
||||||
var intent = new Intent(context, typeof(MainActivity));
|
var intent = new Intent(context, typeof(MainActivity));
|
||||||
|
intent.PutExtra(Constants.NotificationData, JsonConvert.SerializeObject(data));
|
||||||
|
|
||||||
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
||||||
var builder = new NotificationCompat.Builder(context, Constants.AndroidNotificationChannelId)
|
var builder = new NotificationCompat.Builder(context, Constants.AndroidNotificationChannelId)
|
||||||
.SetContentIntent(pendingIntent)
|
.SetContentIntent(pendingIntent)
|
||||||
.SetContentTitle(title)
|
.SetContentTitle(title)
|
||||||
.SetContentText(message)
|
.SetContentText(message)
|
||||||
.SetTimeoutAfter(Constants.PasswordlessNotificationTimeoutInMinutes * 60000)
|
|
||||||
.SetSmallIcon(Resource.Drawable.ic_notification)
|
.SetSmallIcon(Resource.Drawable.ic_notification)
|
||||||
.SetColor((int)Android.Graphics.Color.White)
|
.SetColor((int)Android.Graphics.Color.White)
|
||||||
.SetAutoCancel(true);
|
.SetAutoCancel(true);
|
||||||
|
|
||||||
|
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
|
||||||
|
{
|
||||||
|
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
|
||||||
|
}
|
||||||
|
|
||||||
var notificationManager = NotificationManagerCompat.From(context);
|
var notificationManager = NotificationManagerCompat.From(context);
|
||||||
notificationManager.Notify(int.Parse(notificationId), builder.Build());
|
notificationManager.Notify(int.Parse(data.Id), builder.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
|
@ -9,6 +10,7 @@ namespace Bit.App.Abstractions
|
||||||
Task OnRegisteredAsync(string token, string device);
|
Task OnRegisteredAsync(string token, string device);
|
||||||
void OnUnregistered(string device);
|
void OnUnregistered(string device);
|
||||||
void OnError(string message, string device);
|
void OnError(string message, string device);
|
||||||
|
Task OnNotificationTapped(BaseNotificationData data);
|
||||||
bool ShouldShowNotification();
|
bool ShouldShowNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
|
@ -10,7 +11,7 @@ namespace Bit.App.Abstractions
|
||||||
Task<string> GetTokenAsync();
|
Task<string> GetTokenAsync();
|
||||||
Task RegisterAsync();
|
Task RegisterAsync();
|
||||||
Task UnregisterAsync();
|
Task UnregisterAsync();
|
||||||
void SendLocalNotification(string title, string message, string notificationId);
|
void SendLocalNotification(string title, string message, BaseNotificationData data);
|
||||||
void DismissLocalNotification(string notificationId);
|
void DismissLocalNotification(string notificationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
src/App/Models/NotificationData.cs
Normal file
23
src/App/Models/NotificationData.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
namespace Bit.App.Models
|
||||||
|
{
|
||||||
|
public abstract class BaseNotificationData
|
||||||
|
{
|
||||||
|
public abstract string Type { get; }
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasswordlessNotificationData : BaseNotificationData
|
||||||
|
{
|
||||||
|
public const string TYPE = "passwordlessNotificationData";
|
||||||
|
|
||||||
|
public override string Type => TYPE;
|
||||||
|
|
||||||
|
public int TimeoutInMinutes { get; set; }
|
||||||
|
|
||||||
|
public string UserEmail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Bit.App.Services
|
namespace Bit.App.Services
|
||||||
|
@ -28,5 +29,10 @@ namespace Bit.App.Services
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task OnNotificationTapped(BaseNotificationData data)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
|
||||||
namespace Bit.App.Services
|
namespace Bit.App.Services
|
||||||
{
|
{
|
||||||
|
@ -30,6 +31,6 @@ namespace Bit.App.Services
|
||||||
|
|
||||||
public void DismissLocalNotification(string notificationId) { }
|
public void DismissLocalNotification(string notificationId) { }
|
||||||
|
|
||||||
public void SendLocalNotification(string title, string message, string notificationId) { }
|
public void SendLocalNotification(string title, string message, BaseNotificationData data) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
@ -11,6 +13,7 @@ using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
@ -30,6 +33,7 @@ namespace Bit.App.Services
|
||||||
private IApiService _apiService;
|
private IApiService _apiService;
|
||||||
private IMessagingService _messagingService;
|
private IMessagingService _messagingService;
|
||||||
private IPushNotificationService _pushNotificationService;
|
private IPushNotificationService _pushNotificationService;
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
public async Task OnMessageAsync(JObject value, string deviceType)
|
public async Task OnMessageAsync(JObject value, string deviceType)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +151,14 @@ namespace Bit.App.Services
|
||||||
await _stateService.SetPasswordlessLoginNotificationAsync(passwordlessLoginMessage, passwordlessLoginMessage?.UserId);
|
await _stateService.SetPasswordlessLoginNotificationAsync(passwordlessLoginMessage, passwordlessLoginMessage?.UserId);
|
||||||
var userEmail = await _stateService.GetEmailAsync(passwordlessLoginMessage?.UserId);
|
var userEmail = await _stateService.GetEmailAsync(passwordlessLoginMessage?.UserId);
|
||||||
|
|
||||||
_pushNotificationService.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), Constants.PasswordlessNotificationId);
|
var notificationData = new PasswordlessNotificationData()
|
||||||
|
{
|
||||||
|
Id = Constants.PasswordlessNotificationId,
|
||||||
|
TimeoutInMinutes = Constants.PasswordlessNotificationTimeoutInMinutes,
|
||||||
|
UserEmail = userEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
_pushNotificationService.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
|
||||||
_messagingService.Send("passwordlessLoginRequest", passwordlessLoginMessage);
|
_messagingService.Send("passwordlessLoginRequest", passwordlessLoginMessage);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -213,6 +224,27 @@ namespace Bit.App.Services
|
||||||
Debug.WriteLine($"{TAG} error - {message}");
|
Debug.WriteLine($"{TAG} error - {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task OnNotificationTapped(BaseNotificationData data)
|
||||||
|
{
|
||||||
|
Resolve();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (data is PasswordlessNotificationData passwordlessNotificationData)
|
||||||
|
{
|
||||||
|
var notificationUserId = await _stateService.GetUserIdAsync(passwordlessNotificationData.UserEmail);
|
||||||
|
if (notificationUserId != null)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(notificationUserId);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShouldShowNotification()
|
public bool ShouldShowNotification()
|
||||||
{
|
{
|
||||||
return _showNotification;
|
return _showNotification;
|
||||||
|
@ -230,6 +262,7 @@ namespace Bit.App.Services
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
_resolved = true;
|
_resolved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
public const string PasswordlessNotificationId = "26072022";
|
public const string PasswordlessNotificationId = "26072022";
|
||||||
public const string AndroidNotificationChannelId = "general_notification_channel";
|
public const string AndroidNotificationChannelId = "general_notification_channel";
|
||||||
|
public const string NotificationData = "notificationData";
|
||||||
|
public const string NotificationDataType = "NotificationType";
|
||||||
public const int SelectFileRequestCode = 42;
|
public const int SelectFileRequestCode = 42;
|
||||||
public const int SelectFilePermissionRequestCode = 43;
|
public const int SelectFilePermissionRequestCode = 43;
|
||||||
public const int SaveFileRequestCode = 44;
|
public const int SaveFileRequestCode = 44;
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
using CoreData;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using UserNotifications;
|
using UserNotifications;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
@ -92,9 +97,20 @@ namespace Bit.iOS.Services
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"{TAG} DidReceiveNotificationResponse {response?.Notification?.Request?.Content?.UserInfo}");
|
Debug.WriteLine($"{TAG} DidReceiveNotificationResponse {response?.Notification?.Request?.Content?.UserInfo}");
|
||||||
|
|
||||||
if (response.IsDefaultAction)
|
if (response.IsDefaultAction && response?.Notification?.Request?.Content?.UserInfo != null)
|
||||||
{
|
{
|
||||||
OnMessageReceived(response?.Notification?.Request?.Content?.UserInfo);
|
var userInfo = response?.Notification?.Request?.Content?.UserInfo;
|
||||||
|
OnMessageReceived(userInfo);
|
||||||
|
|
||||||
|
if (userInfo.TryGetValue(NSString.FromObject(Constants.NotificationData), out NSObject nsObject))
|
||||||
|
{
|
||||||
|
var token = JToken.Parse(NSString.FromObject(nsObject).ToString());
|
||||||
|
var typeToken = token.SelectToken(Constants.NotificationDataType);
|
||||||
|
if (typeToken.ToString() == PasswordlessNotificationData.TYPE)
|
||||||
|
{
|
||||||
|
_pushNotificationListenerService.OnNotificationTapped(token.ToObject<PasswordlessNotificationData>());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform caller it has been handled
|
// Inform caller it has been handled
|
||||||
|
|
|
@ -4,8 +4,13 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Services;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using UserNotifications;
|
using UserNotifications;
|
||||||
|
|
||||||
|
@ -69,9 +74,9 @@ namespace Bit.iOS.Services
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendLocalNotification(string title, string message, string notificationId)
|
public void SendLocalNotification(string title, string message, BaseNotificationData data)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(notificationId))
|
if (string.IsNullOrEmpty(data.Id))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
||||||
}
|
}
|
||||||
|
@ -82,7 +87,12 @@ namespace Bit.iOS.Services
|
||||||
Body = message
|
Body = message
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = UNNotificationRequest.FromIdentifier(notificationId, content, null);
|
if (data != null)
|
||||||
|
{
|
||||||
|
content.UserInfo = NSDictionary.FromObjectAndKey(NSData.FromString(JsonConvert.SerializeObject(data), NSStringEncoding.UTF8), new NSString(Constants.NotificationData));
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = UNNotificationRequest.FromIdentifier(data.Id, content, null);
|
||||||
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
|
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
|
||||||
{
|
{
|
||||||
if (err != null)
|
if (err != null)
|
||||||
|
|
Loading…
Reference in a new issue