mirror of
https://github.com/bitwarden/mobile
synced 2026-01-19 00:43:18 +00:00
Merge branch 'master' into feature/totp-tab
# Conflicts: # src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs # src/App/Pages/Vault/ScanPage.xaml.cs
This commit is contained in:
@@ -84,7 +84,7 @@
|
||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||
<PackageReference Include="Xamarin.Essentials">
|
||||
<Version>1.7.2</Version>
|
||||
<Version>1.7.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||
<Version>122.0.0</Version>
|
||||
|
||||
@@ -12,7 +12,6 @@ using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Services;
|
||||
using Bit.Droid.Utilities;
|
||||
using Plugin.CurrentActivity;
|
||||
using Plugin.Fingerprint;
|
||||
using Xamarin.Android.Net;
|
||||
@@ -20,6 +19,7 @@ using System.Net.Http;
|
||||
using System.Net;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
@@ -62,6 +62,15 @@ namespace Bit.Droid
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||
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 (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
@@ -121,13 +130,14 @@ namespace Bit.Droid
|
||||
var secureStorageService = new SecureStorageService();
|
||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||
var stateMigrationService =
|
||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var deviceActionService = new DeviceActionService(stateService, messagingService,
|
||||
var clipboardService = new ClipboardService(stateService);
|
||||
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
|
||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||
messagingService, broadcasterService);
|
||||
var biometricService = new BiometricService();
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
@@ -142,7 +152,7 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||
ServiceContainer.Register<IStateService>("stateService", stateService);
|
||||
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
||||
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
|
||||
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.Core;
|
||||
using Android.OS;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Droid.Receivers;
|
||||
using Plugin.CurrentActivity;
|
||||
@@ -26,13 +26,41 @@ namespace Bit.Droid.Services
|
||||
PendingIntentFlags.UpdateCurrent));
|
||||
}
|
||||
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||
{
|
||||
await Clipboard.SetTextAsync(text);
|
||||
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
{
|
||||
await Clipboard.SetTextAsync(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyToClipboard(text, isSensitive);
|
||||
}
|
||||
|
||||
await ClearClipboardAlarmAsync(expiresInMs);
|
||||
}
|
||||
|
||||
public bool IsCopyNotificationHandledByPlatform()
|
||||
{
|
||||
// Android 13+ provides built-in notification when text is copied to the clipboard
|
||||
return (int)Build.VERSION.SdkInt >= 33;
|
||||
}
|
||||
|
||||
private void CopyToClipboard(string text, bool isSensitive = true)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var clipboardManager = activity.GetSystemService(
|
||||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
||||
var clipData = ClipData.NewPlainText("bitwarden", text);
|
||||
if (isSensitive)
|
||||
{
|
||||
clipData.Description.Extras ??= new PersistableBundle();
|
||||
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
|
||||
}
|
||||
clipboardManager.PrimaryClip = clipData;
|
||||
}
|
||||
|
||||
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
|
||||
{
|
||||
var clearMs = expiresInMs;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
public class DeviceActionService : IDeviceActionService
|
||||
{
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
@@ -47,11 +48,13 @@ namespace Bit.Droid.Services
|
||||
private string _userAgent;
|
||||
|
||||
public DeviceActionService(
|
||||
IClipboardService clipboardService,
|
||||
IStateService stateService,
|
||||
IMessagingService messagingService,
|
||||
IBroadcasterService broadcasterService,
|
||||
Func<IEventService> eventServiceFunc)
|
||||
{
|
||||
_clipboardService = clipboardService;
|
||||
_stateService = stateService;
|
||||
_messagingService = messagingService;
|
||||
_broadcasterService = broadcasterService;
|
||||
@@ -929,20 +932,12 @@ namespace Bit.Droid.Services
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (totp != null)
|
||||
{
|
||||
CopyToClipboard(totp);
|
||||
await _clipboardService.CopyTextAsync(totp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToClipboard(string text)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var clipboardManager = activity.GetSystemService(
|
||||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
|
||||
}
|
||||
|
||||
public float GetSystemFontSizeScale()
|
||||
{
|
||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
||||
{
|
||||
theme = "dark";
|
||||
theme = ThemeManager.Dark;
|
||||
}
|
||||
|
||||
if (theme == "dark" || theme == "black" || theme == "nord")
|
||||
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
|
||||
{
|
||||
LightTheme = false;
|
||||
}
|
||||
|
||||
12
src/App/Abstractions/IAccountsManager.cs
Normal file
12
src/App/Abstractions/IAccountsManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal file
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
|
||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||
</ItemGroup>
|
||||
@@ -128,6 +128,7 @@
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
<Folder Include="Utilities\AccountManagement\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -420,5 +421,6 @@
|
||||
<None Remove="Behaviors\" />
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||
<None Remove="Utilities\AccountManagement\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -16,7 +17,7 @@ using Xamarin.Forms.Xaml;
|
||||
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
namespace Bit.App
|
||||
{
|
||||
public partial class App : Application
|
||||
public partial class App : Application, IAccountsManagerHost
|
||||
{
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
@@ -27,6 +28,7 @@ namespace Bit.App
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IStorageService _secureStorageService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IAccountsManager _accountsManager;
|
||||
|
||||
private static bool _isResumed;
|
||||
|
||||
@@ -47,6 +49,9 @@ namespace Bit.App
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
|
||||
_accountsManager.Init(() => Options, this);
|
||||
|
||||
Bootstrap();
|
||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||
@@ -71,30 +76,6 @@ namespace Bit.App
|
||||
_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")
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
@@ -109,22 +90,10 @@ namespace Bit.App
|
||||
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")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await SetMainPageAsync();
|
||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||
}
|
||||
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||
message.Command == "popAllAndGoToTabMyVault" ||
|
||||
@@ -168,7 +137,6 @@ namespace Bit.App
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,6 +202,7 @@ namespace Bit.App
|
||||
|
||||
private async Task ResumedAsync()
|
||||
{
|
||||
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
_messagingService.Send("startEventTimer");
|
||||
await UpdateThemeAsync();
|
||||
@@ -263,102 +232,6 @@ namespace Bit.App
|
||||
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()
|
||||
{
|
||||
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
||||
@@ -420,7 +293,7 @@ namespace Bit.App
|
||||
UpdateThemeAsync();
|
||||
};
|
||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||
var mainPageTask = SetMainPageAsync();
|
||||
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
|
||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||
}
|
||||
|
||||
@@ -441,23 +314,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;
|
||||
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
@@ -483,8 +341,44 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ namespace Bit.App.Controls
|
||||
|
||||
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
||||
|
||||
public bool LongPressAccountEnabled { get; set; } = true;
|
||||
|
||||
public Action AfterHide { get; set; }
|
||||
|
||||
public async Task ToggleVisibilityAsync()
|
||||
{
|
||||
if (IsVisible)
|
||||
@@ -135,6 +139,8 @@ namespace Bit.App.Controls
|
||||
|
||||
// remove overlay
|
||||
IsVisible = false;
|
||||
|
||||
AfterHide?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,7 +173,7 @@ namespace Bit.App.Controls
|
||||
|
||||
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
||||
{
|
||||
if (!item.IsAccount)
|
||||
if (!LongPressAccountEnabled || !item.IsAccount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,23 +45,28 @@ namespace Bit.App.Controls
|
||||
|
||||
public ICommand LongPressAccountCommand { get; }
|
||||
|
||||
public bool FromIOSExtension { get; set; }
|
||||
|
||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||
{
|
||||
if (item.AccountView.IsAccount)
|
||||
if (!item.AccountView.IsAccount)
|
||||
{
|
||||
if (!item.AccountView.IsActive)
|
||||
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.AccountView.IsActive)
|
||||
{
|
||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
if (FromIOSExtension)
|
||||
{
|
||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||
_messagingService.Send("switchedAccount");
|
||||
}
|
||||
else if (AllowActiveAccountSelection)
|
||||
{
|
||||
_messagingService.Send("switchedAccount");
|
||||
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (AllowActiveAccountSelection)
|
||||
{
|
||||
_messagingService.Send("addAccount");
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedCollectionView : CollectionView
|
||||
{
|
||||
public string ExtraDataForLogging { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||
ItemsSource="{Binding History}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
StyleClass="list, list-platform">
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Generator History Page">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
|
||||
<Grid
|
||||
|
||||
@@ -58,8 +58,7 @@ namespace Bit.App.Pages
|
||||
private async void CopyAsync(GeneratedPasswordHistory ph)
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(ph.Password);
|
||||
_platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
}
|
||||
|
||||
public async Task UpdateOnThemeChanged()
|
||||
|
||||
@@ -5,7 +5,6 @@ using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -319,8 +318,7 @@ namespace Bit.App.Pages
|
||||
public async Task CopyAsync()
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(Password);
|
||||
_platformUtilsService.ShowToast("success", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
}
|
||||
|
||||
private void LoadFromOptions()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.SendGroupingsPage"
|
||||
@@ -138,7 +138,8 @@
|
||||
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform" />
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Send Groupings Page" />
|
||||
</RefreshView>
|
||||
</StackLayout>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
@@ -66,7 +66,8 @@
|
||||
VerticalOptions="FillAndExpand"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform">
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Sends Page">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="views:SendView">
|
||||
<controls:SendViewCell
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
VerticalOptions="FillAndExpand"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform">
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Folders Page">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="views:FolderView">
|
||||
<controls:ExtendedStackLayout
|
||||
|
||||
@@ -33,6 +33,23 @@
|
||||
StyleClass="box-footer-label"
|
||||
Text="{u:I18n ThemeDescription}" />
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
StyleClass="box"
|
||||
IsVisible="{Binding ShowAutoDarkThemeOptions}">
|
||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||
<Label
|
||||
Text="{u:I18n DefaultDarkTheme}"
|
||||
StyleClass="box-label" />
|
||||
<Picker
|
||||
x:Name="_autoDarkThemePicker"
|
||||
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
||||
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
<Label
|
||||
StyleClass="box-footer-label"
|
||||
Text="{u:I18n DefaultDarkThemeDescription}" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||
<Label
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Bit.App.Pages
|
||||
_vm = BindingContext as OptionsPageViewModel;
|
||||
_vm.Page = this;
|
||||
_themePicker.ItemDisplayBinding = new Binding("Value");
|
||||
_autoDarkThemePicker.ItemDisplayBinding = new Binding("Value");
|
||||
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
|
||||
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
@@ -29,6 +30,7 @@ namespace Bit.App.Pages
|
||||
else
|
||||
{
|
||||
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
|
||||
private bool _disableAutoTotpCopy;
|
||||
private int _clearClipboardSelectedIndex;
|
||||
private int _themeSelectedIndex;
|
||||
private int _autoDarkThemeSelectedIndex;
|
||||
private int _uriMatchSelectedIndex;
|
||||
private bool _inited;
|
||||
private bool _updatingAutofill;
|
||||
@@ -53,10 +54,16 @@ namespace Bit.App.Pages
|
||||
ThemeOptions = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
|
||||
new KeyValuePair<string, string>("light", AppResources.Light),
|
||||
new KeyValuePair<string, string>("dark", AppResources.Dark),
|
||||
new KeyValuePair<string, string>("black", AppResources.Black),
|
||||
new KeyValuePair<string, string>("nord", "Nord"),
|
||||
new KeyValuePair<string, string>(ThemeManager.Light, AppResources.Light),
|
||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||
};
|
||||
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||
};
|
||||
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
||||
{
|
||||
@@ -71,6 +78,7 @@ namespace Bit.App.Pages
|
||||
|
||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
|
||||
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
|
||||
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
|
||||
|
||||
public int ClearClipboardSelectedIndex
|
||||
@@ -80,7 +88,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (SetProperty(ref _clearClipboardSelectedIndex, value))
|
||||
{
|
||||
var task = SaveClipboardChangedAsync();
|
||||
SaveClipboardChangedAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,9 +98,25 @@ namespace Bit.App.Pages
|
||||
get => _themeSelectedIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _themeSelectedIndex, value))
|
||||
if (SetProperty(ref _themeSelectedIndex, value,
|
||||
additionalPropertyNames: new[] { nameof(ShowAutoDarkThemeOptions) })
|
||||
)
|
||||
{
|
||||
var task = SaveThemeAsync();
|
||||
SaveThemeAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowAutoDarkThemeOptions => ThemeOptions[ThemeSelectedIndex].Key == null;
|
||||
|
||||
public int AutoDarkThemeSelectedIndex
|
||||
{
|
||||
get => _autoDarkThemeSelectedIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _autoDarkThemeSelectedIndex, value))
|
||||
{
|
||||
SaveThemeAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +128,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (SetProperty(ref _uriMatchSelectedIndex, value))
|
||||
{
|
||||
var task = SaveDefaultUriAsync();
|
||||
SaveDefaultUriAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +140,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (SetProperty(ref _disableFavicon, value))
|
||||
{
|
||||
var task = UpdateDisableFaviconAsync();
|
||||
UpdateDisableFaviconAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +152,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (SetProperty(ref _disableAutoTotpCopy, value))
|
||||
{
|
||||
var task = UpdateAutoTotpCopyAsync();
|
||||
UpdateAutoTotpCopyAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +164,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (SetProperty(ref _autofillDisableSavePrompt, value))
|
||||
{
|
||||
var task = UpdateAutofillDisableSavePromptAsync();
|
||||
UpdateAutofillDisableSavePromptAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +190,8 @@ namespace Bit.App.Pages
|
||||
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||
var theme = await _stateService.GetThemeAsync();
|
||||
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
||||
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
|
||||
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
|
||||
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
|
||||
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
|
||||
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
|
||||
@@ -202,8 +228,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (_inited && ThemeSelectedIndex > -1)
|
||||
{
|
||||
var theme = ThemeOptions[ThemeSelectedIndex].Key;
|
||||
await _stateService.SetThemeAsync(theme);
|
||||
await _stateService.SetThemeAsync(ThemeOptions[ThemeSelectedIndex].Key);
|
||||
await _stateService.SetAutoDarkThemeAsync(AutoDarkThemeOptions[AutoDarkThemeSelectedIndex].Key);
|
||||
ThemeManager.SetTheme(Application.Current.Resources);
|
||||
_messagingService.Send("updatedTheme");
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform" />
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Settings Page" />
|
||||
|
||||
</pages:BaseContentPage>
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await _vm.UpdatePinAsync();
|
||||
}
|
||||
else if (item.Name == AppResources.ReportCrashLogs)
|
||||
else if (item.Name == AppResources.SubmitCrashLogs)
|
||||
{
|
||||
await _vm.LoggerReportingAsync();
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ namespace Bit.App.Pages
|
||||
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
||||
};
|
||||
|
||||
var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options);
|
||||
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
|
||||
|
||||
if (selection == null || selection == AppResources.Cancel)
|
||||
{
|
||||
@@ -525,7 +525,7 @@ namespace Bit.App.Pages
|
||||
#if !FDROID
|
||||
new SettingsPageListItem
|
||||
{
|
||||
Name = AppResources.ReportCrashLogs,
|
||||
Name = AppResources.SubmitCrashLogs,
|
||||
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||
},
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
||||
CipherType? type = null,
|
||||
string folderId = null,
|
||||
string collectionId = null,
|
||||
string organizationId = null,
|
||||
string name = null,
|
||||
string uri = null,
|
||||
bool fromAutofill = false,
|
||||
@@ -51,6 +52,7 @@ namespace Bit.App.Pages
|
||||
_vm.CipherId = cipherId;
|
||||
_vm.FolderId = folderId == "none" ? null : folderId;
|
||||
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
|
||||
_vm.OrganizationId = organizationId;
|
||||
_vm.Type = type;
|
||||
_vm.DefaultName = name ?? appOptions?.SaveName;
|
||||
_vm.DefaultUri = uri ?? appOptions?.Uri;
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform" />
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Autofill Ciphers Page" />
|
||||
</StackLayout>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
@@ -49,6 +49,31 @@
|
||||
</ContentPage.Resources>
|
||||
|
||||
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
|
||||
<StackLayout
|
||||
IsVisible="{Binding ShowVaultFilter}"
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
Margin="0,5,0,0">
|
||||
<Label
|
||||
Text="{Binding VaultFilterDescription}"
|
||||
LineBreakMode="TailTruncation"
|
||||
Margin="10,0"
|
||||
StyleClass="text-md, text-muted"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Filter}" />
|
||||
<controls:MiButton
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
StyleClass="list-row-button-text, list-row-button-platform"
|
||||
Command="{Binding VaultFilterCommand}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Filter}" />
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
|
||||
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
|
||||
StyleClass="text-muted"
|
||||
@@ -68,7 +93,8 @@
|
||||
VerticalOptions="FillAndExpand"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform">
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Ciphers Page">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="views:CipherView">
|
||||
<controls:CipherViewCell
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace Bit.App.Pages
|
||||
private CiphersPageViewModel _vm;
|
||||
private bool _hasFocused;
|
||||
|
||||
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string autofillUrl = null, bool deleted = false)
|
||||
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
|
||||
string autofillUrl = null, bool deleted = false)
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as CiphersPageViewModel;
|
||||
@@ -33,6 +34,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
_vm.PageTitle = AppResources.SearchVault;
|
||||
}
|
||||
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
|
||||
@@ -5,17 +5,17 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class CiphersPageViewModel : BaseViewModel
|
||||
public class CiphersPageViewModel : VaultFilterViewModel
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly ICipherService _cipherService;
|
||||
@@ -23,7 +23,10 @@ namespace Bit.App.Pages
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private CancellationTokenSource _searchCancellationTokenSource;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private bool _showNoData;
|
||||
private bool _showList;
|
||||
@@ -37,6 +40,9 @@ namespace Bit.App.Pages
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
@@ -48,6 +54,11 @@ namespace Bit.App.Pages
|
||||
public string AutofillUrl { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
protected override ICipherService cipherService => _cipherService;
|
||||
protected override IPolicyService policyService => _policyService;
|
||||
protected override IOrganizationService organizationService => _organizationService;
|
||||
protected override ILogger logger => _logger;
|
||||
|
||||
public bool ShowNoData
|
||||
{
|
||||
get => _showNoData;
|
||||
@@ -76,11 +87,9 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
await InitVaultFilterAsync(true);
|
||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
||||
{
|
||||
Search((Page as CiphersPage).SearchBar.Text, 200);
|
||||
}
|
||||
PerformSearchIfPopulated();
|
||||
}
|
||||
|
||||
public void Search(string searchText, int? timeout = null)
|
||||
@@ -107,8 +116,9 @@ namespace Bit.App.Pages
|
||||
}
|
||||
try
|
||||
{
|
||||
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
|
||||
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
||||
cts.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -192,6 +202,19 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformSearchIfPopulated()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
||||
{
|
||||
Search((Page as CiphersPage).SearchBar.Text, 200);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnVaultFilterSelectedAsync()
|
||||
{
|
||||
PerformSearchIfPopulated();
|
||||
}
|
||||
|
||||
private async void CipherOptionsAsync(CipherView cipher)
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
AutomationProperties.Name="{u:I18n Filter}" />
|
||||
<controls:MiButton
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
StyleClass="list-row-button-text, list-row-button-platform"
|
||||
Command="{Binding VaultFilterCommand}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="End"
|
||||
@@ -185,7 +185,8 @@
|
||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform" />
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Groupings Page" />
|
||||
</RefreshView>
|
||||
</StackLayout>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (!_vm.Deleted && DoOnce())
|
||||
{
|
||||
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
|
||||
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class GroupingsPageViewModel : BaseViewModel
|
||||
public class GroupingsPageViewModel : VaultFilterViewModel
|
||||
{
|
||||
private const int NoFolderListSize = 100;
|
||||
|
||||
@@ -30,12 +30,9 @@ namespace Bit.App.Pages
|
||||
private bool _showList;
|
||||
private bool _websiteIconsEnabled;
|
||||
private bool _syncRefreshing;
|
||||
private bool _showVaultFilter;
|
||||
private bool _showTOTPFilter;
|
||||
private bool _showTotpFilter;
|
||||
private bool _totpFilterEnable;
|
||||
private string _vaultFilterSelection;
|
||||
private string _noDataText;
|
||||
private List<Organization> _organizations;
|
||||
private List<CipherView> _allCiphers;
|
||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||
@@ -113,6 +110,11 @@ namespace Bit.App.Pages
|
||||
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
||||
|
||||
protected override ICipherService cipherService => _cipherService;
|
||||
protected override IPolicyService policyService => _policyService;
|
||||
protected override IOrganizationService organizationService => _organizationService;
|
||||
protected override ILogger logger => _logger;
|
||||
|
||||
public bool Refreshing
|
||||
{
|
||||
get => _refreshing;
|
||||
@@ -158,41 +160,20 @@ namespace Bit.App.Pages
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
public bool ShowVaultFilter
|
||||
{
|
||||
get => _showVaultFilter;
|
||||
set => SetProperty(ref _showVaultFilter, value);
|
||||
}
|
||||
public bool ShowTotpFilter
|
||||
{
|
||||
get => _showTOTPFilter;
|
||||
set => SetProperty(ref _showTOTPFilter, value);
|
||||
get => _showTotpFilter;
|
||||
set => SetProperty(ref _showTotpFilter, value);
|
||||
}
|
||||
public bool TotpFilterEnable
|
||||
{
|
||||
get => _totpFilterEnable;
|
||||
set => SetProperty(ref _totpFilterEnable, value);
|
||||
}
|
||||
|
||||
public string VaultFilterDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||
{
|
||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||
}
|
||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||
}
|
||||
set => SetProperty(ref _vaultFilterSelection, value);
|
||||
}
|
||||
|
||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||
|
||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||
public Command RefreshCommand { get; set; }
|
||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||
public ICommand VaultFilterCommand { get; }
|
||||
public ICommand TotpFilterCommand { get; }
|
||||
public bool LoadedOnce { get; set; }
|
||||
|
||||
@@ -218,14 +199,9 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
_organizations = await _organizationService.GetAllAsync();
|
||||
await InitVaultFilterAsync(MainPage);
|
||||
if (MainPage)
|
||||
{
|
||||
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||
{
|
||||
_vaultFilterSelection = AppResources.AllVaults;
|
||||
}
|
||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||
}
|
||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
@@ -436,22 +412,8 @@ namespace Bit.App.Pages
|
||||
SyncRefreshing = false;
|
||||
}
|
||||
|
||||
public async Task VaultFilterOptionsAsync()
|
||||
protected override async Task OnVaultFilterSelectedAsync()
|
||||
{
|
||||
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||
if (_organizations.Any())
|
||||
{
|
||||
options.AddRange(_organizations.Select(o => o.Name));
|
||||
}
|
||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||
options.ToArray());
|
||||
if (selection == AppResources.Cancel ||
|
||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
VaultFilterDescription = selection;
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
@@ -538,8 +500,8 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||
NoDataText = AppResources.NoItems;
|
||||
_allCiphers = await GetAllCiphersAsync();
|
||||
HasCiphers = _allCiphers.Any();
|
||||
FavoriteCiphers?.Clear();
|
||||
NoFolderCiphers?.Clear();
|
||||
@@ -553,7 +515,7 @@ namespace Bit.App.Pages
|
||||
|
||||
if (MainPage)
|
||||
{
|
||||
await FillFoldersAndCollectionsAsync(orgId);
|
||||
await FillFoldersAndCollectionsAsync();
|
||||
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||
@@ -677,28 +639,9 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||
{
|
||||
string orgId = null;
|
||||
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||
if (IsVaultFilterMyVault)
|
||||
{
|
||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||
}
|
||||
else if (IsVaultFilterOrgVault)
|
||||
{
|
||||
orgId = GetVaultFilterOrgId();
|
||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_allCiphers = decCiphers;
|
||||
}
|
||||
return orgId;
|
||||
}
|
||||
|
||||
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||
private async Task FillFoldersAndCollectionsAsync()
|
||||
{
|
||||
var orgId = GetVaultFilterOrgId();
|
||||
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||
if (IsVaultFilterMyVault)
|
||||
@@ -718,16 +661,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||
|
||||
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||
_vaultFilterSelection != AppResources.MyVault;
|
||||
|
||||
private string GetVaultFilterOrgId()
|
||||
{
|
||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||
}
|
||||
|
||||
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||
{
|
||||
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||
ItemsSource="{Binding History}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
StyleClass="list, list-platform">
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Password History Page">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="views:PasswordHistoryView">
|
||||
<Grid
|
||||
|
||||
@@ -51,8 +51,7 @@ namespace Bit.App.Pages
|
||||
private async void CopyAsync(PasswordHistoryView ph)
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(ph.Password);
|
||||
_platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace Bit.App.Pages
|
||||
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
||||
|
||||
var autofocusCts = _autofocusCts;
|
||||
// this task is needed to be awaited OnDisappearing to avoid some crashes
|
||||
// when changing the value of _zxing.IsScanning
|
||||
_continuousAutofocusTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -70,7 +72,7 @@ namespace Bit.App.Pages
|
||||
while (!autofocusCts.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
if (!autofocusCts.IsCancellationRequested)
|
||||
{
|
||||
@@ -99,7 +101,10 @@ namespace Bit.App.Pages
|
||||
protected override async void OnDisappearing()
|
||||
{
|
||||
_autofocusCts?.Cancel();
|
||||
await _continuousAutofocusTask;
|
||||
if (_continuousAutofocusTask != null)
|
||||
{
|
||||
await _continuousAutofocusTask;
|
||||
}
|
||||
_zxing.IsScanning = false;
|
||||
_pageIsActive = false;
|
||||
base.OnDisappearing();
|
||||
|
||||
@@ -688,7 +688,7 @@ namespace Bit.App.Pages
|
||||
await _clipboardService.CopyTextAsync(text);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
|
||||
_platformUtilsService.ShowToastForCopiedValue(name);
|
||||
}
|
||||
if (id == "LoginPassword")
|
||||
{
|
||||
|
||||
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public abstract class VaultFilterViewModel : BaseViewModel
|
||||
{
|
||||
protected abstract ICipherService cipherService { get; }
|
||||
protected abstract IPolicyService policyService { get; }
|
||||
protected abstract IOrganizationService organizationService { get; }
|
||||
protected abstract ILogger logger { get; }
|
||||
|
||||
protected bool _showVaultFilter;
|
||||
protected bool _personalOwnershipPolicyApplies;
|
||||
protected string _vaultFilterSelection;
|
||||
protected List<Organization> _organizations;
|
||||
|
||||
public VaultFilterViewModel()
|
||||
{
|
||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||
onException: ex => logger.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
public ICommand VaultFilterCommand { get; set; }
|
||||
|
||||
public bool ShowVaultFilter
|
||||
{
|
||||
get => _showVaultFilter;
|
||||
set => SetProperty(ref _showVaultFilter, value);
|
||||
}
|
||||
|
||||
public string VaultFilterDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||
{
|
||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||
}
|
||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||
}
|
||||
set => SetProperty(ref _vaultFilterSelection, value);
|
||||
}
|
||||
|
||||
public string GetVaultFilterOrgId()
|
||||
{
|
||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||
}
|
||||
|
||||
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||
|
||||
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||
_vaultFilterSelection != AppResources.MyVault;
|
||||
|
||||
protected async Task InitVaultFilterAsync(bool shouldUpdateShowVaultFilter)
|
||||
{
|
||||
_organizations = await organizationService.GetAllAsync();
|
||||
if (_organizations?.Any() ?? false)
|
||||
{
|
||||
_personalOwnershipPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
var singleOrgPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||
if (_vaultFilterSelection == null || (_personalOwnershipPolicyApplies && singleOrgPolicyApplies))
|
||||
{
|
||||
VaultFilterDescription = AppResources.AllVaults;
|
||||
}
|
||||
}
|
||||
if (shouldUpdateShowVaultFilter)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<List<CipherView>> GetAllCiphersAsync()
|
||||
{
|
||||
var decCiphers = await cipherService.GetAllDecryptedAsync();
|
||||
if (IsVaultFilterMyVault)
|
||||
{
|
||||
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||
}
|
||||
if (IsVaultFilterOrgVault)
|
||||
{
|
||||
var orgId = GetVaultFilterOrgId();
|
||||
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||
}
|
||||
return decCiphers;
|
||||
}
|
||||
|
||||
protected async Task VaultFilterOptionsAsync()
|
||||
{
|
||||
var options = new List<string> { AppResources.AllVaults };
|
||||
if (!_personalOwnershipPolicyApplies)
|
||||
{
|
||||
options.Add(AppResources.MyVault);
|
||||
}
|
||||
if (_organizations.Any())
|
||||
{
|
||||
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||
}
|
||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||
options.ToArray());
|
||||
if (selection == null || selection == AppResources.Cancel ||
|
||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
VaultFilterDescription = selection;
|
||||
await OnVaultFilterSelectedAsync();
|
||||
}
|
||||
|
||||
protected abstract Task OnVaultFilterSelectedAsync();
|
||||
}
|
||||
}
|
||||
30
src/App/Resources/AppResources.Designer.cs
generated
30
src/App/Resources/AppResources.Designer.cs
generated
@@ -1,7 +1,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@@ -10,10 +9,9 @@
|
||||
|
||||
namespace Bit.App.Resources {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.1.0.0")]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class AppResources {
|
||||
@@ -2705,6 +2703,18 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string DefaultDarkTheme {
|
||||
get {
|
||||
return ResourceManager.GetString("DefaultDarkTheme", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DefaultDarkThemeDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("DefaultDarkThemeDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string CopyNotes {
|
||||
get {
|
||||
return ResourceManager.GetString("CopyNotes", resourceCulture);
|
||||
@@ -2735,6 +2745,12 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string Nord {
|
||||
get {
|
||||
return ResourceManager.GetString("Nord", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string BlacklistedUris {
|
||||
get {
|
||||
return ResourceManager.GetString("BlacklistedUris", resourceCulture);
|
||||
@@ -3929,15 +3945,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReportCrashLogs {
|
||||
public static string SubmitCrashLogs {
|
||||
get {
|
||||
return ResourceManager.GetString("ReportCrashLogs", resourceCulture);
|
||||
return ResourceManager.GetString("SubmitCrashLogs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReportCrashLogsDescription {
|
||||
public static string SubmitCrashLogsDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ReportCrashLogsDescription", resourceCulture);
|
||||
return ResourceManager.GetString("SubmitCrashLogsDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1545,6 +1545,12 @@
|
||||
<data name="ThemeDefault" xml:space="preserve">
|
||||
<value>Default (System)</value>
|
||||
</data>
|
||||
<data name="DefaultDarkTheme" xml:space="preserve">
|
||||
<value>Default Dark Theme</value>
|
||||
</data>
|
||||
<data name="DefaultDarkThemeDescription" xml:space="preserve">
|
||||
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled</value>
|
||||
</data>
|
||||
<data name="CopyNotes" xml:space="preserve">
|
||||
<value>Copy Note</value>
|
||||
</data>
|
||||
@@ -1561,6 +1567,10 @@
|
||||
<value>Black</value>
|
||||
<comment>The color black</comment>
|
||||
</data>
|
||||
<data name="Nord" xml:space="preserve">
|
||||
<value>Nord</value>
|
||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="BlacklistedUris" xml:space="preserve">
|
||||
<value>Blacklisted URIs</value>
|
||||
</data>
|
||||
@@ -2200,11 +2210,11 @@
|
||||
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
||||
<value>Enter the verification code that was sent to your email</value>
|
||||
</data>
|
||||
<data name="ReportCrashLogs" xml:space="preserve">
|
||||
<value>Report crash logs</value>
|
||||
<data name="SubmitCrashLogs" xml:space="preserve">
|
||||
<value>Submit crash logs</value>
|
||||
</data>
|
||||
<data name="ReportCrashLogsDescription" xml:space="preserve">
|
||||
<value>Help Bitwarden improve app stability by allowing crash reports.</value>
|
||||
<data name="SubmitCrashLogsDescription" xml:space="preserve">
|
||||
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
|
||||
</data>
|
||||
<data name="OptionsExpanded" xml:space="preserve">
|
||||
<value>Options are expanded, tap to collapse.</value>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Bit.App.Services
|
||||
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
||||
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
|
||||
@@ -28,10 +29,12 @@ namespace Bit.App.Services
|
||||
|
||||
public MobilePlatformUtilsService(
|
||||
IDeviceActionService deviceActionService,
|
||||
IClipboardService clipboardService,
|
||||
IMessagingService messagingService,
|
||||
IBroadcasterService broadcasterService)
|
||||
{
|
||||
_deviceActionService = deviceActionService;
|
||||
_clipboardService = clipboardService;
|
||||
_messagingService = messagingService;
|
||||
_broadcasterService = broadcasterService;
|
||||
}
|
||||
@@ -129,6 +132,15 @@ namespace Bit.App.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ShowToastForCopiedValue(string valueNameCopied)
|
||||
{
|
||||
if (!_clipboardService.IsCopyNotificationHandledByPlatform())
|
||||
{
|
||||
ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, valueNameCopied));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsFido2()
|
||||
{
|
||||
return _deviceActionService.SupportsFido2();
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Bit.App.Services
|
||||
Constants.LastBuildKey,
|
||||
Constants.ClearCiphersCacheKey,
|
||||
Constants.BiometricIntegrityKey,
|
||||
Constants.iOSExtensionActiveUserIdKey,
|
||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||
Constants.iOSExtensionClearCiphersCacheKey,
|
||||
@@ -32,7 +33,7 @@ namespace Bit.App.Services
|
||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||
Constants.RememberedEmailKey,
|
||||
Constants.RememberedOrgIdentifierKey,
|
||||
Constants.RememberedOrgIdentifierKey
|
||||
};
|
||||
|
||||
public MobileStorageService(
|
||||
|
||||
@@ -268,6 +268,16 @@
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonColor}" />
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="list-row-button-text">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonTextColor}" />
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="segmented-button">
|
||||
|
||||
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
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 Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities.AccountManagement
|
||||
{
|
||||
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();
|
||||
await Device.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||
{
|
||||
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await NavigateOnAccountChangeAsync();
|
||||
}
|
||||
await Task.Delay(50);
|
||||
await _accountsManagerHost.UpdateThemeAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -97,16 +97,14 @@ namespace Bit.App.Utilities
|
||||
else if (selection == AppResources.CopyUsername)
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||
}
|
||||
else if (selection == AppResources.CopyPassword)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
}
|
||||
}
|
||||
@@ -119,8 +117,7 @@ namespace Bit.App.Utilities
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
await clipboardService.CopyTextAsync(totp);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,8 +130,7 @@ namespace Bit.App.Utilities
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopySecurityCode)
|
||||
@@ -142,16 +138,14 @@ namespace Bit.App.Utilities
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyNotes)
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Notes);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
@@ -262,8 +256,7 @@ namespace Bit.App.Utilities
|
||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
await clipboardService.CopyTextAsync(GetSendUrl(send));
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.SendLink);
|
||||
}
|
||||
|
||||
public static async Task ShareSendUrlAsync(SendView send)
|
||||
|
||||
@@ -17,13 +17,18 @@ namespace Bit.App.Utilities
|
||||
|
||||
public static bool IsThemeDirty = false;
|
||||
|
||||
public static void SetThemeStyle(string name, ResourceDictionary resources)
|
||||
public const string Light = "light";
|
||||
public const string Dark = "dark";
|
||||
public const string Black = "black";
|
||||
public const string Nord = "nord";
|
||||
|
||||
public static void SetThemeStyle(string name, string autoDarkName, ResourceDictionary resources)
|
||||
{
|
||||
try
|
||||
{
|
||||
Resources = () => resources;
|
||||
|
||||
var newTheme = NeedsThemeUpdate(name, resources);
|
||||
var newTheme = NeedsThemeUpdate(name, autoDarkName, resources);
|
||||
if (newTheme is null)
|
||||
{
|
||||
return;
|
||||
@@ -85,22 +90,30 @@ namespace Bit.App.Utilities
|
||||
: Activator.CreateInstance(themeType) as ResourceDictionary;
|
||||
}
|
||||
|
||||
static ResourceDictionary NeedsThemeUpdate(string themeName, ResourceDictionary resources)
|
||||
static ResourceDictionary NeedsThemeUpdate(string themeName, string autoDarkThemeName, ResourceDictionary resources)
|
||||
{
|
||||
switch (themeName)
|
||||
{
|
||||
case "dark":
|
||||
case Dark:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||
case "black":
|
||||
case Black:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
||||
case "nord":
|
||||
case Nord:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
||||
case "light":
|
||||
case Light:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||
default:
|
||||
if (OsDarkModeEnabled())
|
||||
{
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||
switch (autoDarkThemeName)
|
||||
{
|
||||
case Black:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
||||
case Nord:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
||||
default:
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||
}
|
||||
}
|
||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||
}
|
||||
@@ -108,7 +121,7 @@ namespace Bit.App.Utilities
|
||||
|
||||
public static void SetTheme(ResourceDictionary resources)
|
||||
{
|
||||
SetThemeStyle(GetTheme(), resources);
|
||||
SetThemeStyle(GetTheme(), GetAutoDarkTheme(), resources);
|
||||
}
|
||||
|
||||
public static string GetTheme()
|
||||
@@ -117,6 +130,12 @@ namespace Bit.App.Utilities
|
||||
return stateService.GetThemeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static string GetAutoDarkTheme()
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
return stateService.GetAutoDarkThemeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static bool OsDarkModeEnabled()
|
||||
{
|
||||
if (Application.Current == null)
|
||||
|
||||
@@ -8,9 +8,17 @@ namespace Bit.Core.Abstractions
|
||||
/// Copies the <paramref name="text"/> to the Clipboard.
|
||||
/// If <paramref name="expiresInMs"/> is set > 0 then the Clipboard will be cleared after this time in milliseconds.
|
||||
/// if less than 0 then it takes the configuration that the user set in Options.
|
||||
/// If <paramref name="isSensitive"/> is true the sensitive flag is passed to the clipdata to obfuscate the
|
||||
/// clipboard text in the popup (Android 13+ only)
|
||||
/// </summary>
|
||||
/// <param name="text">Text to be copied to the Clipboard</param>
|
||||
/// <param name="expiresInMs">Expiration time in milliseconds of the copied text</param>
|
||||
Task CopyTextAsync(string text, int expiresInMs = -1);
|
||||
/// <param name="isSensitive">Flag to mark copied text as sensitive</param>
|
||||
Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the platform provides its own notification when text is copied to the clipboard
|
||||
/// </summary>
|
||||
bool IsCopyNotificationHandledByPlatform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator);
|
||||
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
||||
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
||||
void ShowToastForCopiedValue(string valueNameCopied);
|
||||
bool SupportsFido2();
|
||||
bool SupportsDuo();
|
||||
Task<bool> SupportsBiometricAsync();
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<string> GetActiveUserIdAsync();
|
||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||
Task SetActiveUserAsync(string userId);
|
||||
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||
Task<string> GetUserIdAsync(string email);
|
||||
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
||||
@@ -109,6 +110,8 @@ namespace Bit.Core.Abstractions
|
||||
Task SetRememberedOrgIdentifierAsync(string value);
|
||||
Task<string> GetThemeAsync(string userId = null);
|
||||
Task SetThemeAsync(string value, string userId = null);
|
||||
Task<string> GetAutoDarkThemeAsync(string userId = null);
|
||||
Task SetAutoDarkThemeAsync(string value, string userId = null);
|
||||
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
|
||||
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
|
||||
Task<bool?> GetPushInitialPromptShownAsync();
|
||||
@@ -145,5 +148,6 @@ namespace Bit.Core.Abstractions
|
||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
||||
public static string EventCollectionKey = "eventCollection";
|
||||
public static string RememberedEmailKey = "rememberedEmail";
|
||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||
@@ -72,6 +73,7 @@
|
||||
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
|
||||
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
|
||||
public static string ThemeKey(string userId) => $"theme_{userId}";
|
||||
public static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}";
|
||||
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
|
||||
public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
|
||||
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";
|
||||
|
||||
13
src/Core/Enums/NavigationTarget.cs
Normal file
13
src/Core/Enums/NavigationTarget.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum NavigationTarget
|
||||
{
|
||||
HomeLogin,
|
||||
Login,
|
||||
Lock,
|
||||
Home,
|
||||
AddEditCipher,
|
||||
AutofillCiphers,
|
||||
SendAddEdit
|
||||
}
|
||||
}
|
||||
@@ -249,9 +249,14 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||
{
|
||||
var organizations = await _organizationService.GetAllAsync();
|
||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
|
||||
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var organizations = await _organizationService.GetAllAsync();
|
||||
return organizations?.Any() ?? false;
|
||||
}
|
||||
|
||||
private bool? GetPolicyBool(Policy policy, string key)
|
||||
|
||||
@@ -15,16 +15,20 @@ namespace Bit.Core.Services
|
||||
{
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IStorageService _secureStorageService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
|
||||
private State _state;
|
||||
private bool _migrationChecked;
|
||||
|
||||
public List<AccountView> AccountViews { get; set; }
|
||||
|
||||
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
||||
public StateService(IStorageService storageService,
|
||||
IStorageService secureStorageService,
|
||||
IMessagingService messagingService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_secureStorageService = secureStorageService;
|
||||
_messagingService = messagingService;
|
||||
}
|
||||
|
||||
public async Task<string> GetActiveUserIdAsync()
|
||||
@@ -67,6 +71,28 @@ namespace Bit.Core.Services
|
||||
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
||||
}
|
||||
|
||||
public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync()
|
||||
{
|
||||
var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync();
|
||||
if (string.IsNullOrEmpty(extensionUserId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state?.ActiveUserId == extensionUserId)
|
||||
{
|
||||
// Clear the value in case the user changes the active user from the app
|
||||
// so if that happens and the user sends the app to background and comes back
|
||||
// the user is not changed again.
|
||||
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||
return;
|
||||
}
|
||||
|
||||
await SetActiveUserAsync(extensionUserId);
|
||||
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
}
|
||||
|
||||
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
||||
{
|
||||
return await GetAccessTokenAsync(userId) != null;
|
||||
@@ -898,6 +924,25 @@ namespace Bit.Core.Services
|
||||
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
|
||||
}
|
||||
|
||||
public async Task<string> GetAutoDarkThemeAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
||||
return await GetValueAsync<string>(key, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task SetAutoDarkThemeAsync(string value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
||||
await SetValueAsync(key, value, reconciledOptions);
|
||||
|
||||
// TODO remove this to restore per-account Theme support
|
||||
SetValueGloballyAsync(Constants.AutoDarkThemeKey, value, reconciledOptions).FireAndForget();
|
||||
}
|
||||
|
||||
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
@@ -1388,6 +1433,7 @@ namespace Bit.Core.Services
|
||||
await SetPasswordVerifiedAutofillAsync(null, userId);
|
||||
await SetSyncOnRefreshAsync(null, userId);
|
||||
await SetThemeAsync(null, userId);
|
||||
await SetAutoDarkThemeAsync(null, userId);
|
||||
await SetAddSitePromptShownAsync(null, userId);
|
||||
await SetPasswordGenerationOptionsAsync(null, userId);
|
||||
}
|
||||
@@ -1397,6 +1443,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await CheckStateAsync();
|
||||
var currentTheme = await GetThemeAsync();
|
||||
var currentAutoDarkTheme = await GetAutoDarkThemeAsync();
|
||||
var currentDisableFavicons = await GetDisableFaviconAsync();
|
||||
|
||||
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
||||
@@ -1426,6 +1473,7 @@ namespace Bit.Core.Services
|
||||
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
|
||||
}
|
||||
await SetThemeAsync(currentTheme, account.Profile.UserId);
|
||||
await SetAutoDarkThemeAsync(currentAutoDarkTheme, account.Profile.UserId);
|
||||
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
|
||||
|
||||
state.Accounts[account.Profile.UserId] = account;
|
||||
@@ -1510,6 +1558,16 @@ namespace Bit.Core.Services
|
||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
||||
}
|
||||
|
||||
private async Task<string> GetExtensionActiveUserIdFromStorageAsync()
|
||||
{
|
||||
return await _storageService.GetAsync<string>(Constants.iOSExtensionActiveUserIdKey);
|
||||
}
|
||||
|
||||
public async Task SaveExtensionActiveUserIdToStorageAsync(string userId)
|
||||
{
|
||||
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
|
||||
}
|
||||
|
||||
private async Task CheckStateAsync()
|
||||
{
|
||||
if (!_migrationChecked)
|
||||
|
||||
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,32 @@
|
||||
using AuthenticationServices;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AuthenticationServices;
|
||||
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.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Autofill.Models;
|
||||
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 CoreNFC;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController
|
||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
|
||||
{
|
||||
private Context _context;
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
private IAccountsManager _accountsManager;
|
||||
|
||||
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||
|
||||
public CredentialProviderViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
@@ -56,7 +61,7 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
LaunchHomePage();
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
}
|
||||
else if (await IsLocked())
|
||||
{
|
||||
@@ -78,9 +83,8 @@ namespace Bit.iOS.Autofill
|
||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
await stateService.SetPasswordRepromptAutofillAsync(false);
|
||||
await stateService.SetPasswordVerifiedAutofillAsync(false);
|
||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||
if (!await IsAuthed() || await IsLocked())
|
||||
{
|
||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||
@@ -97,7 +101,7 @@ namespace Bit.iOS.Autofill
|
||||
InitAppIfNeeded();
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
LaunchHomePage();
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
return;
|
||||
}
|
||||
_context.CredentialIdentity = credentialIdentity;
|
||||
@@ -110,7 +114,7 @@ namespace Bit.iOS.Autofill
|
||||
_context.Configuring = true;
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
LaunchHomePage();
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
return;
|
||||
}
|
||||
CheckLock(() => PerformSegue("setupSegue", this));
|
||||
@@ -229,7 +233,6 @@ namespace Bit.iOS.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var decCipher = await cipher.DecryptAsync();
|
||||
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
||||
{
|
||||
@@ -237,13 +240,13 @@ namespace Bit.iOS.Autofill
|
||||
// already verified the password.
|
||||
if (!userInteraction)
|
||||
{
|
||||
await stateService.SetPasswordRepromptAutofillAsync(true);
|
||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||
ExtensionContext?.CancelRequest(err);
|
||||
return;
|
||||
}
|
||||
else if (!await stateService.GetPasswordVerifiedAutofillAsync())
|
||||
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
||||
{
|
||||
// Add a timeout to resolve keyboard not always showing up.
|
||||
await Task.Delay(250);
|
||||
@@ -258,10 +261,10 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
}
|
||||
string totpCode = null;
|
||||
var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync();
|
||||
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
||||
if (!disableTotpCopy.GetValueOrDefault(false))
|
||||
{
|
||||
var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync();
|
||||
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
||||
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
||||
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
||||
{
|
||||
@@ -275,8 +278,7 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private async void CheckLock(Action notLockedAction)
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync())
|
||||
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||
{
|
||||
PerformSegue("lockPasswordSegue", this);
|
||||
}
|
||||
@@ -294,8 +296,7 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private Task<bool> IsAuthed()
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
return stateService.IsAuthenticatedAsync();
|
||||
return _stateService.Value.IsAuthenticatedAsync();
|
||||
}
|
||||
|
||||
private void LogoutIfAuthed()
|
||||
@@ -304,8 +305,7 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
if (await IsAuthed())
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
|
||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
if (deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
@@ -331,12 +331,16 @@ namespace Bit.iOS.Autofill
|
||||
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||
iOSCoreHelpers.InitLogger();
|
||||
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);
|
||||
iOSCoreHelpers.AppearanceAdjustments();
|
||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||
messagingService.Send("gotYubiKeyOTP", message));
|
||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
||||
|
||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
_accountsManager.Init(() => appOptions, this);
|
||||
}
|
||||
|
||||
private void InitAppIfNeeded()
|
||||
@@ -514,5 +518,35 @@ namespace Bit.iOS.Autofill
|
||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
|
||||
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)
|
||||
: base(handle)
|
||||
{
|
||||
@@ -20,6 +27,21 @@ namespace Bit.iOS.Autofill
|
||||
public override Action Success => () => CPViewController.DismissLockAndContinue();
|
||||
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)
|
||||
{
|
||||
var task = CheckPasswordAsync();
|
||||
|
||||
109
src/iOS.Autofill/LockPasswordViewController.designer.cs
generated
109
src/iOS.Autofill/LockPasswordViewController.designer.cs
generated
@@ -1,64 +1,79 @@
|
||||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
// This file has been generated automatically by Visual Studio to store outlets and
|
||||
// actions made in the UI designer. If it is removed, they will be lost.
|
||||
// Manual changes to this file may not be handled correctly.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
[Register ("LockPasswordViewController")]
|
||||
partial class LockPasswordViewController
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem CancelButton { get; set; }
|
||||
[Register ("LockPasswordViewController")]
|
||||
partial class LockPasswordViewController
|
||||
{
|
||||
[Outlet]
|
||||
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UITableView MainTableView { get; set; }
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem CancelButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UINavigationItem NavItem { get; set; }
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UITableView MainTableView { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem SubmitButton { get; set; }
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UINavigationItem NavItem { get; set; }
|
||||
|
||||
[Action ("CancelButton_Activated:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
[Outlet]
|
||||
UIKit.UIView OverlayView { get; set; }
|
||||
|
||||
[Action ("SubmitButton_Activated:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem SubmitButton { get; set; }
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (CancelButton != null) {
|
||||
CancelButton.Dispose ();
|
||||
CancelButton = null;
|
||||
}
|
||||
[Action ("AccountSwitchingBarButton_Activated:")]
|
||||
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
if (MainTableView != null) {
|
||||
MainTableView.Dispose ();
|
||||
MainTableView = null;
|
||||
}
|
||||
[Action ("CancelButton_Activated:")]
|
||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
if (NavItem != null) {
|
||||
NavItem.Dispose ();
|
||||
NavItem = null;
|
||||
}
|
||||
[Action ("SubmitButton_Activated:")]
|
||||
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (AccountSwitchingBarButton != null) {
|
||||
AccountSwitchingBarButton.Dispose ();
|
||||
AccountSwitchingBarButton = null;
|
||||
}
|
||||
|
||||
if (SubmitButton != null) {
|
||||
SubmitButton.Dispose ();
|
||||
SubmitButton = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CancelButton != null) {
|
||||
CancelButton.Dispose ();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
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.Utilities;
|
||||
using Bit.iOS.Core.Controllers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
using CoreFoundation;
|
||||
using Foundation;
|
||||
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
|
||||
{
|
||||
public partial class LoginListViewController : ExtendedUITableViewController
|
||||
public partial class LoginListViewController : ExtendedUIViewController
|
||||
{
|
||||
public LoginListViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
@@ -26,17 +28,30 @@ namespace Bit.iOS.Autofill
|
||||
public CredentialProviderViewController CPViewController { get; 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()
|
||||
{
|
||||
base.ViewDidLoad();
|
||||
|
||||
SubscribeSyncCompleted();
|
||||
|
||||
NavItem.Title = AppResources.Items;
|
||||
CancelBarButton.Title = AppResources.Cancel;
|
||||
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 44;
|
||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||
TableView.Source = new TableSource(this);
|
||||
await ((TableSource)TableView.Source).LoadItemsAsync();
|
||||
|
||||
_alreadyLoadItemsOnce = true;
|
||||
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
|
||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||
@@ -44,6 +59,16 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
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)
|
||||
@@ -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()
|
||||
{
|
||||
DismissViewController(true, async () =>
|
||||
@@ -99,13 +153,11 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
public class TableSource : ExtensionTableSource
|
||||
{
|
||||
private Context _context;
|
||||
private LoginListViewController _controller;
|
||||
|
||||
public TableSource(LoginListViewController controller)
|
||||
: base(controller.Context, controller)
|
||||
{
|
||||
_context = controller.Context;
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
|
||||
116
src/iOS.Autofill/LoginListViewController.designer.cs
generated
116
src/iOS.Autofill/LoginListViewController.designer.cs
generated
@@ -1,59 +1,89 @@
|
||||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
// This file has been generated automatically by Visual Studio to store outlets and
|
||||
// actions made in the UI designer. If it is removed, they will be lost.
|
||||
// Manual changes to this file may not be handled correctly.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
[Register ("LoginListViewController")]
|
||||
partial class LoginListViewController
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem AddBarButton { get; set; }
|
||||
[Register ("LoginListViewController")]
|
||||
partial class LoginListViewController
|
||||
{
|
||||
[Outlet]
|
||||
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem CancelBarButton { get; set; }
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem AddBarButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UINavigationItem NavItem { get; set; }
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIBarButtonItem CancelBarButton { get; set; }
|
||||
|
||||
[Action ("AddBarButton_Activated:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
[Outlet]
|
||||
UIKit.UIView MainView { get; set; }
|
||||
|
||||
[Action ("CancelBarButton_Activated:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UINavigationItem NavItem { get; set; }
|
||||
|
||||
[Action ("SearchBarButton_Activated:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
[Outlet]
|
||||
UIKit.UIView OverlayView { get; set; }
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (AddBarButton != null) {
|
||||
AddBarButton.Dispose ();
|
||||
AddBarButton = null;
|
||||
}
|
||||
[Outlet]
|
||||
UIKit.UITableView TableView { get; set; }
|
||||
|
||||
if (CancelBarButton != null) {
|
||||
CancelBarButton.Dispose ();
|
||||
CancelBarButton = null;
|
||||
}
|
||||
[Action ("AccountSwitchingBarButton_Activated:")]
|
||||
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
if (NavItem != null) {
|
||||
NavItem.Dispose ();
|
||||
NavItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[Action ("AddBarButton_Activated:")]
|
||||
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,7 @@ namespace Bit.iOS.Autofill
|
||||
CancelBarButton.Title = AppResources.Cancel;
|
||||
SearchBar.Placeholder = AppResources.Search;
|
||||
SearchBar.BackgroundColor = SearchBar.BarTintColor = ThemeHelpers.ListHeaderBackgroundColor;
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
SearchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
||||
SearchBar.UpdateThemeIfNeeded();
|
||||
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 44;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -9,30 +13,27 @@
|
||||
<scene sceneID="42">
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="YG6-2d-qpF"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<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>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
|
||||
<segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/>
|
||||
<segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/>
|
||||
<segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/>
|
||||
<segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/>
|
||||
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@@ -45,7 +46,7 @@
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -67,7 +68,7 @@
|
||||
<objects>
|
||||
<navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController">
|
||||
<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"/>
|
||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
@@ -87,7 +88,7 @@
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -125,50 +126,79 @@
|
||||
<!--Logins-->
|
||||
<scene sceneID="2303">
|
||||
<objects>
|
||||
<tableViewController 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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<viewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="q9o-3n-3xL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
|
||||
<rect key="frame" x="0.0" y="22" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
|
||||
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
|
||||
<subviews>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
|
||||
<rect key="frame" x="0.0" y="44.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
|
||||
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="2304" id="2306"/>
|
||||
<outlet property="delegate" destination="2304" id="2307"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
|
||||
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
|
||||
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<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/>
|
||||
<navigationItem key="navigationItem" title="Logins" id="3734">
|
||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="3735">
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<leftBarButtonItems>
|
||||
<barButtonItem title="Cancel" id="3735">
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
|
||||
</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>
|
||||
<barButtonItem systemItem="add" id="3736">
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -186,13 +216,17 @@
|
||||
</navigationItem>
|
||||
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="AccountSwitchingBarButton" destination="I0b-et-FGw" id="KZj-EO-7wd"/>
|
||||
<outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/>
|
||||
<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="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="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1157" y="566"/>
|
||||
@@ -202,7 +236,7 @@
|
||||
<objects>
|
||||
<navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController">
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -222,37 +256,34 @@
|
||||
<scene sceneID="4579">
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<connections>
|
||||
<segue destination="4912" kind="embed" id="6480"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Kvt-lG-wyu"/>
|
||||
<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="leading" secondItem="4930" secondAttribute="leading" id="6486"/>
|
||||
<constraint firstItem="4940" firstAttribute="leading" secondItem="4930" secondAttribute="leading" constant="15" id="6487"/>
|
||||
<constraint firstItem="4940" firstAttribute="top" secondItem="4571" secondAttribute="bottom" constant="35" id="6488"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
|
||||
<constraint firstItem="4572" firstAttribute="top" secondItem="4933" secondAttribute="bottom" id="6490"/>
|
||||
<constraint firstItem="4933" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" id="6486"/>
|
||||
<constraint firstItem="4940" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" constant="15" id="6487"/>
|
||||
<constraint firstItem="4940" firstAttribute="top" secondItem="Kvt-lG-wyu" secondAttribute="top" constant="35" id="6488"/>
|
||||
<constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
|
||||
<constraint firstItem="Kvt-lG-wyu" firstAttribute="bottom" secondItem="4933" secondAttribute="bottom" id="6490"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Generate Password" id="4580">
|
||||
@@ -287,7 +318,7 @@
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
@@ -301,15 +332,11 @@
|
||||
<point key="canvasLocation" x="4708" y="-194"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<!--Verify Fingerprint-->
|
||||
<!--Verify PIN-->
|
||||
<!--Navigation Controller-->
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="6854">
|
||||
<objects>
|
||||
<navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController">
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -318,88 +345,139 @@
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="7413" kind="relationship" relationship="rootViewController" id="8266"/>
|
||||
<segue destination="cn5-F4-59n" kind="relationship" relationship="rootViewController" id="Q23-VB-h61"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="390" y="1407"/>
|
||||
<point key="canvasLocation" x="120" y="1407"/>
|
||||
</scene>
|
||||
<!--Verify Master Password-->
|
||||
<scene sceneID="7412">
|
||||
<scene sceneID="5CE-bQ-Rfq">
|
||||
<objects>
|
||||
<tableViewController id="7413" 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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<viewController id="cn5-F4-59n" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="0qM-RN-J2i" userLabel="Main View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="7413" id="7415"/>
|
||||
<outlet property="delegate" destination="7413" id="7416"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Verify Master Password" id="8265">
|
||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="8268">
|
||||
<subviews>
|
||||
<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">
|
||||
<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"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="oQZ-wW-5uB">
|
||||
<rect key="frame" x="0.0" y="49.5" width="414" height="44"/>
|
||||
<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>
|
||||
<action selector="CancelButton_Activated:" destination="7413" id="8287"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" title="Submit" id="8269">
|
||||
<connections>
|
||||
<action selector="SubmitButton_Activated:" destination="7413" id="8288"/>
|
||||
<action selector="SubmitButton_Activated:" destination="cn5-F4-59n" id="O1U-fk-BDh"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="CancelButton" destination="8268" id="name-outlet-8268"/>
|
||||
<outlet property="MainTableView" destination="7414" id="name-outlet-7414"/>
|
||||
<outlet property="NavItem" destination="8265" id="name-outlet-8265"/>
|
||||
<outlet property="SubmitButton" destination="8269" id="name-outlet-8269"/>
|
||||
<outlet property="AccountSwitchingBarButton" destination="nwd-aM-kFD" id="T8F-CN-2il"/>
|
||||
<outlet property="CancelButton" destination="Xoh-Zv-hhd" id="mwi-4K-maj"/>
|
||||
<outlet property="MainTableView" destination="FcI-Ph-m9e" id="Ybv-5r-VGA"/>
|
||||
<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>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7419" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="vNI-gq-ubp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="955" y="1407"/>
|
||||
<point key="canvasLocation" x="836" y="1407"/>
|
||||
</scene>
|
||||
<!--Setup View Controller-->
|
||||
<scene sceneID="10573">
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="hcg-mr-Ilx"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="11092" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11114"/>
|
||||
<constraint 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="11093" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11119"/>
|
||||
<constraint firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
|
||||
<constraint firstItem="11092" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11114"/>
|
||||
<constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
|
||||
<constraint firstItem="11092" firstAttribute="top" secondItem="hcg-mr-Ilx" secondAttribute="top" constant="30" id="11116"/>
|
||||
<constraint firstItem="11093" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11119"/>
|
||||
<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="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"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -429,7 +507,7 @@
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
@@ -450,7 +528,7 @@
|
||||
<objects>
|
||||
<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">
|
||||
<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"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<searchBar key="tableHeaderView" contentMode="redraw" id="13084">
|
||||
@@ -463,10 +541,10 @@
|
||||
</searchBar>
|
||||
<prototypes>
|
||||
<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"/>
|
||||
<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"/>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
@@ -506,7 +584,7 @@
|
||||
<objects>
|
||||
<navigationController id="11552" sceneMemberID="viewController">
|
||||
<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"/>
|
||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
@@ -522,8 +600,19 @@
|
||||
<point key="canvasLocation" x="1920" y="908"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="12574"/>
|
||||
<segue reference="3731"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="check.png" width="90" height="90"/>
|
||||
<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>
|
||||
</document>
|
||||
</document>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Bit.iOS.Autofill.Utilities
|
||||
{
|
||||
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
|
||||
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
|
||||
UITableViewController controller, IPasswordRepromptService passwordRepromptService,
|
||||
UIViewController controller, IPasswordRepromptService passwordRepromptService,
|
||||
string loginAddSegue)
|
||||
{
|
||||
tableView.DeselectRow(indexPath, true);
|
||||
|
||||
509
src/iOS.Core/Controllers/BaseLockPasswordViewController.cs
Normal file
509
src/iOS.Core/Controllers/BaseLockPasswordViewController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ using Bit.Core;
|
||||
|
||||
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
|
||||
{
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Bit.App.Controls;
|
||||
using Bit.iOS.Core.Renderers.CollectionView;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedCollectionView), typeof(ExtendedCollectionViewRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||
{
|
||||
public class ExtendedCollectionViewRenderer : GroupableItemsViewRenderer<ExtendedCollectionView, GroupableItemsViewController<ExtendedCollectionView>>
|
||||
{
|
||||
protected override GroupableItemsViewController<ExtendedCollectionView> CreateController(ExtendedCollectionView itemsView, ItemsViewLayout layout)
|
||||
{
|
||||
return new ExtendedGroupableItemsViewController<ExtendedCollectionView>(itemsView, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Foundation;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||
{
|
||||
public class ExtendedGroupableItemsViewController<TItemsView> : GroupableItemsViewController<TItemsView>
|
||||
where TItemsView : ExtendedCollectionView
|
||||
{
|
||||
public ExtendedGroupableItemsViewController(TItemsView groupableItemsView, ItemsViewLayout layout)
|
||||
: base(groupableItemsView, layout)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.UpdateTemplatedCell(cell, indexPath);
|
||||
}
|
||||
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
||||
{
|
||||
throw new Exception("Error in ExtendedCollectionView, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,10 @@ namespace Bit.iOS.Core.Services
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||
{
|
||||
// isSensitive is only used by Android for now
|
||||
|
||||
int clearSeconds = -1;
|
||||
if (expiresInMs < 0)
|
||||
{
|
||||
@@ -36,5 +38,11 @@ namespace Bit.iOS.Core.Services
|
||||
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null
|
||||
}));
|
||||
}
|
||||
|
||||
public bool IsCopyNotificationHandledByPlatform()
|
||||
{
|
||||
// return true for any future versions of iOS that notify the user when text is copied to the clipboard
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
85
src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs
Normal file
85
src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
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,
|
||||
AfterHide = () =>
|
||||
{
|
||||
if (containerView != null)
|
||||
{
|
||||
containerView.Hidden = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||
{
|
||||
FromIOSExtension = true
|
||||
};
|
||||
overlay.BindingContext = vm;
|
||||
overlay.IsVisible = false;
|
||||
|
||||
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;
|
||||
if (!overlayVisible)
|
||||
{
|
||||
// So that the animation doesn't break we only care about showing it
|
||||
// and the hiding if done through AccountSwitchingOverlayView -> AfterHide
|
||||
containerView.Hidden = false;
|
||||
}
|
||||
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
|
||||
containerView.UserInteractionEnabled = !overlayVisible;
|
||||
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/iOS.Core/Utilities/ImageSourceExtensions.cs
Normal file
44
src/iOS.Core/Utilities/ImageSourceExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,10 +83,10 @@ namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
||||
{
|
||||
theme = "dark";
|
||||
theme = ThemeManager.Dark;
|
||||
}
|
||||
|
||||
if (theme == "dark" || theme == "black" || theme == "nord")
|
||||
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
|
||||
{
|
||||
LightTheme = false;
|
||||
}
|
||||
|
||||
20
src/iOS.Core/Utilities/UISearchBarExtensions.cs
Normal file
20
src/iOS.Core/Utilities/UISearchBarExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class UISearchBarExtensions
|
||||
{
|
||||
public static void UpdateThemeIfNeeded(this UISearchBar searchBar)
|
||||
{
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
searchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
|
||||
{
|
||||
searchBar.SearchTextField.TextColor = UIColor.White;
|
||||
searchBar.SearchTextField.LeftView.TintColor = UIColor.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -58,13 +59,13 @@ namespace Bit.iOS.Core.Utilities
|
||||
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
|
||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||
var stateMigrationService =
|
||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||
var clipboardService = new ClipboardService(stateService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||
messagingService, broadcasterService);
|
||||
var biometricService = new BiometricService(mobileStorageService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
@@ -172,6 +173,15 @@ namespace Bit.iOS.Core.Utilities
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||
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)
|
||||
{
|
||||
await postBootstrapFunc.Invoke();
|
||||
|
||||
@@ -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.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -6,12 +12,6 @@ using Bit.iOS.Core.Controllers;
|
||||
using Bit.iOS.Core.Models;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
@@ -122,7 +122,10 @@ namespace Bit.iOS.Core.Views
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Effects\" />
|
||||
<Folder Include="Renderers\CollectionView\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Constants.cs" />
|
||||
@@ -194,6 +195,12 @@
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Utilities\FontElementExtensions.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\ExtendedGroupableItemsViewController.cs" />
|
||||
<Compile Include="Utilities\UISearchBarExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\App\App.csproj">
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
||||
<Version>4.4.0</Version>
|
||||
</PackageReference>
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Essentials">
|
||||
<Version>1.7.2</Version>
|
||||
<Version>1.7.3</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
|
||||
Reference in New Issue
Block a user