1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-06-26 10:36:21 +02:00

SG-210 Account Switching in Autofill (iOS) (#1909)

* SG-210 Set up account switching on Autofill iOS

* SG-210 Fix refresh after sync on autofill ciphers, also added account switching on lock view on autofill. Also fix possible crash when scrolling when no items were displayed and also fixed navigation when login in on an automatically logged out account.

* SG-210 Added reference on iOS.Core project

* Fix formatting on AccountManager

* SG-210 Fix background color for dark theme
This commit is contained in:
Federico Maccaroni 2022-06-08 14:24:01 -03:00 committed by GitHub
parent 88b406544b
commit fba407f3b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1500 additions and 417 deletions

View File

@ -20,6 +20,7 @@ using System.Net.Http;
using System.Net; using System.Net;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
#if !FDROID #if !FDROID
using Android.Gms.Security; using Android.Gms.Security;
#endif #endif
@ -62,6 +63,15 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"), ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService")); ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper); ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)

View File

@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions
{
public interface IAccountsManager
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
}
}

View File

@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
public interface INavigationParams { }
public interface IAccountsManagerHost
{
Task SetPreviousPageInfoAsync();
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
Task UpdateThemeAsync();
}
}

View File

@ -128,6 +128,7 @@
<Folder Include="Resources\" /> <Folder Include="Resources\" />
<Folder Include="Behaviors\" /> <Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" /> <Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -420,5 +421,6 @@
<None Remove="Behaviors\" /> <None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" /> <None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" /> <None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -6,6 +6,7 @@ using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@ -16,7 +17,7 @@ using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)] [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App namespace Bit.App
{ {
public partial class App : Application public partial class App : Application, IAccountsManagerHost
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
@ -27,6 +28,7 @@ namespace Bit.App
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IStorageService _secureStorageService; private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IAccountsManager _accountsManager;
private static bool _isResumed; private static bool _isResumed;
@ -47,6 +49,9 @@ namespace Bit.App
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService"); _secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => Options, this);
Bootstrap(); Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) => _broadcasterService.Subscribe(nameof(App), async (message) =>
@ -71,30 +76,6 @@ namespace Bit.App
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
}); });
} }
else if (message.Command == "locked")
{
var extras = message.Data as Tuple<string, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
else if (message.Command == "lockVault")
{
await _vaultTimeoutService.LockAsync(true);
}
else if (message.Command == "logout")
{
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
}
else if (message.Command == "resumed") else if (message.Command == "resumed")
{ {
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
@ -109,22 +90,10 @@ namespace Bit.App
await SleptAsync(); await SleptAsync();
} }
} }
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated") else if (message.Command == "migrated")
{ {
await Task.Delay(1000); await Task.Delay(1000);
await SetMainPageAsync(); await _accountsManager.NavigateOnAccountChangeAsync();
} }
else if (message.Command == "popAllAndGoToTabGenerator" || else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" || message.Command == "popAllAndGoToTabMyVault" ||
@ -168,7 +137,6 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
}); });
} }
}); });
} }
@ -263,102 +231,6 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar(); new System.Globalization.UmAlQuraCalendar();
} }
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync(userId, userInitiated);
await SetMainPageAsync();
_authService.LogOut(() =>
{
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Options.HideAccountSwitcher = false;
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
await UpdateThemeAsync();
});
}
private async Task SetMainPageAsync()
{
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
}
else if (Options.Uri != null)
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);
}
}
else
{
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
}
private async Task ClearCacheIfNeededAsync() private async Task ClearCacheIfNeededAsync()
{ {
var lastClear = await _stateService.GetLastFileCacheClearAsync(); var lastClear = await _stateService.GetLastFileCacheClearAsync();
@ -420,7 +292,7 @@ namespace Bit.App
UpdateThemeAsync(); UpdateThemeAsync();
}; };
Current.MainPage = new NavigationPage(new HomePage(Options)); Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync(); var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
} }
@ -441,23 +313,8 @@ namespace Bit.App
}); });
} }
private async Task LockedAsync(string userId, bool userInitiated) public async Task SetPreviousPageInfoAsync()
{ {
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
PreviousPageInfo lastPageBeforeLock = null; PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0) if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{ {
@ -483,8 +340,44 @@ namespace Bit.App
} }
} }
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock); await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
var lockPage = new LockPage(Options, autoPromptBiometric); }
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
}
break;
case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams)
{
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
break;
case NavigationTarget.Home:
Current.MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
break;
case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
break;
}
} }
} }
} }

View File

@ -63,6 +63,8 @@ namespace Bit.App.Controls
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70; public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
public async Task ToggleVisibilityAsync() public async Task ToggleVisibilityAsync()
{ {
if (IsVisible) if (IsVisible)
@ -167,7 +169,7 @@ namespace Bit.App.Controls
private async Task LongPressAccountAsync(AccountViewCellViewModel item) private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{ {
if (!item.IsAccount) if (!LongPressAccountEnabled || !item.IsAccount)
{ {
return; return;
} }

View File

@ -0,0 +1,227 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Xamarin.Forms;
namespace Bit.App.Utilities.AccountManagement
{
public static class AccountsManagerMessageCommands
{
public const string LOCKED = "locked";
public const string LOCK_VAULT = "lockVault";
public const string LOGOUT = "logout";
public const string LOGGED_OUT = "loggedOut";
public const string ADD_ACCOUNT = "addAccount";
public const string ACCOUNT_ADDED = "accountAdded";
public const string SWITCHED_ACCOUNT = "switchedAccount";
}
public class AccountsManager : IAccountsManager
{
private readonly IBroadcasterService _broadcasterService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _secureStorageService;
private readonly IStateService _stateService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
Func<AppOptions> _getOptionsFunc;
private IAccountsManagerHost _accountsManagerHost;
public AccountsManager(IBroadcasterService broadcasterService,
IVaultTimeoutService vaultTimeoutService,
IStorageService secureStorageService,
IStateService stateService,
IPlatformUtilsService platformUtilsService,
IAuthService authService)
{
_broadcasterService = broadcasterService;
_vaultTimeoutService = vaultTimeoutService;
_secureStorageService = secureStorageService;
_stateService = stateService;
_platformUtilsService = platformUtilsService;
_authService = authService;
}
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
public void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost)
{
_getOptionsFunc = getOptionsFunc;
_accountsManagerHost = accountsManagerHost;
_broadcasterService.Subscribe(nameof(AccountsManager), OnMessage);
}
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
{
// TODO: this could be improved by doing chain of responsability pattern
// but for now it may be an overkill, if logic gets more complex consider refactoring it
var authed = isAuthed ?? await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
_accountsManagerHost.Navigate(NavigationTarget.Lock);
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
_accountsManagerHost.Navigate(NavigationTarget.AddEditCipher);
}
else if (Options.Uri != null)
{
_accountsManagerHost.Navigate(NavigationTarget.AutofillCiphers);
}
else if (Options.CreateSend != null)
{
_accountsManagerHost.Navigate(NavigationTarget.SendAddEdit);
}
else
{
_accountsManagerHost.Navigate(NavigationTarget.Home);
}
}
else
{
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
}
else
{
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
}
}
}
private async void OnMessage(Message message)
{
switch (message.Command)
{
case AccountsManagerMessageCommands.LOCKED:
Locked(message.Data as Tuple<string, bool>);
break;
case AccountsManagerMessageCommands.LOCK_VAULT:
await _vaultTimeoutService.LockAsync(true);
break;
case AccountsManagerMessageCommands.LOGOUT:
LogOut(message.Data as Tuple<string, bool, bool>);
break;
case AccountsManagerMessageCommands.LOGGED_OUT:
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
break;
case AccountsManagerMessageCommands.ADD_ACCOUNT:
AddAccount();
break;
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
await _accountsManagerHost.UpdateThemeAsync();
break;
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
await SwitchedAccountAsync();
break;
}
}
private void Locked(Tuple<string, bool> extras)
{
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
private async Task LockedAsync(string userId, bool userInitiated)
{
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
await _accountsManagerHost.SetPreviousPageInfoAsync();
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
}
private void AddAccount()
{
Device.BeginInvokeOnMainThread(() =>
{
Options.HideAccountSwitcher = false;
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
});
}
private void LogOut(Tuple<string, bool, bool> extras)
{
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync(userId, userInitiated);
await NavigateOnAccountChangeAsync();
_authService.LogOut(() =>
{
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await NavigateOnAccountChangeAsync();
}
await Task.Delay(50);
await _accountsManagerHost.UpdateThemeAsync();
});
}
}
}

View File

@ -0,0 +1,14 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class LockNavigationParams : INavigationParams
{
public LockNavigationParams(bool autoPromptBiometric = true)
{
AutoPromptBiometric = autoPromptBiometric;
}
public bool AutoPromptBiometric { get; }
}
}

View File

@ -0,0 +1,14 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class LoginNavigationParams : INavigationParams
{
public LoginNavigationParams(string email)
{
Email = email;
}
public string Email { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace Bit.Core.Enums
{
public enum NavigationTarget
{
HomeLogin,
Login,
Lock,
Home,
AddEditCipher,
AutofillCiphers,
SendAddEdit
}
}

View File

@ -1,27 +1,32 @@
using AuthenticationServices; using System;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models; using Bit.iOS.Autofill.Models;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Foundation;
using System;
using System.Threading.Tasks;
using Bit.App.Pages;
using UIKit;
using Xamarin.Forms;
using Bit.App.Utilities;
using Bit.App.Models;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using CoreNFC; using CoreNFC;
using Foundation;
using UIKit;
using Xamarin.Forms;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
public partial class CredentialProviderViewController : ASCredentialProviderViewController public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
{ {
private Context _context; private Context _context;
private NFCNdefReaderSession _nfcSession = null; private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null; private Core.NFCReaderDelegate _nfcDelegate = null;
private IAccountsManager _accountsManager;
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
public CredentialProviderViewController(IntPtr handle) public CredentialProviderViewController(IntPtr handle)
: base(handle) : base(handle)
@ -56,7 +61,7 @@ namespace Bit.iOS.Autofill
} }
if (!await IsAuthed()) if (!await IsAuthed())
{ {
LaunchHomePage(); await _accountsManager.NavigateOnAccountChangeAsync(false);
} }
else if (await IsLocked()) else if (await IsLocked())
{ {
@ -78,9 +83,8 @@ namespace Bit.iOS.Autofill
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
{ {
InitAppIfNeeded(); InitAppIfNeeded();
var stateService = ServiceContainer.Resolve<IStateService>("stateService"); await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
await stateService.SetPasswordRepromptAutofillAsync(false); await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
await stateService.SetPasswordVerifiedAutofillAsync(false);
if (!await IsAuthed() || await IsLocked()) if (!await IsAuthed() || await IsLocked())
{ {
var err = new NSError(new NSString("ASExtensionErrorDomain"), var err = new NSError(new NSString("ASExtensionErrorDomain"),
@ -97,7 +101,7 @@ namespace Bit.iOS.Autofill
InitAppIfNeeded(); InitAppIfNeeded();
if (!await IsAuthed()) if (!await IsAuthed())
{ {
LaunchHomePage(); await _accountsManager.NavigateOnAccountChangeAsync(false);
return; return;
} }
_context.CredentialIdentity = credentialIdentity; _context.CredentialIdentity = credentialIdentity;
@ -110,7 +114,7 @@ namespace Bit.iOS.Autofill
_context.Configuring = true; _context.Configuring = true;
if (!await IsAuthed()) if (!await IsAuthed())
{ {
LaunchHomePage(); await _accountsManager.NavigateOnAccountChangeAsync(false);
return; return;
} }
CheckLock(() => PerformSegue("setupSegue", this)); CheckLock(() => PerformSegue("setupSegue", this));
@ -229,7 +233,6 @@ namespace Bit.iOS.Autofill
return; return;
} }
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var decCipher = await cipher.DecryptAsync(); var decCipher = await cipher.DecryptAsync();
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None) if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
{ {
@ -237,13 +240,13 @@ namespace Bit.iOS.Autofill
// already verified the password. // already verified the password.
if (!userInteraction) if (!userInteraction)
{ {
await stateService.SetPasswordRepromptAutofillAsync(true); await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
var err = new NSError(new NSString("ASExtensionErrorDomain"), var err = new NSError(new NSString("ASExtensionErrorDomain"),
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null); Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
ExtensionContext?.CancelRequest(err); ExtensionContext?.CancelRequest(err);
return; return;
} }
else if (!await stateService.GetPasswordVerifiedAutofillAsync()) else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
{ {
// Add a timeout to resolve keyboard not always showing up. // Add a timeout to resolve keyboard not always showing up.
await Task.Delay(250); await Task.Delay(250);
@ -258,10 +261,10 @@ namespace Bit.iOS.Autofill
} }
} }
string totpCode = null; string totpCode = null;
var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync(); var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
if (!disableTotpCopy.GetValueOrDefault(false)) if (!disableTotpCopy.GetValueOrDefault(false))
{ {
var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync(); var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) && if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
(canAccessPremiumAsync || cipher.OrganizationUseTotp)) (canAccessPremiumAsync || cipher.OrganizationUseTotp))
{ {
@ -275,8 +278,7 @@ namespace Bit.iOS.Autofill
private async void CheckLock(Action notLockedAction) private async void CheckLock(Action notLockedAction)
{ {
var stateService = ServiceContainer.Resolve<IStateService>("stateService"); if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync())
{ {
PerformSegue("lockPasswordSegue", this); PerformSegue("lockPasswordSegue", this);
} }
@ -294,8 +296,7 @@ namespace Bit.iOS.Autofill
private Task<bool> IsAuthed() private Task<bool> IsAuthed()
{ {
var stateService = ServiceContainer.Resolve<IStateService>("stateService"); return _stateService.Value.IsAuthenticatedAsync();
return stateService.IsAuthenticatedAsync();
} }
private void LogoutIfAuthed() private void LogoutIfAuthed()
@ -304,8 +305,7 @@ namespace Bit.iOS.Autofill
{ {
if (await IsAuthed()) if (await IsAuthed())
{ {
var stateService = ServiceContainer.Resolve<IStateService>("stateService"); await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
if (deviceActionService.SystemMajorVersion() >= 12) if (deviceActionService.SystemMajorVersion() >= 12)
{ {
@ -331,12 +331,16 @@ namespace Bit.iOS.Autofill
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys); Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
iOSCoreHelpers.InitLogger(); iOSCoreHelpers.InitLogger();
iOSCoreHelpers.Bootstrap(); iOSCoreHelpers.Bootstrap();
var app = new App.App(new AppOptions { IosExtension = true }); var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions);
ThemeManager.SetTheme(app.Resources); ThemeManager.SetTheme(app.Resources);
iOSCoreHelpers.AppearanceAdjustments(); iOSCoreHelpers.AppearanceAdjustments();
_nfcDelegate = new Core.NFCReaderDelegate((success, message) => _nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
messagingService.Send("gotYubiKeyOTP", message)); messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate); iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => appOptions, this);
} }
private void InitAppIfNeeded() private void InitAppIfNeeded()
@ -514,5 +518,35 @@ namespace Bit.iOS.Autofill
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(updateTempPasswordController, true, null); PresentViewController(updateTempPasswordController, true, null);
} }
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
public Task UpdateThemeAsync() => Task.CompletedTask;
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
DismissViewController(false, () => LaunchHomePage());
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
DismissViewController(false, () => LaunchLoginFlow(loginParams.Email));
}
else
{
DismissViewController(false, () => LaunchLoginFlow());
}
break;
case NavigationTarget.Lock:
DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
break;
case NavigationTarget.AutofillCiphers:
case NavigationTarget.Home:
DismissViewController(false, () => PerformSegue("loginListSegue", this));
break;
}
}
} }
} }

View File

@ -1,10 +1,17 @@
using System; using System;
using Bit.App.Controls;
using Bit.iOS.Core.Utilities;
using UIKit; using UIKit;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController public partial class LockPasswordViewController : Core.Controllers.BaseLockPasswordViewController
{ {
AccountSwitchingOverlayView _accountSwitchingOverlayView;
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
public override UITableView TableView => MainTableView;
public LockPasswordViewController(IntPtr handle) public LockPasswordViewController(IntPtr handle)
: base(handle) : base(handle)
{ {
@ -20,6 +27,21 @@ namespace Bit.iOS.Autofill
public override Action Success => () => CPViewController.DismissLockAndContinue(); public override Action Success => () => CPViewController.DismissLockAndContinue();
public override Action Cancel => () => CPViewController.CompleteRequest(); public override Action Cancel => () => CPViewController.CompleteRequest();
public override async void ViewDidLoad()
{
base.ViewDidLoad();
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
{
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
}
partial void SubmitButton_Activated(UIBarButtonItem sender) partial void SubmitButton_Activated(UIBarButtonItem sender)
{ {
var task = CheckPasswordAsync(); var task = CheckPasswordAsync();

View File

@ -1,64 +1,79 @@
// WARNING // WARNING
// //
// This file has been generated automatically by Visual Studio from the outlets and // This file has been generated automatically by Visual Studio to store outlets and
// actions declared in your storyboard file. // actions made in the UI designer. If it is removed, they will be lost.
// Manual changes to this file will not be maintained. // Manual changes to this file may not be handled correctly.
// //
using Foundation; using Foundation;
using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using UIKit;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
[Register ("LockPasswordViewController")] [Register ("LockPasswordViewController")]
partial class LockPasswordViewController partial class LockPasswordViewController
{ {
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
UIKit.UIBarButtonItem CancelButton { get; set; }
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
UIKit.UITableView MainTableView { get; set; } UIKit.UIBarButtonItem CancelButton { get; set; }
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; } UIKit.UITableView MainTableView { get; set; }
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem SubmitButton { get; set; } UIKit.UINavigationItem NavItem { get; set; }
[Action ("CancelButton_Activated:")] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] UIKit.UIView OverlayView { get; set; }
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
[Action ("SubmitButton_Activated:")] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender); UIKit.UIBarButtonItem SubmitButton { get; set; }
void ReleaseDesignerOutlets () [Action ("AccountSwitchingBarButton_Activated:")]
{ partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
if (CancelButton != null) {
CancelButton.Dispose ();
CancelButton = null;
}
if (MainTableView != null) { [Action ("CancelButton_Activated:")]
MainTableView.Dispose (); partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
MainTableView = null;
}
if (NavItem != null) { [Action ("SubmitButton_Activated:")]
NavItem.Dispose (); partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
NavItem = null;
} void ReleaseDesignerOutlets ()
{
if (AccountSwitchingBarButton != null) {
AccountSwitchingBarButton.Dispose ();
AccountSwitchingBarButton = null;
}
if (SubmitButton != null) { if (CancelButton != null) {
SubmitButton.Dispose (); CancelButton.Dispose ();
SubmitButton = null; CancelButton = null;
} }
}
} if (MainTableView != null) {
} MainTableView.Dispose ();
MainTableView = null;
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
if (SubmitButton != null) {
SubmitButton.Dispose ();
SubmitButton = null;
}
if (OverlayView != null) {
OverlayView.Dispose ();
OverlayView = null;
}
}
}
}

View File

@ -1,19 +1,21 @@
using System; using System;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models; using Bit.iOS.Autofill.Models;
using Bit.iOS.Autofill.Utilities;
using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using CoreFoundation;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Bit.iOS.Core.Controllers;
using Bit.App.Resources;
using Bit.iOS.Core.Views;
using Bit.iOS.Autofill.Utilities;
using Bit.iOS.Core.Utilities;
using Bit.Core.Utilities;
using Bit.Core.Abstractions;
using Bit.App.Abstractions;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
public partial class LoginListViewController : ExtendedUITableViewController public partial class LoginListViewController : ExtendedUIViewController
{ {
public LoginListViewController(IntPtr handle) public LoginListViewController(IntPtr handle)
: base(handle) : base(handle)
@ -26,17 +28,30 @@ namespace Bit.iOS.Autofill
public CredentialProviderViewController CPViewController { get; set; } public CredentialProviderViewController CPViewController { get; set; }
public IPasswordRepromptService PasswordRepromptService { get; private set; } public IPasswordRepromptService PasswordRepromptService { get; private set; }
AccountSwitchingOverlayView _accountSwitchingOverlayView;
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
LazyResolve<IBroadcasterService> _broadcasterService = new LazyResolve<IBroadcasterService>("broadcasterService");
LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
bool _alreadyLoadItemsOnce = false;
public async override void ViewDidLoad() public async override void ViewDidLoad()
{ {
base.ViewDidLoad(); base.ViewDidLoad();
SubscribeSyncCompleted();
NavItem.Title = AppResources.Items; NavItem.Title = AppResources.Items;
CancelBarButton.Title = AppResources.Cancel; CancelBarButton.Title = AppResources.Cancel;
TableView.RowHeight = UITableView.AutomaticDimension; TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 44; TableView.EstimatedRowHeight = 44;
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
TableView.Source = new TableSource(this); TableView.Source = new TableSource(this);
await ((TableSource)TableView.Source).LoadItemsAsync(); await ((TableSource)TableView.Source).LoadItemsAsync();
_alreadyLoadItemsOnce = true;
var storageService = ServiceContainer.Resolve<IStorageService>("storageService"); var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var needsAutofillReplacement = await storageService.GetAsync<bool?>( var needsAutofillReplacement = await storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey); Core.Constants.AutofillNeedsIdentityReplacementKey);
@ -44,6 +59,16 @@ namespace Bit.iOS.Autofill
{ {
await ASHelpers.ReplaceAllIdentities(); await ASHelpers.ReplaceAllIdentities();
} }
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
{
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
} }
partial void CancelBarButton_Activated(UIBarButtonItem sender) partial void CancelBarButton_Activated(UIBarButtonItem sender)
@ -88,6 +113,35 @@ namespace Bit.iOS.Autofill
} }
} }
private void SubscribeSyncCompleted()
{
_broadcasterService.Value.Subscribe(nameof(LoginListViewController), message =>
{
if (message.Command == "syncCompleted" && _alreadyLoadItemsOnce)
{
DispatchQueue.MainQueue.DispatchAsync(async () =>
{
try
{
await ((TableSource)TableView.Source).LoadItemsAsync();
TableView.ReloadData();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
});
}
});
}
public override void ViewDidUnload()
{
base.ViewDidUnload();
_broadcasterService.Value.Unsubscribe(nameof(LoginListViewController));
}
public void DismissModal() public void DismissModal()
{ {
DismissViewController(true, async () => DismissViewController(true, async () =>
@ -99,13 +153,11 @@ namespace Bit.iOS.Autofill
public class TableSource : ExtensionTableSource public class TableSource : ExtensionTableSource
{ {
private Context _context;
private LoginListViewController _controller; private LoginListViewController _controller;
public TableSource(LoginListViewController controller) public TableSource(LoginListViewController controller)
: base(controller.Context, controller) : base(controller.Context, controller)
{ {
_context = controller.Context;
_controller = controller; _controller = controller;
} }

View File

@ -1,59 +1,89 @@
// WARNING // WARNING
// //
// This file has been generated automatically by Visual Studio from the outlets and // This file has been generated automatically by Visual Studio to store outlets and
// actions declared in your storyboard file. // actions made in the UI designer. If it is removed, they will be lost.
// Manual changes to this file will not be maintained. // Manual changes to this file may not be handled correctly.
// //
using Foundation; using Foundation;
using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using UIKit;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
[Register ("LoginListViewController")] [Register ("LoginListViewController")]
partial class LoginListViewController partial class LoginListViewController
{ {
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
UIKit.UIBarButtonItem AddBarButton { get; set; }
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem CancelBarButton { get; set; } UIKit.UIBarButtonItem AddBarButton { get; set; }
[Outlet] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; } UIKit.UIBarButtonItem CancelBarButton { get; set; }
[Action ("AddBarButton_Activated:")] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] UIKit.UIView MainView { get; set; }
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
[Action ("CancelBarButton_Activated:")] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] [GeneratedCode ("iOS Designer", "1.0")]
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender); UIKit.UINavigationItem NavItem { get; set; }
[Action ("SearchBarButton_Activated:")] [Outlet]
[GeneratedCode ("iOS Designer", "1.0")] UIKit.UIView OverlayView { get; set; }
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
void ReleaseDesignerOutlets () [Outlet]
{ UIKit.UITableView TableView { get; set; }
if (AddBarButton != null) {
AddBarButton.Dispose ();
AddBarButton = null;
}
if (CancelBarButton != null) { [Action ("AccountSwitchingBarButton_Activated:")]
CancelBarButton.Dispose (); partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
CancelBarButton = null;
}
if (NavItem != null) { [Action ("AddBarButton_Activated:")]
NavItem.Dispose (); partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
NavItem = null;
} [Action ("CancelBarButton_Activated:")]
} partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
}
} [Action ("SearchBarButton_Activated:")]
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
void ReleaseDesignerOutlets ()
{
if (AddBarButton != null) {
AddBarButton.Dispose ();
AddBarButton = null;
}
if (CancelBarButton != null) {
CancelBarButton.Dispose ();
CancelBarButton = null;
}
if (MainView != null) {
MainView.Dispose ();
MainView = null;
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
if (OverlayView != null) {
OverlayView.Dispose ();
OverlayView = null;
}
if (TableView != null) {
TableView.Dispose ();
TableView = null;
}
if (AccountSwitchingBarButton != null) {
AccountSwitchingBarButton.Dispose ();
AccountSwitchingBarButton = null;
}
}
}
}

View File

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="43"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="43">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -9,30 +13,27 @@
<scene sceneID="42"> <scene sceneID="42">
<objects> <objects>
<viewController id="43" customClass="CredentialProviderViewController" sceneMemberID="viewController"> <viewController id="43" customClass="CredentialProviderViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="40"/>
<viewControllerLayoutGuide type="bottom" id="41"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="44"> <view key="view" contentMode="scaleToFill" id="44">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="1713"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="1713">
<rect key="frame" x="66" y="316" width="282" height="44"/> <rect key="frame" x="66" y="396" width="282" height="44"/>
</imageView> </imageView>
</subviews> </subviews>
<viewLayoutGuide key="safeArea" id="YG6-2d-qpF"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstItem="1713" firstAttribute="centerY" secondItem="44" secondAttribute="centerY" constant="-30" id="1763"/> <constraint firstItem="1713" firstAttribute="centerY" secondItem="44" secondAttribute="centerY" constant="-30" id="1763"/>
<constraint firstItem="1713" firstAttribute="centerX" secondItem="44" secondAttribute="centerX" id="1764"/> <constraint firstItem="1713" firstAttribute="centerX" secondItem="YG6-2d-qpF" secondAttribute="centerX" id="1764"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
<segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/> <segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/>
<segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/> <segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/>
<segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/> <segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/>
<segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/> <segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/>
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -45,7 +46,7 @@
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController"> <navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController">
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="8A5-AR-QHS"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="8A5-AR-QHS">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -67,7 +68,7 @@
<objects> <objects>
<navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController"> <navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="1848"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="1848">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes"> <textAttributes key="titleTextAttributes">
@ -87,7 +88,7 @@
<objects> <objects>
<tableViewController id="2087" customClass="LoginAddViewController" sceneMemberID="viewController"> <tableViewController id="2087" customClass="LoginAddViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" allowsSelection="NO" rowHeight="50" sectionHeaderHeight="22" sectionFooterHeight="22" id="2088"> <tableView key="view" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" allowsSelection="NO" rowHeight="50" sectionHeaderHeight="22" sectionFooterHeight="22" id="2088">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@ -125,50 +126,79 @@
<!--Logins--> <!--Logins-->
<scene sceneID="2303"> <scene sceneID="2303">
<objects> <objects>
<tableViewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController"> <viewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="2305"> <view key="view" contentMode="scaleToFill" id="q9o-3n-3xL">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <subviews>
<prototypes> <tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2305">
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761"> <rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
<rect key="frame" x="0.0" y="22" width="414" height="44"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<autoresizingMask key="autoresizingMask"/> <prototypes>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/> <rect key="frame" x="0.0" y="44.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763"> <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/> <subviews>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/> <label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
<nil key="highlightedColor"/> <rect key="frame" x="20" y="4" width="35" height="21.5"/>
</label> <autoresizingMask key="autoresizingMask"/>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764"> <fontDescription key="fontDescription" type="system" pointSize="18"/>
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/> <color key="textColor" systemColor="darkTextColor"/>
<autoresizingMask key="autoresizingMask"/> <nil key="highlightedColor"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/> </label>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/> <label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
<nil key="highlightedColor"/> <rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
</label> <autoresizingMask key="autoresizingMask"/>
</subviews> <fontDescription key="fontDescription" type="system" pointSize="12"/>
</tableViewCellContentView> <color key="textColor" systemColor="darkTextColor"/>
</tableViewCell> <nil key="highlightedColor"/>
</prototypes> </label>
<connections> </subviews>
<outlet property="dataSource" destination="2304" id="2306"/> </tableViewCellContentView>
<outlet property="delegate" destination="2304" id="2307"/> </tableViewCell>
</connections> </prototypes>
</tableView> <connections>
<outlet property="dataSource" destination="2304" id="2306"/>
<outlet property="delegate" destination="2304" id="2307"/>
</connections>
</tableView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tq0-Ep-tHr" userLabel="OverlayView">
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="BQW-dG-XMM"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="4wL-FF-CVk"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="Tq0-Ep-tHr" secondAttribute="trailing" id="5BV-0y-vU1"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="2305" secondAttribute="bottom" id="6EB-rh-lLS"/>
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="eT6-Bv-JaR"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="2305" secondAttribute="trailing" id="ofJ-fL-adF"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="Tq0-Ep-tHr" secondAttribute="bottom" id="pBa-o1-Mtx"/>
<constraint firstItem="2305" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="pGe-1e-B4s"/>
<constraint firstItem="2305" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="xfQ-VQ-yWe"/>
</constraints>
</view>
<toolbarItems/> <toolbarItems/>
<navigationItem key="navigationItem" title="Logins" id="3734"> <navigationItem key="navigationItem" title="Logins" id="3734">
<barButtonItem key="leftBarButtonItem" title="Cancel" id="3735"> <leftBarButtonItems>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <barButtonItem title="Cancel" id="3735">
<connections> <color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/> <connections>
</connections> <action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
</barButtonItem> </connections>
</barButtonItem>
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="I0b-et-FGw" userLabel="Accoutn Switching Button">
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<action selector="AccountSwitchingBarButton_Activated:" destination="2304" id="dZn-bd-bC6"/>
</connections>
</barButtonItem>
</leftBarButtonItems>
<rightBarButtonItems> <rightBarButtonItems>
<barButtonItem systemItem="add" id="3736"> <barButtonItem systemItem="add" id="3736">
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -186,13 +216,17 @@
</navigationItem> </navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/> <simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections> <connections>
<outlet property="AccountSwitchingBarButton" destination="I0b-et-FGw" id="KZj-EO-7wd"/>
<outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/> <outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/>
<outlet property="CancelBarButton" destination="3735" id="name-outlet-3735"/> <outlet property="CancelBarButton" destination="3735" id="name-outlet-3735"/>
<outlet property="MainView" destination="q9o-3n-3xL" id="gjJ-12-71Q"/>
<outlet property="NavItem" destination="3734" id="name-outlet-3734"/> <outlet property="NavItem" destination="3734" id="name-outlet-3734"/>
<outlet property="OverlayView" destination="Tq0-Ep-tHr" id="igj-R2-gXJ"/>
<outlet property="TableView" destination="2305" id="aUe-Uz-iIb"/>
<segue destination="1845" kind="presentation" identifier="loginAddSegue" modalPresentationStyle="fullScreen" modalTransitionStyle="coverVertical" id="3731"/> <segue destination="1845" kind="presentation" identifier="loginAddSegue" modalPresentationStyle="fullScreen" modalTransitionStyle="coverVertical" id="3731"/>
<segue destination="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/> <segue destination="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/>
</connections> </connections>
</tableViewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1157" y="566"/> <point key="canvasLocation" x="1157" y="566"/>
@ -202,7 +236,7 @@
<objects> <objects>
<navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController"> <navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="4577"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="4577">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -222,37 +256,34 @@
<scene sceneID="4579"> <scene sceneID="4579">
<objects> <objects>
<viewController id="4576" customClass="PasswordGeneratorViewController" sceneMemberID="viewController"> <viewController id="4576" customClass="PasswordGeneratorViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="4571"/>
<viewControllerLayoutGuide type="bottom" id="4572"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="4930"> <view key="view" contentMode="scaleToFill" id="4930">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<containerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4933"> <containerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4933">
<rect key="frame" x="0.0" y="160.5" width="414" height="575.5"/> <rect key="frame" x="0.0" y="90.5" width="414" height="683.5"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections> <connections>
<segue destination="4912" kind="embed" id="6480"/> <segue destination="4912" kind="embed" id="6480"/>
</connections> </connections>
</containerView> </containerView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4940"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4940">
<rect key="frame" x="15" y="105" width="384" height="20.5"/> <rect key="frame" x="15" y="35" width="384" height="20.5"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/> <color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
<viewLayoutGuide key="safeArea" id="Kvt-lG-wyu"/>
<constraints> <constraints>
<constraint firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/> <constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/>
<constraint firstItem="4933" firstAttribute="top" secondItem="4940" secondAttribute="bottom" constant="35" id="6485"/> <constraint firstItem="4933" firstAttribute="top" secondItem="4940" secondAttribute="bottom" constant="35" id="6485"/>
<constraint firstItem="4933" firstAttribute="leading" secondItem="4930" secondAttribute="leading" id="6486"/> <constraint firstItem="4933" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" id="6486"/>
<constraint firstItem="4940" firstAttribute="leading" secondItem="4930" secondAttribute="leading" constant="15" id="6487"/> <constraint firstItem="4940" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" constant="15" id="6487"/>
<constraint firstItem="4940" firstAttribute="top" secondItem="4571" secondAttribute="bottom" constant="35" id="6488"/> <constraint firstItem="4940" firstAttribute="top" secondItem="Kvt-lG-wyu" secondAttribute="top" constant="35" id="6488"/>
<constraint firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/> <constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
<constraint firstItem="4572" firstAttribute="top" secondItem="4933" secondAttribute="bottom" id="6490"/> <constraint firstItem="Kvt-lG-wyu" firstAttribute="bottom" secondItem="4933" secondAttribute="bottom" id="6490"/>
</constraints> </constraints>
</view> </view>
<navigationItem key="navigationItem" title="Generate Password" id="4580"> <navigationItem key="navigationItem" title="Generate Password" id="4580">
@ -287,7 +318,7 @@
<objects> <objects>
<tableViewController id="4912" sceneMemberID="viewController"> <tableViewController id="4912" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="4913"> <tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="4913">
<rect key="frame" x="0.0" y="0.0" width="414" height="575.5"/> <rect key="frame" x="0.0" y="0.0" width="414" height="683.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections> <connections>
@ -301,15 +332,11 @@
<point key="canvasLocation" x="4708" y="-194"/> <point key="canvasLocation" x="4708" y="-194"/>
</scene> </scene>
<!--Navigation Controller--> <!--Navigation Controller-->
<!--Verify Fingerprint-->
<!--Verify PIN-->
<!--Navigation Controller-->
<!--Navigation Controller-->
<scene sceneID="6854"> <scene sceneID="6854">
<objects> <objects>
<navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController"> <navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="6857"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="6857">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -318,88 +345,139 @@
</textAttributes> </textAttributes>
</navigationBar> </navigationBar>
<connections> <connections>
<segue destination="7413" kind="relationship" relationship="rootViewController" id="8266"/> <segue destination="cn5-F4-59n" kind="relationship" relationship="rootViewController" id="Q23-VB-h61"/>
</connections> </connections>
</navigationController> </navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="390" y="1407"/> <point key="canvasLocation" x="120" y="1407"/>
</scene> </scene>
<!--Verify Master Password--> <!--Verify Master Password-->
<scene sceneID="7412"> <scene sceneID="5CE-bQ-Rfq">
<objects> <objects>
<tableViewController id="7413" customClass="LockPasswordViewController" sceneMemberID="viewController"> <viewController id="cn5-F4-59n" customClass="LockPasswordViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="7414"> <view key="view" contentMode="scaleToFill" id="0qM-RN-J2i" userLabel="Main View">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <subviews>
<connections> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="FcI-Ph-m9e">
<outlet property="dataSource" destination="7413" id="7415"/> <rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<outlet property="delegate" destination="7413" id="7416"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</connections> <prototypes>
</tableView> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="oQZ-wW-5uB">
<navigationItem key="navigationItem" title="Verify Master Password" id="8265"> <rect key="frame" x="0.0" y="49.5" width="414" height="44"/>
<barButtonItem key="leftBarButtonItem" title="Cancel" id="8268"> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oQZ-wW-5uB" id="SUk-LD-cXo">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="1cB-yn-7ii">
<rect key="frame" x="0.0" y="93.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1cB-yn-7ii" id="xFt-Jc-feN">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Fzo-Cj-aM4">
<rect key="frame" x="0.0" y="137.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fzo-Cj-aM4" id="vSb-0G-Bic">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<sections/>
</tableView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sDX-BN-qLw" userLabel="OverlayView">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="uMZ-kT-NSt"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="FcI-Ph-m9e" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="68e-VU-o4s"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="FcI-Ph-m9e" secondAttribute="bottom" id="7UM-br-sxz"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="sDX-BN-qLw" secondAttribute="bottom" id="QGT-ck-8TL"/>
<constraint firstItem="sDX-BN-qLw" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="aVW-Nw-awb"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="sDX-BN-qLw" secondAttribute="trailing" id="f5n-9J-y3c"/>
<constraint firstItem="FcI-Ph-m9e" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="sKL-iw-CWd"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="FcI-Ph-m9e" secondAttribute="trailing" id="xgg-of-dPl"/>
<constraint firstItem="sDX-BN-qLw" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="yO9-oz-zRn"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Verify Master Password" id="NCb-RV-Vqq">
<leftBarButtonItems>
<barButtonItem title="Cancel" id="Xoh-Zv-hhd">
<connections>
<action selector="CancelButton_Activated:" destination="cn5-F4-59n" id="1gM-mE-phn"/>
</connections>
</barButtonItem>
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="nwd-aM-kFD" userLabel="Accoutn Switching Button">
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<action selector="AccountSwitchingBarButton_Activated:" destination="cn5-F4-59n" id="vVZ-IM-rkU"/>
</connections>
</barButtonItem>
</leftBarButtonItems>
<barButtonItem key="rightBarButtonItem" title="Submit" id="gju-yD-EmI">
<connections> <connections>
<action selector="CancelButton_Activated:" destination="7413" id="8287"/> <action selector="SubmitButton_Activated:" destination="cn5-F4-59n" id="O1U-fk-BDh"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" title="Submit" id="8269">
<connections>
<action selector="SubmitButton_Activated:" destination="7413" id="8288"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>
<connections> <connections>
<outlet property="CancelButton" destination="8268" id="name-outlet-8268"/> <outlet property="AccountSwitchingBarButton" destination="nwd-aM-kFD" id="T8F-CN-2il"/>
<outlet property="MainTableView" destination="7414" id="name-outlet-7414"/> <outlet property="CancelButton" destination="Xoh-Zv-hhd" id="mwi-4K-maj"/>
<outlet property="NavItem" destination="8265" id="name-outlet-8265"/> <outlet property="MainTableView" destination="FcI-Ph-m9e" id="Ybv-5r-VGA"/>
<outlet property="SubmitButton" destination="8269" id="name-outlet-8269"/> <outlet property="NavItem" destination="NCb-RV-Vqq" id="L9b-At-x0A"/>
<outlet property="OverlayView" destination="sDX-BN-qLw" id="veu-q4-CeW"/>
<outlet property="SubmitButton" destination="gju-yD-EmI" id="mjg-yP-09M"/>
</connections> </connections>
</tableViewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="7419" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="vNI-gq-ubp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="955" y="1407"/> <point key="canvasLocation" x="836" y="1407"/>
</scene> </scene>
<!--Setup View Controller--> <!--Setup View Controller-->
<scene sceneID="10573"> <scene sceneID="10573">
<objects> <objects>
<viewController id="10570" customClass="SetupViewController" sceneMemberID="viewController"> <viewController id="10570" customClass="SetupViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="10565"/>
<viewControllerLayoutGuide type="bottom" id="10566"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="10575"> <view key="view" contentMode="scaleToFill" id="10575">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Extension Activated!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11092"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Extension Activated!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11092">
<rect key="frame" x="15" y="100" width="384" height="20.5"/> <rect key="frame" x="15" y="30" width="384" height="20.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/> <color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11093"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11093">
<rect key="frame" x="15" y="134.5" width="570" height="41"/> <rect key="frame" x="15" y="134.5" width="570" height="41"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/> <color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="check.png" translatesAutoresizingMaskIntoConstraints="NO" id="11094"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="check.png" translatesAutoresizingMaskIntoConstraints="NO" id="11094">
<rect key="frame" x="255" y="205.5" width="90" height="90"/> <rect key="frame" x="255" y="205.5" width="90" height="90"/>
</imageView> </imageView>
</subviews> </subviews>
<viewLayoutGuide key="safeArea" id="hcg-mr-Ilx"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstItem="11092" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11114"/> <constraint firstItem="11092" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11114"/>
<constraint firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/> <constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
<constraint firstItem="11092" firstAttribute="top" secondItem="10565" secondAttribute="bottom" constant="30" id="11116"/> <constraint firstItem="11092" firstAttribute="top" secondItem="hcg-mr-Ilx" secondAttribute="top" constant="30" id="11116"/>
<constraint firstItem="11093" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11119"/> <constraint firstItem="11093" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11119"/>
<constraint firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/> <constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
<constraint firstItem="11093" firstAttribute="top" secondItem="11092" secondAttribute="bottom" constant="20" id="11121"/> <constraint firstItem="11093" firstAttribute="top" secondItem="11092" secondAttribute="bottom" constant="20" id="11121"/>
<constraint firstItem="11094" firstAttribute="centerX" secondItem="10575" secondAttribute="centerX" id="11122"/> <constraint firstItem="11094" firstAttribute="centerX" secondItem="hcg-mr-Ilx" secondAttribute="centerX" id="11122"/>
<constraint firstItem="11094" firstAttribute="top" secondItem="11093" secondAttribute="bottom" constant="30" id="11123"/> <constraint firstItem="11094" firstAttribute="top" secondItem="11093" secondAttribute="bottom" constant="30" id="11123"/>
</constraints> </constraints>
</view> </view>
@ -429,7 +507,7 @@
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController"> <navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController">
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="10583"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="10583">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes"> <textAttributes key="titleTextAttributes">
@ -450,7 +528,7 @@
<objects> <objects>
<tableViewController id="11543" customClass="LoginSearchViewController" sceneMemberID="viewController"> <tableViewController id="11543" customClass="LoginSearchViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="11545"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="11545">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> <rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<searchBar key="tableHeaderView" contentMode="redraw" id="13084"> <searchBar key="tableHeaderView" contentMode="redraw" id="13084">
@ -463,10 +541,10 @@
</searchBar> </searchBar>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="11548"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="11548">
<rect key="frame" x="0.0" y="72" width="414" height="44"/> <rect key="frame" x="0.0" y="88.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="11548" id="11549"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="11548" id="11549">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView> </tableViewCellContentView>
</tableViewCell> </tableViewCell>
@ -506,7 +584,7 @@
<objects> <objects>
<navigationController id="11552" sceneMemberID="viewController"> <navigationController id="11552" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="11554"> <navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="11554">
<rect key="frame" x="0.0" y="20" width="414" height="50"/> <rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes"> <textAttributes key="titleTextAttributes">
@ -522,8 +600,19 @@
<point key="canvasLocation" x="1920" y="908"/> <point key="canvasLocation" x="1920" y="908"/>
</scene> </scene>
</scenes> </scenes>
<inferredMetricsTieBreakers>
<segue reference="12574"/>
<segue reference="3731"/>
</inferredMetricsTieBreakers>
<resources> <resources>
<image name="check.png" width="90" height="90"/> <image name="check.png" width="90" height="90"/>
<image name="logo.png" width="282" height="44"/> <image name="logo.png" width="282" height="44"/>
<image name="person.2" catalog="system" width="128" height="81"/>
<systemColor name="darkTextColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources> </resources>
</document> </document>

View File

@ -15,7 +15,7 @@ namespace Bit.iOS.Autofill.Utilities
{ {
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath, public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController, ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
UITableViewController controller, IPasswordRepromptService passwordRepromptService, UIViewController controller, IPasswordRepromptService passwordRepromptService,
string loginAddSegue) string loginAddSegue)
{ {
tableView.DeselectRow(indexPath, true); tableView.DeselectRow(indexPath, true);

View File

@ -0,0 +1,509 @@
using System;
using UIKit;
using Foundation;
using Bit.iOS.Core.Views;
using Bit.App.Resources;
using Bit.iOS.Core.Utilities;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.Domain;
using Bit.Core.Enums;
using Bit.App.Pages;
using Bit.App.Models;
using Xamarin.Forms;
using Bit.Core;
namespace Bit.iOS.Core.Controllers
{
public abstract class BaseLockPasswordViewController : ExtendedUIViewController
{
private IVaultTimeoutService _vaultTimeoutService;
private ICryptoService _cryptoService;
private IDeviceActionService _deviceActionService;
private IStateService _stateService;
private IStorageService _secureStorageService;
private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
private bool _pinLock;
private bool _biometricLock;
private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false;
private bool _usesKeyConnector;
private bool _biometricUnlockOnly = false;
protected bool autofillExtension = false;
public BaseLockPasswordViewController(IntPtr handle)
: base(handle)
{ }
public abstract UINavigationItem BaseNavItem { get; }
public abstract UIBarButtonItem BaseCancelButton { get; }
public abstract UIBarButtonItem BaseSubmitButton { get; }
public abstract Action Success { get; }
public abstract Action Cancel { get; }
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
AppResources.MasterPassword, useButton: true);
public string BiometricIntegrityKey { get; set; }
public UITableViewCell BiometricCell
{
get
{
var cell = new UITableViewCell();
cell.BackgroundColor = ThemeHelpers.BackgroundColor;
if (_biometricIntegrityValid)
{
var biometricButtonText = _deviceActionService.SupportsFaceBiometric() ?
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;
cell.TextLabel.Text = biometricButtonText;
}
else
{
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
cell.TextLabel.Lines = 0;
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
cell.TextLabel.Text = AppResources.BiometricInvalidatedExtension;
}
return cell;
}
}
public abstract UITableView TableView { get; }
public override async void ViewDidLoad()
{
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
// We re-use the lock screen for autofill extension to verify master password
// when trying to access protected items.
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{
_passwordReprompt = true;
_isPinProtected = false;
_isPinProtectedWithKey = false;
_pinLock = false;
_biometricLock = false;
}
else
{
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _cryptoService.HasKeyAsync();
_biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
}
if (_pinLock)
{
BaseNavItem.Title = AppResources.VerifyPIN;
}
else if (_usesKeyConnector)
{
BaseNavItem.Title = AppResources.UnlockVault;
}
else
{
BaseNavItem.Title = AppResources.VerifyMasterPassword;
}
BaseCancelButton.Title = AppResources.Cancel;
if (_biometricUnlockOnly)
{
BaseSubmitButton.Title = null;
BaseSubmitButton.Enabled = false;
}
else
{
BaseSubmitButton.Title = AppResources.Submit;
}
var descriptor = UIFontDescriptor.PreferredBody;
if (!_biometricUnlockOnly)
{
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
{
CheckPasswordAsync().GetAwaiter().GetResult();
return true;
};
if (_pinLock)
{
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
}
MasterPasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
MasterPasswordCell.Button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
MasterPasswordCell.Button.TouchUpInside += (sender, e) =>
{
MasterPasswordCell.TextField.SecureTextEntry = !MasterPasswordCell.TextField.SecureTextEntry;
MasterPasswordCell.Button.SetTitle(MasterPasswordCell.TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
};
}
if (TableView != null)
{
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
}
TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 70;
TableView.Source = new TableSource(this);
TableView.AllowsSelection = true;
base.ViewDidLoad();
if (_biometricLock)
{
if (!_biometricIntegrityValid)
{
return;
}
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync());
});
}
}
public override async void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
// Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector)
{
if (!(_pinLock || _biometricLock) ||
(_biometricLock && !_biometricIntegrityValid))
{
PromptSSO();
}
}
else if (!_biometricLock || !_biometricIntegrityValid)
{
MasterPasswordCell.TextField.BecomeFirstResponder();
}
}
protected async Task CheckPasswordAsync()
{
if (string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok);
PresentViewController(alert, true, null);
return;
}
var email = await _stateService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock)
{
var failed = true;
try
{
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
else
{
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2);
}
}
catch
{
failed = true;
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
return;
}
InvalidValue();
}
}
else
{
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null)
{
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey)
{
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash);
}
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
if (passwordValid)
{
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
}
else
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
return;
}
InvalidValue();
}
}
}
public async Task PromptBiometricAsync()
{
if (!_biometricLock || !_biometricIntegrityValid)
{
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
DoContinue();
}
}
public void PromptSSO()
{
var loginPage = new LoginSsoPage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesToPage(loginPage);
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
{
vm.SsoAuthSuccessAction = () => DoContinue();
vm.CloseAction = Cancel;
}
var navigationPage = new NavigationPage(loginPage);
var loginController = navigationPage.CreateViewController();
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginController, true, null);
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
{
var hasKey = await _cryptoService.HasKeyAsync();
if (!hasKey)
{
await _cryptoService.SetKeyAsync(key);
}
DoContinue(masterPassword);
}
private async void DoContinue(bool masterPassword = false)
{
if (masterPassword)
{
await _stateService.SetPasswordVerifiedAutofillAsync(true);
}
await EnableBiometricsIfNeeded();
await _stateService.SetBiometricLockedAsync(false);
MasterPasswordCell.TextField.ResignFirstResponder();
Success();
}
private async Task EnableBiometricsIfNeeded()
{
// Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync(BiometricIntegrityKey);
}
}
private void InvalidValue()
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) =>
{
MasterPasswordCell.TextField.Text = string.Empty;
MasterPasswordCell.TextField.BecomeFirstResponder();
});
PresentViewController(alert, true, null);
}
private async Task LogOutAsync()
{
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
var authService = ServiceContainer.Resolve<IAuthService>("authService");
authService.LogOut(() =>
{
Cancel?.Invoke();
});
}
public class TableSource : ExtendedUITableViewSource
{
private readonly BaseLockPasswordViewController _controller;
public TableSource(BaseLockPasswordViewController controller)
{
_controller = controller;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
if (indexPath.Section == 0)
{
if (indexPath.Row == 0)
{
if (_controller._biometricUnlockOnly)
{
return _controller.BiometricCell;
}
else
{
return _controller.MasterPasswordCell;
}
}
}
else if (indexPath.Section == 1)
{
if (indexPath.Row == 0)
{
if (_controller._passwordReprompt)
{
var cell = new ExtendedUITableViewCell();
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
cell.TextLabel.Lines = 0;
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
return cell;
}
else if (!_controller._biometricUnlockOnly)
{
return _controller.BiometricCell;
}
}
}
return new ExtendedUITableViewCell();
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return UITableView.AutomaticDimension;
}
public override nint NumberOfSections(UITableView tableView)
{
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
_controller._passwordReprompt
? 2
: 1;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
if (section <= 1)
{
return 1;
}
return 0;
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
return section == 1 ? 0.00001f : UITableView.AutomaticDimension;
}
public override string TitleForHeader(UITableView tableView, nint section)
{
return null;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
tableView.DeselectRow(indexPath, true);
tableView.EndEditing(true);
if (indexPath.Row == 0 &&
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
indexPath.Section == 1))
{
var task = _controller.PromptBiometricAsync();
return;
}
var cell = tableView.CellAt(indexPath);
if (cell == null)
{
return;
}
if (cell is ISelectable selectableCell)
{
selectableCell.Select();
}
}
}
}
}

View File

@ -18,6 +18,8 @@ using Bit.Core;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
{ {
// TODO: Leaving this here until all inheritance is changed to use BaseLockPasswordViewController instead of UITableViewController
[Obsolete("Use BaseLockPasswordViewController instead")]
public abstract class LockPasswordViewController : ExtendedUITableViewController public abstract class LockPasswordViewController : ExtendedUITableViewController
{ {
private IVaultTimeoutService _vaultTimeoutService; private IVaultTimeoutService _vaultTimeoutService;

View File

@ -0,0 +1,69 @@
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Core.Utilities
{
public class AccountSwitchingOverlayHelper
{
IStateService _stateService;
IMessagingService _messagingService;
ILogger _logger;
public AccountSwitchingOverlayHelper()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
}
public async Task<UIImage> CreateAvatarImageAsync()
{
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
return avatarUIImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
}
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
{
var overlay = new AccountSwitchingOverlayView()
{
LongPressAccountEnabled = false
};
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
overlay.BindingContext = vm;
var renderer = Platform.CreateRenderer(overlay.Content);
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
var view = renderer.NativeView;
view.TranslatesAutoresizingMaskIntoConstraints = false;
containerView.AddSubview(view);
containerView.AddConstraints(new NSLayoutConstraint[]
{
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, view, NSLayoutAttribute.Trailing, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, view, NSLayoutAttribute.Leading, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, view, NSLayoutAttribute.Top, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, view, NSLayoutAttribute.Bottom, 1f, 0f)
});
containerView.Hidden = true;
return overlay;
}
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
{
var overlayVisible = accountSwitchingOverlayView.IsVisible;
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
containerView.Hidden = false;
containerView.UserInteractionEnabled = !overlayVisible;
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Core.Utilities
{
public static class ImageSourceExtensions
{
/// <summary>
/// Gets the native image from the ImageSource.
/// Taken from https://github.com/xamarin/Xamarin.Forms/blob/02dee20dfa1365d0104758e534581d1fa5958990/Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs#L264
/// </summary>
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
{
if (source == null || source.IsEmpty)
return null;
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
if (handler == null)
return null;
try
{
float scale = (float)UIScreen.MainScreen.Scale;
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
}
catch (OperationCanceledException)
{
Log.Warning("Image loading", "Image load cancelled");
}
catch (Exception ex)
{
Log.Warning("Image loading", $"Image load failed: {ex}");
}
return null;
}
}
}

View File

@ -7,6 +7,7 @@ using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -172,6 +173,15 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Resolve<ICryptoService>("cryptoService")); ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper); ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
if (postBootstrapFunc != null) if (postBootstrapFunc != null)
{ {
await postBootstrapFunc.Invoke(); await postBootstrapFunc.Invoke();

View File

@ -1,4 +1,10 @@
using Bit.App.Resources; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -6,12 +12,6 @@ using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Models; using Bit.iOS.Core.Models;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Foundation; using Foundation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UIKit; using UIKit;
namespace Bit.iOS.Core.Views namespace Bit.iOS.Core.Views
@ -122,7 +122,10 @@ namespace Bit.iOS.Core.Views
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath) public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{ {
if (Items == null || Items.Count() == 0 || cell == null) if (Items == null
|| !Items.Any()
|| cell?.TextLabel == null
|| cell.DetailTextLabel == null)
{ {
return; return;
} }

View File

@ -195,6 +195,9 @@
<Compile Include="Services\ClipboardService.cs" /> <Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\FontElementExtensions.cs" /> <Compile Include="Utilities\FontElementExtensions.cs" />
<Compile Include="Effects\ScrollViewContentInsetAdjustmentBehaviorEffect.cs" /> <Compile Include="Effects\ScrollViewContentInsetAdjustmentBehaviorEffect.cs" />
<Compile Include="Utilities\AccountSwitchingOverlayHelper.cs" />
<Compile Include="Utilities\ImageSourceExtensions.cs" />
<Compile Include="Controllers\BaseLockPasswordViewController.cs" />
<Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" /> <Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" />
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" /> <Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" />
<Compile Include="Utilities\UISearchBarExtensions.cs" /> <Compile Include="Utilities\UISearchBarExtensions.cs" />