mirror of
https://github.com/bitwarden/mobile
synced 2026-01-02 08:33:17 +00:00
reset for v2
This commit is contained in:
@@ -1,266 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class EnvironmentPage : ExtendedContentPage
|
||||
{
|
||||
private IAppSettingsService _appSettings;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public EnvironmentPage()
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell BaseUrlCell { get; set; }
|
||||
public FormEntryCell WebVaultUrlCell { get; set; }
|
||||
public FormEntryCell ApiUrlCell { get; set; }
|
||||
public FormEntryCell IdentityUrlCell { get; set; }
|
||||
public FormEntryCell IconsUrlCell { get; set; }
|
||||
public RedrawableStackLayout StackLayout { get; set; }
|
||||
public Label SelfHostLabel { get; set; }
|
||||
public Label CustomLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
IconsUrlCell = new FormEntryCell(AppResources.IconsUrl, entryKeyboard: Keyboard.Url);
|
||||
IconsUrlCell.Entry.Text = _appSettings.IconsUrl;
|
||||
|
||||
IdentityUrlCell = new FormEntryCell(AppResources.IdentityUrl, nextElement: IconsUrlCell.Entry,
|
||||
entryKeyboard: Keyboard.Url);
|
||||
IdentityUrlCell.Entry.Text = _appSettings.IdentityUrl;
|
||||
|
||||
ApiUrlCell = new FormEntryCell(AppResources.ApiUrl, nextElement: IdentityUrlCell.Entry,
|
||||
entryKeyboard: Keyboard.Url);
|
||||
ApiUrlCell.Entry.Text = _appSettings.ApiUrl;
|
||||
|
||||
WebVaultUrlCell = new FormEntryCell(AppResources.WebVaultUrl, nextElement: ApiUrlCell.Entry,
|
||||
entryKeyboard: Keyboard.Url);
|
||||
WebVaultUrlCell.Entry.Text = _appSettings.WebVaultUrl;
|
||||
|
||||
BaseUrlCell = new FormEntryCell(AppResources.ServerUrl, nextElement: WebVaultUrlCell.Entry,
|
||||
entryKeyboard: Keyboard.Url);
|
||||
BaseUrlCell.Entry.Text = _appSettings.BaseUrl;
|
||||
|
||||
var table = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.SelfHostedEnvironment)
|
||||
{
|
||||
BaseUrlCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SelfHostLabel = new Label
|
||||
{
|
||||
Text = AppResources.SelfHostedEnvironmentFooter,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 5)
|
||||
};
|
||||
|
||||
var table2 = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.CustomEnvironment)
|
||||
{
|
||||
WebVaultUrlCell,
|
||||
ApiUrlCell,
|
||||
IdentityUrlCell,
|
||||
IconsUrlCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CustomLabel = new Label
|
||||
{
|
||||
Text = AppResources.CustomEnvironmentFooter,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 5)
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { table, SelfHostLabel, table2, CustomLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
var toolbarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () => await SaveAsync(),
|
||||
ToolbarItemOrder.Default, 0);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = table2.RowHeight = -1;
|
||||
table.EstimatedRowHeight = table2.EstimatedRowHeight = 70;
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close, () =>
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
}));
|
||||
}
|
||||
|
||||
ToolbarItems.Add(toolbarItem);
|
||||
Title = AppResources.Settings;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
BaseUrlCell.InitEvents();
|
||||
IconsUrlCell.InitEvents();
|
||||
IdentityUrlCell.InitEvents();
|
||||
ApiUrlCell.InitEvents();
|
||||
WebVaultUrlCell.InitEvents();
|
||||
BaseUrlCell.Entry.FocusWithDelay();
|
||||
}
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
BaseUrlCell.Dispose();
|
||||
IconsUrlCell.Dispose();
|
||||
IdentityUrlCell.Dispose();
|
||||
ApiUrlCell.Dispose();
|
||||
WebVaultUrlCell.Dispose();
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
Uri result;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(BaseUrlCell.Entry.Text))
|
||||
{
|
||||
BaseUrlCell.Entry.Text = FixUrl(BaseUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(BaseUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
await DisplayAlert(null, string.Format(AppResources.FormattedIncorrectly, AppResources.ServerUrl),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(WebVaultUrlCell.Entry.Text))
|
||||
{
|
||||
WebVaultUrlCell.Entry.Text = FixUrl(WebVaultUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(WebVaultUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
await DisplayAlert(null, string.Format(AppResources.FormattedIncorrectly, AppResources.WebVaultUrl),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WebVaultUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(ApiUrlCell.Entry.Text))
|
||||
{
|
||||
ApiUrlCell.Entry.Text = FixUrl(ApiUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(ApiUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
await DisplayAlert(null, string.Format(AppResources.FormattedIncorrectly, AppResources.ApiUrl),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ApiUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(IdentityUrlCell.Entry.Text))
|
||||
{
|
||||
IdentityUrlCell.Entry.Text = FixUrl(IdentityUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(IdentityUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
await DisplayAlert(null, string.Format(AppResources.FormattedIncorrectly, AppResources.IdentityUrl),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IdentityUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(IconsUrlCell.Entry.Text))
|
||||
{
|
||||
IconsUrlCell.Entry.Text = FixUrl(IconsUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(IconsUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
await DisplayAlert(null, string.Format(AppResources.FormattedIncorrectly, AppResources.IconsUrl),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IconsUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
_appSettings.BaseUrl = BaseUrlCell.Entry.Text;
|
||||
_appSettings.IconsUrl = IconsUrlCell.Entry.Text;
|
||||
_appSettings.IdentityUrl = IdentityUrlCell.Entry.Text;
|
||||
_appSettings.ApiUrl = ApiUrlCell.Entry.Text;
|
||||
_appSettings.WebVaultUrl = WebVaultUrlCell.Entry.Text;
|
||||
_deviceActionService.Toast(AppResources.EnvironmentSaved);
|
||||
_googleAnalyticsService.TrackAppEvent("SetEnvironmentUrls");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
|
||||
private string FixUrl(string url)
|
||||
{
|
||||
url = url.TrimEnd('/');
|
||||
if(!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||
{
|
||||
url = $"https://{url}";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private class FormTableView : ExtendedTableView
|
||||
{
|
||||
public FormTableView(EnvironmentPage page)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class HomePage : ExtendedContentPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public HomePage()
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
|
||||
var settingsButton = new ExtendedButton
|
||||
{
|
||||
Image = "cog.png",
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
WidthRequest = 25,
|
||||
HeightRequest = 25,
|
||||
BackgroundColor = Color.Transparent,
|
||||
Margin = new Thickness(-20, -30, 0, 0),
|
||||
Command = new Command(async () => await SettingsAsync())
|
||||
};
|
||||
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo.png",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
Margin = new Thickness(0, 30, 0, 0),
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var message = new Label
|
||||
{
|
||||
Text = AppResources.LoginOrCreateNewAccount,
|
||||
VerticalOptions = LayoutOptions.StartAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
|
||||
TextColor = Color.FromHex("333333")
|
||||
};
|
||||
|
||||
var createAccountButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.CreateAccount,
|
||||
Command = new Command(async () => await RegisterAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"],
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
|
||||
};
|
||||
|
||||
var loginButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.LogIn,
|
||||
Command = new Command(async () => await LoginAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
BackgroundColor = Color.Transparent,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
|
||||
};
|
||||
|
||||
var buttonStackLayout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 10,
|
||||
Children = { settingsButton, logo, message, createAccountButton, loginButton }
|
||||
};
|
||||
|
||||
Title = AppResources.Bitwarden;
|
||||
NavigationPage.SetHasNavigationBar(this, false);
|
||||
Content = new ScrollView { Content = buttonStackLayout };
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
}
|
||||
|
||||
public async Task LoginAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
await Navigation.PushForDeviceAsync(new LoginPage());
|
||||
}
|
||||
|
||||
public async Task RegisterAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
await Navigation.PushForDeviceAsync(new RegisterPage(this));
|
||||
}
|
||||
|
||||
public async Task DismissRegisterAndLoginAsync(string email)
|
||||
{
|
||||
await Navigation.PopForDeviceAsync();
|
||||
await Navigation.PushForDeviceAsync(new LoginPage(email));
|
||||
_deviceActionService.Toast(AppResources.AccountCreated);
|
||||
}
|
||||
|
||||
public async Task SettingsAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
await Navigation.PushForDeviceAsync(new EnvironmentPage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class BaseLockPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
|
||||
public BaseLockPage()
|
||||
: base(false, false)
|
||||
{
|
||||
AuthService = Resolver.Resolve<IAuthService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
}
|
||||
|
||||
protected IAuthService AuthService { get; set; }
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
_deviceActionService.Background();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task LogoutAsync()
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.LogoutConfirmation, AppResources.Yes, AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
AuthService.LogOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LockFingerprintPage : BaseLockPage
|
||||
{
|
||||
private readonly IFingerprint _fingerprint;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly bool _checkFingerprintImmediately;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public LockFingerprintPage(bool checkFingerprintImmediately)
|
||||
{
|
||||
_checkFingerprintImmediately = checkFingerprintImmediately;
|
||||
_fingerprint = Resolver.Resolve<IFingerprint>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var biometricIcon = Helpers.OnPlatform(
|
||||
iOS: _deviceInfoService.HasFaceIdSupport ? "smile.png" : "fingerprint.png",
|
||||
Android: "fingerprint.png",
|
||||
Windows: "smile.png");
|
||||
var biometricText = Helpers.OnPlatform(
|
||||
iOS: _deviceInfoService.HasFaceIdSupport ?
|
||||
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock,
|
||||
Android: AppResources.UseFingerprintToUnlock,
|
||||
Windows: AppResources.UseWindowsHelloToUnlock);
|
||||
var biometricTitle = Helpers.OnPlatform(
|
||||
iOS: _deviceInfoService.HasFaceIdSupport ?
|
||||
AppResources.VerifyFaceID : AppResources.VerifyFingerprint,
|
||||
Android: AppResources.VerifyFingerprint,
|
||||
Windows: AppResources.VerifyWindowsHello);
|
||||
|
||||
|
||||
var fingerprintIcon = new ExtendedButton
|
||||
{
|
||||
Image = biometricIcon,
|
||||
BackgroundColor = Color.Transparent,
|
||||
Command = new Command(async () => await CheckFingerprintAsync()),
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Margin = new Thickness(0, 0, 0, 15)
|
||||
};
|
||||
|
||||
var fingerprintButton = new ExtendedButton
|
||||
{
|
||||
Text = biometricText,
|
||||
Command = new Command(async () => await CheckFingerprintAsync()),
|
||||
VerticalOptions = LayoutOptions.EndAndExpand,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
|
||||
var logoutButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.LogOut,
|
||||
Command = new Command(async () => await LogoutAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
BackgroundColor = Color.Transparent,
|
||||
Uppercase = false
|
||||
};
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 10,
|
||||
Children = { fingerprintIcon, fingerprintButton, logoutButton }
|
||||
};
|
||||
|
||||
Title = biometricTitle;
|
||||
Content = stackLayout;
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
if(_checkFingerprintImmediately)
|
||||
{
|
||||
await Task.Delay(Device.RuntimePlatform == Device.Android ? 500 : 200);
|
||||
await CheckFingerprintAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckFingerprintAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
var direction = _deviceInfoService.HasFaceIdSupport ?
|
||||
AppResources.FaceIDDirection : AppResources.FingerprintDirection;
|
||||
|
||||
var fingerprintRequest = new AuthenticationRequestConfiguration(direction)
|
||||
{
|
||||
AllowAlternativeAuthentication = true,
|
||||
CancelTitle = AppResources.Cancel,
|
||||
FallbackTitle = AppResources.LogOut
|
||||
};
|
||||
var result = await _fingerprint.AuthenticateAsync(fingerprintRequest);
|
||||
if(result.Authenticated)
|
||||
{
|
||||
_appSettings.Locked = false;
|
||||
if(Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
||||
{
|
||||
AuthService.LogOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using System.Linq;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LockPasswordPage : BaseLockPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public LockPasswordPage()
|
||||
{
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell PasswordCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(15, 8),
|
||||
Windows: new Thickness(10, 8));
|
||||
|
||||
PasswordCell = new FormEntryCell(AppResources.MasterPassword, isPassword: true,
|
||||
useLabelAsPlaceholder: true, imageSource: "lock.png", containerPadding: padding);
|
||||
|
||||
PasswordCell.Entry.TargetReturnType = Enums.ReturnType.Go;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = false,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = false,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
NoFooter = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
PasswordCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var logoutButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.LogOut,
|
||||
Command = new Command(async () => await LogoutAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
BackgroundColor = Color.Transparent,
|
||||
Uppercase = false
|
||||
};
|
||||
|
||||
var stackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Spacing = 10,
|
||||
Children = { table, logoutButton }
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => stackLayout;
|
||||
|
||||
var scrollView = new ScrollView { Content = stackLayout };
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 70;
|
||||
}
|
||||
|
||||
var loginToolbarItem = new ToolbarItem(AppResources.Submit, Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
await CheckPasswordAsync();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(loginToolbarItem);
|
||||
Title = AppResources.VerifyMasterPassword;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await CheckPasswordAsync();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
if(!PasswordCell.Entry.IsFocused)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => PasswordCell.Entry.FocusWithDelay());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
PasswordCell.Entry.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
protected async Task CheckPasswordAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(PasswordCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.MasterPassword), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email,
|
||||
_authService.Kdf, _authService.KdfIterations);
|
||||
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
|
||||
{
|
||||
_appSettingsService.Locked = false;
|
||||
if(Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: keep track of invalid attempts and logout?
|
||||
|
||||
await DisplayAlert(null, AppResources.InvalidMasterPassword, AppResources.Ok);
|
||||
PasswordCell.Entry.Text = string.Empty;
|
||||
PasswordCell.Entry.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Controls;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LockPinPage : BaseLockPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private TapGestureRecognizer _tgr;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public LockPinPage()
|
||||
{
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public PinPageModel Model { get; set; } = new PinPageModel();
|
||||
public PinControl PinControl { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var instructionLabel = new Label
|
||||
{
|
||||
Text = AppResources.EnterPIN,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.Label.SetBinding(Label.TextProperty, nameof(PinPageModel.LabelText));
|
||||
PinControl.Entry.SetBinding(Entry.TextProperty, nameof(PinPageModel.PIN));
|
||||
|
||||
var logoutButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.LogOut,
|
||||
Command = new Command(async () => await LogoutAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
BackgroundColor = Color.Transparent,
|
||||
Uppercase = false
|
||||
};
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 20,
|
||||
Children = { PinControl.Label, instructionLabel, logoutButton, PinControl.Entry }
|
||||
};
|
||||
|
||||
_tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(_tgr);
|
||||
instructionLabel.GestureRecognizers.Add(_tgr);
|
||||
|
||||
Title = AppResources.VerifyPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(_tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
if(!PinControl.Entry.IsFocused)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => PinControl.Entry.Focus());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
_tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
PinControl.Dispose();
|
||||
}
|
||||
|
||||
protected async void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(Model.PIN == _authService.PIN)
|
||||
{
|
||||
_appSettingsService.Locked = false;
|
||||
_appSettingsService.FailedPinAttempts = 0;
|
||||
PinControl.Entry.Unfocus();
|
||||
if(Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_appSettingsService.FailedPinAttempts++;
|
||||
if(_appSettingsService.FailedPinAttempts >= 5)
|
||||
{
|
||||
PinControl.Entry.Unfocus();
|
||||
AuthService.LogOut();
|
||||
return;
|
||||
}
|
||||
|
||||
await DisplayAlert(null, AppResources.InvalidPIN, AppResources.Ok);
|
||||
Model.PIN = string.Empty;
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginPage : ExtendedContentPage
|
||||
{
|
||||
private IAuthService _authService;
|
||||
private ISyncService _syncService;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private ISettings _settings;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private IPushNotificationService _pushNotification;
|
||||
private readonly string _email;
|
||||
|
||||
public LoginPage(string email = null)
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_email = email;
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotificationService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell PasswordCell { get; set; }
|
||||
public FormEntryCell EmailCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
var padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(15, 8),
|
||||
Windows: new Thickness(10, 8));
|
||||
|
||||
PasswordCell = new FormEntryCell(AppResources.MasterPassword, isPassword: true,
|
||||
useLabelAsPlaceholder: true, imageSource: "lock.png", containerPadding: padding);
|
||||
EmailCell = new FormEntryCell(AppResources.EmailAddress, nextElement: PasswordCell.Entry,
|
||||
entryKeyboard: Keyboard.Email, useLabelAsPlaceholder: true, imageSource: "envelope.png",
|
||||
containerPadding: padding);
|
||||
|
||||
var lastLoginEmail = _settings.GetValueOrDefault(Constants.LastLoginEmail, string.Empty);
|
||||
if(!string.IsNullOrWhiteSpace(_email))
|
||||
{
|
||||
EmailCell.Entry.Text = _email;
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(lastLoginEmail))
|
||||
{
|
||||
EmailCell.Entry.Text = lastLoginEmail;
|
||||
}
|
||||
|
||||
PasswordCell.Entry.TargetReturnType = Enums.ReturnType.Go;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = false,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = true,
|
||||
NoFooter = true,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
EmailCell,
|
||||
PasswordCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var forgotPasswordButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.GetPasswordHint,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
Command = new Command(async () => await ForgotPasswordAsync()),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Uppercase = false,
|
||||
BackgroundColor = Color.Transparent
|
||||
};
|
||||
|
||||
var layout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { table, forgotPasswordButton },
|
||||
Spacing = 10
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => layout;
|
||||
|
||||
var scrollView = new ScrollView { Content = layout };
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 70;
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel, () =>
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
}));
|
||||
}
|
||||
|
||||
var loginToolbarItem = new ToolbarItem(AppResources.LogIn, Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
await LogIn();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(loginToolbarItem);
|
||||
Title = AppResources.Bitwarden;
|
||||
Content = scrollView;
|
||||
NavigationPage.SetBackButtonTitle(this, AppResources.LogIn);
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
EmailCell.InitEvents();
|
||||
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_email))
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(EmailCell.Entry.Text))
|
||||
{
|
||||
PasswordCell.Entry.FocusWithDelay();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await LogIn();
|
||||
}
|
||||
|
||||
private async Task ForgotPasswordAsync()
|
||||
{
|
||||
await Navigation.PushAsync(new PasswordHintPage());
|
||||
}
|
||||
|
||||
private async Task LogIn()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(EmailCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.EmailAddress), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(PasswordCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.MasterPassword), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||
var result = await _authService.TokenPostAsync(EmailCell.Entry.Text, PasswordCell.Entry.Text);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(!result.Success)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, result.ErrorMessage, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordCell.Entry.Text = string.Empty;
|
||||
|
||||
if(result.TwoFactorRequired)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step");
|
||||
await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result));
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn");
|
||||
|
||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||
Application.Current.MainPage = new MainPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,614 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginTwoFactorPage : ExtendedContentPage
|
||||
{
|
||||
private DateTime? _lastAction;
|
||||
private IAuthService _authService;
|
||||
private ISyncService _syncService;
|
||||
private IDeviceInfoService _deviceInfoService;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private ITwoFactorApiRepository _twoFactorApiRepository;
|
||||
private IPushNotificationService _pushNotification;
|
||||
private IAppSettingsService _appSettingsService;
|
||||
private readonly string _email;
|
||||
private readonly string _masterPasswordHash;
|
||||
private readonly SymmetricCryptoKey _key;
|
||||
private readonly Dictionary<TwoFactorProviderType, Dictionary<string, object>> _providers;
|
||||
private TwoFactorProviderType? _providerType;
|
||||
private readonly FullLoginResult _result;
|
||||
private readonly string _duoOrgTitle;
|
||||
|
||||
public LoginTwoFactorPage(string email, FullLoginResult result, TwoFactorProviderType? type = null)
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_duoOrgTitle = $"Duo ({AppResources.Organization})";
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
|
||||
_email = email;
|
||||
_result = result;
|
||||
_masterPasswordHash = result.MasterPasswordHash;
|
||||
_key = result.Key;
|
||||
_providers = result.TwoFactorProviders;
|
||||
_providerType = type ?? GetDefaultProvider();
|
||||
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_twoFactorApiRepository = Resolver.Resolve<ITwoFactorApiRepository>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotificationService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell TokenCell { get; set; }
|
||||
public ExtendedSwitchCell RememberCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
SubscribeYubiKey(true);
|
||||
if(_providers.Count > 1)
|
||||
{
|
||||
var sendEmailTask = SendEmailAsync(false);
|
||||
}
|
||||
|
||||
ToolbarItems.Clear();
|
||||
var scrollView = new ScrollView();
|
||||
|
||||
var anotherMethodButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.UseAnotherTwoStepMethod,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
Margin = new Thickness(15, 0, 15, 25),
|
||||
Command = new Command(() => AnotherMethodAsync()),
|
||||
Uppercase = false,
|
||||
BackgroundColor = Color.Transparent,
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
};
|
||||
|
||||
var instruction = new Label
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Margin = new Thickness(15),
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
RememberCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.RememberMe,
|
||||
On = false
|
||||
};
|
||||
|
||||
var continueToolbarItem = new ToolbarItem(AppResources.Continue,
|
||||
Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
var token = TokenCell?.Entry.Text.Trim().Replace(" ", "");
|
||||
await LogInAsync(token);
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
if(!_providerType.HasValue)
|
||||
{
|
||||
instruction.Text = AppResources.NoTwoStepAvailable;
|
||||
|
||||
var layout = new StackLayout
|
||||
{
|
||||
Children = { instruction, anotherMethodButton },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
scrollView.Content = layout;
|
||||
|
||||
Title = AppResources.LoginUnavailable;
|
||||
Content = scrollView;
|
||||
}
|
||||
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
|
||||
_providerType.Value == TwoFactorProviderType.Email)
|
||||
{
|
||||
var padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(15, 8),
|
||||
Windows: new Thickness(10, 8));
|
||||
|
||||
TokenCell = new FormEntryCell(AppResources.VerificationCode, useLabelAsPlaceholder: true,
|
||||
imageSource: "lock", containerPadding: padding);
|
||||
|
||||
TokenCell.Entry.Keyboard = Keyboard.Numeric;
|
||||
TokenCell.Entry.TargetReturnType = Enums.ReturnType.Go;
|
||||
|
||||
var table = new TwoFactorTable(
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
TokenCell,
|
||||
RememberCell
|
||||
});
|
||||
|
||||
var layout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { instruction, table },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => layout;
|
||||
scrollView.Content = layout;
|
||||
|
||||
switch(_providerType.Value)
|
||||
{
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
instruction.Text = AppResources.EnterVerificationCodeApp;
|
||||
layout.Children.Add(anotherMethodButton);
|
||||
break;
|
||||
case TwoFactorProviderType.Email:
|
||||
var emailParams = _providers[TwoFactorProviderType.Email];
|
||||
var redactedEmail = emailParams["Email"].ToString();
|
||||
|
||||
instruction.Text = string.Format(AppResources.EnterVerificationCodeEmail, redactedEmail);
|
||||
var resendEmailButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.SendVerificationCodeAgain,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
Margin = new Thickness(15, 0, 15, 0),
|
||||
Command = new Command(async () => await SendEmailAsync(true)),
|
||||
Uppercase = false,
|
||||
BackgroundColor = Color.Transparent,
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
};
|
||||
|
||||
layout.Children.Add(resendEmailButton);
|
||||
layout.Children.Add(anotherMethodButton);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ToolbarItems.Add(continueToolbarItem);
|
||||
Title = AppResources.VerificationCode;
|
||||
|
||||
Content = scrollView;
|
||||
TokenCell.Entry.FocusWithDelay();
|
||||
}
|
||||
else if(_providerType == TwoFactorProviderType.Duo ||
|
||||
_providerType == TwoFactorProviderType.OrganizationDuo)
|
||||
{
|
||||
var duoParams = _providers[_providerType.Value];
|
||||
|
||||
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
|
||||
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
|
||||
|
||||
var webVaultUrl = "https://vault.bitwarden.com";
|
||||
if(!string.IsNullOrWhiteSpace(_appSettingsService.BaseUrl))
|
||||
{
|
||||
webVaultUrl = _appSettingsService.BaseUrl;
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(_appSettingsService.WebVaultUrl))
|
||||
{
|
||||
webVaultUrl = _appSettingsService.WebVaultUrl;
|
||||
}
|
||||
|
||||
var webView = new HybridWebView
|
||||
{
|
||||
Uri = $"{webVaultUrl}/duo-connector.html?host={host}&request={req}",
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
MinimumHeightRequest = 400
|
||||
};
|
||||
webView.RegisterAction(async (sig) =>
|
||||
{
|
||||
await LogInAsync(sig);
|
||||
});
|
||||
|
||||
var table = new TwoFactorTable(
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
RememberCell
|
||||
});
|
||||
|
||||
var layout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { webView, table, anotherMethodButton },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => layout;
|
||||
scrollView.Content = layout;
|
||||
|
||||
Title = _providerType == TwoFactorProviderType.Duo ? "Duo" : _duoOrgTitle;
|
||||
Content = scrollView;
|
||||
}
|
||||
else if(_providerType == TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
instruction.Text = Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
|
||||
AppResources.YubiKeyInstruction;
|
||||
|
||||
var image = new CachedImage
|
||||
{
|
||||
Source = "yubikey.png",
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 266,
|
||||
HeightRequest = 160,
|
||||
Margin = new Thickness(0, 0, 0, 25)
|
||||
};
|
||||
|
||||
var section = new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
RememberCell
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform != Device.iOS)
|
||||
{
|
||||
TokenCell = new FormEntryCell("", isPassword: true, imageSource: "lock",
|
||||
useLabelAsPlaceholder: true);
|
||||
TokenCell.Entry.TargetReturnType = Enums.ReturnType.Go;
|
||||
section.Insert(0, TokenCell);
|
||||
}
|
||||
|
||||
var table = new TwoFactorTable(section);
|
||||
var layout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { instruction, image, table },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
var tryAgainButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.TryAgain,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
Margin = new Thickness(15, 0, 15, 0),
|
||||
Command = new Command(() => ListenYubiKey(true, true)),
|
||||
Uppercase = false,
|
||||
BackgroundColor = Color.Transparent,
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
};
|
||||
layout.Children.Add(tryAgainButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(continueToolbarItem);
|
||||
}
|
||||
|
||||
layout.Children.Add(anotherMethodButton);
|
||||
|
||||
table.WrappingStackLayout = () => layout;
|
||||
scrollView.Content = layout;
|
||||
|
||||
Title = AppResources.YubiKeyTitle;
|
||||
Content = scrollView;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListenYubiKey(true);
|
||||
|
||||
InitEvents();
|
||||
if(TokenCell == null && Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
_deviceActionService.DismissKeyboard();
|
||||
}
|
||||
|
||||
if(TokenCell != null)
|
||||
{
|
||||
TokenCell.Entry.FocusWithDelay();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitEvents()
|
||||
{
|
||||
if(TokenCell != null)
|
||||
{
|
||||
TokenCell.InitEvents();
|
||||
TokenCell.Entry.Completed += Entry_Completed;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListenYubiKey(false);
|
||||
|
||||
if(TokenCell != null)
|
||||
{
|
||||
TokenCell.Dispose();
|
||||
TokenCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
|
||||
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
// ref: https://github.com/bitwarden/mobile/issues/350
|
||||
if(Device.RuntimePlatform == Device.Android && _providerType.HasValue &&
|
||||
_providerType.Value == TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private async void AnotherMethodAsync()
|
||||
{
|
||||
var beforeProviderType = _providerType;
|
||||
|
||||
var options = new List<string>();
|
||||
if(_providers.ContainsKey(TwoFactorProviderType.OrganizationDuo))
|
||||
{
|
||||
options.Add(_duoOrgTitle);
|
||||
}
|
||||
|
||||
if(_providers.ContainsKey(TwoFactorProviderType.Authenticator))
|
||||
{
|
||||
options.Add(AppResources.AuthenticatorAppTitle);
|
||||
}
|
||||
|
||||
if(_providers.ContainsKey(TwoFactorProviderType.Duo))
|
||||
{
|
||||
options.Add("Duo");
|
||||
}
|
||||
|
||||
if(_providers.ContainsKey(TwoFactorProviderType.YubiKey))
|
||||
{
|
||||
var nfcKey = _providers[TwoFactorProviderType.YubiKey].ContainsKey("Nfc") &&
|
||||
(bool)_providers[TwoFactorProviderType.YubiKey]["Nfc"];
|
||||
if((_deviceInfoService.NfcEnabled && nfcKey) || Device.RuntimePlatform != Device.iOS)
|
||||
{
|
||||
options.Add(AppResources.YubiKeyTitle);
|
||||
}
|
||||
}
|
||||
|
||||
if(_providers.ContainsKey(TwoFactorProviderType.Email))
|
||||
{
|
||||
options.Add(AppResources.Email);
|
||||
}
|
||||
|
||||
options.Add(AppResources.RecoveryCodeTitle);
|
||||
|
||||
var selection = await DisplayActionSheet(AppResources.TwoStepLoginOptions, AppResources.Cancel, null,
|
||||
options.ToArray());
|
||||
if(selection == AppResources.AuthenticatorAppTitle)
|
||||
{
|
||||
_providerType = TwoFactorProviderType.Authenticator;
|
||||
}
|
||||
else if(selection == "Duo")
|
||||
{
|
||||
_providerType = TwoFactorProviderType.Duo;
|
||||
}
|
||||
else if(selection == _duoOrgTitle)
|
||||
{
|
||||
_providerType = TwoFactorProviderType.OrganizationDuo;
|
||||
}
|
||||
else if(selection == AppResources.YubiKeyTitle)
|
||||
{
|
||||
_providerType = TwoFactorProviderType.YubiKey;
|
||||
}
|
||||
else if(selection == AppResources.Email)
|
||||
{
|
||||
_providerType = TwoFactorProviderType.Email;
|
||||
}
|
||||
else if(selection == AppResources.RecoveryCodeTitle)
|
||||
{
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/lost-two-step-device/"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(beforeProviderType != _providerType)
|
||||
{
|
||||
Init();
|
||||
ListenYubiKey(false, beforeProviderType == TwoFactorProviderType.YubiKey);
|
||||
ListenYubiKey(true);
|
||||
InitEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEmailAsync(bool doToast)
|
||||
{
|
||||
if(_providerType != TwoFactorProviderType.Email)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var response = await _twoFactorApiRepository.PostSendEmailLoginAsync(new Models.Api.TwoFactorEmailRequest
|
||||
{
|
||||
Email = _email,
|
||||
MasterPasswordHash = _masterPasswordHash
|
||||
});
|
||||
|
||||
if(response.Succeeded && doToast)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.VerificationEmailSent);
|
||||
}
|
||||
else if(!response.Succeeded)
|
||||
{
|
||||
await DisplayAlert(null, AppResources.VerificationEmailNotSent, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
var token = TokenCell.Entry.Text.Trim().Replace(" ", "");
|
||||
await LogInAsync(token);
|
||||
}
|
||||
|
||||
private async Task LogInAsync(string token)
|
||||
{
|
||||
if(!_providerType.HasValue || _lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.VerificationCode), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(string.Concat(AppResources.Validating, "..."));
|
||||
var response = await _authService.TokenPostTwoFactorAsync(_providerType.Value, token, RememberCell.On,
|
||||
_email, _masterPasswordHash, _key);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(!response.Success)
|
||||
{
|
||||
ListenYubiKey(true);
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step", _providerType.Value.ToString());
|
||||
|
||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Application.Current.MainPage = new MainPage();
|
||||
});
|
||||
}
|
||||
|
||||
private TwoFactorProviderType? GetDefaultProvider()
|
||||
{
|
||||
TwoFactorProviderType? provider = null;
|
||||
|
||||
if(_providers != null)
|
||||
{
|
||||
foreach(var p in _providers)
|
||||
{
|
||||
switch(p.Key)
|
||||
{
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
if(provider == TwoFactorProviderType.Duo || provider == TwoFactorProviderType.YubiKey ||
|
||||
provider == TwoFactorProviderType.OrganizationDuo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case TwoFactorProviderType.Email:
|
||||
if(provider.HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case TwoFactorProviderType.Duo:
|
||||
if(provider == TwoFactorProviderType.YubiKey ||
|
||||
provider == TwoFactorProviderType.OrganizationDuo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case TwoFactorProviderType.YubiKey:
|
||||
if(provider == TwoFactorProviderType.OrganizationDuo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var nfcKey = p.Value.ContainsKey("Nfc") && (bool)p.Value["Nfc"];
|
||||
if((!_deviceInfoService.NfcEnabled || !nfcKey) && Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
provider = p.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private void ListenYubiKey(bool listen, bool overrideCheck = false)
|
||||
{
|
||||
if(_providerType == TwoFactorProviderType.YubiKey || overrideCheck)
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", listen);
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeYubiKey(bool subscribe)
|
||||
{
|
||||
if(_providerType != TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
|
||||
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
|
||||
if(!subscribe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessagingCenter.Subscribe<Application, string>(Application.Current, "GotYubiKeyOTP", async (sender, otp) =>
|
||||
{
|
||||
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
|
||||
if(_providerType == TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
await LogInAsync(otp);
|
||||
}
|
||||
});
|
||||
|
||||
SubscribeYubiKeyResume();
|
||||
}
|
||||
|
||||
private void SubscribeYubiKeyResume()
|
||||
{
|
||||
MessagingCenter.Subscribe<Application>(Application.Current, "ResumeYubiKey", (sender) =>
|
||||
{
|
||||
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
|
||||
if(_providerType == TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", true);
|
||||
SubscribeYubiKeyResume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class TwoFactorTable : ExtendedTableView
|
||||
{
|
||||
public TwoFactorTable(TableSection section)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
NoFooter = true;
|
||||
NoHeader = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
Root = new TableRoot
|
||||
{
|
||||
section
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RowHeight = -1;
|
||||
EstimatedRowHeight = 70;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class MainPage : ExtendedTabbedPage
|
||||
{
|
||||
private ExtendedNavigationPage _vaultPage;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
TintColor = Color.FromHex("3c8dbc");
|
||||
|
||||
_vaultPage = new ExtendedNavigationPage(new VaultListGroupingsPage());
|
||||
var passwordGeneratorNavigation = new ExtendedNavigationPage(new ToolsPasswordGeneratorPage(this));
|
||||
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage(this));
|
||||
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage(this));
|
||||
|
||||
_vaultPage.Icon = "fa_lock.png";
|
||||
passwordGeneratorNavigation.Icon = "refresh.png";
|
||||
toolsNavigation.Icon = "tools.png";
|
||||
settingsNavigation.Icon = "cogs.png";
|
||||
|
||||
Children.Add(_vaultPage);
|
||||
Children.Add(passwordGeneratorNavigation);
|
||||
Children.Add(toolsNavigation);
|
||||
Children.Add(settingsNavigation);
|
||||
}
|
||||
|
||||
public void ResetToVaultPage()
|
||||
{
|
||||
CurrentPage = _vaultPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class PasswordHintPage : ExtendedContentPage
|
||||
{
|
||||
private IAccountsApiRepository _accountApiRepository;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
|
||||
public PasswordHintPage()
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_accountApiRepository = Resolver.Resolve<IAccountsApiRepository>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell EmailCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(15, 8),
|
||||
Windows: new Thickness(10, 8));
|
||||
|
||||
EmailCell = new FormEntryCell(AppResources.EmailAddress, entryKeyboard: Keyboard.Email,
|
||||
useLabelAsPlaceholder: true, imageSource: "envelope.png", containerPadding: padding);
|
||||
|
||||
EmailCell.Entry.TargetReturnType = Enums.ReturnType.Go;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = false,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = true,
|
||||
NoFooter = true,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
EmailCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var hintLabel = new Label
|
||||
{
|
||||
Text = AppResources.EnterEmailForHint,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
var layout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { table, hintLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => layout;
|
||||
var scrollView = new ScrollView { Content = layout };
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 70;
|
||||
}
|
||||
|
||||
var submitToolbarItem = new ToolbarItem(AppResources.Submit, Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
await SubmitAsync();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(submitToolbarItem);
|
||||
Title = AppResources.PasswordHint;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.InitEvents();
|
||||
EmailCell.Entry.Completed += Entry_Completed;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
EmailCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await SubmitAsync();
|
||||
}
|
||||
|
||||
private async Task SubmitAsync()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(EmailCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.EmailAddress), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new PasswordHintRequest
|
||||
{
|
||||
Email = EmailCell.Entry.Text
|
||||
};
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Submitting);
|
||||
var response = await _accountApiRepository.PostPasswordHintAsync(request);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(!response.Succeeded)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.PasswordHintAlert, AppResources.Ok);
|
||||
}
|
||||
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class RegisterPage : ExtendedContentPage
|
||||
{
|
||||
private ICryptoService _cryptoService;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IAccountsApiRepository _accountsApiRepository;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private HomePage _homePage;
|
||||
|
||||
public RegisterPage(HomePage homePage)
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_homePage = homePage;
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_accountsApiRepository = Resolver.Resolve<IAccountsApiRepository>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell EmailCell { get; set; }
|
||||
public FormEntryCell PasswordCell { get; set; }
|
||||
public FormEntryCell ConfirmPasswordCell { get; set; }
|
||||
public FormEntryCell PasswordHintCell { get; set; }
|
||||
public RedrawableStackLayout StackLayout { get; set; }
|
||||
public Label PasswordLabel { get; set; }
|
||||
public Label HintLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
var padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(15, 8),
|
||||
Windows: new Thickness(10, 8));
|
||||
|
||||
PasswordHintCell = new FormEntryCell(AppResources.MasterPasswordHint, useLabelAsPlaceholder: true,
|
||||
imageSource: "lightbulb.png", containerPadding: padding);
|
||||
ConfirmPasswordCell = new FormEntryCell(AppResources.RetypeMasterPassword, isPassword: true,
|
||||
nextElement: PasswordHintCell.Entry, useLabelAsPlaceholder: true, imageSource: "lock.png",
|
||||
containerPadding: padding);
|
||||
PasswordCell = new FormEntryCell(AppResources.MasterPassword, isPassword: true,
|
||||
nextElement: ConfirmPasswordCell.Entry, useLabelAsPlaceholder: true, imageSource: "lock.png",
|
||||
containerPadding: padding);
|
||||
EmailCell = new FormEntryCell(AppResources.EmailAddress, nextElement: PasswordCell.Entry,
|
||||
entryKeyboard: Keyboard.Email, useLabelAsPlaceholder: true, imageSource: "envelope.png",
|
||||
containerPadding: padding);
|
||||
|
||||
PasswordHintCell.Entry.TargetReturnType = Enums.ReturnType.Done;
|
||||
|
||||
var table = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
EmailCell,
|
||||
PasswordCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PasswordLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
var table2 = new FormTableView(this)
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
ConfirmPasswordCell,
|
||||
PasswordHintCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HintLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordHintDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { table, PasswordLabel, table2, HintLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
var loginToolbarItem = new ToolbarItem(AppResources.Submit, Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
await Register();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = table2.RowHeight = -1;
|
||||
table.EstimatedRowHeight = table2.EstimatedRowHeight = 70;
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel, () =>
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
}));
|
||||
}
|
||||
|
||||
ToolbarItems.Add(loginToolbarItem);
|
||||
Title = AppResources.CreateAccount;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
EmailCell.InitEvents();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordHintCell.InitEvents();
|
||||
ConfirmPasswordCell.InitEvents();
|
||||
PasswordHintCell.Entry.Completed += Entry_Completed;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Dispose();
|
||||
PasswordHintCell.Dispose();
|
||||
ConfirmPasswordCell.Dispose();
|
||||
PasswordHintCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await Register();
|
||||
}
|
||||
|
||||
private async Task Register()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(EmailCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(PasswordCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(PasswordCell.Entry.Text.Length < 8)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.MasterPasswordLengthValMessage,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ConfirmPasswordCell.Entry.Text != PasswordCell.Entry.Text)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.MasterPasswordConfirmationValMessage,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var kdf = Enums.KdfType.PBKDF2_SHA256;
|
||||
var kdfIterations = 100000;
|
||||
var normalizedEmail = EmailCell.Entry.Text.ToLower().Trim();
|
||||
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail, kdf, kdfIterations);
|
||||
var encKey = _cryptoService.MakeEncKey(key);
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
Email = normalizedEmail,
|
||||
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text),
|
||||
MasterPasswordHint = !string.IsNullOrWhiteSpace(PasswordHintCell.Entry.Text)
|
||||
? PasswordHintCell.Entry.Text : null,
|
||||
Key = encKey.Item2.EncryptedString,
|
||||
Kdf = kdf,
|
||||
KdfIterations = kdfIterations
|
||||
};
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
||||
var response = await _accountsApiRepository.PostRegisterAsync(request);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(!response.Succeeded)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("Registered");
|
||||
await _homePage.DismissRegisterAndLoginAsync(normalizedEmail);
|
||||
}
|
||||
|
||||
private class FormTableView : ExtendedTableView
|
||||
{
|
||||
public FormTableView(RegisterPage page)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
using ZXing.Net.Mobile.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ScanPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ZXingScannerView _zxing;
|
||||
private readonly OverlayGrid _overlay;
|
||||
private DateTime? _timerStarted = null;
|
||||
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(3);
|
||||
|
||||
public ScanPage(Action<string> callback)
|
||||
: base(updateActivity: false, requireAuth: false)
|
||||
{
|
||||
_zxing = new ZXingScannerView
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
AutomationId = "zxingScannerView",
|
||||
Options = new ZXing.Mobile.MobileBarcodeScanningOptions
|
||||
{
|
||||
UseNativeScanning = true,
|
||||
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
|
||||
AutoRotate = false
|
||||
}
|
||||
};
|
||||
|
||||
_zxing.OnScanResult += (result) =>
|
||||
{
|
||||
// Stop analysis until we navigate away so we don't keep reading barcodes
|
||||
_zxing.IsAnalyzing = false;
|
||||
_zxing.IsScanning = false;
|
||||
|
||||
Uri uri;
|
||||
if(!string.IsNullOrWhiteSpace(result.Text) && Uri.TryCreate(result.Text, UriKind.Absolute, out uri) &&
|
||||
!string.IsNullOrWhiteSpace(uri.Query))
|
||||
{
|
||||
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
|
||||
foreach(var part in queryParts)
|
||||
{
|
||||
if(part.StartsWith("secret="))
|
||||
{
|
||||
callback(part.Substring(7)?.ToUpperInvariant());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(null);
|
||||
};
|
||||
|
||||
_overlay = new OverlayGrid
|
||||
{
|
||||
AutomationId = "zxingDefaultOverlay"
|
||||
};
|
||||
|
||||
_overlay.TopLabel.Text = AppResources.CameraInstructionTop;
|
||||
_overlay.BottomLabel.Text = AppResources.CameraInstructionBottom;
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { _zxing, _overlay }
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.ScanQrTitle;
|
||||
Content = grid;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_zxing.IsScanning = true;
|
||||
_timerStarted = DateTime.Now;
|
||||
Device.StartTimer(new TimeSpan(0, 0, 2), () =>
|
||||
{
|
||||
if(_timerStarted == null || (DateTime.Now - _timerStarted) > _timerMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_zxing.AutoFocus();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
_timerStarted = null;
|
||||
_zxing.IsScanning = false;
|
||||
base.OnDisappearing();
|
||||
}
|
||||
|
||||
public class OverlayGrid : Grid
|
||||
{
|
||||
public OverlayGrid()
|
||||
{
|
||||
VerticalOptions = LayoutOptions.FillAndExpand;
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand;
|
||||
|
||||
RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
RowDefinitions.Add(new RowDefinition { Height = new GridLength(2, GridUnitType.Star) });
|
||||
RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
Children.Add(new BoxView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Fill,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
BackgroundColor = Color.Black,
|
||||
Opacity = 0.7,
|
||||
}, 0, 0);
|
||||
|
||||
Children.Add(new BoxView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
BackgroundColor = Color.Transparent
|
||||
}, 0, 1);
|
||||
|
||||
Children.Add(new BoxView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Fill,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
BackgroundColor = Color.Black,
|
||||
Opacity = 0.7,
|
||||
}, 0, 2);
|
||||
|
||||
TopLabel = new Label
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
TextColor = Color.White,
|
||||
AutomationId = "zxingDefaultOverlay_TopTextLabel",
|
||||
};
|
||||
Children.Add(TopLabel, 0, 0);
|
||||
|
||||
BottomLabel = new Label
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
TextColor = Color.White,
|
||||
AutomationId = "zxingDefaultOverlay_BottomTextLabel",
|
||||
};
|
||||
Children.Add(BottomLabel, 0, 2);
|
||||
}
|
||||
|
||||
public Label TopLabel { get; set; }
|
||||
public Label BottomLabel { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsAboutPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
|
||||
public SettingsAboutPage()
|
||||
{
|
||||
_appInfoService = Resolver.Resolve<IAppInfoService>();
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell CreditsCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var versionLabel = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
Text = $@"{AppResources.Version} {_appInfoService.Version} ({_appInfoService.Build})
|
||||
© 8bit Solutions LLC 2015-{DateTime.Now.Year}",
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
var logoVersionStackLayout = new StackLayout
|
||||
{
|
||||
Children = { logo, versionLabel },
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(0, 40, 0, 0)
|
||||
};
|
||||
|
||||
CreditsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Credits,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
EnableScrolling = false,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
CreditsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 44;
|
||||
}
|
||||
|
||||
var stackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { logoVersionStackLayout, table },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => stackLayout;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.About;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
}
|
||||
|
||||
private void RateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushAsync(new SettingsCreditsPage());
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CreditsCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CreditsCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Linq;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsAddFolderPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public SettingsAddFolderPage()
|
||||
{
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = false,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
NameCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
table.BottomPadding = 50;
|
||||
}
|
||||
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var folder = new Folder
|
||||
{
|
||||
Name = NameCell.Entry.Text.Encrypt()
|
||||
};
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveResult.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderCreated);
|
||||
_googleAnalyticsService.TrackAppEvent("CreatedFolder");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveResult.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveResult.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
Title = AppResources.AddFolder;
|
||||
Content = table;
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsCreditsPage : ExtendedContentPage
|
||||
{
|
||||
public SettingsCreditsPage()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
EnableScrolling = true,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = false,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.Translations)
|
||||
{
|
||||
new CustomViewCell(@"@felixqu - Chinese Simplified
|
||||
@thomassth - Chinese Traditional
|
||||
@Primokorn, @maxlandry - French
|
||||
@bestHippos - Italian
|
||||
@SW1FT - Portuguese
|
||||
@majod - Slovak
|
||||
@King-Tut-Tut - Swedish
|
||||
@Igetin - Finnish")
|
||||
},
|
||||
new TableSection(AppResources.Icons)
|
||||
{
|
||||
new CustomViewCell(@"Tools by Alex Auda Samora from the Noun Project
|
||||
Fingerprint by masterpage.com from the Noun Project")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 100;
|
||||
}
|
||||
|
||||
Title = AppResources.ThankYou;
|
||||
Content = table;
|
||||
}
|
||||
|
||||
public class CustomViewCell : ViewCell
|
||||
{
|
||||
public CustomViewCell(string text)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Text = text,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
|
||||
};
|
||||
|
||||
var layout = new StackLayout
|
||||
{
|
||||
Children = { label },
|
||||
Padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(16, 20),
|
||||
Windows: new Thickness(10, 8)),
|
||||
BackgroundColor = Color.White
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
label.TextColor = Color.Black;
|
||||
}
|
||||
|
||||
View = layout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Linq;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsEditFolderPage : ExtendedContentPage
|
||||
{
|
||||
private readonly string _folderId;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public SettingsEditFolderPage(string folderId)
|
||||
{
|
||||
_folderId = folderId;
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
public ExtendedTextCell DeleteCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var folder = _folderService.GetByIdAsync(_folderId).GetAwaiter().GetResult();
|
||||
if(folder == null)
|
||||
{
|
||||
// TODO: handle error. navigate back? should never happen...
|
||||
return;
|
||||
}
|
||||
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell.Entry.Text = folder.Name.Decrypt();
|
||||
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var mainTable = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
NameCell
|
||||
},
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
DeleteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
mainTable.RowHeight = -1;
|
||||
mainTable.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
mainTable.BottomPadding = 50;
|
||||
}
|
||||
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
folder.Name = NameCell.Entry.Text.Encrypt();
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveResult.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderUpdated);
|
||||
_googleAnalyticsService.TrackAppEvent("EditedFolder");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveResult.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveResult.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
Title = AppResources.EditFolder;
|
||||
Content = mainTable;
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Validate the delete operation. ex. Cannot delete a folder that has ciphers in it?
|
||||
|
||||
var confirmed = await DisplayAlert(null, AppResources.DoYouReallyWantToDelete, AppResources.Yes, AppResources.No);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
|
||||
var deleteTask = await _folderService.DeleteAsync(_folderId);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(deleteTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderDeleted);
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(deleteTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, deleteTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsHelpPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public SettingsHelpPage()
|
||||
{
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell EmailCell { get; set; }
|
||||
public ExtendedTextCell WebsiteCell { get; set; }
|
||||
public ExtendedTextCell BugCell { get; set; }
|
||||
public RedrawableStackLayout StackLayout { get; set; }
|
||||
private CustomLabel EmailLabel { get; set; }
|
||||
private CustomLabel WebsiteLabel { get; set; }
|
||||
private CustomLabel BugLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
EmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.EmailUs,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var emailTable = new CustomTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
EmailCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EmailLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.EmailUsDescription
|
||||
};
|
||||
|
||||
WebsiteCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.VisitOurWebsite,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var websiteTable = new CustomTableView(this)
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
WebsiteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebsiteLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.VisitOurWebsiteDescription
|
||||
};
|
||||
|
||||
BugCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.FileBugReport,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var bugTable = new CustomTableView(this)
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
BugCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BugLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.FileBugReportDescription
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { emailTable, EmailLabel, websiteTable, WebsiteLabel, bugTable, BugLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.HelpAndFeedback;
|
||||
Content = new ScrollView { Content = StackLayout };
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.Tapped += EmailCell_Tapped;
|
||||
WebsiteCell.Tapped += WebsiteCell_Tapped;
|
||||
BugCell.Tapped += BugCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Tapped -= EmailCell_Tapped;
|
||||
WebsiteCell.Tapped -= WebsiteCell_Tapped;
|
||||
BugCell.Tapped -= BugCell_Tapped;
|
||||
}
|
||||
|
||||
private void EmailCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpEmail");
|
||||
Device.OpenUri(new Uri("mailto:hello@bitwarden.com"));
|
||||
}
|
||||
|
||||
private void WebsiteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpWebsite");
|
||||
Device.OpenUri(new Uri("https://bitwarden.com/contact/"));
|
||||
}
|
||||
|
||||
private void BugCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpBug");
|
||||
Device.OpenUri(new Uri("https://github.com/bitwarden/mobile"));
|
||||
}
|
||||
|
||||
private class CustomTableView : ExtendedTableView
|
||||
{
|
||||
public CustomTableView(SettingsHelpPage page)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RowHeight = -1;
|
||||
EstimatedRowHeight = 44;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomLabel : Label
|
||||
{
|
||||
public CustomLabel(ContentPage page)
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap;
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
Style = (Style)Application.Current.Resources["text-muted"];
|
||||
Margin = new Thickness(15, (page.IsLandscape() ? 5 : 0), 15, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsListFoldersPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IFolderService _folderService;
|
||||
|
||||
public SettingsListFoldersPage()
|
||||
{
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<SettingsFolderPageModel> Folders { get; private set; }
|
||||
= new ExtendedObservableCollection<SettingsFolderPageModel>();
|
||||
public ExtendedListView ListView { get; set; }
|
||||
private AddFolderToolBarItem AddItem { get; set; }
|
||||
public Fab Fab { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ListView = new ExtendedListView
|
||||
{
|
||||
ItemsSource = Folders,
|
||||
ItemTemplate = new DataTemplate(() => new SettingsFolderListViewCell(this))
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
var fabLayout = new FabLayout(ListView);
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Fab = new Fab(fabLayout, "plus.png", async (sender, args) =>
|
||||
{
|
||||
await Navigation.PushForDeviceAsync(new SettingsAddFolderPage());
|
||||
});
|
||||
ListView.BottomPadding = 50;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddItem = new AddFolderToolBarItem(this);
|
||||
ToolbarItems.Add(AddItem);
|
||||
}
|
||||
|
||||
Title = AppResources.Folders;
|
||||
Content = fabLayout;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += FolderSelected;
|
||||
AddItem?.InitEvents();
|
||||
LoadFoldersAsync().Wait();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= FolderSelected;
|
||||
AddItem?.Dispose();
|
||||
}
|
||||
|
||||
private async Task LoadFoldersAsync()
|
||||
{
|
||||
var folders = await _folderService.GetAllAsync();
|
||||
var pageFolders = folders.Select(f => new SettingsFolderPageModel(f)).OrderBy(f => f.Name);
|
||||
Folders.ResetWithRange(pageFolders);
|
||||
}
|
||||
|
||||
private async void FolderSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var folder = e.SelectedItem as SettingsFolderPageModel;
|
||||
var page = new SettingsEditFolderPage(folder.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private class AddFolderToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly SettingsListFoldersPage _page;
|
||||
|
||||
public AddFolderToolBarItem(SettingsListFoldersPage page)
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Add;
|
||||
Icon = "plus.png";
|
||||
ClickAction = () => ClickedItem();
|
||||
}
|
||||
|
||||
private async void ClickedItem()
|
||||
{
|
||||
var page = new SettingsAddFolderPage();
|
||||
await _page.Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsFolderListViewCell : ExtendedTextCell
|
||||
{
|
||||
public SettingsFolderListViewCell(SettingsListFoldersPage page)
|
||||
{
|
||||
this.SetBinding(TextProperty, nameof(SettingsFolderPageModel.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsOptionsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
|
||||
public SettingsOptionsPage()
|
||||
{
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private RedrawableStackLayout StackLayout { get; set; }
|
||||
private ExtendedSwitchCell CopyTotpCell { get; set; }
|
||||
private Label CopyTotpLabel { get; set; }
|
||||
private ExtendedSwitchCell WebsiteIconsCell { get; set; }
|
||||
private Label WebsiteIconsLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillPersistNotificationCell { get; set; }
|
||||
private Label AutofillPersistNotificationLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillPasswordFieldCell { get; set; }
|
||||
private Label AutofillPasswordFieldLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillAlwaysCell { get; set; }
|
||||
private Label AutofillAlwaysLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
WebsiteIconsCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.DisableWebsiteIcons,
|
||||
On = _appSettings.DisableWebsiteIcons
|
||||
};
|
||||
|
||||
var websiteIconsTable = new FormTableView(this, true)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
WebsiteIconsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CopyTotpCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.DisableAutoTotpCopy,
|
||||
On = _settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false)
|
||||
};
|
||||
|
||||
var totpTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
CopyTotpCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CopyTotpLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.DisableAutoTotpCopyDescription
|
||||
};
|
||||
|
||||
WebsiteIconsLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.DisableWebsiteIconsDescription
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children =
|
||||
{
|
||||
websiteIconsTable, WebsiteIconsLabel,
|
||||
totpTable, CopyTotpLabel
|
||||
},
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillAlways,
|
||||
On = !_appSettings.AutofillPersistNotification && !_appSettings.AutofillPasswordField
|
||||
};
|
||||
|
||||
var autofillAlwaysTable = new FormTableView(this, true)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.AutofillAccessibilityService)
|
||||
{
|
||||
AutofillAlwaysCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillAlwaysLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillAlwaysDescription
|
||||
};
|
||||
|
||||
AutofillPersistNotificationCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillPersistNotification,
|
||||
On = _appSettings.AutofillPersistNotification
|
||||
};
|
||||
|
||||
var autofillPersistNotificationTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
AutofillPersistNotificationCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillPersistNotificationLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillPersistNotificationDescription
|
||||
};
|
||||
|
||||
AutofillPasswordFieldCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillPasswordField,
|
||||
On = _appSettings.AutofillPasswordField
|
||||
};
|
||||
|
||||
var autofillPasswordFieldTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
AutofillPasswordFieldCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillPasswordFieldLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillPasswordFieldDescription
|
||||
};
|
||||
|
||||
StackLayout.Children.Add(autofillAlwaysTable);
|
||||
StackLayout.Children.Add(AutofillAlwaysLabel);
|
||||
StackLayout.Children.Add(autofillPasswordFieldTable);
|
||||
StackLayout.Children.Add(AutofillPasswordFieldLabel);
|
||||
StackLayout.Children.Add(autofillPersistNotificationTable);
|
||||
StackLayout.Children.Add(AutofillPersistNotificationLabel);
|
||||
}
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.Options;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
WebsiteIconsCell.OnChanged += WebsiteIconsCell_Changed;
|
||||
CopyTotpCell.OnChanged += CopyTotpCell_OnChanged;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell.OnChanged += AutofillAlwaysCell_OnChanged;
|
||||
AutofillPasswordFieldCell.OnChanged += AutofillPasswordFieldCell_OnChanged;
|
||||
AutofillPersistNotificationCell.OnChanged += AutofillPersistNotificationCell_OnChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
WebsiteIconsCell.OnChanged -= WebsiteIconsCell_Changed;
|
||||
CopyTotpCell.OnChanged -= CopyTotpCell_OnChanged;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell.OnChanged -= AutofillAlwaysCell_OnChanged;
|
||||
AutofillPasswordFieldCell.OnChanged -= AutofillPasswordFieldCell_OnChanged;
|
||||
AutofillPersistNotificationCell.OnChanged -= AutofillPersistNotificationCell_OnChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void WebsiteIconsCell_Changed(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.DisableWebsiteIcons = cell.On;
|
||||
}
|
||||
|
||||
private void CopyTotpCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingDisableTotpCopy, cell.On);
|
||||
}
|
||||
|
||||
private void AutofillAlwaysCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPasswordFieldCell.On = false;
|
||||
AutofillPersistNotificationCell.On = false;
|
||||
_appSettings.AutofillPersistNotification = false;
|
||||
_appSettings.AutofillPasswordField = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutofillPersistNotificationCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.AutofillPersistNotification = cell.On;
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPasswordFieldCell.On = false;
|
||||
AutofillAlwaysCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutofillPasswordFieldCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.AutofillPasswordField = cell.On;
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPersistNotificationCell.On = false;
|
||||
AutofillAlwaysCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class FormTableView : ExtendedTableView
|
||||
{
|
||||
public FormTableView(SettingsOptionsPage page, bool header = false)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
NoHeader = !header;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
}
|
||||
}
|
||||
|
||||
private class FormTableLabel : Label
|
||||
{
|
||||
public FormTableLabel(Page page)
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap;
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
Style = (Style)Application.Current.Resources["text-muted"];
|
||||
Margin = new Thickness(15, (page.IsLandscape() ? 5 : 0), 15, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,552 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IFingerprint _fingerprint;
|
||||
private readonly IPushNotificationService _pushNotification;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly MainPage _mainPage;
|
||||
|
||||
// TODO: Model binding context?
|
||||
|
||||
public SettingsPage(MainPage mainPage)
|
||||
{
|
||||
_mainPage = mainPage;
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_fingerprint = Resolver.Resolve<IFingerprint>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotificationService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_lockService = Resolver.Resolve<ILockService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private ExtendedSwitchCell PinCell { get; set; }
|
||||
private ExtendedSwitchCell FingerprintCell { get; set; }
|
||||
private ExtendedTextCell LockOptionsCell { get; set; }
|
||||
private ExtendedTextCell TwoStepCell { get; set; }
|
||||
private ExtendedTextCell ChangeMasterPasswordCell { get; set; }
|
||||
private ExtendedTextCell ChangeEmailCell { get; set; }
|
||||
private ExtendedTextCell FoldersCell { get; set; }
|
||||
private ExtendedTextCell SyncCell { get; set; }
|
||||
private ExtendedTextCell LockCell { get; set; }
|
||||
private ExtendedTextCell LogOutCell { get; set; }
|
||||
private ExtendedTextCell AboutCell { get; set; }
|
||||
private ExtendedTextCell HelpCell { get; set; }
|
||||
private ExtendedTextCell RateCell { get; set; }
|
||||
private ExtendedTextCell OptionsCell { get; set; }
|
||||
private LongDetailViewCell RateCellLong { get; set; }
|
||||
private ExtendedTableView Table { get; set; }
|
||||
|
||||
private async void Init()
|
||||
{
|
||||
PinCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.UnlockWithPIN,
|
||||
On = _settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false)
|
||||
};
|
||||
|
||||
LockOptionsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.LockOptions,
|
||||
Detail = GetLockOptionsDetailsText(),
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
TwoStepCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.TwoStepLogin,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
LockCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Lock
|
||||
};
|
||||
|
||||
var securitySecion = new TableSection(AppResources.Security)
|
||||
{
|
||||
LockOptionsCell,
|
||||
PinCell,
|
||||
LockCell,
|
||||
TwoStepCell
|
||||
};
|
||||
|
||||
if((await _fingerprint.GetAvailabilityAsync()) == FingerprintAvailability.Available)
|
||||
{
|
||||
var fingerprintName = Helpers.OnPlatform(
|
||||
iOS: _deviceInfoService.HasFaceIdSupport ? AppResources.FaceID : AppResources.TouchID,
|
||||
Android: AppResources.Fingerprint,
|
||||
Windows: AppResources.WindowsHello);
|
||||
FingerprintCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = string.Format(AppResources.UnlockWith, fingerprintName),
|
||||
On = _settings.GetValueOrDefault(Constants.SettingFingerprintUnlockOn, false),
|
||||
IsEnabled = true
|
||||
};
|
||||
securitySecion.Insert(1, FingerprintCell);
|
||||
}
|
||||
|
||||
ChangeMasterPasswordCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeMasterPassword,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
ChangeEmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeEmail,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
FoldersCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Folders,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
SyncCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Sync,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
LogOutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.LogOut
|
||||
};
|
||||
|
||||
AboutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.About,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
HelpCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.HelpAndFeedback,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
OptionsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Options,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var otherSection = new TableSection(AppResources.Other)
|
||||
{
|
||||
OptionsCell,
|
||||
AboutCell,
|
||||
HelpCell
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RateCellLong = new LongDetailViewCell(AppResources.RateTheApp, AppResources.RateTheAppDescriptionAppStore);
|
||||
otherSection.Add(RateCellLong);
|
||||
}
|
||||
else
|
||||
{
|
||||
RateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.RateTheApp,
|
||||
Detail = AppResources.RateTheAppDescription,
|
||||
ShowDisclousure = true,
|
||||
DetailLineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
otherSection.Add(RateCell);
|
||||
}
|
||||
|
||||
Table = new CustomTable
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
securitySecion,
|
||||
new TableSection(AppResources.Account)
|
||||
{
|
||||
ChangeMasterPasswordCell,
|
||||
ChangeEmailCell,
|
||||
LogOutCell
|
||||
},
|
||||
new TableSection(AppResources.Manage)
|
||||
{
|
||||
FoldersCell,
|
||||
SyncCell
|
||||
},
|
||||
otherSection
|
||||
}
|
||||
};
|
||||
|
||||
Title = AppResources.Settings;
|
||||
Content = Table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
PinCell.OnChanged += PinCell_Changed;
|
||||
LockOptionsCell.Tapped += LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped += TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped += ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged += FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped += ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped += FoldersCell_Tapped;
|
||||
SyncCell.Tapped += SyncCell_Tapped;
|
||||
LockCell.Tapped += LockCell_Tapped;
|
||||
LogOutCell.Tapped += LogOutCell_Tapped;
|
||||
AboutCell.Tapped += AboutCell_Tapped;
|
||||
HelpCell.Tapped += HelpCell_Tapped;
|
||||
OptionsCell.Tapped += OptionsCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
PinCell.OnChanged -= PinCell_Changed;
|
||||
LockOptionsCell.Tapped -= LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped -= TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped -= ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged -= FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped -= ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped -= FoldersCell_Tapped;
|
||||
SyncCell.Tapped -= SyncCell_Tapped;
|
||||
LockCell.Tapped -= LockCell_Tapped;
|
||||
LogOutCell.Tapped -= LogOutCell_Tapped;
|
||||
AboutCell.Tapped -= AboutCell_Tapped;
|
||||
HelpCell.Tapped -= HelpCell_Tapped;
|
||||
OptionsCell.Tapped -= OptionsCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android && _mainPage != null)
|
||||
{
|
||||
_mainPage.ResetToVaultPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private async void TwoStepCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.TwoStepLoginConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "TwoStep");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/setup-two-step-login/"));
|
||||
}
|
||||
|
||||
private async void LockOptionsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var selection = await DisplayActionSheet(AppResources.LockOptions, AppResources.Cancel, null,
|
||||
AppResources.LockOptionImmediately, AppResources.LockOption1Minute, AppResources.LockOption15Minutes,
|
||||
AppResources.LockOption1Hour, AppResources.LockOption4Hours, AppResources.Never);
|
||||
|
||||
if(selection == null || selection == AppResources.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(selection == AppResources.LockOptionImmediately)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 0);
|
||||
}
|
||||
else if(selection == AppResources.LockOption1Minute)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60);
|
||||
}
|
||||
else if(selection == AppResources.LockOption15Minutes)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 15);
|
||||
}
|
||||
else if(selection == AppResources.LockOption1Hour)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 60);
|
||||
}
|
||||
else if(selection == AppResources.LockOption4Hours)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 60 * 4);
|
||||
}
|
||||
else if(selection == AppResources.Never)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LockOptionsCell.Detail = selection;
|
||||
}
|
||||
|
||||
private void SyncCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsSyncPage()));
|
||||
}
|
||||
|
||||
private void AboutCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsAboutPage()));
|
||||
}
|
||||
|
||||
private void RateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "RateApp");
|
||||
_deviceActionService.RateApp();
|
||||
}
|
||||
|
||||
private void HelpCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsHelpPage()));
|
||||
}
|
||||
|
||||
private void LockCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("Locked");
|
||||
Device.BeginInvokeOnMainThread(async () => await _lockService.CheckLockAsync(true));
|
||||
}
|
||||
|
||||
private async void LogOutCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.LogoutConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_authService.LogOut();
|
||||
}
|
||||
|
||||
private async void ChangeMasterPasswordCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.ChangePasswordConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "ChangePassword");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/change-your-master-password/"));
|
||||
}
|
||||
|
||||
private async void ChangeEmailCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.ChangeEmailConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "ChangeEmail");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/change-your-email/"));
|
||||
}
|
||||
|
||||
private void FingerprintCell_Changed(object sender, EventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingFingerprintUnlockOn, cell.On);
|
||||
|
||||
if(cell.On)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, false);
|
||||
PinCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PinCell_Changed(object sender, EventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(cell.On && !_settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false))
|
||||
{
|
||||
cell.On = false;
|
||||
var pinPage = new SettingsPinPage((page) => PinEntered(page));
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(pinPage));
|
||||
}
|
||||
else if(!cell.On)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PinEntered(SettingsPinPage page)
|
||||
{
|
||||
page.PinControl.Entry.Unfocus();
|
||||
page.Navigation.PopModalAsync();
|
||||
|
||||
_authService.PIN = page.Model.PIN;
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, true);
|
||||
_settings.AddOrUpdateValue(Constants.SettingFingerprintUnlockOn, false);
|
||||
PinCell.On = true;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OptionsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsOptionsPage()));
|
||||
}
|
||||
|
||||
private void FoldersCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsListFoldersPage()));
|
||||
}
|
||||
|
||||
private string GetLockOptionsDetailsText()
|
||||
{
|
||||
var lockSeconds = _settings.GetValueOrDefault(Constants.SettingLockSeconds, 60 * 15);
|
||||
if(lockSeconds == -1)
|
||||
{
|
||||
return AppResources.Never;
|
||||
}
|
||||
else if(lockSeconds == 60)
|
||||
{
|
||||
return AppResources.LockOption1Minute;
|
||||
}
|
||||
else if(lockSeconds == 60 * 15)
|
||||
{
|
||||
return AppResources.LockOption15Minutes;
|
||||
}
|
||||
else if(lockSeconds == 60 * 60)
|
||||
{
|
||||
return AppResources.LockOption1Hour;
|
||||
}
|
||||
else if(lockSeconds == 60 * 60 * 4)
|
||||
{
|
||||
return AppResources.LockOption4Hours;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppResources.LockOptionImmediately;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomTable : ExtendedTableView
|
||||
{
|
||||
public CustomTable()
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
HasUnevenRows = true;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RowHeight = -1;
|
||||
EstimatedRowHeight = 44;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LongDetailViewCell : ExtendedViewCell
|
||||
{
|
||||
public LongDetailViewCell(string labelText, string detailText)
|
||||
{
|
||||
Label = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
Text = labelText
|
||||
};
|
||||
|
||||
Detail = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Text = detailText
|
||||
};
|
||||
|
||||
var labelDetailStackLayout = new StackLayout
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.StartAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { Label, Detail },
|
||||
Padding = new Thickness(15)
|
||||
};
|
||||
|
||||
ShowDisclousure = true;
|
||||
View = labelDetailStackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public Label Detail { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Controls;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsPinPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private Action<SettingsPinPage> _pinEnteredAction;
|
||||
|
||||
public SettingsPinPage(Action<SettingsPinPage> pinEnteredAction)
|
||||
{
|
||||
_pinEnteredAction = pinEnteredAction;
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public PinPageModel Model { get; set; } = new PinPageModel();
|
||||
public PinControl PinControl { get; set; }
|
||||
public TapGestureRecognizer Tgr { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var instructionLabel = new Label
|
||||
{
|
||||
Text = AppResources.SetPINDirection,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.Label.SetBinding(Label.TextProperty, nameof(PinPageModel.LabelText));
|
||||
PinControl.Entry.SetBinding(Entry.TextProperty, nameof(PinPageModel.PIN));
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 20,
|
||||
Children = { PinControl.Label, instructionLabel, PinControl.Entry }
|
||||
};
|
||||
|
||||
Tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(Tgr);
|
||||
instructionLabel.GestureRecognizers.Add(Tgr);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
|
||||
Title = AppResources.SetPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(Tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PinControl.Dispose();
|
||||
Tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
_pinEnteredAction?.Invoke(this);
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsSyncPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public SettingsSyncPage()
|
||||
{
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public Label LastSyncLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var syncButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.SyncVaultNow,
|
||||
Command = new Command(async () => await SyncAsync()),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
};
|
||||
|
||||
LastSyncLabel = new Label
|
||||
{
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
SetLastSync();
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Children = { syncButton, LastSyncLabel },
|
||||
Padding = new Thickness(15, 0)
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.Sync;
|
||||
Content = stackLayout;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
private void SetLastSync()
|
||||
{
|
||||
DateTime? lastSyncDate = null;
|
||||
if(_settings.Contains(Constants.LastSync))
|
||||
{
|
||||
lastSyncDate = _settings.GetValueOrDefault(Constants.LastSync, DateTime.UtcNow);
|
||||
}
|
||||
try
|
||||
{
|
||||
LastSyncLabel.Text = AppResources.LastSync + " " + lastSyncDate?.ToLocalTime().ToString() ?? AppResources.Never;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// some users with different calendars have issues with ToString()ing a date
|
||||
// it seems the linker is at fault. just catch for now since this isn't that important.
|
||||
// ref http://bit.ly/2c2JU7b
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SyncAsync()
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
|
||||
var succeeded = await _syncService.FullSyncAsync(true);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if(succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.SyncingComplete);
|
||||
_googleAnalyticsService.TrackAppEvent("Synced");
|
||||
}
|
||||
else
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.SyncingFailed);
|
||||
}
|
||||
|
||||
SetLastSync();
|
||||
}
|
||||
|
||||
public void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsAccessibilityServicePage : ExtendedContentPage
|
||||
{
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private DateTime? _timerStarted = null;
|
||||
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5);
|
||||
|
||||
public ToolsAccessibilityServicePage()
|
||||
{
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_appInfoService = Resolver.Resolve<IAppInfoService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public StackLayout EnabledStackLayout { get; set; }
|
||||
public StackLayout DisabledStackLayout { get; set; }
|
||||
public ScrollView ScrollView { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var enabledFs = new FormattedString();
|
||||
var statusSpan = new Span { Text = string.Concat(AppResources.Status, " ") };
|
||||
enabledFs.Spans.Add(statusSpan);
|
||||
enabledFs.Spans.Add(new Span
|
||||
{
|
||||
Text = AppResources.Enabled,
|
||||
ForegroundColor = Color.Green,
|
||||
FontAttributes = FontAttributes.Bold,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
|
||||
});
|
||||
|
||||
var statusEnabledLabel = new Label
|
||||
{
|
||||
FormattedText = enabledFs,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var disabledFs = new FormattedString();
|
||||
disabledFs.Spans.Add(statusSpan);
|
||||
disabledFs.Spans.Add(new Span
|
||||
{
|
||||
Text = AppResources.Disabled,
|
||||
ForegroundColor = Color.FromHex("c62929"),
|
||||
FontAttributes = FontAttributes.Bold,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
|
||||
});
|
||||
|
||||
var statusDisabledLabel = new Label
|
||||
{
|
||||
FormattedText = disabledFs,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var step1Label = new Label
|
||||
{
|
||||
Text = AppResources.BitwardenAutofillServiceStep1,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var step1Image = new CachedImage
|
||||
{
|
||||
Source = "accessibility_step1",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0),
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 98
|
||||
};
|
||||
|
||||
var step2Label = new Label
|
||||
{
|
||||
Text = AppResources.BitwardenAutofillServiceStep2,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var step2Image = new CachedImage
|
||||
{
|
||||
Source = "accessibility_step2",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0),
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 67
|
||||
};
|
||||
|
||||
var stepsStackLayout = new StackLayout
|
||||
{
|
||||
Children = { statusDisabledLabel, step1Image, step1Label, step2Image, step2Label },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 10,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
var notificationsLabel = new Label
|
||||
{
|
||||
Text = AppResources.BitwardenAutofillServiceNotification,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var tapNotificationImage = new CachedImage
|
||||
{
|
||||
Source = "accessibility_notification.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0),
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 74
|
||||
};
|
||||
|
||||
var tapNotificationIcon = new CachedImage
|
||||
{
|
||||
Source = "accessibility_notification_icon.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0),
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 54
|
||||
};
|
||||
|
||||
var notificationsStackLayout = new StackLayout
|
||||
{
|
||||
Children = { statusEnabledLabel, tapNotificationIcon, tapNotificationImage, notificationsLabel },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 10,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
DisabledStackLayout = new StackLayout
|
||||
{
|
||||
Children = { BuildServiceLabel(), stepsStackLayout, BuildGoButton() },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 30),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
EnabledStackLayout = new StackLayout
|
||||
{
|
||||
Children = { BuildServiceLabel(), notificationsStackLayout, BuildGoButton() },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 30),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
ScrollView = new ScrollView { Content = DisabledStackLayout };
|
||||
Title = AppResources.AutofillAccessibilityService;
|
||||
Content = ScrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
UpdateEnabled();
|
||||
_timerStarted = DateTime.Now;
|
||||
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Check timer on accessibility");
|
||||
if(_timerStarted == null || (DateTime.Now - _timerStarted) > _timerMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateEnabled();
|
||||
return true;
|
||||
});
|
||||
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
_timerStarted = null;
|
||||
base.OnDisappearing();
|
||||
}
|
||||
|
||||
private void UpdateEnabled()
|
||||
{
|
||||
ScrollView.Content = _appInfoService.AutofillAccessibilityServiceEnabled ?
|
||||
EnabledStackLayout : DisabledStackLayout;
|
||||
}
|
||||
|
||||
private Label BuildServiceLabel()
|
||||
{
|
||||
return new Label
|
||||
{
|
||||
Text = AppResources.AutofillAccessibilityDescription,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
|
||||
};
|
||||
}
|
||||
|
||||
private ExtendedButton BuildGoButton()
|
||||
{
|
||||
return new ExtendedButton
|
||||
{
|
||||
Text = AppResources.BitwardenAutofillServiceOpenAccessibilitySettings,
|
||||
Command = new Command(() =>
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenAccessibilitySettings");
|
||||
_deviceActionService.OpenAccessibilitySettings();
|
||||
}),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsAutofillPage : ExtendedContentPage
|
||||
{
|
||||
public ToolsAutofillPage()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var getAccessLabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionInstantAccess,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
|
||||
Margin = new Thickness(0, 0, 0, 15),
|
||||
};
|
||||
|
||||
var turnOnLabel = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Margin = new Thickness(0, 0, 0, 15),
|
||||
};
|
||||
|
||||
var turnOnLabel1 = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn1,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Start,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var turnOnLabel2 = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn2,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Start,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var turnOnLabel3 = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn3,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Start,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var turnOnLabel4 = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn4,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Start,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var turnOnLabel5 = new Label
|
||||
{
|
||||
Text = AppResources.AutofillTurnOn5,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Start,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var keyboardImge = new CachedImage
|
||||
{
|
||||
Source = "autofill-kb.png",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 5,
|
||||
Padding = new Thickness(20, 20, 20, 30),
|
||||
Children = { getAccessLabel, turnOnLabel, turnOnLabel1, turnOnLabel2,
|
||||
turnOnLabel3, turnOnLabel4, turnOnLabel5, keyboardImge },
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.PasswordAutofill;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsAutofillServicePage : ExtendedContentPage
|
||||
{
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private DateTime? _timerStarted = null;
|
||||
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5);
|
||||
|
||||
public ToolsAutofillServicePage()
|
||||
{
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_appInfoService = Resolver.Resolve<IAppInfoService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public StackLayout EnabledStackLayout { get; set; }
|
||||
public StackLayout DisabledStackLayout { get; set; }
|
||||
public ScrollView ScrollView { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var enabledFs = new FormattedString();
|
||||
var statusSpan = new Span { Text = string.Concat(AppResources.Status, " ") };
|
||||
enabledFs.Spans.Add(statusSpan);
|
||||
enabledFs.Spans.Add(new Span
|
||||
{
|
||||
Text = AppResources.Enabled,
|
||||
ForegroundColor = Color.Green,
|
||||
FontAttributes = FontAttributes.Bold,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
|
||||
});
|
||||
|
||||
var statusEnabledLabel = new Label
|
||||
{
|
||||
FormattedText = enabledFs,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
TextColor = Color.Black,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand
|
||||
};
|
||||
|
||||
var disabledFs = new FormattedString();
|
||||
disabledFs.Spans.Add(statusSpan);
|
||||
disabledFs.Spans.Add(new Span
|
||||
{
|
||||
Text = AppResources.Disabled,
|
||||
ForegroundColor = Color.FromHex("c62929"),
|
||||
FontAttributes = FontAttributes.Bold,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
|
||||
});
|
||||
|
||||
var statusDisabledLabel = new Label
|
||||
{
|
||||
FormattedText = disabledFs,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var enableImage = new CachedImage
|
||||
{
|
||||
Source = "autofill_enable.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 118
|
||||
};
|
||||
|
||||
var useImage = new CachedImage
|
||||
{
|
||||
Source = "autofill_use.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
WidthRequest = 300,
|
||||
HeightRequest = 128
|
||||
};
|
||||
|
||||
var goButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.BitwardenAutofillServiceOpenAutofillSettings,
|
||||
Command = new Command(() =>
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenAutofillSettings");
|
||||
_deviceActionService.OpenAutofillSettings();
|
||||
}),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
|
||||
DisabledStackLayout = new StackLayout
|
||||
{
|
||||
Children = { BuildServiceLabel(), statusDisabledLabel, enableImage, goButton },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 30),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
EnabledStackLayout = new StackLayout
|
||||
{
|
||||
Children = { BuildServiceLabel(), statusEnabledLabel, useImage },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 30),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
ScrollView = new ScrollView { Content = DisabledStackLayout };
|
||||
Title = AppResources.AutofillService;
|
||||
Content = ScrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
UpdateEnabled();
|
||||
_timerStarted = DateTime.Now;
|
||||
Device.StartTimer(new TimeSpan(0, 0, 2), () =>
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Check timer on autofill");
|
||||
if(_timerStarted == null || (DateTime.Now - _timerStarted) > _timerMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateEnabled();
|
||||
return true;
|
||||
});
|
||||
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
_timerStarted = null;
|
||||
base.OnDisappearing();
|
||||
}
|
||||
|
||||
private void UpdateEnabled()
|
||||
{
|
||||
ScrollView.Content = _appInfoService.AutofillServiceEnabled ? EnabledStackLayout : DisabledStackLayout;
|
||||
}
|
||||
|
||||
private Label BuildServiceLabel()
|
||||
{
|
||||
return new Label
|
||||
{
|
||||
Text = AppResources.AutofillServiceDescription,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsExtensionPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public ToolsExtensionPage()
|
||||
{
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
Model = new AppExtensionPageModel(_settings);
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public AppExtensionPageModel Model { get; private set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Not Started
|
||||
|
||||
var notStartedLabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionInstantAccess,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
|
||||
};
|
||||
|
||||
var notStartedSublabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionTurnOn,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var notStartedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-more",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var notStartedButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.ExtensionEnable,
|
||||
Command = new Command(() => ShowExtension("NotStartedEnable")),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
|
||||
var notStartedStackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 20, 20, 30),
|
||||
Children = { notStartedLabel, notStartedSublabel, notStartedImage, notStartedButton },
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
notStartedStackLayout.SetBinding(IsVisibleProperty, nameof(AppExtensionPageModel.NotStarted));
|
||||
|
||||
// Not Activated
|
||||
|
||||
var notActivatedLabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionAlmostDone,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
|
||||
};
|
||||
|
||||
var notActivatedSublabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionTapIcon,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var notActivatedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-act",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var notActivatedButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.ExtensionEnable,
|
||||
Command = new Command(() => ShowExtension("NotActivatedEnable")),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
|
||||
var notActivatedStackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(20, 20, 20, 30),
|
||||
Children = { notActivatedLabel, notActivatedSublabel, notActivatedImage, notActivatedButton },
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
notActivatedStackLayout.SetBinding(IsVisibleProperty, nameof(AppExtensionPageModel.StartedAndNotActivated));
|
||||
|
||||
// Activated
|
||||
|
||||
var activatedLabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionReady,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
|
||||
};
|
||||
|
||||
var activatedSublabel = new Label
|
||||
{
|
||||
Text = AppResources.ExtensionInSafari,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
var activatedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-use",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var activatedButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.ExtensionSeeApps,
|
||||
Command = new Command(() =>
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("SeeSupportedApps");
|
||||
Device.OpenUri(new Uri("https://bitwarden.com/ios/"));
|
||||
}),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primary"]
|
||||
};
|
||||
|
||||
var activatedButtonReenable = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.ExntesionReenable,
|
||||
Command = new Command(() => ShowExtension("Re-enable")),
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
};
|
||||
|
||||
var activatedStackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 10,
|
||||
Padding = new Thickness(20, 20, 20, 30),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { activatedLabel, activatedSublabel, activatedImage, activatedButton, activatedButtonReenable }
|
||||
};
|
||||
|
||||
activatedStackLayout.SetBinding(IsVisibleProperty, nameof(AppExtensionPageModel.StartedAndActivated));
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Children = { notStartedStackLayout, notActivatedStackLayout, activatedStackLayout },
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.AppExtension;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
private void ShowExtension(string type)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("ShowExtension", type);
|
||||
MessagingCenter.Send(Application.Current, "ShowAppExtension", this);
|
||||
}
|
||||
|
||||
public void EnabledExtension(bool enabled)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("EnabledExtension", enabled.ToString());
|
||||
Model.Started = true;
|
||||
if(!Model.Activated && enabled)
|
||||
{
|
||||
Model.Activated = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using FFImageLoading.Forms;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly MainPage _mainPage;
|
||||
|
||||
public ToolsPage(MainPage mainPage)
|
||||
{
|
||||
_mainPage = mainPage;
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ToolsViewCell WebCell { get; set; }
|
||||
public ToolsViewCell ShareCell { get; set; }
|
||||
public ToolsViewCell ImportCell { get; set; }
|
||||
public ToolsViewCell ExtensionCell { get; set; }
|
||||
public ToolsViewCell AutofillCell { get; set; }
|
||||
public ToolsViewCell AccessibilityCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
WebCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe.png");
|
||||
ShareCell = new ToolsViewCell(AppResources.ShareVault, AppResources.ShareVaultDescription, "share_tools.png");
|
||||
ImportCell = new ToolsViewCell(AppResources.ImportItems, AppResources.ImportItemsDescription, "cloudup.png");
|
||||
|
||||
var section = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
if(_deviceInfoService.Version < 12)
|
||||
{
|
||||
ExtensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension,
|
||||
AppResources.BitwardenAppExtensionDescription, "upload.png");
|
||||
section.Add(ExtensionCell);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionCell = new ToolsViewCell(AppResources.PasswordAutofill,
|
||||
AppResources.BitwardenAutofillDescription, "magic.png");
|
||||
section.Add(ExtensionCell);
|
||||
}
|
||||
}
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var accessibilityDesc = AppResources.BitwardenAutofillAccessibilityServiceDescription;
|
||||
if(_deviceInfoService.AutofillServiceSupported)
|
||||
{
|
||||
AutofillCell = new ToolsViewCell(AppResources.AutofillService,
|
||||
AppResources.BitwardenAutofillServiceDescription, "upload2.png");
|
||||
section.Add(AutofillCell);
|
||||
accessibilityDesc += (" " + AppResources.BitwardenAutofillAccessibilityServiceDescription2);
|
||||
}
|
||||
AccessibilityCell = new ToolsViewCell(AppResources.AutofillAccessibilityService,
|
||||
accessibilityDesc, "upload.png");
|
||||
section.Add(AccessibilityCell);
|
||||
}
|
||||
|
||||
section.Add(WebCell);
|
||||
section.Add(ShareCell);
|
||||
section.Add(ImportCell);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
EnableScrolling = true,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
section
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 100;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
table.BottomPadding = 50;
|
||||
}
|
||||
|
||||
Title = AppResources.Tools;
|
||||
Content = table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
WebCell.Tapped += WebCell_Tapped;
|
||||
ShareCell.Tapped += ShareCell_Tapped;
|
||||
ImportCell.Tapped += ImportCell_Tapped;
|
||||
if(ExtensionCell != null)
|
||||
{
|
||||
ExtensionCell.Tapped += ExtensionCell_Tapped;
|
||||
}
|
||||
if(AutofillCell != null)
|
||||
{
|
||||
AutofillCell.Tapped += AutofillCell_Tapped;
|
||||
}
|
||||
if(AccessibilityCell != null)
|
||||
{
|
||||
AccessibilityCell.Tapped += AccessibilityCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
WebCell.Tapped -= WebCell_Tapped;
|
||||
ShareCell.Tapped -= ShareCell_Tapped;
|
||||
ImportCell.Tapped -= ImportCell_Tapped;
|
||||
if(ExtensionCell != null)
|
||||
{
|
||||
ExtensionCell.Tapped -= ExtensionCell_Tapped;
|
||||
}
|
||||
if(AutofillCell != null)
|
||||
{
|
||||
AutofillCell.Tapped -= AutofillCell_Tapped;
|
||||
}
|
||||
if(AccessibilityCell != null)
|
||||
{
|
||||
AccessibilityCell.Tapped -= AccessibilityCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android && _mainPage != null)
|
||||
{
|
||||
_mainPage.ResetToVaultPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private void AutofillCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage()));
|
||||
}
|
||||
|
||||
private void AccessibilityCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAccessibilityServicePage()));
|
||||
}
|
||||
|
||||
private void ExtensionCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(_deviceInfoService.Version < 12)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillPage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void WebCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedTool", "Web");
|
||||
var appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
if(!string.IsNullOrWhiteSpace(appSettings.BaseUrl))
|
||||
{
|
||||
Device.OpenUri(new Uri(appSettings.BaseUrl));
|
||||
}
|
||||
else
|
||||
{
|
||||
Device.OpenUri(new Uri("https://vault.bitwarden.com"));
|
||||
}
|
||||
}
|
||||
|
||||
private void ShareCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedTool", "Share");
|
||||
Device.OpenUri(new Uri("https://vault.bitwarden.com/#/?org=free"));
|
||||
}
|
||||
|
||||
private async void ImportCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.ImportItemsConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedTool", "Import");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/import-data/"));
|
||||
}
|
||||
|
||||
public class ToolsViewCell : ExtendedViewCell
|
||||
{
|
||||
public ToolsViewCell(string labelText, string detailText, string imageSource)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
Text = labelText
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
label.TextColor = Color.Black;
|
||||
}
|
||||
|
||||
var detail = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Text = detailText
|
||||
};
|
||||
|
||||
var image = new CachedImage
|
||||
{
|
||||
Source = imageSource,
|
||||
WidthRequest = 44,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnSpacing = 15,
|
||||
RowSpacing = 0,
|
||||
Padding = new Thickness(15, 20)
|
||||
};
|
||||
grid.AdjustPaddingForDevice();
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(44, GridUnitType.Absolute) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.Children.Add(image, 0, 0);
|
||||
Grid.SetRowSpan(image, 2);
|
||||
grid.Children.Add(label, 1, 0);
|
||||
grid.Children.Add(detail, 1, 1);
|
||||
|
||||
ShowDisclousure = true;
|
||||
View = grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class ToolsPasswordGeneratorPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly Action<string> _passwordValueAction;
|
||||
private readonly bool _fromAutofill;
|
||||
private readonly MainPage _mainPage;
|
||||
|
||||
public ToolsPasswordGeneratorPage(Action<string> passwordValueAction = null, bool fromAutofill = false)
|
||||
{
|
||||
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_passwordValueAction = passwordValueAction;
|
||||
_fromAutofill = fromAutofill;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ToolsPasswordGeneratorPage(MainPage mainPage)
|
||||
: this()
|
||||
{
|
||||
_mainPage = mainPage;
|
||||
}
|
||||
|
||||
public PasswordGeneratorPageModel Model { get; private set; } = new PasswordGeneratorPageModel();
|
||||
public Label Password { get; private set; }
|
||||
public SliderViewCell SliderCell { get; private set; }
|
||||
public TapGestureRecognizer Tgr { get; set; }
|
||||
public ExtendedTextCell RegenerateCell { get; set; }
|
||||
public ExtendedTextCell CopyCell { get; set; }
|
||||
public ExtendedSwitchCell UppercaseCell { get; set; }
|
||||
public ExtendedSwitchCell LowercaseCell { get; set; }
|
||||
public ExtendedSwitchCell SpecialCell { get; set; }
|
||||
public ExtendedSwitchCell NumbersCell { get; set; }
|
||||
public ExtendedSwitchCell AvoidAmbiguousCell { get; set; }
|
||||
public StepperCell SpecialMinCell { get; set; }
|
||||
public StepperCell NumbersMinCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
Password = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
|
||||
Margin = new Thickness(15, 30, 15, 30),
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier"),
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
Tgr = new TapGestureRecognizer();
|
||||
Password.GestureRecognizers.Add(Tgr);
|
||||
// Password.SetBinding(Label.FormattedTextProperty, nameof(PasswordGeneratorPageModel.FormattedPassword));
|
||||
Password.SetBinding(Label.TextProperty, nameof(PasswordGeneratorPageModel.Password));
|
||||
|
||||
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
|
||||
|
||||
RegenerateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.RegeneratePassword,
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = Colors.Primary };
|
||||
|
||||
UppercaseCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "A-Z",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true)
|
||||
};
|
||||
|
||||
LowercaseCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "a-z",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true)
|
||||
};
|
||||
|
||||
SpecialCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "!@#$%^&*",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true)
|
||||
};
|
||||
|
||||
NumbersCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "0-9",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true)
|
||||
};
|
||||
|
||||
AvoidAmbiguousCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AvoidAmbiguousCharacters,
|
||||
On = !_settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false)
|
||||
};
|
||||
|
||||
NumbersMinCell = new StepperCell(AppResources.MinNumbers,
|
||||
_settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1), 0, 5, 1, () =>
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorMinNumbers,
|
||||
Convert.ToInt32(NumbersMinCell.Stepper.Value));
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
});
|
||||
|
||||
SpecialMinCell = new StepperCell(AppResources.MinSpecial,
|
||||
_settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1), 0, 5, 1, () =>
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorMinSpecial,
|
||||
Convert.ToInt32(SpecialMinCell.Stepper.Value));
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
});
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
EnableScrolling = false,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
RegenerateCell,
|
||||
CopyCell
|
||||
},
|
||||
new TableSection(AppResources.Options)
|
||||
{
|
||||
SliderCell,
|
||||
UppercaseCell,
|
||||
LowercaseCell,
|
||||
NumbersCell,
|
||||
SpecialCell
|
||||
},
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
NumbersMinCell,
|
||||
SpecialMinCell
|
||||
},
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
AvoidAmbiguousCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 44;
|
||||
|
||||
if(_passwordValueAction != null)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
table.BottomPadding = 50;
|
||||
}
|
||||
|
||||
var stackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Children = { Password, table },
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => stackLayout;
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = stackLayout,
|
||||
Orientation = ScrollOrientation.Vertical,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
if(_passwordValueAction != null)
|
||||
{
|
||||
var selectToolBarItem = new ToolbarItem(AppResources.Select, Helpers.ToolbarImage("ion_chevron_right.png"), async () =>
|
||||
{
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("SelectedGeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("SelectedGeneratedPassword");
|
||||
}
|
||||
|
||||
_passwordValueAction(Model.Password);
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(selectToolBarItem);
|
||||
}
|
||||
|
||||
Title = AppResources.PasswordGenerator;
|
||||
Content = scrollView;
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
CopyPassword();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Tgr.Tapped += Tgr_Tapped;
|
||||
RegenerateCell.Tapped += RegenerateCell_Tapped;
|
||||
CopyCell.Tapped += CopyCell_Tapped;
|
||||
SliderCell.InitEvents();
|
||||
SpecialCell.OnChanged += SpecialCell_OnChanged;
|
||||
AvoidAmbiguousCell.OnChanged += AvoidAmbiguousCell_OnChanged;
|
||||
UppercaseCell.OnChanged += UppercaseCell_OnChanged;
|
||||
LowercaseCell.OnChanged += LowercaseCell_OnChanged;
|
||||
NumbersCell.OnChanged += NumbersCell_OnChanged;
|
||||
NumbersMinCell.InitEvents();
|
||||
SpecialMinCell.InitEvents();
|
||||
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("GeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("GeneratedPassword");
|
||||
}
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
Model.Length = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10).ToString();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
Tgr.Tapped -= Tgr_Tapped;
|
||||
RegenerateCell.Tapped -= RegenerateCell_Tapped;
|
||||
SpecialCell.OnChanged -= SpecialCell_OnChanged;
|
||||
AvoidAmbiguousCell.OnChanged -= AvoidAmbiguousCell_OnChanged;
|
||||
UppercaseCell.OnChanged -= UppercaseCell_OnChanged;
|
||||
LowercaseCell.OnChanged -= LowercaseCell_OnChanged;
|
||||
NumbersCell.OnChanged -= NumbersCell_OnChanged;
|
||||
NumbersMinCell.Dispose();
|
||||
SpecialMinCell.Dispose();
|
||||
CopyCell.Tapped -= CopyCell_Tapped;
|
||||
SliderCell.Dispose();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android && _mainPage != null)
|
||||
{
|
||||
_mainPage.ResetToVaultPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private void RegenerateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("RegeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("RegeneratedPassword");
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
CopyPassword();
|
||||
}
|
||||
|
||||
private void CopyPassword()
|
||||
{
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("CopiedGeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("CopiedGeneratedPassword");
|
||||
}
|
||||
_deviceActionService.CopyToClipboard(Model.Password);
|
||||
_deviceActionService.Toast(string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
}
|
||||
|
||||
private void AvoidAmbiguousCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorAmbiguous, !AvoidAmbiguousCell.On);
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
private void NumbersCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorNumbers, NumbersCell.On);
|
||||
|
||||
if(InvalidState())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
|
||||
LowercaseCell.On = true;
|
||||
}
|
||||
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
private void SpecialCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorSpecial, SpecialCell.On);
|
||||
|
||||
if(InvalidState())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
|
||||
LowercaseCell.On = true;
|
||||
}
|
||||
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
private void LowercaseCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, LowercaseCell.On);
|
||||
|
||||
if(InvalidState())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, true);
|
||||
UppercaseCell.On = true;
|
||||
}
|
||||
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
private void UppercaseCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, UppercaseCell.On);
|
||||
|
||||
if(InvalidState())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
|
||||
LowercaseCell.On = true;
|
||||
}
|
||||
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
private bool InvalidState()
|
||||
{
|
||||
return !LowercaseCell.On && !UppercaseCell.On && !NumbersCell.On && !SpecialCell.On;
|
||||
}
|
||||
|
||||
|
||||
// TODO: move to standalone reusable control
|
||||
public class SliderViewCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
private readonly ToolsPasswordGeneratorPage _page;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly ISettings _settings;
|
||||
|
||||
public Label Value { get; set; }
|
||||
public Slider LengthSlider { get; set; }
|
||||
|
||||
public SliderViewCell(
|
||||
ToolsPasswordGeneratorPage page,
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
ISettings settings)
|
||||
{
|
||||
_page = page;
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_settings = settings;
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
Text = AppResources.Length,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand
|
||||
};
|
||||
|
||||
LengthSlider = new Slider(5, 64, _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10))
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
MaximumTrackColor = Color.LightGray,
|
||||
MinimumTrackColor = Color.LightGray,
|
||||
};
|
||||
|
||||
Value = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier"),
|
||||
};
|
||||
|
||||
Value.SetBinding(Label.TextProperty, nameof(PasswordGeneratorPageModel.Length));
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
Spacing = 15,
|
||||
Children = { label, LengthSlider, Value },
|
||||
Padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 8),
|
||||
Android: new Thickness(16, 10),
|
||||
Windows: new Thickness(15, 8))
|
||||
};
|
||||
|
||||
stackLayout.AdjustPaddingForDevice();
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
label.TextColor = Color.Black;
|
||||
}
|
||||
|
||||
View = stackLayout;
|
||||
}
|
||||
|
||||
private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
var length = Convert.ToInt32(LengthSlider.Value);
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLength, length);
|
||||
_page.Model.Length = length.ToString();
|
||||
_page.Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
LengthSlider.ValueChanged += Slider_ValueChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LengthSlider.ValueChanged -= Slider_ValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,855 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultAddCipherPage : ExtendedContentPage
|
||||
{
|
||||
private const string AddedLoginAlertKey = "addedSiteAlert";
|
||||
|
||||
private readonly CipherType _type;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
private readonly IDeviceInfoService _deviceInfo;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly string _defaultFolderId;
|
||||
private readonly string _defaultUri;
|
||||
private readonly string _defaultName;
|
||||
private readonly string _defaultUsername;
|
||||
private readonly string _defaultPassword;
|
||||
private readonly string _defaultCardName;
|
||||
private readonly string _defaultCardNumber;
|
||||
private readonly int? _defaultCardExpMonth;
|
||||
private readonly string _defaultCardExpYear;
|
||||
private readonly string _defaultCardCode;
|
||||
private readonly bool _fromAutofill;
|
||||
private readonly bool _fromAutofillFramework;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public VaultAddCipherPage(AppOptions options)
|
||||
: this(options.SaveType.Value, options.Uri, options.SaveName, options.FromAutofillFramework, false)
|
||||
{
|
||||
_fromAutofillFramework = options.FromAutofillFramework;
|
||||
_defaultUsername = options.SaveUsername;
|
||||
_defaultPassword = options.SavePassword;
|
||||
_defaultCardCode = options.SaveCardCode;
|
||||
if(int.TryParse(options.SaveCardExpMonth, out int month) && month <= 12 && month >= 1)
|
||||
{
|
||||
_defaultCardExpMonth = month;
|
||||
}
|
||||
_defaultCardExpYear = options.SaveCardExpYear;
|
||||
_defaultCardName = options.SaveCardName;
|
||||
_defaultCardNumber = options.SaveCardNumber;
|
||||
Init();
|
||||
}
|
||||
|
||||
public VaultAddCipherPage(CipherType type, string defaultUri = null, string defaultName = null,
|
||||
bool fromAutofill = false, bool doInit = true, string defaultFolderId = null)
|
||||
{
|
||||
_defaultFolderId = defaultFolderId;
|
||||
_type = type;
|
||||
_defaultUri = defaultUri;
|
||||
_defaultName = defaultName;
|
||||
_fromAutofill = fromAutofill;
|
||||
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appInfoService = Resolver.Resolve<IAppInfoService>();
|
||||
_deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
|
||||
if(doInit)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Folder> Folders { get; set; }
|
||||
public TableRoot TableRoot { get; set; }
|
||||
public TableSection TopSection { get; set; }
|
||||
public TableSection UrisSection { get; set; }
|
||||
public TableSection MiddleSection { get; set; }
|
||||
public TableSection FieldsSection { get; set; }
|
||||
public ExtendedTableView Table { get; set; }
|
||||
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
public FormEditorCell NotesCell { get; private set; }
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||
public ExtendedTextCell AddUriCell { get; private set; }
|
||||
|
||||
// Login
|
||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||
public FormEntryCell LoginUsernameCell { get; private set; }
|
||||
public FormEntryCell LoginTotpCell { get; private set; }
|
||||
|
||||
// Card
|
||||
public FormEntryCell CardNameCell { get; private set; }
|
||||
public FormEntryCell CardNumberCell { get; private set; }
|
||||
public FormPickerCell CardBrandCell { get; private set; }
|
||||
public FormPickerCell CardExpMonthCell { get; private set; }
|
||||
public FormEntryCell CardExpYearCell { get; private set; }
|
||||
public FormEntryCell CardCodeCell { get; private set; }
|
||||
|
||||
// Identity
|
||||
public FormPickerCell IdTitleCell { get; private set; }
|
||||
public FormEntryCell IdFirstNameCell { get; private set; }
|
||||
public FormEntryCell IdMiddleNameCell { get; private set; }
|
||||
public FormEntryCell IdLastNameCell { get; private set; }
|
||||
public FormEntryCell IdUsernameCell { get; private set; }
|
||||
public FormEntryCell IdCompanyCell { get; private set; }
|
||||
public FormEntryCell IdSsnCell { get; private set; }
|
||||
public FormEntryCell IdPassportNumberCell { get; private set; }
|
||||
public FormEntryCell IdLicenseNumberCell { get; private set; }
|
||||
public FormEntryCell IdEmailCell { get; private set; }
|
||||
public FormEntryCell IdPhoneCell { get; private set; }
|
||||
public FormEntryCell IdAddress1Cell { get; private set; }
|
||||
public FormEntryCell IdAddress2Cell { get; private set; }
|
||||
public FormEntryCell IdAddress3Cell { get; private set; }
|
||||
public FormEntryCell IdCityCell { get; private set; }
|
||||
public FormEntryCell IdStateCell { get; private set; }
|
||||
public FormEntryCell IdPostalCodeCell { get; private set; }
|
||||
public FormEntryCell IdCountryCell { get; private set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
// Name
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultName))
|
||||
{
|
||||
NameCell.Entry.Text = _defaultName;
|
||||
}
|
||||
|
||||
// Notes
|
||||
NotesCell = new FormEditorCell(Keyboard.Text, _type == CipherType.SecureNote ? 500 : 180);
|
||||
|
||||
// Folders
|
||||
var folderOptions = new List<string> { AppResources.FolderNone };
|
||||
Folders = _folderService.GetAllAsync().GetAwaiter().GetResult()
|
||||
.OrderBy(f => f.Name?.Decrypt()).ToList();
|
||||
var selectedIndex = 0;
|
||||
var i = 1;
|
||||
foreach(var folder in Folders)
|
||||
{
|
||||
if(folder.Id == _defaultFolderId)
|
||||
{
|
||||
selectedIndex = i;
|
||||
}
|
||||
folderOptions.Add(folder.Name.Decrypt());
|
||||
i++;
|
||||
}
|
||||
FolderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
FolderCell.Picker.SelectedIndex = selectedIndex;
|
||||
|
||||
// Favorite
|
||||
FavoriteCell = new ExtendedSwitchCell { Text = AppResources.Favorite };
|
||||
|
||||
InitTable();
|
||||
InitSave();
|
||||
|
||||
Title = AppResources.AddItem;
|
||||
Content = Table;
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
NameCell.InitEvents();
|
||||
NotesCell.InitEvents();
|
||||
FolderCell.InitEvents();
|
||||
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||
}
|
||||
if(AddUriCell != null)
|
||||
{
|
||||
AddUriCell.Tapped += AddUriCell_Tapped;
|
||||
}
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
LoginPasswordCell.InitEvents();
|
||||
LoginUsernameCell.InitEvents();
|
||||
LoginTotpCell.InitEvents();
|
||||
LoginPasswordCell.Button1.Clicked += PasswordButton_Clicked;
|
||||
LoginPasswordCell.Button2.Clicked += PasswordButton2_Clicked;
|
||||
if(LoginTotpCell?.Button1 != null)
|
||||
{
|
||||
LoginTotpCell.Button1.Clicked += TotpButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Card:
|
||||
CardBrandCell.InitEvents();
|
||||
CardCodeCell.InitEvents();
|
||||
CardExpMonthCell.InitEvents();
|
||||
CardExpYearCell.InitEvents();
|
||||
CardNameCell.InitEvents();
|
||||
CardNumberCell.InitEvents();
|
||||
CardCodeCell.Button1.Clicked += CardCodeButton_Clicked;
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
IdTitleCell.InitEvents();
|
||||
IdFirstNameCell.InitEvents();
|
||||
IdMiddleNameCell.InitEvents();
|
||||
IdLastNameCell.InitEvents();
|
||||
IdUsernameCell.InitEvents();
|
||||
IdCompanyCell.InitEvents();
|
||||
IdSsnCell.InitEvents();
|
||||
IdPassportNumberCell.InitEvents();
|
||||
IdLicenseNumberCell.InitEvents();
|
||||
IdEmailCell.InitEvents();
|
||||
IdPhoneCell.InitEvents();
|
||||
IdAddress1Cell.InitEvents();
|
||||
IdAddress2Cell.InitEvents();
|
||||
IdAddress3Cell.InitEvents();
|
||||
IdCityCell.InitEvents();
|
||||
IdStateCell.InitEvents();
|
||||
IdPostalCodeCell.InitEvents();
|
||||
IdCountryCell.InitEvents();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Helpers.InitSectionEvents(FieldsSection);
|
||||
Helpers.InitSectionEvents(UrisSection);
|
||||
|
||||
if(_type == CipherType.Login && !_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
|
||||
{
|
||||
_settings.AddOrUpdateValue(AddedLoginAlertKey, true);
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
if(_deviceInfo.Version < 12)
|
||||
{
|
||||
DisplayAlert(AppResources.BitwardenAppExtension, AppResources.BitwardenAppExtensionAlert,
|
||||
AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayAlert(AppResources.PasswordAutofill, AppResources.BitwardenAutofillAlert,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android && !_appInfoService.AutofillAccessibilityServiceEnabled)
|
||||
{
|
||||
DisplayAlert(AppResources.BitwardenAutofillService, AppResources.BitwardenAutofillServiceAlert,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
if(NameCell != null && string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
NameCell.Entry.FocusWithDelay();
|
||||
}
|
||||
else if(LoginUsernameCell != null && string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text))
|
||||
{
|
||||
LoginUsernameCell.Entry.FocusWithDelay();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
NameCell.Dispose();
|
||||
NotesCell.Dispose();
|
||||
FolderCell.Dispose();
|
||||
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||
}
|
||||
if(AddUriCell != null)
|
||||
{
|
||||
AddUriCell.Tapped -= AddUriCell_Tapped;
|
||||
}
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
LoginTotpCell.Dispose();
|
||||
LoginPasswordCell.Dispose();
|
||||
LoginUsernameCell.Dispose();
|
||||
LoginPasswordCell.Button1.Clicked -= PasswordButton_Clicked;
|
||||
LoginPasswordCell.Button2.Clicked -= PasswordButton2_Clicked;
|
||||
if(LoginTotpCell?.Button1 != null)
|
||||
{
|
||||
LoginTotpCell.Button1.Clicked -= TotpButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Card:
|
||||
CardBrandCell.Dispose();
|
||||
CardCodeCell.Dispose();
|
||||
CardExpMonthCell.Dispose();
|
||||
CardExpYearCell.Dispose();
|
||||
CardNameCell.Dispose();
|
||||
CardNumberCell.Dispose();
|
||||
CardCodeCell.Button1.Clicked -= CardCodeButton_Clicked;
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
IdTitleCell.Dispose();
|
||||
IdFirstNameCell.Dispose();
|
||||
IdMiddleNameCell.Dispose();
|
||||
IdLastNameCell.Dispose();
|
||||
IdUsernameCell.Dispose();
|
||||
IdCompanyCell.Dispose();
|
||||
IdSsnCell.Dispose();
|
||||
IdPassportNumberCell.Dispose();
|
||||
IdLicenseNumberCell.Dispose();
|
||||
IdEmailCell.Dispose();
|
||||
IdPhoneCell.Dispose();
|
||||
IdAddress1Cell.Dispose();
|
||||
IdAddress2Cell.Dispose();
|
||||
IdAddress3Cell.Dispose();
|
||||
IdCityCell.Dispose();
|
||||
IdStateCell.Dispose();
|
||||
IdPostalCodeCell.Dispose();
|
||||
IdCountryCell.Dispose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Helpers.DisposeSectionEvents(FieldsSection);
|
||||
Helpers.DisposeSectionEvents(UrisSection);
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(_fromAutofillFramework)
|
||||
{
|
||||
Application.Current.MainPage = new MainPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
LoginPasswordCell.Entry.InvokeToggleIsPassword();
|
||||
LoginPasswordCell.Button1.Image =
|
||||
"eye" + (!LoginPasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
}
|
||||
|
||||
private async void PasswordButton2_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var page = new ToolsPasswordGeneratorPage((password) =>
|
||||
{
|
||||
LoginPasswordCell.Entry.Text = password;
|
||||
_deviceActionService.Toast(AppResources.PasswordGenerated);
|
||||
}, _fromAutofill);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private async void TotpButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var scanPage = new ScanPage((key) =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
if(!string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
LoginTotpCell.Entry.Text = key;
|
||||
_deviceActionService.Toast(AppResources.AuthenticatorKeyAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AuthenticatorKeyReadError, AppResources.Ok);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
|
||||
}
|
||||
|
||||
private void CardCodeButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
CardCodeCell.Entry.InvokeToggleIsPassword();
|
||||
CardCodeCell.Button1.Image =
|
||||
"eye" + (!CardCodeCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
|
||||
private void InitTable()
|
||||
{
|
||||
// Sections
|
||||
TopSection = new TableSection(AppResources.ItemInformation)
|
||||
{
|
||||
NameCell
|
||||
};
|
||||
|
||||
MiddleSection = new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
FolderCell,
|
||||
FavoriteCell
|
||||
};
|
||||
|
||||
if(_type == CipherType.Login)
|
||||
{
|
||||
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey,
|
||||
button1: _deviceInfo.HasCamera ? "camera.png" : null);
|
||||
LoginTotpCell.Entry.DisableAutocapitalize = true;
|
||||
LoginTotpCell.Entry.Autocorrect = false;
|
||||
LoginTotpCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
LoginPasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: LoginTotpCell.Entry,
|
||||
button1: "eye.png", button2: "refresh_alt.png");
|
||||
LoginPasswordCell.Entry.DisableAutocapitalize = true;
|
||||
LoginPasswordCell.Entry.Autocorrect = false;
|
||||
LoginPasswordCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
if(!string.IsNullOrWhiteSpace(_defaultPassword))
|
||||
{
|
||||
LoginPasswordCell.Entry.Text = _defaultPassword;
|
||||
}
|
||||
|
||||
LoginUsernameCell = new FormEntryCell(AppResources.Username, nextElement: LoginPasswordCell.Entry);
|
||||
LoginUsernameCell.Entry.DisableAutocapitalize = true;
|
||||
LoginUsernameCell.Entry.Autocorrect = false;
|
||||
if(!string.IsNullOrWhiteSpace(_defaultUsername))
|
||||
{
|
||||
LoginUsernameCell.Entry.Text = _defaultUsername;
|
||||
}
|
||||
|
||||
NameCell.NextElement = LoginUsernameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(LoginUsernameCell);
|
||||
TopSection.Add(LoginPasswordCell);
|
||||
TopSection.Add(LoginTotpCell);
|
||||
|
||||
// Uris
|
||||
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||
AddUriCell = new ExtendedTextCell
|
||||
{
|
||||
Text = $"+ {AppResources.NewUri}",
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
UrisSection.Add(AddUriCell);
|
||||
UrisSection.Insert(0, Helpers.MakeUriCell(_defaultUri ?? string.Empty, null, UrisSection, this));
|
||||
}
|
||||
else if(_type == CipherType.Card)
|
||||
{
|
||||
CardCodeCell = new FormEntryCell(AppResources.SecurityCode, Keyboard.Numeric,
|
||||
isPassword: true, nextElement: NotesCell.Editor, button1: "eye.png");
|
||||
if(!string.IsNullOrWhiteSpace(_defaultCardCode))
|
||||
{
|
||||
CardCodeCell.Entry.Text = _defaultCardCode;
|
||||
}
|
||||
CardCodeCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
CardExpYearCell = new FormEntryCell(AppResources.ExpirationYear, Keyboard.Numeric,
|
||||
nextElement: CardCodeCell.Entry);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultCardExpYear))
|
||||
{
|
||||
CardExpYearCell.Entry.Text = _defaultCardExpYear;
|
||||
}
|
||||
CardExpMonthCell = new FormPickerCell(AppResources.ExpirationMonth, new string[] {
|
||||
"--", AppResources.January, AppResources.February, AppResources.March, AppResources.April,
|
||||
AppResources.May, AppResources.June, AppResources.July, AppResources.August, AppResources.September,
|
||||
AppResources.October, AppResources.November, AppResources.December
|
||||
});
|
||||
if(_defaultCardExpMonth.HasValue)
|
||||
{
|
||||
CardExpMonthCell.Picker.SelectedIndex = _defaultCardExpMonth.Value;
|
||||
}
|
||||
CardBrandCell = new FormPickerCell(AppResources.Brand, new string[] {
|
||||
"--", "Visa", "Mastercard", "American Express", "Discover", "Diners Club",
|
||||
"JCB", "Maestro", "UnionPay", AppResources.Other
|
||||
});
|
||||
CardNumberCell = new FormEntryCell(AppResources.Number, Keyboard.Numeric);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultCardNumber))
|
||||
{
|
||||
CardNumberCell.Entry.Text = _defaultCardNumber;
|
||||
}
|
||||
CardNameCell = new FormEntryCell(AppResources.CardholderName, nextElement: CardNumberCell.Entry);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultCardName))
|
||||
{
|
||||
CardNameCell.Entry.Text = _defaultCardName;
|
||||
}
|
||||
NameCell.NextElement = CardNameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(CardNameCell);
|
||||
TopSection.Add(CardNumberCell);
|
||||
TopSection.Add(CardBrandCell);
|
||||
TopSection.Add(CardExpMonthCell);
|
||||
TopSection.Add(CardExpYearCell);
|
||||
TopSection.Add(CardCodeCell);
|
||||
}
|
||||
else if(_type == CipherType.Identity)
|
||||
{
|
||||
IdCountryCell = new FormEntryCell(AppResources.Country, nextElement: NotesCell.Editor);
|
||||
IdPostalCodeCell = new FormEntryCell(AppResources.ZipPostalCode, nextElement: IdCountryCell.Entry);
|
||||
IdPostalCodeCell.Entry.DisableAutocapitalize = true;
|
||||
IdPostalCodeCell.Entry.Autocorrect = false;
|
||||
IdStateCell = new FormEntryCell(AppResources.StateProvince, nextElement: IdPostalCodeCell.Entry);
|
||||
IdCityCell = new FormEntryCell(AppResources.CityTown, nextElement: IdStateCell.Entry);
|
||||
IdAddress3Cell = new FormEntryCell(AppResources.Address3, nextElement: IdCityCell.Entry);
|
||||
IdAddress2Cell = new FormEntryCell(AppResources.Address2, nextElement: IdAddress3Cell.Entry);
|
||||
IdAddress1Cell = new FormEntryCell(AppResources.Address1, nextElement: IdAddress2Cell.Entry);
|
||||
IdPhoneCell = new FormEntryCell(AppResources.Phone, nextElement: IdAddress1Cell.Entry);
|
||||
IdPhoneCell.Entry.DisableAutocapitalize = true;
|
||||
IdPhoneCell.Entry.Autocorrect = false;
|
||||
IdEmailCell = new FormEntryCell(AppResources.Email, Keyboard.Email, nextElement: IdPhoneCell.Entry);
|
||||
IdEmailCell.Entry.DisableAutocapitalize = true;
|
||||
IdEmailCell.Entry.Autocorrect = false;
|
||||
IdLicenseNumberCell = new FormEntryCell(AppResources.LicenseNumber, nextElement: IdEmailCell.Entry);
|
||||
IdLicenseNumberCell.Entry.DisableAutocapitalize = true;
|
||||
IdLicenseNumberCell.Entry.Autocorrect = false;
|
||||
IdPassportNumberCell = new FormEntryCell(AppResources.PassportNumber, nextElement: IdLicenseNumberCell.Entry);
|
||||
IdPassportNumberCell.Entry.DisableAutocapitalize = true;
|
||||
IdPassportNumberCell.Entry.Autocorrect = false;
|
||||
IdSsnCell = new FormEntryCell(AppResources.SSN, nextElement: IdPassportNumberCell.Entry);
|
||||
IdSsnCell.Entry.DisableAutocapitalize = true;
|
||||
IdSsnCell.Entry.Autocorrect = false;
|
||||
IdCompanyCell = new FormEntryCell(AppResources.Company, nextElement: IdSsnCell.Entry);
|
||||
IdUsernameCell = new FormEntryCell(AppResources.Username, nextElement: IdCompanyCell.Entry);
|
||||
IdUsernameCell.Entry.DisableAutocapitalize = true;
|
||||
IdUsernameCell.Entry.Autocorrect = false;
|
||||
IdLastNameCell = new FormEntryCell(AppResources.LastName, nextElement: IdUsernameCell.Entry);
|
||||
IdMiddleNameCell = new FormEntryCell(AppResources.MiddleName, nextElement: IdLastNameCell.Entry);
|
||||
IdFirstNameCell = new FormEntryCell(AppResources.FirstName, nextElement: IdMiddleNameCell.Entry);
|
||||
IdTitleCell = new FormPickerCell(AppResources.Title, new string[] {
|
||||
"--", AppResources.Mr, AppResources.Mrs, AppResources.Ms, AppResources.Dr
|
||||
});
|
||||
|
||||
// Name
|
||||
NameCell.NextElement = IdFirstNameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(IdTitleCell);
|
||||
TopSection.Add(IdFirstNameCell);
|
||||
TopSection.Add(IdMiddleNameCell);
|
||||
TopSection.Add(IdLastNameCell);
|
||||
TopSection.Add(IdUsernameCell);
|
||||
TopSection.Add(IdCompanyCell);
|
||||
TopSection.Add(IdSsnCell);
|
||||
TopSection.Add(IdPassportNumberCell);
|
||||
TopSection.Add(IdLicenseNumberCell);
|
||||
TopSection.Add(IdEmailCell);
|
||||
TopSection.Add(IdPhoneCell);
|
||||
TopSection.Add(IdAddress1Cell);
|
||||
TopSection.Add(IdAddress2Cell);
|
||||
TopSection.Add(IdAddress3Cell);
|
||||
TopSection.Add(IdCityCell);
|
||||
TopSection.Add(IdStateCell);
|
||||
TopSection.Add(IdPostalCodeCell);
|
||||
TopSection.Add(IdCountryCell);
|
||||
}
|
||||
else if(_type == CipherType.SecureNote)
|
||||
{
|
||||
// Name
|
||||
NameCell.NextElement = NotesCell.Editor;
|
||||
}
|
||||
|
||||
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||
AddFieldCell = new ExtendedTextCell
|
||||
{
|
||||
Text = $"+ {AppResources.NewCustomField}",
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
FieldsSection.Add(AddFieldCell);
|
||||
|
||||
// Make table
|
||||
TableRoot = new TableRoot
|
||||
{
|
||||
TopSection,
|
||||
MiddleSection,
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
NotesCell
|
||||
},
|
||||
FieldsSection
|
||||
};
|
||||
|
||||
if(UrisSection != null)
|
||||
{
|
||||
TableRoot.Insert(1, UrisSection);
|
||||
}
|
||||
|
||||
Table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = true,
|
||||
HasUnevenRows = true,
|
||||
Root = TableRoot
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
Table.RowHeight = -1;
|
||||
Table.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Table.BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitSave()
|
||||
{
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var cipher = new Cipher
|
||||
{
|
||||
Name = NameCell.Entry.Text.Encrypt(),
|
||||
Notes = string.IsNullOrWhiteSpace(NotesCell.Editor.Text) ? null : NotesCell.Editor.Text.Encrypt(),
|
||||
Favorite = FavoriteCell.On,
|
||||
Type = _type
|
||||
};
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
cipher.Login = new Login
|
||||
{
|
||||
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
||||
LoginUsernameCell.Entry.Text.Encrypt(),
|
||||
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
||||
LoginPasswordCell.Entry.Text.Encrypt(),
|
||||
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
||||
LoginTotpCell.Entry.Text.Encrypt(),
|
||||
};
|
||||
|
||||
Helpers.ProcessUrisSectionForSave(UrisSection, cipher);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
cipher.SecureNote = new SecureNote
|
||||
{
|
||||
Type = SecureNoteType.Generic
|
||||
};
|
||||
break;
|
||||
case CipherType.Card:
|
||||
string brand;
|
||||
switch(CardBrandCell.Picker.SelectedIndex)
|
||||
{
|
||||
case 1:
|
||||
brand = "Visa";
|
||||
break;
|
||||
case 2:
|
||||
brand = "Mastercard";
|
||||
break;
|
||||
case 3:
|
||||
brand = "Amex";
|
||||
break;
|
||||
case 4:
|
||||
brand = "Discover";
|
||||
break;
|
||||
case 5:
|
||||
brand = "Diners Club";
|
||||
break;
|
||||
case 6:
|
||||
brand = "JCB";
|
||||
break;
|
||||
case 7:
|
||||
brand = "Maestro";
|
||||
break;
|
||||
case 8:
|
||||
brand = "UnionPay";
|
||||
break;
|
||||
case 9:
|
||||
brand = "Other";
|
||||
break;
|
||||
default:
|
||||
brand = null;
|
||||
break;
|
||||
}
|
||||
|
||||
var expMonth = CardExpMonthCell.Picker.SelectedIndex > 0 ?
|
||||
CardExpMonthCell.Picker.SelectedIndex.ToString() : null;
|
||||
|
||||
cipher.Card = new Card
|
||||
{
|
||||
CardholderName = string.IsNullOrWhiteSpace(CardNameCell.Entry.Text) ? null :
|
||||
CardNameCell.Entry.Text.Encrypt(),
|
||||
Number = string.IsNullOrWhiteSpace(CardNumberCell.Entry.Text) ? null :
|
||||
CardNumberCell.Entry.Text.Encrypt(),
|
||||
ExpYear = string.IsNullOrWhiteSpace(CardExpYearCell.Entry.Text) ? null :
|
||||
CardExpYearCell.Entry.Text.Encrypt(),
|
||||
Code = string.IsNullOrWhiteSpace(CardCodeCell.Entry.Text) ? null :
|
||||
CardCodeCell.Entry.Text.Encrypt(),
|
||||
Brand = string.IsNullOrWhiteSpace(brand) ? null : brand.Encrypt(),
|
||||
ExpMonth = string.IsNullOrWhiteSpace(expMonth) ? null : expMonth.Encrypt(),
|
||||
};
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
string title;
|
||||
switch(IdTitleCell.Picker.SelectedIndex)
|
||||
{
|
||||
case 1:
|
||||
title = AppResources.Mr;
|
||||
break;
|
||||
case 2:
|
||||
title = AppResources.Mrs;
|
||||
break;
|
||||
case 3:
|
||||
title = AppResources.Ms;
|
||||
break;
|
||||
case 4:
|
||||
title = AppResources.Dr;
|
||||
break;
|
||||
default:
|
||||
title = null;
|
||||
break;
|
||||
}
|
||||
|
||||
cipher.Identity = new Identity
|
||||
{
|
||||
Title = string.IsNullOrWhiteSpace(title) ? null : title.Encrypt(),
|
||||
FirstName = string.IsNullOrWhiteSpace(IdFirstNameCell.Entry.Text) ? null :
|
||||
IdFirstNameCell.Entry.Text.Encrypt(),
|
||||
MiddleName = string.IsNullOrWhiteSpace(IdMiddleNameCell.Entry.Text) ? null :
|
||||
IdMiddleNameCell.Entry.Text.Encrypt(),
|
||||
LastName = string.IsNullOrWhiteSpace(IdLastNameCell.Entry.Text) ? null :
|
||||
IdLastNameCell.Entry.Text.Encrypt(),
|
||||
Username = string.IsNullOrWhiteSpace(IdUsernameCell.Entry.Text) ? null :
|
||||
IdUsernameCell.Entry.Text.Encrypt(),
|
||||
Company = string.IsNullOrWhiteSpace(IdCompanyCell.Entry.Text) ? null :
|
||||
IdCompanyCell.Entry.Text.Encrypt(),
|
||||
SSN = string.IsNullOrWhiteSpace(IdSsnCell.Entry.Text) ? null :
|
||||
IdSsnCell.Entry.Text.Encrypt(),
|
||||
PassportNumber = string.IsNullOrWhiteSpace(IdPassportNumberCell.Entry.Text) ? null :
|
||||
IdPassportNumberCell.Entry.Text.Encrypt(),
|
||||
LicenseNumber = string.IsNullOrWhiteSpace(IdLicenseNumberCell.Entry.Text) ? null :
|
||||
IdLicenseNumberCell.Entry.Text.Encrypt(),
|
||||
Email = string.IsNullOrWhiteSpace(IdEmailCell.Entry.Text) ? null :
|
||||
IdEmailCell.Entry.Text.Encrypt(),
|
||||
Phone = string.IsNullOrWhiteSpace(IdPhoneCell.Entry.Text) ? null :
|
||||
IdPhoneCell.Entry.Text.Encrypt(),
|
||||
Address1 = string.IsNullOrWhiteSpace(IdAddress1Cell.Entry.Text) ? null :
|
||||
IdAddress1Cell.Entry.Text.Encrypt(),
|
||||
Address2 = string.IsNullOrWhiteSpace(IdAddress2Cell.Entry.Text) ? null :
|
||||
IdAddress2Cell.Entry.Text.Encrypt(),
|
||||
Address3 = string.IsNullOrWhiteSpace(IdAddress3Cell.Entry.Text) ? null :
|
||||
IdAddress3Cell.Entry.Text.Encrypt(),
|
||||
City = string.IsNullOrWhiteSpace(IdCityCell.Entry.Text) ? null :
|
||||
IdCityCell.Entry.Text.Encrypt(),
|
||||
State = string.IsNullOrWhiteSpace(IdStateCell.Entry.Text) ? null :
|
||||
IdStateCell.Entry.Text.Encrypt(),
|
||||
PostalCode = string.IsNullOrWhiteSpace(IdPostalCodeCell.Entry.Text) ? null :
|
||||
IdPostalCodeCell.Entry.Text.Encrypt(),
|
||||
Country = string.IsNullOrWhiteSpace(IdCountryCell.Entry.Text) ? null :
|
||||
IdCountryCell.Entry.Text.Encrypt()
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(FolderCell.Picker.SelectedIndex > 0)
|
||||
{
|
||||
cipher.FolderId = Folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||
}
|
||||
|
||||
Helpers.ProcessFieldsSectionForSave(FieldsSection, cipher);
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(cipher);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.NewItemCreated);
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("CreatedCipher");
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("CreatedCipher");
|
||||
}
|
||||
|
||||
if(_fromAutofillFramework)
|
||||
{
|
||||
// close and go back to app
|
||||
_deviceActionService.CloseAutofill();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
}
|
||||
|
||||
private async void AddFieldCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Helpers.AddField(this, FieldsSection);
|
||||
}
|
||||
|
||||
private void AddUriCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var cell = Helpers.MakeUriCell(string.Empty, null, UrisSection, this);
|
||||
if(cell != null)
|
||||
{
|
||||
UrisSection.Insert(UrisSection.Count - 1, cell);
|
||||
cell.InitEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultAttachmentsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly string _cipherId;
|
||||
private Cipher _cipher;
|
||||
private byte[] _fileBytes;
|
||||
private DateTime? _lastAction;
|
||||
private bool _canUseAttachments = true;
|
||||
|
||||
public VaultAttachmentsPage(string cipherId)
|
||||
: base(true)
|
||||
{
|
||||
_cipherId = cipherId;
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<VaultAttachmentsPageModel.Attachment> PresentationAttchments { get; private set; }
|
||||
= new ExtendedObservableCollection<VaultAttachmentsPageModel.Attachment>();
|
||||
public ExtendedListView ListView { get; set; }
|
||||
public RedrawableStackLayout NoDataStackLayout { get; set; }
|
||||
public StackLayout AddNewStackLayout { get; set; }
|
||||
public Label FileLabel { get; set; }
|
||||
public ExtendedTableView NewTable { get; set; }
|
||||
public Label NoDataLabel { get; set; }
|
||||
public ToolbarItem SaveToolbarItem { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_canUseAttachments = _cryptoService.EncKey != null;
|
||||
|
||||
SubscribeFileResult(true);
|
||||
var selectButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.ChooseFile,
|
||||
Command = new Command(async () => await _deviceActionService.SelectFileAsync()),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
|
||||
};
|
||||
|
||||
FileLabel = new Label
|
||||
{
|
||||
Text = AppResources.NoFileChosen,
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
AddNewStackLayout = new StackLayout
|
||||
{
|
||||
Children = { selectButton, FileLabel },
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Padding = new Thickness(20, Helpers.OnPlatform(iOS: 10, Android: 20), 20, 20),
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
};
|
||||
|
||||
NewTable = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
NoFooter = true,
|
||||
EnableScrolling = false,
|
||||
EnableSelection = false,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Margin = new Thickness(0, Helpers.OnPlatform(iOS: 10, Android: 30), 0, 0),
|
||||
WrappingStackLayout = () => NoDataStackLayout,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.AddNewAttachment)
|
||||
{
|
||||
new ExtendedViewCell
|
||||
{
|
||||
View = AddNewStackLayout,
|
||||
BackgroundColor = Color.White
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ListView = new ExtendedListView(ListViewCachingStrategy.RecycleElement)
|
||||
{
|
||||
ItemsSource = PresentationAttchments,
|
||||
HasUnevenRows = true,
|
||||
ItemTemplate = new DataTemplate(() => new VaultAttachmentsViewCell()),
|
||||
VerticalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
NoDataLabel = new Label
|
||||
{
|
||||
Text = AppResources.NoAttachments,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
NoDataStackLayout = new RedrawableStackLayout
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Spacing = 0,
|
||||
Margin = new Thickness(0, 40, 0, 0)
|
||||
};
|
||||
|
||||
SaveToolbarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent() || _cipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
|
||||
if(!_canUseAttachments)
|
||||
{
|
||||
await ShowUpdateKeyAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(_fileBytes == null)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.File), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveTask = await _cipherService.EncryptAndSaveAttachmentAsync(_cipher, _fileBytes, FileLabel.Text);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_fileBytes = null;
|
||||
FileLabel.Text = AppResources.NoFileChosen;
|
||||
_deviceActionService.Toast(AppResources.AttachementAdded);
|
||||
_googleAnalyticsService.TrackAppEvent("AddedAttachment");
|
||||
await LoadAttachmentsAsync();
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
Title = AppResources.Attachments;
|
||||
Content = ListView;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ListView.RowHeight = -1;
|
||||
NewTable.RowHeight = -1;
|
||||
NewTable.EstimatedRowHeight = 44;
|
||||
NewTable.HeightRequest = 180;
|
||||
ListView.BackgroundColor = Color.Transparent;
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
ListView.BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += AttachmentSelected;
|
||||
await LoadAttachmentsAsync();
|
||||
|
||||
// Prevent from adding multiple save buttons
|
||||
if(Device.RuntimePlatform == Device.iOS && ToolbarItems.Count > 1)
|
||||
{
|
||||
ToolbarItems.RemoveAt(1);
|
||||
}
|
||||
else if(Device.RuntimePlatform != Device.iOS && ToolbarItems.Count > 0)
|
||||
{
|
||||
ToolbarItems.RemoveAt(0);
|
||||
}
|
||||
|
||||
if(_cipher != null && (Helpers.CanAccessPremium() || _cipher.OrganizationId != null))
|
||||
{
|
||||
ToolbarItems.Add(SaveToolbarItem);
|
||||
ListView.Footer = NewTable;
|
||||
|
||||
if(!_canUseAttachments)
|
||||
{
|
||||
await ShowUpdateKeyAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.PremiumRequired, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= AttachmentSelected;
|
||||
}
|
||||
|
||||
private async Task LoadAttachmentsAsync()
|
||||
{
|
||||
_cipher = await _cipherService.GetByIdAsync(_cipherId);
|
||||
if(_cipher == null)
|
||||
{
|
||||
await Navigation.PopForDeviceAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var attachmentsToAdd = _cipher.Attachments
|
||||
.Select(a => new VaultAttachmentsPageModel.Attachment(a, _cipher.OrganizationId))
|
||||
.OrderBy(s => s.Name);
|
||||
PresentationAttchments.ResetWithRange(attachmentsToAdd);
|
||||
AdjustContent();
|
||||
}
|
||||
|
||||
private void AdjustContent()
|
||||
{
|
||||
if(PresentationAttchments.Count == 0)
|
||||
{
|
||||
NoDataStackLayout.Children.Clear();
|
||||
NoDataStackLayout.Children.Add(NoDataLabel);
|
||||
NoDataStackLayout.Children.Add(NewTable);
|
||||
Content = NoDataStackLayout;
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = ListView;
|
||||
}
|
||||
}
|
||||
|
||||
private async void AttachmentSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var attachment = e.SelectedItem as VaultAttachmentsPageModel.Attachment;
|
||||
if(attachment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
((ListView)sender).SelectedItem = null;
|
||||
|
||||
var confirmed = await DisplayAlert(null, AppResources.DoYouReallyWantToDelete, AppResources.Yes,
|
||||
AppResources.No);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
|
||||
var saveTask = await _cipherService.DeleteAttachmentAsync(_cipher, attachment.Id);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.AttachmentDeleted);
|
||||
_googleAnalyticsService.TrackAppEvent("DeletedAttachment");
|
||||
await LoadAttachmentsAsync();
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
|
||||
private void SubscribeFileResult(bool subscribe)
|
||||
{
|
||||
MessagingCenter.Unsubscribe<Application, Tuple<byte[], string>>(Application.Current, "SelectFileResult");
|
||||
if(!subscribe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessagingCenter.Subscribe<Application, Tuple<byte[], string>>(
|
||||
Application.Current, "SelectFileResult", (sender, result) =>
|
||||
{
|
||||
FileLabel.Text = result.Item2;
|
||||
_fileBytes = result.Item1;
|
||||
SubscribeFileResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ShowUpdateKeyAsync()
|
||||
{
|
||||
var confirmed = await DisplayAlert(AppResources.FeatureUnavailable, AppResources.UpdateKey,
|
||||
AppResources.LearnMore, AppResources.Cancel);
|
||||
if(confirmed)
|
||||
{
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/update-encryption-key/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using System.Threading;
|
||||
using Bit.App.Models;
|
||||
using System.Collections.Generic;
|
||||
using Bit.App.Enums;
|
||||
using static Bit.App.Models.Page.VaultListPageModel;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultAutofillListCiphersPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
public readonly IConnectivity _connectivity;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
private readonly string _name;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
public VaultAutofillListCiphersPage(AppOptions appOptions)
|
||||
: base(true)
|
||||
{
|
||||
_appOptions = appOptions;
|
||||
Uri = appOptions.Uri;
|
||||
if(Uri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
_name = Uri.Substring(Constants.AndroidAppProtocol.Length);
|
||||
}
|
||||
else if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out Uri uri) ||
|
||||
!DomainName.TryParseBaseDomain(uri.Host, out _name))
|
||||
{
|
||||
_name = "--";
|
||||
}
|
||||
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
DeviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_settingsService = Resolver.Resolve<ISettingsService>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ContentView ContentView { get; set; }
|
||||
public Fab Fab { get; set; }
|
||||
public ExtendedObservableCollection<Section<AutofillCipher>> PresentationCiphersGroup { get; private set; }
|
||||
= new ExtendedObservableCollection<Section<AutofillCipher>>();
|
||||
public StackLayout NoDataStackLayout { get; set; }
|
||||
public ExtendedListView ListView { get; set; }
|
||||
public ActivityIndicator LoadingIndicator { get; set; }
|
||||
private SearchToolBarItem SearchItem { get; set; }
|
||||
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
|
||||
private IDeviceActionService DeviceActionService { get; set; }
|
||||
private string Uri { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var noDataLabel = new Label
|
||||
{
|
||||
Text = string.Format(AppResources.NoItemsForUri, _name ?? "--"),
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
var addCipherButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.AddAnItem,
|
||||
Command = new Command(() => AddCipherAsync()),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
};
|
||||
|
||||
NoDataStackLayout = new StackLayout
|
||||
{
|
||||
Children = { noDataLabel, addCipherButton },
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Padding = new Thickness(20, 0),
|
||||
Spacing = 20
|
||||
};
|
||||
|
||||
SearchItem = new SearchToolBarItem(this);
|
||||
ToolbarItems.Add(SearchItem);
|
||||
|
||||
ListView = new ExtendedListView(ListViewCachingStrategy.RecycleElement)
|
||||
{
|
||||
IsGroupingEnabled = true,
|
||||
ItemsSource = PresentationCiphersGroup,
|
||||
HasUnevenRows = true,
|
||||
GroupHeaderTemplate = new DataTemplate(() => new SectionHeaderViewCell(
|
||||
nameof(Section<AutofillCipher>.Name))),
|
||||
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
|
||||
(VaultListPageModel.Cipher c) => Helpers.CipherMoreClickedAsync(this, c, true)))
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ListView.RowHeight = -1;
|
||||
}
|
||||
|
||||
Title = string.Format(AppResources.ItemsForUri, _name ?? "--");
|
||||
|
||||
LoadingIndicator = new ActivityIndicator
|
||||
{
|
||||
IsRunning = true,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
ContentView = new ContentView
|
||||
{
|
||||
Content = LoadingIndicator
|
||||
};
|
||||
|
||||
var fabLayout = new FabLayout(ContentView);
|
||||
Fab = new Fab(fabLayout, "plus.png", async (sender, args) => await AddCipherAsync());
|
||||
ListView.BottomPadding = 170;
|
||||
|
||||
Content = fabLayout;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += CipherSelected;
|
||||
SearchItem.InitEvents();
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= CipherSelected;
|
||||
SearchItem.Dispose();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
|
||||
DeviceActionService.CloseAutofill();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AdjustContent()
|
||||
{
|
||||
if(PresentationCiphersGroup.Count > 0)
|
||||
{
|
||||
ContentView.Content = ListView;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentView.Content = NoDataStackLayout;
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource FetchAndLoadVault()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
_filterResultsCancellationTokenSource?.Cancel();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var autofillGroupings = new List<Section<AutofillCipher>>();
|
||||
var ciphers = await _cipherService.GetAllAsync(Uri);
|
||||
|
||||
if(_appOptions.FillType.HasValue && _appOptions.FillType.Value != CipherType.Login)
|
||||
{
|
||||
var others = ciphers?.Item3.Where(c => c.Type == _appOptions.FillType.Value)
|
||||
.Select(c => new AutofillCipher(c, _appSettingsService, false))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Subtitle)
|
||||
.ToList();
|
||||
if(others?.Any() ?? false)
|
||||
{
|
||||
autofillGroupings.Add(new Section<AutofillCipher>(others, AppResources.Items));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var normalLogins = ciphers?.Item1
|
||||
.Select(l => new AutofillCipher(l, _appSettingsService, false))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Subtitle)
|
||||
.ToList();
|
||||
if(normalLogins?.Any() ?? false)
|
||||
{
|
||||
autofillGroupings.Add(new Section<AutofillCipher>(normalLogins,
|
||||
AppResources.MatchingItems));
|
||||
}
|
||||
|
||||
var fuzzyLogins = ciphers?.Item2
|
||||
.Select(l => new AutofillCipher(l, _appSettingsService, true))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Subtitle)
|
||||
.ToList();
|
||||
if(fuzzyLogins?.Any() ?? false)
|
||||
{
|
||||
autofillGroupings.Add(new Section<AutofillCipher>(fuzzyLogins,
|
||||
AppResources.PossibleMatchingItems));
|
||||
}
|
||||
}
|
||||
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
if(autofillGroupings.Any())
|
||||
{
|
||||
PresentationCiphersGroup.ResetWithRange(autofillGroupings);
|
||||
}
|
||||
|
||||
AdjustContent();
|
||||
});
|
||||
}, cts.Token);
|
||||
|
||||
return cts;
|
||||
}
|
||||
|
||||
private async void CipherSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var cipher = e.SelectedItem as AutofillCipher;
|
||||
if(cipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_deviceInfoService.Version < 21)
|
||||
{
|
||||
Helpers.CipherMoreClickedAsync(this, cipher, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var autofillResponse = AppResources.Yes;
|
||||
if(cipher.Fuzzy)
|
||||
{
|
||||
var options = new List<string> { AppResources.Yes };
|
||||
if(cipher.Type == CipherType.Login && _connectivity.IsConnected)
|
||||
{
|
||||
options.Add(AppResources.YesAndSave);
|
||||
}
|
||||
|
||||
autofillResponse = await DeviceActionService.DisplayAlertAsync(null,
|
||||
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name), AppResources.No,
|
||||
options.ToArray());
|
||||
}
|
||||
|
||||
if(autofillResponse == AppResources.YesAndSave && cipher.Type == CipherType.Login)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
Helpers.AlertNoConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var uris = cipher.CipherModel.Login?.Uris?.ToList();
|
||||
if(uris == null)
|
||||
{
|
||||
uris = new List<LoginUri>();
|
||||
}
|
||||
|
||||
uris.Add(new LoginUri
|
||||
{
|
||||
Uri = Uri.Encrypt(cipher.CipherModel.OrganizationId),
|
||||
Match = null
|
||||
});
|
||||
|
||||
cipher.CipherModel.Login.Uris = uris;
|
||||
|
||||
await DeviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(cipher.CipherModel);
|
||||
await DeviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
GoogleAnalyticsService.TrackAppEvent("AddedLoginUriDuringAutofill");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
|
||||
{
|
||||
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled",
|
||||
Uri.StartsWith("http") ? "Website" : "App");
|
||||
DeviceActionService.Autofill(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
((ListView)sender).SelectedItem = null;
|
||||
}
|
||||
|
||||
private async Task AddCipherAsync()
|
||||
{
|
||||
if(_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login)
|
||||
{
|
||||
var pageForOther = new VaultAddCipherPage(_appOptions.FillType.Value, null, null, true);
|
||||
await Navigation.PushForDeviceAsync(pageForOther);
|
||||
return;
|
||||
}
|
||||
|
||||
var pageForLogin = new VaultAddCipherPage(CipherType.Login, Uri, _name, true);
|
||||
await Navigation.PushForDeviceAsync(pageForLogin);
|
||||
}
|
||||
|
||||
private class SearchToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultAutofillListCiphersPage _page;
|
||||
|
||||
public SearchToolBarItem(VaultAutofillListCiphersPage page)
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Search;
|
||||
Icon = "search.png";
|
||||
Priority = 1;
|
||||
ClickAction = () => DoClick();
|
||||
}
|
||||
|
||||
private void DoClick()
|
||||
{
|
||||
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch",
|
||||
_page.Uri.StartsWith("http") ? "Website" : "App");
|
||||
Application.Current.MainPage = new ExtendedNavigationPage(new VaultListCiphersPage(uri: _page.Uri));
|
||||
_page.DeviceActionService.Toast(string.Format(AppResources.BitwardenAutofillServiceSearch, _page._name),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,981 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultEditCipherPage : ExtendedContentPage
|
||||
{
|
||||
private readonly string _cipherId;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IDeviceInfoService _deviceInfo;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private DateTime? _lastAction;
|
||||
private string _originalLoginPassword = null;
|
||||
private List<Tuple<string, string>> _originalHiddenFields = new List<Tuple<string, string>>();
|
||||
|
||||
public VaultEditCipherPage(string cipherId)
|
||||
{
|
||||
_cipherId = cipherId;
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public Cipher Cipher { get; set; }
|
||||
public List<Folder> Folders { get; set; }
|
||||
public TableRoot TableRoot { get; set; }
|
||||
public TableSection TopSection { get; set; }
|
||||
public TableSection UrisSection { get; set; }
|
||||
public TableSection MiddleSection { get; set; }
|
||||
public TableSection FieldsSection { get; set; }
|
||||
public ExtendedTableView Table { get; set; }
|
||||
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
public FormEditorCell NotesCell { get; private set; }
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||
public ExtendedTextCell AttachmentsCell { get; private set; }
|
||||
public ExtendedTextCell DeleteCell { get; private set; }
|
||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||
public ExtendedTextCell AddUriCell { get; private set; }
|
||||
|
||||
// Login
|
||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||
public FormEntryCell LoginUsernameCell { get; private set; }
|
||||
public FormEntryCell LoginTotpCell { get; private set; }
|
||||
|
||||
// Card
|
||||
public FormEntryCell CardNameCell { get; private set; }
|
||||
public FormEntryCell CardNumberCell { get; private set; }
|
||||
public FormPickerCell CardBrandCell { get; private set; }
|
||||
public FormPickerCell CardExpMonthCell { get; private set; }
|
||||
public FormEntryCell CardExpYearCell { get; private set; }
|
||||
public FormEntryCell CardCodeCell { get; private set; }
|
||||
|
||||
// Identity
|
||||
public FormPickerCell IdTitleCell { get; private set; }
|
||||
public FormEntryCell IdFirstNameCell { get; private set; }
|
||||
public FormEntryCell IdMiddleNameCell { get; private set; }
|
||||
public FormEntryCell IdLastNameCell { get; private set; }
|
||||
public FormEntryCell IdUsernameCell { get; private set; }
|
||||
public FormEntryCell IdCompanyCell { get; private set; }
|
||||
public FormEntryCell IdSsnCell { get; private set; }
|
||||
public FormEntryCell IdPassportNumberCell { get; private set; }
|
||||
public FormEntryCell IdLicenseNumberCell { get; private set; }
|
||||
public FormEntryCell IdEmailCell { get; private set; }
|
||||
public FormEntryCell IdPhoneCell { get; private set; }
|
||||
public FormEntryCell IdAddress1Cell { get; private set; }
|
||||
public FormEntryCell IdAddress2Cell { get; private set; }
|
||||
public FormEntryCell IdAddress3Cell { get; private set; }
|
||||
public FormEntryCell IdCityCell { get; private set; }
|
||||
public FormEntryCell IdStateCell { get; private set; }
|
||||
public FormEntryCell IdPostalCodeCell { get; private set; }
|
||||
public FormEntryCell IdCountryCell { get; private set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Cipher = _cipherService.GetByIdAsync(_cipherId).GetAwaiter().GetResult();
|
||||
if(Cipher == null)
|
||||
{
|
||||
// TODO: handle error. navigate back? should never happen...
|
||||
return;
|
||||
}
|
||||
|
||||
// Name
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell.Entry.Text = Cipher.Name?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
// Notes
|
||||
NotesCell = new FormEditorCell(Keyboard.Text, Cipher.Type == CipherType.SecureNote ? 500 : 180);
|
||||
NotesCell.Editor.Text = Cipher.Notes?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
// Folders
|
||||
var folderOptions = new List<string> { AppResources.FolderNone };
|
||||
Folders = _folderService.GetAllAsync().GetAwaiter().GetResult()
|
||||
.OrderBy(f => f.Name?.Decrypt()).ToList();
|
||||
int selectedIndex = 0;
|
||||
int i = 0;
|
||||
foreach(var folder in Folders)
|
||||
{
|
||||
i++;
|
||||
if(folder.Id == Cipher.FolderId)
|
||||
{
|
||||
selectedIndex = i;
|
||||
}
|
||||
|
||||
folderOptions.Add(folder.Name.Decrypt());
|
||||
}
|
||||
FolderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
FolderCell.Picker.SelectedIndex = selectedIndex;
|
||||
|
||||
// Favorite
|
||||
FavoriteCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.Favorite,
|
||||
On = Cipher.Favorite
|
||||
};
|
||||
|
||||
// Delete
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
InitTable();
|
||||
InitSave();
|
||||
|
||||
Title = AppResources.EditItem;
|
||||
Content = Table;
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
private void InitTable()
|
||||
{
|
||||
AttachmentsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Attachments,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
// Sections
|
||||
TopSection = new TableSection(AppResources.ItemInformation)
|
||||
{
|
||||
NameCell
|
||||
};
|
||||
|
||||
MiddleSection = new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
FolderCell,
|
||||
FavoriteCell,
|
||||
AttachmentsCell
|
||||
};
|
||||
|
||||
// Types
|
||||
if(Cipher.Type == CipherType.Login)
|
||||
{
|
||||
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey,
|
||||
button1: _deviceInfo.HasCamera ? "camera.png" : null);
|
||||
LoginTotpCell.Entry.Text = Cipher.Login?.Totp?.Decrypt(Cipher.OrganizationId);
|
||||
LoginTotpCell.Entry.DisableAutocapitalize = true;
|
||||
LoginTotpCell.Entry.Autocorrect = false;
|
||||
LoginTotpCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
LoginPasswordCell = new FormEntryCell(AppResources.Password, isPassword: true,
|
||||
nextElement: LoginTotpCell.Entry, button1: "eye.png", button2: "refresh_alt.png");
|
||||
LoginPasswordCell.Entry.Text = _originalLoginPassword =
|
||||
Cipher.Login?.Password?.Decrypt(Cipher.OrganizationId);
|
||||
LoginPasswordCell.Entry.DisableAutocapitalize = true;
|
||||
LoginPasswordCell.Entry.Autocorrect = false;
|
||||
LoginPasswordCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
LoginUsernameCell = new FormEntryCell(AppResources.Username, nextElement: LoginPasswordCell.Entry);
|
||||
LoginUsernameCell.Entry.Text = Cipher.Login?.Username?.Decrypt(Cipher.OrganizationId);
|
||||
LoginUsernameCell.Entry.DisableAutocapitalize = true;
|
||||
LoginUsernameCell.Entry.Autocorrect = false;
|
||||
|
||||
// Name
|
||||
NameCell.NextElement = LoginUsernameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(LoginUsernameCell);
|
||||
TopSection.Add(LoginPasswordCell);
|
||||
TopSection.Add(LoginTotpCell);
|
||||
|
||||
// Uris
|
||||
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||
AddUriCell = new ExtendedTextCell
|
||||
{
|
||||
Text = $"+ {AppResources.NewUri}",
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
UrisSection.Add(AddUriCell);
|
||||
if(Cipher.Login?.Uris != null)
|
||||
{
|
||||
foreach(var uri in Cipher.Login.Uris)
|
||||
{
|
||||
var value = uri.Uri?.Decrypt(Cipher.OrganizationId);
|
||||
UrisSection.Insert(UrisSection.Count - 1,
|
||||
Helpers.MakeUriCell(value, uri.Match, UrisSection, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(Cipher.Type == CipherType.Card)
|
||||
{
|
||||
CardCodeCell = new FormEntryCell(AppResources.SecurityCode, Keyboard.Numeric,
|
||||
isPassword: true, nextElement: NotesCell.Editor, button1: "eye.png");
|
||||
CardCodeCell.Entry.Text = Cipher.Card.Code?.Decrypt(Cipher.OrganizationId);
|
||||
CardCodeCell.Entry.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
CardExpYearCell = new FormEntryCell(AppResources.ExpirationYear, Keyboard.Numeric,
|
||||
nextElement: CardCodeCell.Entry);
|
||||
CardExpYearCell.Entry.Text = Cipher.Card.ExpYear?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
var month = Cipher.Card.ExpMonth?.Decrypt(Cipher.OrganizationId);
|
||||
CardExpMonthCell = new FormPickerCell(AppResources.ExpirationMonth, new string[] {
|
||||
"--", AppResources.January, AppResources.February, AppResources.March, AppResources.April,
|
||||
AppResources.May, AppResources.June, AppResources.July, AppResources.August, AppResources.September,
|
||||
AppResources.October, AppResources.November, AppResources.December
|
||||
});
|
||||
if(!string.IsNullOrWhiteSpace(month) && int.TryParse(month, out int monthIndex))
|
||||
{
|
||||
CardExpMonthCell.Picker.SelectedIndex = monthIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardExpMonthCell.Picker.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
var brandOptions = new string[] {
|
||||
"--", "Visa", "Mastercard", "American Express", "Discover", "Diners Club",
|
||||
"JCB", "Maestro", "UnionPay", AppResources.Other
|
||||
};
|
||||
var brand = Cipher.Card.Brand?.Decrypt(Cipher.OrganizationId);
|
||||
CardBrandCell = new FormPickerCell(AppResources.Brand, brandOptions);
|
||||
CardBrandCell.Picker.SelectedIndex = 0;
|
||||
if(!string.IsNullOrWhiteSpace(brand))
|
||||
{
|
||||
var i = 0;
|
||||
foreach(var o in brandOptions)
|
||||
{
|
||||
var option = o;
|
||||
if(option == AppResources.Other)
|
||||
{
|
||||
option = "Other";
|
||||
}
|
||||
|
||||
if(option == brand)
|
||||
{
|
||||
CardBrandCell.Picker.SelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
CardNumberCell = new FormEntryCell(AppResources.Number, Keyboard.Numeric);
|
||||
CardNumberCell.Entry.Text = Cipher.Card.Number?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
CardNameCell = new FormEntryCell(AppResources.CardholderName, nextElement: CardNumberCell.Entry);
|
||||
CardNameCell.Entry.Text = Cipher.Card.CardholderName?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
// Name
|
||||
NameCell.NextElement = CardNameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(CardNameCell);
|
||||
TopSection.Add(CardNumberCell);
|
||||
TopSection.Add(CardBrandCell);
|
||||
TopSection.Add(CardExpMonthCell);
|
||||
TopSection.Add(CardExpYearCell);
|
||||
TopSection.Add(CardCodeCell);
|
||||
}
|
||||
else if(Cipher.Type == CipherType.Identity)
|
||||
{
|
||||
IdCountryCell = new FormEntryCell(AppResources.Country, nextElement: NotesCell.Editor);
|
||||
IdCountryCell.Entry.Text = Cipher.Identity.Country?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdPostalCodeCell = new FormEntryCell(AppResources.ZipPostalCode, nextElement: IdCountryCell.Entry);
|
||||
IdPostalCodeCell.Entry.Text = Cipher.Identity.PostalCode?.Decrypt(Cipher.OrganizationId);
|
||||
IdPostalCodeCell.Entry.DisableAutocapitalize = true;
|
||||
IdPostalCodeCell.Entry.Autocorrect = false;
|
||||
|
||||
IdStateCell = new FormEntryCell(AppResources.StateProvince, nextElement: IdPostalCodeCell.Entry);
|
||||
IdStateCell.Entry.Text = Cipher.Identity.State?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdCityCell = new FormEntryCell(AppResources.CityTown, nextElement: IdStateCell.Entry);
|
||||
IdCityCell.Entry.Text = Cipher.Identity.City?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdAddress3Cell = new FormEntryCell(AppResources.Address3, nextElement: IdCityCell.Entry);
|
||||
IdAddress3Cell.Entry.Text = Cipher.Identity.Address3?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdAddress2Cell = new FormEntryCell(AppResources.Address2, nextElement: IdAddress3Cell.Entry);
|
||||
IdAddress2Cell.Entry.Text = Cipher.Identity.Address2?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdAddress1Cell = new FormEntryCell(AppResources.Address1, nextElement: IdAddress2Cell.Entry);
|
||||
IdAddress1Cell.Entry.Text = Cipher.Identity.Address1?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdPhoneCell = new FormEntryCell(AppResources.Phone, nextElement: IdAddress1Cell.Entry);
|
||||
IdPhoneCell.Entry.Text = Cipher.Identity.Phone?.Decrypt(Cipher.OrganizationId);
|
||||
IdPhoneCell.Entry.DisableAutocapitalize = true;
|
||||
IdPhoneCell.Entry.Autocorrect = false;
|
||||
|
||||
IdEmailCell = new FormEntryCell(AppResources.Email, Keyboard.Email, nextElement: IdPhoneCell.Entry);
|
||||
IdEmailCell.Entry.Text = Cipher.Identity.Email?.Decrypt(Cipher.OrganizationId);
|
||||
IdEmailCell.Entry.DisableAutocapitalize = true;
|
||||
IdEmailCell.Entry.Autocorrect = false;
|
||||
|
||||
IdLicenseNumberCell = new FormEntryCell(AppResources.LicenseNumber, nextElement: IdEmailCell.Entry);
|
||||
IdLicenseNumberCell.Entry.Text = Cipher.Identity.LicenseNumber?.Decrypt(Cipher.OrganizationId);
|
||||
IdLicenseNumberCell.Entry.DisableAutocapitalize = true;
|
||||
IdLicenseNumberCell.Entry.Autocorrect = false;
|
||||
|
||||
IdPassportNumberCell = new FormEntryCell(AppResources.PassportNumber, nextElement: IdLicenseNumberCell.Entry);
|
||||
IdPassportNumberCell.Entry.Text = Cipher.Identity.PassportNumber?.Decrypt(Cipher.OrganizationId);
|
||||
IdPassportNumberCell.Entry.DisableAutocapitalize = true;
|
||||
IdPassportNumberCell.Entry.Autocorrect = false;
|
||||
|
||||
IdSsnCell = new FormEntryCell(AppResources.SSN, nextElement: IdPassportNumberCell.Entry);
|
||||
IdSsnCell.Entry.Text = Cipher.Identity.SSN?.Decrypt(Cipher.OrganizationId);
|
||||
IdSsnCell.Entry.DisableAutocapitalize = true;
|
||||
IdSsnCell.Entry.Autocorrect = false;
|
||||
|
||||
IdCompanyCell = new FormEntryCell(AppResources.Company, nextElement: IdSsnCell.Entry);
|
||||
IdCompanyCell.Entry.Text = Cipher.Identity.Company?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdUsernameCell = new FormEntryCell(AppResources.Username, nextElement: IdCompanyCell.Entry);
|
||||
IdUsernameCell.Entry.Text = Cipher.Identity.Username?.Decrypt(Cipher.OrganizationId);
|
||||
IdUsernameCell.Entry.DisableAutocapitalize = true;
|
||||
IdUsernameCell.Entry.Autocorrect = false;
|
||||
|
||||
IdLastNameCell = new FormEntryCell(AppResources.LastName, nextElement: IdUsernameCell.Entry);
|
||||
IdLastNameCell.Entry.Text = Cipher.Identity.LastName?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdMiddleNameCell = new FormEntryCell(AppResources.MiddleName, nextElement: IdLastNameCell.Entry);
|
||||
IdMiddleNameCell.Entry.Text = Cipher.Identity.MiddleName?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
IdFirstNameCell = new FormEntryCell(AppResources.FirstName, nextElement: IdMiddleNameCell.Entry);
|
||||
IdFirstNameCell.Entry.Text = Cipher.Identity.FirstName?.Decrypt(Cipher.OrganizationId);
|
||||
|
||||
var titleOptions = new string[] {
|
||||
"--", AppResources.Mr, AppResources.Mrs, AppResources.Ms, AppResources.Dr
|
||||
};
|
||||
IdTitleCell = new FormPickerCell(AppResources.Title, titleOptions);
|
||||
var title = Cipher.Identity.Title?.Decrypt(Cipher.OrganizationId);
|
||||
IdTitleCell.Picker.SelectedIndex = 0;
|
||||
if(!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
var i = 0;
|
||||
foreach(var o in titleOptions)
|
||||
{
|
||||
i++;
|
||||
if(o == title)
|
||||
{
|
||||
IdTitleCell.Picker.SelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name
|
||||
NameCell.NextElement = IdFirstNameCell.Entry;
|
||||
|
||||
// Build sections
|
||||
TopSection.Add(IdTitleCell);
|
||||
TopSection.Add(IdFirstNameCell);
|
||||
TopSection.Add(IdMiddleNameCell);
|
||||
TopSection.Add(IdLastNameCell);
|
||||
TopSection.Add(IdUsernameCell);
|
||||
TopSection.Add(IdCompanyCell);
|
||||
TopSection.Add(IdSsnCell);
|
||||
TopSection.Add(IdPassportNumberCell);
|
||||
TopSection.Add(IdLicenseNumberCell);
|
||||
TopSection.Add(IdEmailCell);
|
||||
TopSection.Add(IdPhoneCell);
|
||||
TopSection.Add(IdAddress1Cell);
|
||||
TopSection.Add(IdAddress2Cell);
|
||||
TopSection.Add(IdAddress3Cell);
|
||||
TopSection.Add(IdCityCell);
|
||||
TopSection.Add(IdStateCell);
|
||||
TopSection.Add(IdPostalCodeCell);
|
||||
TopSection.Add(IdCountryCell);
|
||||
}
|
||||
else if(Cipher.Type == CipherType.SecureNote)
|
||||
{
|
||||
// Name
|
||||
NameCell.NextElement = NotesCell.Editor;
|
||||
}
|
||||
|
||||
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||
if(Cipher.Fields != null)
|
||||
{
|
||||
foreach(var field in Cipher.Fields)
|
||||
{
|
||||
var label = field.Name?.Decrypt(Cipher.OrganizationId) ?? string.Empty;
|
||||
var value = field.Value?.Decrypt(Cipher.OrganizationId);
|
||||
var cell = Helpers.MakeFieldCell(field.Type, label, value, FieldsSection, this);
|
||||
if(cell != null)
|
||||
{
|
||||
FieldsSection.Add(cell);
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(label) && !string.IsNullOrWhiteSpace(value) &&
|
||||
field.Type == FieldType.Hidden)
|
||||
{
|
||||
_originalHiddenFields.Add(new Tuple<string, string>(label, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
AddFieldCell = new ExtendedTextCell
|
||||
{
|
||||
Text = $"+ {AppResources.NewCustomField}",
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
FieldsSection.Add(AddFieldCell);
|
||||
|
||||
// Make table
|
||||
TableRoot = new TableRoot
|
||||
{
|
||||
TopSection,
|
||||
MiddleSection,
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
NotesCell
|
||||
},
|
||||
FieldsSection,
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
DeleteCell
|
||||
}
|
||||
};
|
||||
|
||||
if(UrisSection != null)
|
||||
{
|
||||
TableRoot.Insert(1, UrisSection);
|
||||
}
|
||||
|
||||
Table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = true,
|
||||
HasUnevenRows = true,
|
||||
Root = TableRoot
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
Table.RowHeight = -1;
|
||||
Table.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Table.BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitSave()
|
||||
{
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Cipher.Name = NameCell.Entry.Text.Encrypt(Cipher.OrganizationId);
|
||||
Cipher.Notes = string.IsNullOrWhiteSpace(NotesCell.Editor.Text) ? null :
|
||||
NotesCell.Editor.Text.Encrypt(Cipher.OrganizationId);
|
||||
Cipher.Favorite = FavoriteCell.On;
|
||||
|
||||
var passwordHistory = Cipher.PasswordHistory?.ToList() ?? new List<PasswordHistory>();
|
||||
switch(Cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
Cipher.Login = new Login
|
||||
{
|
||||
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
||||
LoginUsernameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
||||
LoginPasswordCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
||||
LoginTotpCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
PasswordRevisionDate = Cipher.Login.PasswordRevisionDate,
|
||||
};
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(_originalLoginPassword) &&
|
||||
LoginPasswordCell.Entry.Text != _originalLoginPassword)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
passwordHistory.Insert(0, new PasswordHistory
|
||||
{
|
||||
LastUsedDate = now,
|
||||
Password = _originalLoginPassword.Encrypt(Cipher.OrganizationId),
|
||||
});
|
||||
Cipher.Login.PasswordRevisionDate = now;
|
||||
}
|
||||
|
||||
Helpers.ProcessUrisSectionForSave(UrisSection, Cipher);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
Cipher.SecureNote = new SecureNote
|
||||
{
|
||||
Type = SecureNoteType.Generic
|
||||
};
|
||||
break;
|
||||
case CipherType.Card:
|
||||
string brand;
|
||||
switch(CardBrandCell.Picker.SelectedIndex)
|
||||
{
|
||||
case 1:
|
||||
brand = "Visa";
|
||||
break;
|
||||
case 2:
|
||||
brand = "Mastercard";
|
||||
break;
|
||||
case 3:
|
||||
brand = "Amex";
|
||||
break;
|
||||
case 4:
|
||||
brand = "Discover";
|
||||
break;
|
||||
case 5:
|
||||
brand = "Diners Club";
|
||||
break;
|
||||
case 6:
|
||||
brand = "JCB";
|
||||
break;
|
||||
case 7:
|
||||
brand = "Maestro";
|
||||
break;
|
||||
case 8:
|
||||
brand = "UnionPay";
|
||||
break;
|
||||
case 9:
|
||||
brand = "Other";
|
||||
break;
|
||||
default:
|
||||
brand = null;
|
||||
break;
|
||||
}
|
||||
|
||||
var expMonth = CardExpMonthCell.Picker.SelectedIndex > 0 ?
|
||||
CardExpMonthCell.Picker.SelectedIndex.ToString() : null;
|
||||
|
||||
Cipher.Card = new Card
|
||||
{
|
||||
CardholderName = string.IsNullOrWhiteSpace(CardNameCell.Entry.Text) ? null :
|
||||
CardNameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Number = string.IsNullOrWhiteSpace(CardNumberCell.Entry.Text) ? null :
|
||||
CardNumberCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
ExpYear = string.IsNullOrWhiteSpace(CardExpYearCell.Entry.Text) ? null :
|
||||
CardExpYearCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Code = string.IsNullOrWhiteSpace(CardCodeCell.Entry.Text) ? null :
|
||||
CardCodeCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Brand = string.IsNullOrWhiteSpace(brand) ? null : brand.Encrypt(Cipher.OrganizationId),
|
||||
ExpMonth = string.IsNullOrWhiteSpace(expMonth) ? null : expMonth.Encrypt(Cipher.OrganizationId)
|
||||
};
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
string title;
|
||||
switch(IdTitleCell.Picker.SelectedIndex)
|
||||
{
|
||||
case 1:
|
||||
title = AppResources.Mr;
|
||||
break;
|
||||
case 2:
|
||||
title = AppResources.Mrs;
|
||||
break;
|
||||
case 3:
|
||||
title = AppResources.Ms;
|
||||
break;
|
||||
case 4:
|
||||
title = AppResources.Dr;
|
||||
break;
|
||||
default:
|
||||
title = null;
|
||||
break;
|
||||
}
|
||||
|
||||
Cipher.Identity = new Identity
|
||||
{
|
||||
Title = string.IsNullOrWhiteSpace(title) ? null : title.Encrypt(Cipher.OrganizationId),
|
||||
FirstName = string.IsNullOrWhiteSpace(IdFirstNameCell.Entry.Text) ? null :
|
||||
IdFirstNameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
MiddleName = string.IsNullOrWhiteSpace(IdMiddleNameCell.Entry.Text) ? null :
|
||||
IdMiddleNameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
LastName = string.IsNullOrWhiteSpace(IdLastNameCell.Entry.Text) ? null :
|
||||
IdLastNameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Username = string.IsNullOrWhiteSpace(IdUsernameCell.Entry.Text) ? null :
|
||||
IdUsernameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Company = string.IsNullOrWhiteSpace(IdCompanyCell.Entry.Text) ? null :
|
||||
IdCompanyCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
SSN = string.IsNullOrWhiteSpace(IdSsnCell.Entry.Text) ? null :
|
||||
IdSsnCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
PassportNumber = string.IsNullOrWhiteSpace(IdPassportNumberCell.Entry.Text) ? null :
|
||||
IdPassportNumberCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
LicenseNumber = string.IsNullOrWhiteSpace(IdLicenseNumberCell.Entry.Text) ? null :
|
||||
IdLicenseNumberCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Email = string.IsNullOrWhiteSpace(IdEmailCell.Entry.Text) ? null :
|
||||
IdEmailCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Phone = string.IsNullOrWhiteSpace(IdPhoneCell.Entry.Text) ? null :
|
||||
IdPhoneCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Address1 = string.IsNullOrWhiteSpace(IdAddress1Cell.Entry.Text) ? null :
|
||||
IdAddress1Cell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Address2 = string.IsNullOrWhiteSpace(IdAddress2Cell.Entry.Text) ? null :
|
||||
IdAddress2Cell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Address3 = string.IsNullOrWhiteSpace(IdAddress3Cell.Entry.Text) ? null :
|
||||
IdAddress3Cell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
City = string.IsNullOrWhiteSpace(IdCityCell.Entry.Text) ? null :
|
||||
IdCityCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
State = string.IsNullOrWhiteSpace(IdStateCell.Entry.Text) ? null :
|
||||
IdStateCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
PostalCode = string.IsNullOrWhiteSpace(IdPostalCodeCell.Entry.Text) ? null :
|
||||
IdPostalCodeCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||
Country = string.IsNullOrWhiteSpace(IdCountryCell.Entry.Text) ? null :
|
||||
IdCountryCell.Entry.Text.Encrypt(Cipher.OrganizationId)
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(FolderCell.Picker.SelectedIndex > 0)
|
||||
{
|
||||
Cipher.FolderId = Folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cipher.FolderId = null;
|
||||
}
|
||||
|
||||
var hiddenFields = Helpers.ProcessFieldsSectionForSave(FieldsSection, Cipher);
|
||||
var changedFields = _originalHiddenFields.Where(of =>
|
||||
hiddenFields.Any(f => f.Item1 == of.Item1 && f.Item2 != of.Item2));
|
||||
foreach(var cf in changedFields)
|
||||
{
|
||||
passwordHistory.Insert(0, new PasswordHistory
|
||||
{
|
||||
LastUsedDate = DateTime.UtcNow,
|
||||
Password = (cf.Item1 + ": " + cf.Item2).Encrypt(Cipher.OrganizationId),
|
||||
});
|
||||
}
|
||||
Cipher.PasswordHistory = (passwordHistory?.Count ?? 0) > 0 ? passwordHistory.Take(5) : null;
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(Cipher);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.ItemUpdated);
|
||||
_googleAnalyticsService.TrackAppEvent("EditedCipher");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell?.InitEvents();
|
||||
NotesCell?.InitEvents();
|
||||
FolderCell?.InitEvents();
|
||||
|
||||
if(AttachmentsCell != null)
|
||||
{
|
||||
AttachmentsCell.Tapped += AttachmentsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
}
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||
}
|
||||
if(AddUriCell != null)
|
||||
{
|
||||
AddUriCell.Tapped += AddUriCell_Tapped;
|
||||
}
|
||||
|
||||
switch(Cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
LoginPasswordCell?.InitEvents();
|
||||
LoginUsernameCell?.InitEvents();
|
||||
LoginTotpCell?.InitEvents();
|
||||
if(LoginPasswordCell?.Button1 != null)
|
||||
{
|
||||
LoginPasswordCell.Button1.Clicked += PasswordButton_Clicked;
|
||||
}
|
||||
if(LoginPasswordCell?.Button2 != null)
|
||||
{
|
||||
LoginPasswordCell.Button2.Clicked += PasswordButton2_Clicked;
|
||||
}
|
||||
if(LoginTotpCell?.Button1 != null)
|
||||
{
|
||||
LoginTotpCell.Button1.Clicked += TotpButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Card:
|
||||
CardBrandCell?.InitEvents();
|
||||
CardCodeCell?.InitEvents();
|
||||
CardExpMonthCell?.InitEvents();
|
||||
CardExpYearCell?.InitEvents();
|
||||
CardNameCell?.InitEvents();
|
||||
CardNumberCell?.InitEvents();
|
||||
if(CardCodeCell?.Button1 != null)
|
||||
{
|
||||
CardCodeCell.Button1.Clicked += CardCodeButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
IdTitleCell?.InitEvents();
|
||||
IdFirstNameCell?.InitEvents();
|
||||
IdMiddleNameCell?.InitEvents();
|
||||
IdLastNameCell?.InitEvents();
|
||||
IdUsernameCell?.InitEvents();
|
||||
IdCompanyCell?.InitEvents();
|
||||
IdSsnCell?.InitEvents();
|
||||
IdPassportNumberCell?.InitEvents();
|
||||
IdLicenseNumberCell?.InitEvents();
|
||||
IdEmailCell?.InitEvents();
|
||||
IdPhoneCell?.InitEvents();
|
||||
IdAddress1Cell?.InitEvents();
|
||||
IdAddress2Cell?.InitEvents();
|
||||
IdAddress3Cell?.InitEvents();
|
||||
IdCityCell?.InitEvents();
|
||||
IdStateCell?.InitEvents();
|
||||
IdPostalCodeCell?.InitEvents();
|
||||
IdCountryCell?.InitEvents();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Helpers.InitSectionEvents(FieldsSection);
|
||||
Helpers.InitSectionEvents(UrisSection);
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
NameCell?.Dispose();
|
||||
NotesCell?.Dispose();
|
||||
FolderCell?.Dispose();
|
||||
|
||||
if(AttachmentsCell != null)
|
||||
{
|
||||
AttachmentsCell.Tapped -= AttachmentsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||
}
|
||||
if(AddUriCell != null)
|
||||
{
|
||||
AddUriCell.Tapped -= AddUriCell_Tapped;
|
||||
}
|
||||
|
||||
switch(Cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
LoginTotpCell?.Dispose();
|
||||
LoginPasswordCell?.Dispose();
|
||||
LoginUsernameCell?.Dispose();
|
||||
if(LoginPasswordCell?.Button1 != null)
|
||||
{
|
||||
LoginPasswordCell.Button1.Clicked -= PasswordButton_Clicked;
|
||||
}
|
||||
if(LoginPasswordCell?.Button2 != null)
|
||||
{
|
||||
LoginPasswordCell.Button2.Clicked -= PasswordButton2_Clicked;
|
||||
}
|
||||
if(LoginTotpCell?.Button1 != null)
|
||||
{
|
||||
LoginTotpCell.Button1.Clicked -= TotpButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Card:
|
||||
CardBrandCell?.Dispose();
|
||||
CardCodeCell?.Dispose();
|
||||
CardExpMonthCell?.Dispose();
|
||||
CardExpYearCell?.Dispose();
|
||||
CardNameCell?.Dispose();
|
||||
CardNumberCell?.Dispose();
|
||||
if(CardCodeCell?.Button1 != null)
|
||||
{
|
||||
CardCodeCell.Button1.Clicked -= CardCodeButton_Clicked;
|
||||
}
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
IdTitleCell?.Dispose();
|
||||
IdFirstNameCell?.Dispose();
|
||||
IdMiddleNameCell?.Dispose();
|
||||
IdLastNameCell?.Dispose();
|
||||
IdUsernameCell?.Dispose();
|
||||
IdCompanyCell?.Dispose();
|
||||
IdSsnCell?.Dispose();
|
||||
IdPassportNumberCell?.Dispose();
|
||||
IdLicenseNumberCell?.Dispose();
|
||||
IdEmailCell?.Dispose();
|
||||
IdPhoneCell?.Dispose();
|
||||
IdAddress1Cell?.Dispose();
|
||||
IdAddress2Cell?.Dispose();
|
||||
IdAddress3Cell?.Dispose();
|
||||
IdCityCell?.Dispose();
|
||||
IdStateCell?.Dispose();
|
||||
IdPostalCodeCell?.Dispose();
|
||||
IdCountryCell?.Dispose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Helpers.DisposeSectionEvents(FieldsSection);
|
||||
Helpers.DisposeSectionEvents(UrisSection);
|
||||
}
|
||||
|
||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
LoginPasswordCell.Entry.InvokeToggleIsPassword();
|
||||
LoginPasswordCell.Button1.Image =
|
||||
"eye" + (!LoginPasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
}
|
||||
|
||||
private async void PasswordButton2_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text)
|
||||
&& !(await DisplayAlert(null, AppResources.PasswordOverrideAlert, AppResources.Yes, AppResources.No)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var page = new ToolsPasswordGeneratorPage((password) =>
|
||||
{
|
||||
LoginPasswordCell.Entry.Text = password;
|
||||
_deviceActionService.Toast(AppResources.PasswordGenerated);
|
||||
});
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private async void TotpButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var scanPage = new ScanPage((key) =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
if(!string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
LoginTotpCell.Entry.Text = key;
|
||||
_deviceActionService.Toast(AppResources.AuthenticatorKeyAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AuthenticatorKeyReadError, AppResources.Ok);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
|
||||
}
|
||||
|
||||
private void CardCodeButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
CardCodeCell.Entry.InvokeToggleIsPassword();
|
||||
CardCodeCell.Button1.Image =
|
||||
"eye" + (!CardCodeCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
}
|
||||
|
||||
private async void AttachmentsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var page = new ExtendedNavigationPage(new VaultAttachmentsPage(_cipherId));
|
||||
await Navigation.PushModalAsync(page);
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmed = await DisplayAlert(null, AppResources.DoYouReallyWantToDelete, AppResources.Yes,
|
||||
AppResources.No);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
|
||||
var deleteTask = await _cipherService.DeleteAsync(_cipherId);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(deleteTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.ItemDeleted);
|
||||
_googleAnalyticsService.TrackAppEvent("DeletedCipher");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(deleteTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, deleteTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddFieldCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Helpers.AddField(this, FieldsSection);
|
||||
}
|
||||
|
||||
private void AddUriCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var cell = Helpers.MakeUriCell(string.Empty, null, UrisSection, this);
|
||||
if(cell != null)
|
||||
{
|
||||
UrisSection.Insert(UrisSection.Count - 1, cell);
|
||||
cell.InitEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,547 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using System.Threading;
|
||||
using static Bit.App.Models.Page.VaultListPageModel;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultListCiphersPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly ICollectionService _collectionService;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
private readonly bool _favorites = false;
|
||||
private readonly bool _folder = false;
|
||||
private readonly string _folderId = null;
|
||||
private readonly string _collectionId = null;
|
||||
private readonly string _groupingName = null;
|
||||
private readonly string _uri = null;
|
||||
|
||||
public VaultListCiphersPage(bool folder = false, string folderId = null,
|
||||
string collectionId = null, string groupingName = null, bool favorites = false, string uri = null)
|
||||
: base(true)
|
||||
{
|
||||
_folder = folder;
|
||||
_folderId = folderId;
|
||||
_collectionId = collectionId;
|
||||
_favorites = favorites;
|
||||
_groupingName = groupingName;
|
||||
_uri = uri;
|
||||
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_collectionService = Resolver.Resolve<ICollectionService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<Section<GroupingOrCipher>> PresentationSections { get; private set; }
|
||||
= new ExtendedObservableCollection<Section<GroupingOrCipher>>();
|
||||
public Cipher[] Ciphers { get; set; } = new Cipher[] { };
|
||||
public GroupingOrCipher[] Groupings { get; set; } = new GroupingOrCipher[] { };
|
||||
public ExtendedListView ListView { get; set; }
|
||||
public SearchBar Search { get; set; }
|
||||
public ActivityIndicator LoadingIndicator { get; set; }
|
||||
public StackLayout NoDataStackLayout { get; set; }
|
||||
public StackLayout ResultsStackLayout { get; set; }
|
||||
private AddCipherToolBarItem AddCipherItem { get; set; }
|
||||
public ContentView ContentView { get; set; }
|
||||
public Fab Fab { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ListView = new ExtendedListView(ListViewCachingStrategy.RecycleElement)
|
||||
{
|
||||
IsGroupingEnabled = true,
|
||||
ItemsSource = PresentationSections,
|
||||
HasUnevenRows = true,
|
||||
GroupHeaderTemplate = new DataTemplate(() => new SectionHeaderViewCell(
|
||||
nameof(Section<Grouping>.Name), nameof(Section<Grouping>.Count))),
|
||||
GroupShortNameBinding = new Binding(nameof(Section<Grouping>.NameShort)),
|
||||
ItemTemplate = new GroupingOrCipherDataTemplateSelector(this)
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ListView.RowHeight = -1;
|
||||
}
|
||||
|
||||
Search = new SearchBar
|
||||
{
|
||||
Placeholder = AppResources.Search,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)),
|
||||
CancelButtonColor = Color.FromHex("3c8dbc")
|
||||
};
|
||||
// Bug with search bar on android 7, ref https://bugzilla.xamarin.com/show_bug.cgi?id=43975
|
||||
if(Device.RuntimePlatform == Device.Android && _deviceInfoService.Version >= 24)
|
||||
{
|
||||
Search.HeightRequest = 50;
|
||||
}
|
||||
|
||||
var noDataLabel = new Label
|
||||
{
|
||||
Text = _favorites ? AppResources.NoFavorites : AppResources.NoItems,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
if(_folder || !string.IsNullOrWhiteSpace(_folderId))
|
||||
{
|
||||
noDataLabel.Text = AppResources.NoItemsFolder;
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(_collectionId))
|
||||
{
|
||||
noDataLabel.Text = AppResources.NoItemsCollection;
|
||||
}
|
||||
|
||||
NoDataStackLayout = new StackLayout
|
||||
{
|
||||
Children = { noDataLabel },
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Padding = new Thickness(20, 0),
|
||||
Spacing = 20
|
||||
};
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_collectionId) && !_favorites)
|
||||
{
|
||||
NoDataStackLayout.Children.Add(new ExtendedButton
|
||||
{
|
||||
Text = AppResources.AddAnItem,
|
||||
Command = new Command(() => Helpers.AddCipher(this, _folderId)),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
});
|
||||
}
|
||||
|
||||
ResultsStackLayout = new StackLayout
|
||||
{
|
||||
Children = { Search, ListView },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(_groupingName))
|
||||
{
|
||||
Title = _groupingName;
|
||||
}
|
||||
else if(_favorites)
|
||||
{
|
||||
Title = AppResources.Favorites;
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = AppResources.SearchVault;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this));
|
||||
}
|
||||
}
|
||||
|
||||
LoadingIndicator = new ActivityIndicator
|
||||
{
|
||||
IsRunning = true
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform != Device.UWP)
|
||||
{
|
||||
LoadingIndicator.VerticalOptions = LayoutOptions.CenterAndExpand;
|
||||
LoadingIndicator.HorizontalOptions = LayoutOptions.Center;
|
||||
}
|
||||
|
||||
ContentView = new ContentView
|
||||
{
|
||||
Content = LoadingIndicator
|
||||
};
|
||||
|
||||
var fabLayout = new FabLayout(ContentView);
|
||||
if(!string.IsNullOrWhiteSpace(_uri) || _folder || !string.IsNullOrWhiteSpace(_folderId))
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
ListView.BottomPadding = 170;
|
||||
Fab = new Fab(fabLayout, "plus.png", (sender, args) => Helpers.AddCipher(this, _folderId));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCipherItem = new AddCipherToolBarItem(this, _folderId);
|
||||
ToolbarItems.Add(AddCipherItem);
|
||||
}
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
ListView.BottomPadding = 50;
|
||||
}
|
||||
|
||||
Content = fabLayout;
|
||||
}
|
||||
|
||||
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
|
||||
{
|
||||
_filterResultsCancellationTokenSource = FilterResultsBackground(((SearchBar)sender).Text,
|
||||
_filterResultsCancellationTokenSource);
|
||||
}
|
||||
|
||||
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var oldLength = e.OldTextValue?.Length ?? 0;
|
||||
var newLength = e.NewTextValue?.Length ?? 0;
|
||||
if(oldLength < 2 && newLength < 2 && oldLength < newLength)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_filterResultsCancellationTokenSource = FilterResultsBackground(e.NewTextValue,
|
||||
_filterResultsCancellationTokenSource);
|
||||
}
|
||||
|
||||
private CancellationTokenSource FilterResultsBackground(string searchFilter,
|
||||
CancellationTokenSource previousCts)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(searchFilter))
|
||||
{
|
||||
await Task.Delay(300);
|
||||
if(searchFilter != Search.Text)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousCts?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FilterResults(searchFilter, cts.Token);
|
||||
}
|
||||
catch(OperationCanceledException) { }
|
||||
}, cts.Token);
|
||||
|
||||
return cts;
|
||||
}
|
||||
|
||||
private void FilterResults(string searchFilter, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(searchFilter))
|
||||
{
|
||||
LoadSections(Ciphers, Groupings, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchFilter = searchFilter.ToLower();
|
||||
var filteredCiphers = Ciphers
|
||||
.Where(s => s.Name.ToLower().Contains(searchFilter) ||
|
||||
(s.Subtitle?.ToLower().Contains(searchFilter) ?? false) ||
|
||||
(s.LoginUri?.ToLower().Contains(searchFilter) ?? false))
|
||||
.TakeWhile(s => !ct.IsCancellationRequested)
|
||||
.ToArray();
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
LoadSections(filteredCiphers, null, ct);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackExtensionEvent("BackClosed", _uri.StartsWith("http") ? "Website" : "App");
|
||||
_deviceActionService.CloseAutofill();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Subscribe<Application, bool>(Application.Current, "SyncCompleted", (sender, success) =>
|
||||
{
|
||||
if(success)
|
||||
{
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
}
|
||||
});
|
||||
|
||||
AddCipherItem?.InitEvents();
|
||||
ListView.ItemSelected += GroupingOrCipherSelected;
|
||||
Search.TextChanged += SearchBar_TextChanged;
|
||||
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
MessagingCenter.Unsubscribe<Application, bool>(Application.Current, "SyncCompleted");
|
||||
|
||||
AddCipherItem?.Dispose();
|
||||
ListView.ItemSelected -= GroupingOrCipherSelected;
|
||||
Search.TextChanged -= SearchBar_TextChanged;
|
||||
Search.SearchButtonPressed -= SearchBar_SearchButtonPressed;
|
||||
}
|
||||
|
||||
private CancellationTokenSource FetchAndLoadVault()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
if(PresentationSections.Count > 0 && _syncService.SyncInProgress)
|
||||
{
|
||||
return cts;
|
||||
}
|
||||
|
||||
_filterResultsCancellationTokenSource?.Cancel();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
IEnumerable<Models.Cipher> ciphers;
|
||||
if(_folder || !string.IsNullOrWhiteSpace(_folderId))
|
||||
{
|
||||
ciphers = await _cipherService.GetAllByFolderAsync(_folderId);
|
||||
if(!string.IsNullOrWhiteSpace(_folderId))
|
||||
{
|
||||
var folders = await _folderService.GetAllAsync();
|
||||
var fGroupings = folders.Select(f => new Grouping(f, null)).OrderBy(g => g.Name).ToList();
|
||||
var fTreeNodes = Helpers.GetAllNested(fGroupings);
|
||||
var fTreeNode = Helpers.GetTreeNodeObject(fTreeNodes, _folderId);
|
||||
if(fTreeNode.Children?.Any() ?? false)
|
||||
{
|
||||
Groupings = fTreeNode.Children.Select(n => new GroupingOrCipher(n)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(_collectionId))
|
||||
{
|
||||
ciphers = await _cipherService.GetAllByCollectionAsync(_collectionId);
|
||||
|
||||
var collections = await _collectionService.GetAllAsync();
|
||||
var cGroupings = collections.Select(c => new Grouping(c, null)).OrderBy(g => g.Name).ToList();
|
||||
var cTreeNodes = Helpers.GetAllNested(cGroupings);
|
||||
var cTreeNode = Helpers.GetTreeNodeObject(cTreeNodes, _collectionId);
|
||||
if(cTreeNode.Children?.Any() ?? false)
|
||||
{
|
||||
Groupings = cTreeNode.Children.Select(n => new GroupingOrCipher(n)).ToArray();
|
||||
}
|
||||
}
|
||||
else if(_favorites)
|
||||
{
|
||||
ciphers = await _cipherService.GetAllAsync(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ciphers = await _cipherService.GetAllAsync();
|
||||
}
|
||||
|
||||
Ciphers = ciphers
|
||||
.Select(s => new Cipher(s, _appSettingsService))
|
||||
.OrderBy(s =>
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(s.Name))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return s.Name.Length > 0 && Char.IsDigit(s.Name[0]) ? 0 : (Char.IsLetter(s.Name[0]) ? 1 : 2);
|
||||
})
|
||||
.ThenBy(s => s.Name)
|
||||
.ThenBy(s => s.Subtitle)
|
||||
.ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
FilterResults(Search.Text, cts.Token);
|
||||
}
|
||||
catch(OperationCanceledException) { }
|
||||
}, cts.Token);
|
||||
|
||||
return cts;
|
||||
}
|
||||
|
||||
private void LoadSections(Cipher[] ciphers, GroupingOrCipher[] groupings, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var sections = ciphers.GroupBy(c => c.NameGroup.ToUpperInvariant())
|
||||
.Select(g => new Section<GroupingOrCipher>(g.Select(g2 => new GroupingOrCipher(g2)).ToList(), g.Key))
|
||||
.ToList();
|
||||
|
||||
if(groupings?.Any() ?? false)
|
||||
{
|
||||
sections.Insert(0, new Section<GroupingOrCipher>(groupings.ToList(),
|
||||
_folder ? AppResources.Folders : AppResources.Collections));
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
PresentationSections.ResetWithRange(sections);
|
||||
if(PresentationSections.Count > 0 || !string.IsNullOrWhiteSpace(Search.Text))
|
||||
{
|
||||
ContentView.Content = ResultsStackLayout;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_uri) && !_folder && string.IsNullOrWhiteSpace(_folderId) &&
|
||||
string.IsNullOrWhiteSpace(_collectionId) && !_favorites)
|
||||
{
|
||||
Search.Focus();
|
||||
}
|
||||
}
|
||||
else if(_syncService.SyncInProgress)
|
||||
{
|
||||
ContentView.Content = LoadingIndicator;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentView.Content = NoDataStackLayout;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void GroupingOrCipherSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var groupingOrCipher = e.SelectedItem as GroupingOrCipher;
|
||||
if(groupingOrCipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(groupingOrCipher.Grouping != null)
|
||||
{
|
||||
Page page;
|
||||
if(groupingOrCipher.Grouping.Node.Folder)
|
||||
{
|
||||
page = new VaultListCiphersPage(folder: true,
|
||||
folderId: groupingOrCipher.Grouping.Node.Id, groupingName: groupingOrCipher.Grouping.Node.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
page = new VaultListCiphersPage(collectionId: groupingOrCipher.Grouping.Node.Id,
|
||||
groupingName: groupingOrCipher.Grouping.Node.Name);
|
||||
}
|
||||
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
else if(groupingOrCipher.Cipher != null)
|
||||
{
|
||||
var cipher = groupingOrCipher.Cipher;
|
||||
string selection = null;
|
||||
if(!string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
var options = new List<string> { AppResources.Autofill };
|
||||
if(cipher.Type == Enums.CipherType.Login && _connectivity.IsConnected)
|
||||
{
|
||||
options.Add(AppResources.AutofillAndSave);
|
||||
}
|
||||
options.Add(AppResources.View);
|
||||
selection = await DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null,
|
||||
options.ToArray());
|
||||
}
|
||||
|
||||
if(selection == AppResources.View || string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
var page = new VaultViewCipherPage(cipher.Type, cipher.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
else if(selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
||||
{
|
||||
if(selection == AppResources.AutofillAndSave)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
Helpers.AlertNoConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var uris = cipher.CipherModel.Login?.Uris?.ToList();
|
||||
if(uris == null)
|
||||
{
|
||||
uris = new List<Models.LoginUri>();
|
||||
}
|
||||
|
||||
uris.Add(new Models.LoginUri
|
||||
{
|
||||
Uri = _uri.Encrypt(cipher.CipherModel.OrganizationId),
|
||||
Match = null
|
||||
});
|
||||
|
||||
cipher.CipherModel.Login.Uris = uris;
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(cipher.CipherModel);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("AddedLoginUriDuringAutofill");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_deviceInfoService.Version < 21)
|
||||
{
|
||||
Helpers.CipherMoreClickedAsync(this, cipher, !string.IsNullOrWhiteSpace(_uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("AutoFilled",
|
||||
_uri.StartsWith("http") ? "Website" : "App");
|
||||
_deviceActionService.Autofill(cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((ListView)sender).SelectedItem = null;
|
||||
}
|
||||
|
||||
public class GroupingOrCipherDataTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public GroupingOrCipherDataTemplateSelector(VaultListCiphersPage page)
|
||||
{
|
||||
GroupingTemplate = new DataTemplate(() => new VaultGroupingViewCell());
|
||||
CipherTemplate = new DataTemplate(() => new VaultListViewCell(
|
||||
(Cipher c) => Helpers.CipherMoreClickedAsync(page, c, !string.IsNullOrWhiteSpace(page._uri)),
|
||||
true));
|
||||
}
|
||||
|
||||
public DataTemplate GroupingTemplate { get; set; }
|
||||
public DataTemplate CipherTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
if(item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return ((GroupingOrCipher)item).Cipher == null ? GroupingTemplate : CipherTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using static Bit.App.Models.Page.VaultListPageModel;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultListGroupingsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IPushNotificationService _pushNotification;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
|
||||
public VaultListGroupingsPage()
|
||||
: base(true)
|
||||
{
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_collectionService = Resolver.Resolve<ICollectionService>();
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotificationService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<Section<GroupingOrCipher>> PresentationSections { get; private set; }
|
||||
= new ExtendedObservableCollection<Section<GroupingOrCipher>>();
|
||||
public ExtendedListView ListView { get; set; }
|
||||
public StackLayout NoDataStackLayout { get; set; }
|
||||
public ActivityIndicator LoadingIndicator { get; set; }
|
||||
private AddCipherToolBarItem AddCipherItem { get; set; }
|
||||
private SearchToolBarItem SearchItem { get; set; }
|
||||
public ContentView ContentView { get; set; }
|
||||
public Fab Fab { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
SearchItem = new SearchToolBarItem(this);
|
||||
ToolbarItems.Add(SearchItem);
|
||||
|
||||
ListView = new ExtendedListView(ListViewCachingStrategy.RecycleElement)
|
||||
{
|
||||
IsGroupingEnabled = true,
|
||||
ItemsSource = PresentationSections,
|
||||
HasUnevenRows = true,
|
||||
GroupHeaderTemplate = new DataTemplate(() => new SectionHeaderViewCell(
|
||||
nameof(Section<Grouping>.Name), nameof(Section<Grouping>.Count), new Thickness(16, 12))),
|
||||
ItemTemplate = new GroupingOrCipherDataTemplateSelector(this)
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ListView.RowHeight = -1;
|
||||
}
|
||||
|
||||
var noDataLabel = new Label
|
||||
{
|
||||
Text = AppResources.NoItems,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
var addCipherButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.AddAnItem,
|
||||
Command = new Command(() => Helpers.AddCipher(this, null)),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
};
|
||||
|
||||
NoDataStackLayout = new StackLayout
|
||||
{
|
||||
Children = { noDataLabel, addCipherButton },
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Padding = new Thickness(20, 0),
|
||||
Spacing = 20
|
||||
};
|
||||
|
||||
LoadingIndicator = new ActivityIndicator
|
||||
{
|
||||
IsRunning = true
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform != Device.UWP)
|
||||
{
|
||||
LoadingIndicator.VerticalOptions = LayoutOptions.CenterAndExpand;
|
||||
LoadingIndicator.HorizontalOptions = LayoutOptions.Center;
|
||||
}
|
||||
|
||||
ContentView = new ContentView
|
||||
{
|
||||
Content = LoadingIndicator
|
||||
};
|
||||
|
||||
var fabLayout = new FabLayout(ContentView);
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Fab = new Fab(fabLayout, "plus.png", (sender, args) => Helpers.AddCipher(this, null));
|
||||
ListView.BottomPadding = 170;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCipherItem = new AddCipherToolBarItem(this, null);
|
||||
ToolbarItems.Add(AddCipherItem);
|
||||
}
|
||||
|
||||
Content = fabLayout;
|
||||
Title = AppResources.MyVault;
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Subscribe<Application, bool>(Application.Current, "SyncCompleted", (sender, success) =>
|
||||
{
|
||||
if(success)
|
||||
{
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
}
|
||||
});
|
||||
|
||||
ListView.ItemSelected += GroupingOrCipherSelected;
|
||||
AddCipherItem?.InitEvents();
|
||||
SearchItem?.InitEvents();
|
||||
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
|
||||
// Push registration
|
||||
if(_connectivity.IsConnected)
|
||||
{
|
||||
var lastPushRegistration = _settings.GetValueOrDefault(Constants.PushLastRegistrationDate,
|
||||
DateTime.MinValue);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
var pushPromptShow = _settings.GetValueOrDefault(Constants.PushInitialPromptShown, false);
|
||||
if(!pushPromptShow)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.PushInitialPromptShown, true);
|
||||
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
|
||||
AppResources.OkGotIt);
|
||||
}
|
||||
|
||||
if(!pushPromptShow || DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
_pushNotification.Register();
|
||||
}
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android &&
|
||||
DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
_pushNotification.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
MessagingCenter.Unsubscribe<Application, bool>(Application.Current, "SyncCompleted");
|
||||
|
||||
ListView.ItemSelected -= GroupingOrCipherSelected;
|
||||
AddCipherItem?.Dispose();
|
||||
SearchItem?.Dispose();
|
||||
}
|
||||
|
||||
private CancellationTokenSource FetchAndLoadVault()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
_filterResultsCancellationTokenSource?.Cancel();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var sections = new List<Section<GroupingOrCipher>>();
|
||||
var favoriteCipherGroupings = new List<GroupingOrCipher>();
|
||||
var noFolderCipherGroupings = new List<GroupingOrCipher>();
|
||||
var ciphers = await _cipherService.GetAllAsync();
|
||||
var collectionsDict = (await _collectionService.GetAllCipherAssociationsAsync())
|
||||
.GroupBy(c => c.Item2).ToDictionary(g => g.Key, v => v.ToList());
|
||||
|
||||
var folderCounts = new Dictionary<string, int>();
|
||||
foreach(var cipher in ciphers)
|
||||
{
|
||||
if(cipher.Favorite)
|
||||
{
|
||||
favoriteCipherGroupings.Add(new GroupingOrCipher(new Cipher(cipher, _appSettingsService)));
|
||||
}
|
||||
|
||||
if(cipher.FolderId != null)
|
||||
{
|
||||
if(!folderCounts.ContainsKey(cipher.FolderId))
|
||||
{
|
||||
folderCounts.Add(cipher.FolderId, 0);
|
||||
}
|
||||
folderCounts[cipher.FolderId]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
noFolderCipherGroupings.Add(new GroupingOrCipher(new Cipher(cipher, _appSettingsService)));
|
||||
}
|
||||
}
|
||||
|
||||
if(favoriteCipherGroupings.Any())
|
||||
{
|
||||
sections.Add(new Section<GroupingOrCipher>(
|
||||
favoriteCipherGroupings.OrderBy(g => g.Cipher.Name).ThenBy(g => g.Cipher.Subtitle).ToList(),
|
||||
AppResources.Favorites));
|
||||
}
|
||||
|
||||
var folders = await _folderService.GetAllAsync();
|
||||
var collections = await _collectionService.GetAllAsync();
|
||||
|
||||
var fGroupings = folders
|
||||
.Select(f => new Grouping(f, folderCounts.ContainsKey(f.Id) ? folderCounts[f.Id] : 0))
|
||||
.OrderBy(g => g.Name);
|
||||
var folderGroupings = Helpers.GetAllNested(fGroupings)
|
||||
.Select(n => new GroupingOrCipher(n)).ToList();
|
||||
|
||||
if(collections.Any() || noFolderCipherGroupings.Count >= 100)
|
||||
{
|
||||
var noneFolderGrouping = new Grouping(AppResources.FolderNone, noFolderCipherGroupings.Count);
|
||||
var noneFolderNode = new Bit.App.Models.TreeNode<Grouping>(noneFolderGrouping,
|
||||
noneFolderGrouping.Name, null);
|
||||
folderGroupings.Add(new GroupingOrCipher(noneFolderNode));
|
||||
}
|
||||
|
||||
if(folderGroupings.Any())
|
||||
{
|
||||
sections.Add(new Section<GroupingOrCipher>(folderGroupings, AppResources.Folders));
|
||||
}
|
||||
|
||||
var cGroupings = collections
|
||||
.Select(c => new Grouping(c, collectionsDict.ContainsKey(c.Id) ? collectionsDict[c.Id].Count() : 0))
|
||||
.OrderBy(g => g.Name);
|
||||
var collectionGroupings = Helpers.GetAllNested(cGroupings)
|
||||
.Select(n => new GroupingOrCipher(n)).ToList();
|
||||
|
||||
if(collectionGroupings.Any())
|
||||
{
|
||||
sections.Add(new Section<GroupingOrCipher>(collectionGroupings, AppResources.Collections));
|
||||
}
|
||||
else if(noFolderCipherGroupings.Count > 0 && noFolderCipherGroupings.Count < 100)
|
||||
{
|
||||
sections.Add(new Section<GroupingOrCipher>(
|
||||
noFolderCipherGroupings.OrderBy(g => g.Cipher.Name).ThenBy(g => g.Cipher.Subtitle).ToList(),
|
||||
AppResources.FolderNone));
|
||||
}
|
||||
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
PresentationSections.ResetWithRange(sections);
|
||||
|
||||
if(ciphers.Any() || folders.Any())
|
||||
{
|
||||
ContentView.Content = ListView;
|
||||
}
|
||||
else if(_syncService.SyncInProgress)
|
||||
{
|
||||
ContentView.Content = LoadingIndicator;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentView.Content = NoDataStackLayout;
|
||||
}
|
||||
});
|
||||
}, cts.Token);
|
||||
|
||||
return cts;
|
||||
}
|
||||
|
||||
private async void GroupingOrCipherSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var groupingOrCipher = e.SelectedItem as GroupingOrCipher;
|
||||
if(groupingOrCipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(groupingOrCipher.Grouping != null)
|
||||
{
|
||||
Page page;
|
||||
if(groupingOrCipher.Grouping.Node.Folder)
|
||||
{
|
||||
page = new VaultListCiphersPage(folder: true,
|
||||
folderId: groupingOrCipher.Grouping.Node.Id, groupingName: groupingOrCipher.Grouping.Node.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
page = new VaultListCiphersPage(collectionId: groupingOrCipher.Grouping.Node.Id,
|
||||
groupingName: groupingOrCipher.Grouping.Node.Name);
|
||||
}
|
||||
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
else if(groupingOrCipher.Cipher != null)
|
||||
{
|
||||
var page = new VaultViewCipherPage(groupingOrCipher.Cipher.Type, groupingOrCipher.Cipher.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
((ListView)sender).SelectedItem = null;
|
||||
}
|
||||
|
||||
private async void Search()
|
||||
{
|
||||
var page = new ExtendedNavigationPage(new VaultListCiphersPage());
|
||||
await Navigation.PushModalAsync(page);
|
||||
}
|
||||
|
||||
private class SearchToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
public SearchToolBarItem(VaultListGroupingsPage page)
|
||||
: base(() => page.Search())
|
||||
{
|
||||
Text = AppResources.Search;
|
||||
Icon = "search.png";
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupingOrCipherDataTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public GroupingOrCipherDataTemplateSelector(VaultListGroupingsPage page)
|
||||
{
|
||||
GroupingTemplate = new DataTemplate(() => new VaultGroupingViewCell());
|
||||
CipherTemplate = new DataTemplate(() => new VaultListViewCell(
|
||||
(Cipher c) => Helpers.CipherMoreClickedAsync(page, c, false), true));
|
||||
}
|
||||
|
||||
public DataTemplate GroupingTemplate { get; set; }
|
||||
public DataTemplate CipherTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
if(item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return ((GroupingOrCipher)item).Cipher == null ? GroupingTemplate : CipherTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,666 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using Bit.App.Models;
|
||||
using System.Linq;
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class VaultViewCipherPage : ExtendedContentPage
|
||||
{
|
||||
private readonly CipherType _type;
|
||||
private readonly string _cipherId;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private DateTime? _timerStarted = null;
|
||||
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5);
|
||||
|
||||
public VaultViewCipherPage(CipherType type, string cipherId)
|
||||
{
|
||||
_type = type;
|
||||
_cipherId = cipherId;
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public Fab Fab { get; set; }
|
||||
private VaultViewCipherPageModel Model { get; set; } = new VaultViewCipherPageModel();
|
||||
private ExtendedTableView Table { get; set; }
|
||||
private TableSection ItemInformationSection { get; set; }
|
||||
public TableSection UrisSection { get; set; }
|
||||
private TableSection NotesSection { get; set; }
|
||||
private TableSection AttachmentsSection { get; set; }
|
||||
private TableSection FieldsSection { get; set; }
|
||||
public TableSection OtherSection { get; set; }
|
||||
public LabeledValueCell NotesCell { get; set; }
|
||||
private EditCipherToolBarItem EditItem { get; set; }
|
||||
public List<LabeledValueCell> FieldsCells { get; set; }
|
||||
public List<AttachmentViewCell> AttachmentCells { get; set; }
|
||||
|
||||
// Login
|
||||
public LabeledValueCell LoginUsernameCell { get; set; }
|
||||
public LabeledValueCell LoginPasswordCell { get; set; }
|
||||
public LabeledValueCell LoginPasswordRevisionDateCell { get; set; }
|
||||
public LabeledValueCell LoginTotpCodeCell { get; set; }
|
||||
|
||||
// Card
|
||||
public LabeledValueCell CardNameCell { get; set; }
|
||||
public LabeledValueCell CardNumberCell { get; set; }
|
||||
public LabeledValueCell CardBrandCell { get; set; }
|
||||
public LabeledValueCell CardExpCell { get; set; }
|
||||
public LabeledValueCell CardCodeCell { get; set; }
|
||||
|
||||
// Card
|
||||
public LabeledValueCell IdNameCell { get; set; }
|
||||
public LabeledValueCell IdUsernameCell { get; set; }
|
||||
public LabeledValueCell IdCompanyCell { get; set; }
|
||||
public LabeledValueCell IdSsnCell { get; set; }
|
||||
public LabeledValueCell IdPassportNumberCell { get; set; }
|
||||
public LabeledValueCell IdLicenseNumberCell { get; set; }
|
||||
public LabeledValueCell IdEmailCell { get; set; }
|
||||
public LabeledValueCell IdPhoneCell { get; set; }
|
||||
public LabeledValueCell IdAddressCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this));
|
||||
}
|
||||
|
||||
InitProps();
|
||||
|
||||
var fabLayout = new FabLayout(Table);
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Fab = new Fab(fabLayout, "pencil.png", async (sender, args) =>
|
||||
{
|
||||
await Navigation.PushForDeviceAsync(new VaultEditCipherPage(_cipherId));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EditItem = new EditCipherToolBarItem(this, _cipherId);
|
||||
ToolbarItems.Add(EditItem);
|
||||
}
|
||||
|
||||
Content = fabLayout;
|
||||
Title = AppResources.ViewItem;
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
public void InitProps()
|
||||
{
|
||||
// Name
|
||||
var nameCell = new LabeledValueCell(AppResources.Name);
|
||||
nameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Name));
|
||||
nameCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
// Notes
|
||||
NotesCell = new LabeledValueCell();
|
||||
NotesCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Notes));
|
||||
NotesCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
var revisionDateCell = new LabeledValueCell(AppResources.DateUpdated);
|
||||
revisionDateCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.RevisionDate));
|
||||
revisionDateCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
// Username
|
||||
LoginUsernameCell = new LabeledValueCell(AppResources.Username, button1Image: "clipboard.png");
|
||||
LoginUsernameCell.Value.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginUsername));
|
||||
LoginUsernameCell.Button1.Command =
|
||||
new Command(() => Copy(Model.LoginUsername, AppResources.Username));
|
||||
LoginUsernameCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
// Password
|
||||
LoginPasswordCell = new LabeledValueCell(AppResources.Password, button1Image: string.Empty,
|
||||
button2Image: "clipboard.png");
|
||||
LoginPasswordCell.Value.SetBinding(Label.FormattedTextProperty,
|
||||
nameof(VaultViewCipherPageModel.FormattedLoginPassword));
|
||||
LoginPasswordCell.Button1.SetBinding(Button.ImageProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginShowHideImage));
|
||||
LoginPasswordCell.Button1.Command =
|
||||
new Command(() => Model.RevealLoginPassword = !Model.RevealLoginPassword);
|
||||
LoginPasswordCell.Button2.Command =
|
||||
new Command(() => Copy(Model.LoginPassword, AppResources.Password));
|
||||
LoginPasswordCell.Value.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
LoginPasswordCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
// Totp
|
||||
LoginTotpCodeCell = new LabeledValueCell(
|
||||
AppResources.VerificationCodeTotp, button1Image: "clipboard.png", subText: "--");
|
||||
LoginTotpCodeCell.Value.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginTotpCodeFormatted));
|
||||
LoginTotpCodeCell.Value.SetBinding(Label.TextColorProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginTotpColor));
|
||||
LoginTotpCodeCell.Button1.Command =
|
||||
new Command(() => Copy(Model.LoginTotpCode, AppResources.VerificationCodeTotp));
|
||||
LoginTotpCodeCell.Sub.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginTotpSecond));
|
||||
LoginTotpCodeCell.Sub.SetBinding(Label.TextColorProperty,
|
||||
nameof(VaultViewCipherPageModel.LoginTotpColor));
|
||||
LoginTotpCodeCell.Value.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
|
||||
// Password Revision Date
|
||||
LoginPasswordRevisionDateCell = new LabeledValueCell(AppResources.DatePasswordUpdated);
|
||||
LoginPasswordRevisionDateCell.Value.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.PasswordRevisionDate));
|
||||
LoginPasswordRevisionDateCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
break;
|
||||
case CipherType.Card:
|
||||
CardNameCell = new LabeledValueCell(AppResources.CardholderName);
|
||||
CardNameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.CardName));
|
||||
|
||||
CardNumberCell = new LabeledValueCell(AppResources.Number, button1Image: "clipboard.png");
|
||||
CardNumberCell.Button1.Command = new Command(() => Copy(Model.CardNumber, AppResources.Number));
|
||||
CardNumberCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.CardNumber));
|
||||
CardNumberCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
CardBrandCell = new LabeledValueCell(AppResources.Brand);
|
||||
CardBrandCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.CardBrand));
|
||||
|
||||
CardExpCell = new LabeledValueCell(AppResources.Expiration);
|
||||
CardExpCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.CardExp));
|
||||
|
||||
CardCodeCell = new LabeledValueCell(AppResources.SecurityCode, button1Image: string.Empty,
|
||||
button2Image: "clipboard.png");
|
||||
CardCodeCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.MaskedCardCode));
|
||||
CardCodeCell.Button1.SetBinding(Button.ImageProperty,
|
||||
nameof(VaultViewCipherPageModel.CardCodeShowHideImage));
|
||||
CardCodeCell.Button1.Command = new Command(() => Model.RevealCardCode = !Model.RevealCardCode);
|
||||
CardCodeCell.Button2.Command = new Command(() => Copy(Model.CardCode, AppResources.SecurityCode));
|
||||
CardCodeCell.Value.FontFamily =
|
||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
IdNameCell = new LabeledValueCell(AppResources.Name);
|
||||
IdNameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdName));
|
||||
IdNameCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
IdUsernameCell = new LabeledValueCell(AppResources.Username, button1Image: "clipboard.png");
|
||||
IdUsernameCell.Button1.Command = new Command(() => Copy(Model.IdUsername, AppResources.Username));
|
||||
IdUsernameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdUsername));
|
||||
IdUsernameCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
IdCompanyCell = new LabeledValueCell(AppResources.Company);
|
||||
IdCompanyCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdCompany));
|
||||
|
||||
IdSsnCell = new LabeledValueCell(AppResources.SSN);
|
||||
IdSsnCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdSsn));
|
||||
|
||||
IdPassportNumberCell = new LabeledValueCell(AppResources.PassportNumber,
|
||||
button1Image: "clipboard.png");
|
||||
IdPassportNumberCell.Button1.Command =
|
||||
new Command(() => Copy(Model.IdPassportNumber, AppResources.PassportNumber));
|
||||
IdPassportNumberCell.Value.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.IdPassportNumber));
|
||||
IdPassportNumberCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
IdLicenseNumberCell = new LabeledValueCell(AppResources.LicenseNumber,
|
||||
button1Image: "clipboard.png");
|
||||
IdLicenseNumberCell.Button1.Command =
|
||||
new Command(() => Copy(Model.IdLicenseNumber, AppResources.LicenseNumber));
|
||||
IdLicenseNumberCell.Value.SetBinding(Label.TextProperty,
|
||||
nameof(VaultViewCipherPageModel.IdLicenseNumber));
|
||||
IdLicenseNumberCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
|
||||
IdEmailCell = new LabeledValueCell(AppResources.Email);
|
||||
IdEmailCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdEmail));
|
||||
|
||||
IdPhoneCell = new LabeledValueCell(AppResources.Phone);
|
||||
IdPhoneCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdPhone));
|
||||
|
||||
IdAddressCell = new LabeledValueCell(AppResources.Address, button1Image: "clipboard.png");
|
||||
IdAddressCell.Button1.Command = new Command(() => Copy(Model.IdAddress, AppResources.Address));
|
||||
IdAddressCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.IdAddress));
|
||||
IdAddressCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ItemInformationSection = new TableSection(AppResources.ItemInformation)
|
||||
{
|
||||
nameCell
|
||||
};
|
||||
|
||||
NotesSection = new TableSection(AppResources.Notes)
|
||||
{
|
||||
NotesCell
|
||||
};
|
||||
|
||||
OtherSection = new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
revisionDateCell
|
||||
};
|
||||
|
||||
Table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = true,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
ItemInformationSection
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
Table.RowHeight = -1;
|
||||
Table.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Table.BottomPadding = 170;
|
||||
}
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
NotesCell.Tapped += NotesCell_Tapped;
|
||||
EditItem?.InitEvents();
|
||||
|
||||
var cipher = await _cipherService.GetByIdAsync(_cipherId);
|
||||
if(cipher == null)
|
||||
{
|
||||
await Navigation.PopForDeviceAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
Model.Update(cipher);
|
||||
BuildTable(cipher);
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
_timerStarted = null;
|
||||
NotesCell.Tapped -= NotesCell_Tapped;
|
||||
EditItem?.Dispose();
|
||||
CleanupAttachmentCells();
|
||||
}
|
||||
|
||||
private void BuildTable(Cipher cipher)
|
||||
{
|
||||
// URIs
|
||||
if(UrisSection != null && Table.Root.Contains(UrisSection))
|
||||
{
|
||||
Table.Root.Remove(UrisSection);
|
||||
}
|
||||
if(Model.ShowLoginUris)
|
||||
{
|
||||
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||
foreach(var uri in Model.LoginUris)
|
||||
{
|
||||
UrisSection.Add(new UriViewCell(this, uri));
|
||||
}
|
||||
Table.Root.Add(UrisSection);
|
||||
}
|
||||
|
||||
// Notes
|
||||
if(Table.Root.Contains(NotesSection))
|
||||
{
|
||||
Table.Root.Remove(NotesSection);
|
||||
}
|
||||
if(Model.ShowNotes)
|
||||
{
|
||||
Table.Root.Add(NotesSection);
|
||||
}
|
||||
|
||||
// Fields
|
||||
if(Table.Root.Contains(FieldsSection))
|
||||
{
|
||||
Table.Root.Remove(FieldsSection);
|
||||
}
|
||||
if(Model.ShowFields)
|
||||
{
|
||||
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||
foreach(var field in Model.Fields)
|
||||
{
|
||||
FieldViewCell fieldCell;
|
||||
switch(field.Type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
fieldCell = new FieldViewCell(this, field, null);
|
||||
break;
|
||||
case FieldType.Hidden:
|
||||
fieldCell = new FieldViewCell(this, field, null, null);
|
||||
break;
|
||||
case FieldType.Boolean:
|
||||
fieldCell = new FieldViewCell(this, field);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
FieldsSection.Add(fieldCell);
|
||||
}
|
||||
Table.Root.Add(FieldsSection);
|
||||
}
|
||||
|
||||
// Attachments
|
||||
CleanupAttachmentCells();
|
||||
if(Table.Root.Contains(AttachmentsSection))
|
||||
{
|
||||
Table.Root.Remove(AttachmentsSection);
|
||||
}
|
||||
if(Model.ShowAttachments && (Helpers.CanAccessPremium() || cipher.OrganizationId != null))
|
||||
{
|
||||
AttachmentsSection = new TableSection(AppResources.Attachments);
|
||||
AttachmentCells = new List<AttachmentViewCell>();
|
||||
foreach(var attachment in Model.Attachments.OrderBy(s => s.Name))
|
||||
{
|
||||
var attachmentCell = new AttachmentViewCell(attachment, async () =>
|
||||
{
|
||||
await OpenAttachmentAsync(cipher, attachment);
|
||||
});
|
||||
AttachmentCells.Add(attachmentCell);
|
||||
AttachmentsSection.Add(attachmentCell);
|
||||
attachmentCell.InitEvents();
|
||||
}
|
||||
Table.Root.Add(AttachmentsSection);
|
||||
}
|
||||
|
||||
// Other
|
||||
if(Table.Root.Contains(OtherSection))
|
||||
{
|
||||
Table.Root.Remove(OtherSection);
|
||||
}
|
||||
Table.Root.Add(OtherSection);
|
||||
|
||||
// Various types
|
||||
switch(cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
if(OtherSection.Contains(LoginPasswordRevisionDateCell))
|
||||
{
|
||||
OtherSection.Remove(LoginPasswordRevisionDateCell);
|
||||
}
|
||||
if(Model.ShowPasswordRevisionDate)
|
||||
{
|
||||
OtherSection.Add(LoginPasswordRevisionDateCell);
|
||||
}
|
||||
|
||||
AddSectionCell(LoginUsernameCell, Model.ShowLoginUsername);
|
||||
AddSectionCell(LoginPasswordCell, Model.ShowLoginPassword);
|
||||
|
||||
if(ItemInformationSection.Contains(LoginTotpCodeCell))
|
||||
{
|
||||
ItemInformationSection.Remove(LoginTotpCodeCell);
|
||||
}
|
||||
if(cipher.Login?.Totp != null && (Helpers.CanAccessPremium() || cipher.OrganizationUseTotp))
|
||||
{
|
||||
var totpKey = cipher.Login?.Totp.Decrypt(cipher.OrganizationId);
|
||||
if(!string.IsNullOrWhiteSpace(totpKey))
|
||||
{
|
||||
var otpParams = new OtpAuth(totpKey);
|
||||
Model.LoginTotpCode = Crypto.Totp(totpKey);
|
||||
if(!string.IsNullOrWhiteSpace(Model.LoginTotpCode))
|
||||
{
|
||||
TotpTick(totpKey, otpParams.Period);
|
||||
_timerStarted = DateTime.Now;
|
||||
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
|
||||
{
|
||||
if(_timerStarted == null || (DateTime.Now - _timerStarted) > _timerMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TotpTick(totpKey, otpParams.Period);
|
||||
return true;
|
||||
});
|
||||
|
||||
ItemInformationSection.Add(LoginTotpCodeCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CipherType.Card:
|
||||
AddSectionCell(CardNameCell, Model.ShowCardName);
|
||||
AddSectionCell(CardNumberCell, Model.ShowCardNumber);
|
||||
AddSectionCell(CardBrandCell, Model.ShowCardBrand);
|
||||
AddSectionCell(CardExpCell, Model.ShowCardExp);
|
||||
AddSectionCell(CardCodeCell, Model.ShowCardCode);
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
AddSectionCell(IdNameCell, Model.ShowIdName);
|
||||
AddSectionCell(IdUsernameCell, Model.ShowIdUsername);
|
||||
AddSectionCell(IdCompanyCell, Model.ShowIdCompany);
|
||||
AddSectionCell(IdSsnCell, Model.ShowIdSsn);
|
||||
AddSectionCell(IdPassportNumberCell, Model.ShowIdPassportNumber);
|
||||
AddSectionCell(IdLicenseNumberCell, Model.ShowIdLicenseNumber);
|
||||
AddSectionCell(IdEmailCell, Model.ShowIdEmail);
|
||||
AddSectionCell(IdPhoneCell, Model.ShowIdPhone);
|
||||
AddSectionCell(IdAddressCell, Model.ShowIdAddress);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSectionCell(LabeledValueCell cell, bool show)
|
||||
{
|
||||
if(ItemInformationSection.Contains(cell))
|
||||
{
|
||||
ItemInformationSection.Remove(cell);
|
||||
}
|
||||
if(show)
|
||||
{
|
||||
ItemInformationSection.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupAttachmentCells()
|
||||
{
|
||||
if(AttachmentCells != null)
|
||||
{
|
||||
foreach(var cell in AttachmentCells)
|
||||
{
|
||||
cell.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenAttachmentAsync(Cipher cipher, VaultViewCipherPageModel.Attachment attachment)
|
||||
{
|
||||
if(!Helpers.CanAccessPremium() && !cipher.OrganizationUseTotp)
|
||||
{
|
||||
await DisplayAlert(null, AppResources.PremiumRequired, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// 10 MB warning
|
||||
if(attachment.Size >= 10485760 && !(await DisplayAlert(
|
||||
null, string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName),
|
||||
AppResources.Yes, AppResources.No)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_deviceActionService.CanOpenFile(attachment.Name))
|
||||
{
|
||||
await DisplayAlert(null, AppResources.UnableToOpenFile, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
||||
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment.Url, attachment.Key,
|
||||
cipher.OrganizationId);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(data == null)
|
||||
{
|
||||
await DisplayAlert(null, AppResources.UnableToDownloadFile, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_deviceActionService.OpenFile(data, attachment.Id, attachment.Name))
|
||||
{
|
||||
await DisplayAlert(null, AppResources.UnableToOpenFile, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void NotesCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Copy(Model.Notes, AppResources.Notes);
|
||||
}
|
||||
|
||||
private void Copy(string copyText, string alertLabel)
|
||||
{
|
||||
_deviceActionService.CopyToClipboard(copyText);
|
||||
_deviceActionService.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
||||
}
|
||||
|
||||
private void TotpTick(string totpKey, int interval)
|
||||
{
|
||||
var now = Helpers.EpocUtcNow() / 1000;
|
||||
var mod = now % interval;
|
||||
Model.LoginTotpSecond = (int)(interval - mod);
|
||||
|
||||
if(mod == 0)
|
||||
{
|
||||
Model.LoginTotpCode = Crypto.Totp(totpKey);
|
||||
}
|
||||
}
|
||||
|
||||
private class EditCipherToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultViewCipherPage _page;
|
||||
private readonly string _cipherId;
|
||||
|
||||
public EditCipherToolBarItem(VaultViewCipherPage page, string cipherId)
|
||||
{
|
||||
_page = page;
|
||||
_cipherId = cipherId;
|
||||
Text = AppResources.Edit;
|
||||
Icon = Helpers.ToolbarImage("cog.png");
|
||||
ClickAction = async () => await ClickedItem();
|
||||
}
|
||||
|
||||
private async Task ClickedItem()
|
||||
{
|
||||
var page = new VaultEditCipherPage(_cipherId);
|
||||
await _page.Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
public class AttachmentViewCell : LabeledRightDetailCell, IDisposable
|
||||
{
|
||||
private readonly Action _tapped;
|
||||
|
||||
public AttachmentViewCell(VaultViewCipherPageModel.Attachment attachment, Action tappedAction)
|
||||
{
|
||||
_tapped = tappedAction;
|
||||
Label.Text = attachment.Name;
|
||||
Detail.Text = attachment.SizeName;
|
||||
Icon.Source = "download.png";
|
||||
BackgroundColor = Color.White;
|
||||
Detail.MinimumWidthRequest = 100;
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Tapped += AttachmentViewCell_Tapped;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tapped -= AttachmentViewCell_Tapped;
|
||||
}
|
||||
|
||||
private void AttachmentViewCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_tapped?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public class FieldViewCell : LabeledValueCell
|
||||
{
|
||||
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field)
|
||||
: base(field.Name, field.Value == "true" ? "✓" : "-")
|
||||
{
|
||||
Init(page, field, null);
|
||||
}
|
||||
|
||||
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a)
|
||||
: base(field.Name, field.Value, "clipboard.png")
|
||||
{
|
||||
Init(page, field, Button1);
|
||||
}
|
||||
|
||||
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a, bool? b)
|
||||
: base(field.Name, field.MaskedValue, string.Empty, "clipboard.png")
|
||||
{
|
||||
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||
Button1.Image = "eye";
|
||||
Button1.Command = new Command(() =>
|
||||
{
|
||||
field.Revealed = !field.Revealed;
|
||||
if(field.Revealed)
|
||||
{
|
||||
Button1.Image = "eye_slash.png";
|
||||
Value.Text = field.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Button1.Image = "eye.png";
|
||||
Value.Text = field.MaskedValue;
|
||||
}
|
||||
});
|
||||
|
||||
Init(page, field, Button2);
|
||||
}
|
||||
|
||||
private void Init(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, ExtendedButton copyButton)
|
||||
{
|
||||
Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||
if(copyButton != null)
|
||||
{
|
||||
copyButton.Command = new Command(() => page.Copy(field.Value, field.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UriViewCell : LabeledValueCell
|
||||
{
|
||||
public UriViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.LoginUri uri)
|
||||
: base(uri.Label, uri.Host, uri.ShowLaunch ? "launch.png" : null, "clipboard.png")
|
||||
{
|
||||
Value.LineBreakMode = LineBreakMode.TailTruncation;
|
||||
if(Button1 != null)
|
||||
{
|
||||
Button1.Command = new Command(async () =>
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android && uri.IsApp)
|
||||
{
|
||||
await page._deviceActionService.LaunchAppAsync(uri.Value, page);
|
||||
}
|
||||
else if(uri.IsWebsite)
|
||||
{
|
||||
Device.OpenUri(new Uri(uri.Value));
|
||||
}
|
||||
});
|
||||
}
|
||||
Button2.Command = new Command(() => page.Copy(uri.Value, AppResources.URI));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user