1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-16 23:43:21 +00:00

PM-3349 PM-3350 MAUI Migration Initial

This commit is contained in:
Federico Maccaroni
2023-09-29 11:02:19 -03:00
parent bbef0f8c93
commit 8ef9443b1e
717 changed files with 5367 additions and 4702 deletions

View File

@@ -0,0 +1,424 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Abstractions;
using Bit.App.Pages;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Services;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Services;
using CoreNFC;
using Foundation;
using Microsoft.Maui.Controls.Compatibility.Platform.iOS;
using UIKit;
using WatchConnectivity;
namespace Bit.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => App.MauiProgram.CreateMauiApp();
const int SPLASH_VIEW_TAG = 4321;
private NFCNdefReaderSession _nfcSession = null;
private iOSPushNotificationHandler _pushHandler = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
private NSTimer _clipboardTimer = null;
private nint _clipboardBackgroundTaskId;
private NSTimer _eventTimer = null;
private nint _eventBackgroundTaskId;
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IStorageService _storageService;
private IStateService _stateService;
private IEventService _eventService;
private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
InitApp();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
//LoadApplication(new App.App(null));
//iOSCoreHelpers.AppearanceAdjustments();
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{
try
{
if (message.Command == "startEventTimer")
{
StartEventTimer();
}
else if (message.Command == "stopEventTimer")
{
var task = StopEventTimerAsync();
}
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
Device.BeginInvokeOnMainThread(() =>
{
iOSCoreHelpers.AppearanceAdjustments();
});
}
else if (message.Command == "listenYubiKeyOTP")
{
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "showAppExtension")
{
Device.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
}
else if (message.Command == "syncCompleted")
{
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{
var success = data["successfully"] as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
}
}
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
if (await ASHelpers.IdentitiesCanIncremental())
{
var cipherId = message.Data as string;
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
{
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
if (identity == null)
{
return;
}
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return;
}
}
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
if (await ASHelpers.IdentitiesCanIncremental())
{
var identity = ASHelpers.ToCredentialIdentity(
message.Data as Bit.Core.Models.View.CipherView);
if (identity == null)
{
return;
}
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return;
}
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "logout")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
&& _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else
{
await ASHelpers.ReplaceAllIdentities();
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
var finishedLaunching = base.FinishedLaunching(app, options);
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
iOSCoreHelpers.AppearanceAdjustments();
return finishedLaunching;
}
public override void OnResignActivation(UIApplication uiApplication)
{
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
Tag = SPLASH_VIEW_TAG
};
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToUIColor()
};
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
var imageView = new UIImageView(logo)
{
Center = new CoreGraphics.CGPoint(view.Center.X, view.Center.Y - 30)
};
view.AddSubview(backgroundView);
view.AddSubview(imageView);
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
base.OnResignActivation(uiApplication);
}
public override void DidEnterBackground(UIApplication uiApplication)
{
_stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
_messagingService?.Send("slept");
base.DidEnterBackground(uiApplication);
}
public override void OnActivated(UIApplication uiApplication)
{
base.OnActivated(uiApplication);
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
UIApplication.SharedApplication.KeyWindow?
.ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview();
ThemeManager.UpdateThemeOnPagesAsync();
}
public override void WillEnterForeground(UIApplication uiApplication)
{
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
base.WillEnterForeground(uiApplication);
}
[Export("application:openURL:sourceApplication:annotation:")]
public bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
return true;
}
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
}
// TODO: [MAUI-Migration] Check if this works given that the baes will call the invoke the service lifecycle.
/* public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
UIApplicationRestorationHandler completionHandler)
{
if (Xamarin.Essentials.Platform.ContinueUserActivity(application, userActivity, completionHandler))
{
return true;
}
return base.ContinueUserActivity(application, userActivity, completionHandler);
} */
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
_pushHandler?.OnErrorReceived(error);
}
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
_pushHandler?.OnRegisteredSuccess(deviceToken);
}
[Export("application:didRegisterUserNotificationSettings:")]
public void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings)
{
application.RegisterForRemoteNotifications();
}
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
_pushHandler?.OnMessageReceived(userInfo);
}
[Export("application:didReceiveRemoteNotification:")]
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
_pushHandler?.OnMessageReceived(userInfo);
}
public void InitApp()
{
if (ServiceContainer.RegisteredServices.Count > 0)
{
return;
}
// Migration services
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
// Note: This might cause a race condition. Investigate more.
//Task.Run(() =>
//{
// FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
// FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
// {
// FadeAnimationEnabled = false,
// FadeAnimationForCachedImages = false
// });
//});
iOSCoreHelpers.RegisterLocalServices();
RegisterPush();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Constants.iOSAllClearCipherCacheKeys);
iOSCoreHelpers.InitLogger();
iOSCoreHelpers.RegisterFinallyBeforeBootstrap();
_pushHandler = new iOSPushNotificationHandler(
ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService"));
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
}
private void RegisterPush()
{
var notificationListenerService = new PushNotificationListenerService();
ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService);
var iosPushNotificationService = new iOSPushNotificationService();
ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", iosPushNotificationService);
}
private void ShowAppExtension(ExtensionPageViewModel extensionPageViewModel)
{
var itemProvider = new NSItemProvider(new NSDictionary(), Core.Constants.UTTypeAppExtensionSetup);
var extensionItem = new NSExtensionItem
{
Attachments = new NSItemProvider[] { itemProvider }
};
var activityViewController = new UIActivityViewController(new NSExtensionItem[] { extensionItem }, null)
{
CompletionHandler = (activityType, completed) =>
{
extensionPageViewModel.EnabledExtension(completed && activityType == iOSCoreHelpers.AppExtensionId);
}
};
var modal = UIApplication.SharedApplication.KeyWindow.RootViewController.ModalViewController;
if (activityViewController.PopoverPresentationController != null)
{
activityViewController.PopoverPresentationController.SourceView = modal.View;
var frame = UIScreen.MainScreen.Bounds;
frame.Height /= 2;
activityViewController.PopoverPresentationController.SourceRect = frame;
}
modal.PresentViewController(activityViewController, true, null);
}
private void StartEventTimer()
{
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
Device.BeginInvokeOnMainThread(() =>
{
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
{
var task = Task.Run(() => _eventService.UploadEventsAsync());
});
});
}
private async Task StopEventTimerAsync()
{
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
if (_eventBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
});
await _eventService.UploadEventsAsync();
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
private async Task ApplyManagedSettingsAsync()
{
var userDefaults = NSUserDefaults.StandardUserDefaults;
var managedSettings = userDefaults.DictionaryForKey("com.apple.configuration.managed");
if (managedSettings != null && managedSettings.Count > 0)
{
var dict = new Dictionary<string, string>();
foreach (var setting in managedSettings)
{
dict.Add(setting.Key.ToString(), setting.Value?.ToString());
}
await AppHelpers.SetPreconfiguredSettingsAsync(dict);
}
}
private async Task ConnectToWatchIfNeededAsync()
{
if (_stateService != null && await _stateService.GetShouldConnectToWatchAsync())
{
WCSessionManager.SharedManager.StartSession();
}
}
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.8bit.bitwarden</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.8bit.bitwarden</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:bitwarden.com</string>
</array>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MinimumOSVersion</key>
<string>11.0</string>
<key>CFBundleDisplayName</key>
<string>Bitwarden</string>
<key>CFBundleName</key>
<string>Bitwarden</string>
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2023.9.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>bitwarden</string>
<string>org-appextension-feature-password-management</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.8bit.bitwarden.url</string>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleURLSchemes</key>
<array>
<string>otpauth</string>
</array>
</dict>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
<string>zh-Hans</string>
<string>zh-Hant</string>
<string>pt-PT</string>
<string>pt-BR</string>
<string>sv</string>
<string>sk</string>
<string>it</string>
<string>fi</string>
<string>fr</string>
<string>ro</string>
<string>id</string>
<string>hr</string>
<string>hu</string>
<string>nl</string>
<string>tr</string>
<string>uk</string>
<string>de</string>
<string>dk</string>
<string>cz</string>
<string>nb</string>
<string>ja</string>
<string>et</string>
<string>vi</string>
<string>pl</string>
<string>ko</string>
<string>fa</string>
<string>ru</string>
<string>be</string>
<string>bg</string>
<string>ca</string>
<string>cs</string>
<string>el</string>
<string>th</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile~ipad</key>
<string>LaunchScreen</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIAppFonts</key>
<array>
<string>bwi-font.ttf</string>
<string>MaterialIcons_Regular.ttf</string>
</array>
<key>UIRequiredDeviceCapabilities</key>
<dict>
<key>arm64</key>
<true/>
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>XSAppIconAssets</key>
<string>Resources/Assets.xcassets/AppIcons.appiconset</string>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>
<string>ecf076d3-4824-4d7b-b716-2a9a47d7d296</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app does not require access to the photo library.</string>
<key>NSCameraUsageDescription</key>
<string>Scan QR codes</string>
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID to unlock your vault.</string>
<key>NFCReaderUsageDescription</key>
<string>Use Yubikeys for two-facor authentication.</string>
</dict>
</plist>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="5">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
</dependencies>
<scenes>
<scene sceneID="4">
<objects>
<viewController id="5" sceneMemberID="viewController" customClass="LaunchScreenViewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="2"/>
<viewControllerLayoutGuide type="bottom" id="3"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="6">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" id="16" translatesAutoresizingMaskIntoConstraints="NO" image="LaunchScreen">
<rect key="frame" x="159" y="248" width="282" height="44"/>
</imageView>
</subviews>
<constraints>
<constraint id="19" firstItem="16" firstAttribute="centerY" secondItem="6" secondAttribute="centerY" constant="-30"/>
<constraint id="20" firstItem="6" firstAttribute="centerX" secondItem="16" secondAttribute="centerX"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="7" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-54" y="0.0"/>
</scene>
</scenes>
<resources>
<image name="Default.png" width="320" height="480"/>
<image name="fa-cogs.png" width="22" height="22"/>
<image name="fa-lock.png" width="22" height="22"/>
<image name="fa-refresh.png" width="22" height="22"/>
<image name="fa_folder_open.png" width="14" height="14"/>
<image name="Icon-120.png" width="120" height="120"/>
<image name="Icon-152.png" width="152" height="152"/>
<image name="Icon-16.png" width="16" height="16"/>
<image name="Icon-24.png" width="24" height="24"/>
<image name="Icon-32.png" width="32" height="32"/>
<image name="Icon-40.png" width="40" height="40"/>
<image name="Icon-60.png" width="60" height="60"/>
<image name="Icon-64.png" width="64" height="64"/>
<image name="Icon-72.png" width="72" height="72"/>
<image name="Icon-76.png" width="76" height="76"/>
<image name="Icon-Small-40.png" width="40" height="40"/>
<image name="Icon-Small-50.png" width="50" height="50"/>
<image name="Icon-Small.png" width="29" height="29"/>
<image name="Icon.png" width="57" height="57"/>
<image name="ion_chevron_right.png" width="14" height="14"/>
<image name="ion_plus.png" width="22" height="22"/>
<image name="LaunchScreen" width="282" height="44"/>
<image name="cogs.png" width="29" height="29"/>
<image name="eye.png" width="22" height="22"/>
<image name="eye_slash.png" width="22" height="22"/>
<image name="more.png" width="28" height="28"/>
<image name="more_selected.png" width="28" height="28"/>
<image name="plus.png" width="18" height="18"/>
<image name="star.png" width="25" height="25"/>
<image name="cloudup.png" width="44" height="44"/>
<image name="envelope.png" width="18" height="18"/>
<image name="globe.png" width="44" height="44"/>
<image name="lightbulb-o.png" width="18" height="18"/>
<image name="lock.png" width="18" height="18"/>
<image name="refresh.png" width="44" height="44"/>
<image name="upload.png" width="44" height="44"/>
<image name="user.png" width="18" height="18"/>
<image name="wrench.png" width="22" height="22"/>
<image name="camera.png" width="22" height="22"/>
<image name="download.png" width="18" height="18"/>
<image name="ext-act.png" width="290" height="252"/>
<image name="ext-more.png" width="290" height="252"/>
<image name="ext-use.png" width="290" height="252"/>
<image name="fa_lock.png" width="25" height="25"/>
<image name="fingerprint.png" width="91" height="92"/>
<image name="folder.png" width="18" height="18"/>
<image name="Icon-83.5.png" width="83.5" height="83.5"/>
<image name="lightbulb.png" width="18" height="18"/>
<image name="paperclip.png" width="14" height="14"/>
<image name="photo.png" width="22" height="22"/>
<image name="share.png" width="14" height="14"/>
<image name="share_tools.png" width="44" height="44"/>
<image name="tools.png" width="26" height="26"/>
<image name="trash.png" width="18" height="18"/>
<image name="yubikey.png" width="266" height="160"/>
</resources>
</document>

View File

@@ -0,0 +1,12 @@
using Bit.iOS;
using UIKit;
namespace Bit.App;
public class Program
{
static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,117 @@
{
"images": [
{
"scale": "2x",
"size": "20x20",
"idiom": "iphone",
"filename": "Icon-40.png"
},
{
"scale": "3x",
"size": "20x20",
"idiom": "iphone",
"filename": "Icon-60.png"
},
{
"scale": "2x",
"size": "29x29",
"idiom": "iphone",
"filename": "Icon-58.png"
},
{
"scale": "3x",
"size": "29x29",
"idiom": "iphone",
"filename": "Icon-87.png"
},
{
"scale": "2x",
"size": "40x40",
"idiom": "iphone",
"filename": "Icon-80.png"
},
{
"scale": "3x",
"size": "40x40",
"idiom": "iphone",
"filename": "Icon-120.png"
},
{
"scale": "2x",
"size": "60x60",
"idiom": "iphone",
"filename": "Icon-120.png"
},
{
"scale": "3x",
"size": "60x60",
"idiom": "iphone",
"filename": "Icon-180.png"
},
{
"scale": "1x",
"size": "20x20",
"idiom": "ipad",
"filename": "Icon-20.png"
},
{
"scale": "2x",
"size": "20x20",
"idiom": "ipad",
"filename": "Icon-40.png"
},
{
"scale": "1x",
"size": "29x29",
"idiom": "ipad",
"filename": "Icon-29.png"
},
{
"scale": "2x",
"size": "29x29",
"idiom": "ipad",
"filename": "Icon-58.png"
},
{
"scale": "1x",
"size": "40x40",
"idiom": "ipad",
"filename": "Icon-40.png"
},
{
"scale": "2x",
"size": "40x40",
"idiom": "ipad",
"filename": "Icon-80.png"
},
{
"scale": "1x",
"size": "76x76",
"idiom": "ipad",
"filename": "Icon-76.png"
},
{
"scale": "2x",
"size": "76x76",
"idiom": "ipad",
"filename": "Icon-152.png"
},
{
"scale": "2x",
"size": "83.5x83.5",
"idiom": "ipad",
"filename": "Icon-167.png"
},
{
"scale": "1x",
"size": "1024x1024",
"idiom": "ios-marketing",
"filename": "Icon-1024.png"
}
],
"properties": {},
"info": {
"version": 1,
"author": "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,68 @@
{
"images" : [
{
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal"
},
{
"filename" : "logo.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "logo_white.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "logo@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "logo_white@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "logo@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "logo_white@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "Empty-items-state.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Empty-items-state-dark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "empty_login_requests.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "empty_login_requests_dark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,608 @@
{
"images" : [
{
"filename" : "ic_warning-1.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal"
},
{
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "3x"
},
{
"idiom" : "iphone"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone"
},
{
"idiom" : "iphone",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "1x"
},
{
"idiom" : "iphone",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "2x"
},
{
"idiom" : "iphone",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "3x"
},
{
"idiom" : "iphone",
"scale" : "1x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "1x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "1x",
"subtype" : "retina4"
},
{
"idiom" : "iphone",
"scale" : "2x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "2x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "2x",
"subtype" : "retina4"
},
{
"idiom" : "iphone",
"scale" : "3x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "iphone",
"scale" : "3x",
"subtype" : "retina4"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "iphone",
"scale" : "3x",
"subtype" : "retina4"
},
{
"idiom" : "ipad"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "ipad"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "ipad"
},
{
"idiom" : "ipad",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "ipad",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "ipad",
"scale" : "1x"
},
{
"idiom" : "ipad",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "ipad",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "ipad",
"scale" : "2x"
},
{
"idiom" : "car"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "car"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "car"
},
{
"idiom" : "car",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "car",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "car",
"scale" : "2x"
},
{
"idiom" : "car",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "car",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "car",
"scale" : "3x"
},
{
"idiom" : "mac"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "mac"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "mac"
},
{
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "mac",
"scale" : "1x"
},
{
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "mac",
"scale" : "2x"
},
{
"idiom" : "watch"
},
{
"idiom" : "watch",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"screen-width" : ">183"
},
{
"idiom" : "watch",
"scale" : "2x"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "watch"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "watch"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "watch",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "watch",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,119 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.004333 cm
0.866667 0.294118 0.223529 scn
24.979254 0.004448 m
2.740886 0.004448 l
2.256652 0.000004 1.780215 0.127985 1.360950 0.376835 c
0.941685 0.625685 0.595185 0.983850 0.356966 1.415783 c
0.120480 1.834383 -0.003394 2.309866 0.000071 2.793346 c
0.003536 3.276825 0.132607 3.750528 0.374291 4.165575 c
11.493476 23.511885 l
11.737758 23.932262 12.084259 24.279764 12.499193 24.521503 c
12.914126 24.763245 13.382767 24.890335 13.860070 24.890335 c
14.337375 24.890335 14.806015 24.763245 15.220948 24.521503 c
15.635882 24.279764 15.982384 23.932262 16.226665 23.511885 c
27.345852 4.165575 l
27.587534 3.750528 27.717470 3.276825 27.720068 2.793346 c
27.722668 2.309866 27.599663 1.834383 27.363176 1.415783 c
27.124958 0.983850 26.778458 0.624796 26.359192 0.376835 c
25.939928 0.128874 25.463488 0.000004 24.979254 0.004448 c
24.979254 0.004448 l
h
13.860070 23.111946 m
13.684222 23.114613 13.510106 23.069286 13.356780 22.981300 c
13.203454 22.893314 13.075248 22.764446 12.985158 22.608913 c
1.867706 3.262606 l
1.782813 3.116850 1.737768 2.949764 1.737768 2.780014 c
1.737768 2.610262 1.782813 2.443178 1.867706 2.297422 c
1.956063 2.138336 2.084268 2.006802 2.239327 1.916149 c
2.394386 1.825497 2.570235 1.779282 2.747816 1.781948 c
24.979254 1.781948 l
25.157701 1.779282 25.333551 1.825497 25.487745 1.916149 c
25.641937 2.006802 25.771009 2.138336 25.859367 2.297422 c
25.944260 2.443178 25.989302 2.610262 25.989302 2.780014 c
25.989302 2.949764 25.944260 3.116850 25.859367 3.262606 c
14.734983 22.608913 l
14.644894 22.764446 14.516687 22.892426 14.363361 22.981300 c
14.210035 23.070175 14.035919 23.114613 13.860070 23.111946 c
13.860070 23.111946 l
h
13.860070 7.887661 m
13.630514 7.887661 13.409620 7.980980 13.247631 8.148064 c
13.085643 8.315149 12.993821 8.540892 12.993821 8.776411 c
12.993821 16.904917 l
12.993821 17.140434 13.084776 17.367065 13.247631 17.533262 c
13.410486 17.699459 13.630514 17.793667 13.860070 17.793667 c
14.089627 17.793667 14.310521 17.700348 14.472510 17.533262 c
14.634499 17.366177 14.726320 17.140434 14.726320 16.904917 c
14.726320 8.776411 l
14.726320 8.540892 14.635365 8.314260 14.472510 8.148064 c
14.309655 7.981867 14.089627 7.887661 13.860070 7.887661 c
h
13.860070 3.493681 m
14.427464 3.493681 14.887444 3.965607 14.887444 4.547737 c
14.887444 5.129869 14.427464 5.601795 13.860070 5.601795 c
13.292677 5.601795 12.832698 5.129869 12.832698 4.547737 c
12.832698 3.965607 13.292677 3.493681 13.860070 3.493681 c
h
f
n
Q
endstream
endobj
3 0 obj
2568
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 27.720123 24.886002 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002658 00000 n
0000002681 00000 n
0000002854 00000 n
0000002928 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2987
%%EOF

View File

@@ -0,0 +1,127 @@
using System.Diagnostics;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core;
using Bit.Core.Services;
using Foundation;
using Newtonsoft.Json.Linq;
using UserNotifications;
namespace Bit.iOS.Services
{
public class iOSPushNotificationHandler : NSObject, IUNUserNotificationCenterDelegate
{
private const string TokenSetting = "token";
const string TAG = "##PUSH NOTIFICATIONS";
private readonly IPushNotificationListenerService _pushNotificationListenerService;
public iOSPushNotificationHandler(
IPushNotificationListenerService pushNotificationListenerService)
{
_pushNotificationListenerService = pushNotificationListenerService;
}
public void OnMessageReceived(NSDictionary userInfo)
{
try
{
Debug.WriteLine($"{TAG} - OnMessageReceived.");
var json = DictionaryToJson(userInfo);
var values = JObject.Parse(json);
var keyAps = new NSString("aps");
if (userInfo.ContainsKey(keyAps) && userInfo.ValueForKey(keyAps) is NSDictionary aps)
{
foreach (var apsKey in aps)
{
if (!values.TryGetValue(apsKey.Key.ToString(), out JToken temp))
{
values.Add(apsKey.Key.ToString(), apsKey.Value.ToString());
}
}
}
_pushNotificationListenerService.OnMessageAsync(values, Device.iOS);
}
catch (Exception ex)
{
Logger.Instance.Exception(ex);
}
}
public void OnErrorReceived(NSError error)
{
Debug.WriteLine($"{TAG} - Registration Failed.");
_pushNotificationListenerService.OnError(error.LocalizedDescription, Device.iOS);
}
public void OnRegisteredSuccess(NSData token)
{
Debug.WriteLine($"{TAG} - Successfully Registered.");
var hexDeviceToken = BitConverter.ToString(token.ToArray())
.Replace("-", string.Empty)
.ToLowerInvariant();
Debug.WriteLine($"{TAG} - Token: {hexDeviceToken}");
UNUserNotificationCenter.Current.Delegate = this;
_pushNotificationListenerService.OnRegisteredAsync(hexDeviceToken, Device.iOS);
NSUserDefaults.StandardUserDefaults.SetString(hexDeviceToken, TokenSetting);
NSUserDefaults.StandardUserDefaults.Synchronize();
}
private static string DictionaryToJson(NSDictionary dictionary)
{
var json = NSJsonSerialization.Serialize(dictionary, NSJsonWritingOptions.PrettyPrinted, out NSError error);
return json.ToString(NSStringEncoding.UTF8);
}
// To receive notifications in foreground on iOS 10 devices.
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
Debug.WriteLine($"{TAG} WillPresentNotification {notification?.Request?.Content?.UserInfo}");
OnMessageReceived(notification?.Request?.Content?.UserInfo);
completionHandler(UNNotificationPresentationOptions.Alert);
}
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
Debug.WriteLine($"{TAG} DidReceiveNotificationResponse {response?.Notification?.Request?.Content?.UserInfo}");
if ((response?.Notification?.Request?.Content?.UserInfo) == null)
{
completionHandler();
return;
}
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 (response.IsDefaultAction)
{
if (typeToken.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.OnNotificationTapped(token.ToObject<PasswordlessNotificationData>());
}
}
else if (response.IsDismissAction)
{
if (typeToken.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.OnNotificationDismissed(token.ToObject<PasswordlessNotificationData>());
}
}
}
// Inform caller it has been handled
completionHandler();
}
}
}

View File

@@ -0,0 +1,116 @@
using System.Diagnostics;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Foundation;
using Newtonsoft.Json;
using UIKit;
using UserNotifications;
namespace Bit.iOS.Services
{
public class iOSPushNotificationService : NSObject, IPushNotificationService, IUNUserNotificationCenterDelegate
{
private const string TokenSetting = "token";
const string TAG = "##PUSH NOTIFICATIONS";
public Task<string> GetTokenAsync()
{
return Task.FromResult(NSUserDefaults.StandardUserDefaults.StringForKey(TokenSetting));
}
public bool IsRegisteredForPush => UIApplication.SharedApplication.IsRegisteredForRemoteNotifications;
public async Task<bool> AreNotificationsSettingsEnabledAsync()
{
var settings = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
return settings.AlertSetting == UNNotificationSetting.Enabled;
}
public async Task RegisterAsync()
{
Debug.WriteLine($"{TAG} RegisterAsync");
var tcs = new TaskCompletionSource<bool>();
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) =>
{
if (error != null)
{
Debug.WriteLine($"{TAG} {error}");
}
else
{
Debug.WriteLine($"{TAG} {granted}");
}
tcs.SetResult(granted);
});
if (await tcs.Task)
{
Debug.WriteLine($"{TAG} RegisterForRemoteNotifications");
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
}
public Task UnregisterAsync()
{
Debug.WriteLine($"{TAG} UnregisterAsync");
UIApplication.SharedApplication.UnregisterForRemoteNotifications();
// TODO: unregister call
// _pushNotificationListener.OnUnregistered(Device.iOS);
NSUserDefaults.StandardUserDefaults.SetString(string.Empty, TokenSetting);
NSUserDefaults.StandardUserDefaults.Synchronize();
return Task.FromResult(0);
}
public void SendLocalNotification(string title, string message, BaseNotificationData data)
{
if (string.IsNullOrEmpty(data.Id))
{
throw new ArgumentNullException("notificationId cannot be null or empty.");
}
var content = new UNMutableNotificationContent()
{
Title = title,
Body = message,
CategoryIdentifier = Constants.iOSNotificationCategoryId
};
if (data != null)
{
content.UserInfo = NSDictionary.FromObjectAndKey(NSData.FromString(JsonConvert.SerializeObject(data), NSStringEncoding.UTF8), new NSString(Constants.NotificationData));
}
var actions = new UNNotificationAction[] { UNNotificationAction.FromIdentifier(Constants.iOSNotificationClearActionId, AppResources.Clear, UNNotificationActionOptions.Foreground) };
var category = UNNotificationCategory.FromIdentifier(Constants.iOSNotificationCategoryId, actions, new string[] { }, UNNotificationCategoryOptions.CustomDismissAction);
UNUserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(category));
var request = UNNotificationRequest.FromIdentifier(data.Id, content, null);
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
Logger.Instance.Exception(new Exception($"Failed to schedule notification: {err}"));
}
});
}
public void DismissLocalNotification(string notificationId)
{
if (string.IsNullOrEmpty(notificationId))
{
return;
}
UNUserNotificationCenter.Current.RemovePendingNotificationRequests(new string[] { notificationId });
UNUserNotificationCenter.Current.RemoveDeliveredNotifications(new string[] { notificationId });
}
}
}