1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-02 08:33:17 +00:00

reset for v2

This commit is contained in:
Kyle Spearrin
2019-03-27 16:23:00 -04:00
parent 5a7f106e3e
commit 297beac169
1180 changed files with 0 additions and 126197 deletions

View File

@@ -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;
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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"]
};
}
}
}

View File

@@ -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 };
}
}
}

View File

@@ -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))
};
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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/"));
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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));
}
}
}
}