Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/App/Pages/Accounts/LoginPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
x:Key="getPasswordHint"
x:Name="_getPasswordHint"
Clicked="Hint_Clicked"
Order="Secondary"/>
Order="Secondary" />
<ToolbarItem Text="{u:I18n RemoveAccount}"
x:Key="removeAccount"
x:Name="_removeAccount"
Clicked="RemoveAccount_Clicked"
Order="Secondary" />

<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
Expand Down
22 changes: 20 additions & 2 deletions src/App/Pages/Accounts/LoginPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public LoginPage(string email = null, AppOptions appOptions = null)
ToolbarItems.Add(_getPasswordHint);
}

if (Device.RuntimePlatform == Device.Android && !_email.IsEnabled)
{
ToolbarItems.Add(_removeAccount);
}

if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
Expand Down Expand Up @@ -105,7 +110,7 @@ private async void LogIn_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.LogInAsync();
await _vm.LogInAsync(true, _email.IsEnabled);
}
}

Expand All @@ -117,6 +122,15 @@ private void Hint_Clicked(object sender, EventArgs e)
}
}

private async void RemoveAccount_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
await _vm.RemoveAccountAsync();
}
}

private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
Expand All @@ -134,12 +148,16 @@ private async void More_Clicked(object sender, System.EventArgs e)
}

var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.GetPasswordHint);
AppResources.Cancel, null, AppResources.GetPasswordHint, AppResources.RemoveAccount);

if (selection == AppResources.GetPasswordHint)
{
await Navigation.PushModalAsync(new NavigationPage(new HintPage()));
}
else if (selection == AppResources.RemoveAccount)
{
await _vm.RemoveAccountAsync();
}
}

private async Task StartTwoFactorAsync()
Expand Down
41 changes: 40 additions & 1 deletion src/App/Pages/Accounts/LoginPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Forms;

namespace Bit.App.Pages
Expand Down Expand Up @@ -101,7 +104,7 @@ public async Task InitAsync()
}
}

public async Task LogInAsync(bool showLoading = true)
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
Expand Down Expand Up @@ -133,6 +136,23 @@ await _platformUtilsService.ShowDialogAsync(
ShowPassword = false;
try
{
if (checkForExistingAccount)
{
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
return;
}
}

if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
Expand Down Expand Up @@ -190,5 +210,24 @@ public void TogglePassword()
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}

public async Task RemoveAccountAsync()
{
try
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.RemoveAccountConfirmation,
AppResources.RemoveAccount, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_messagingService.Send("logout");
}
}
catch (Exception e)
{
#if !FDROID
Crashes.TrackError(e);
#endif
}
}
}
}
1 change: 1 addition & 0 deletions src/App/Pages/Settings/OptionsPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ private async Task SaveThemeAsync()
await _stateService.SetThemeAsync(theme);
ThemeManager.SetTheme(Application.Current.Resources);
_messagingService.Send("updatedTheme");
_stateService.ApplyThemeGloballyAsync(theme).FireAndForget();
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/App/Resources/AppResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/App/Resources/AppResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Are you sure you want to log out?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Master Password</value>
<comment>Label for a master password.</comment>
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Abstractions/IStateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public interface IStateService
Task<string> GetActiveUserIdAsync();
Task SetActiveUserAsync(string userId);
Task<bool> IsAuthenticatedAsync(string userId = null);
Task<string> GetUserIdAsync(string email);
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
Task AddAccountAsync(Account account);
Task LogoutAccountAsync(string userId, bool userInitiated);
Expand Down Expand Up @@ -105,6 +106,7 @@ public interface IStateService
Task SetRememberedOrgIdentifierAsync(string value);
Task<string> GetThemeAsync(string userId = null);
Task SetThemeAsync(string value, string userId = null);
Task ApplyThemeGloballyAsync(string value);
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
Task<bool?> GetPushInitialPromptShownAsync();
Expand Down
54 changes: 48 additions & 6 deletions src/Core/Services/StateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,28 @@ public async Task<bool> IsAuthenticatedAsync(string userId = null)
return await GetAccessTokenAsync(userId) != null;
}

public async Task<string> GetUserIdAsync(string email)
{
if (string.IsNullOrWhiteSpace(email))
{
throw new ArgumentNullException(nameof(email));
}

await CheckStateAsync();
if (_state?.Accounts != null)
{
foreach (var account in _state.Accounts)
{
var accountEmail = account.Value?.Profile?.Email;
if (accountEmail == email)
{
return account.Value.Profile.UserId;
}
}
}
return null;
}

public async Task RefreshAccountViewsAsync(bool allowAddAccountRow)
{
await CheckStateAsync();
Expand Down Expand Up @@ -124,9 +146,9 @@ public async Task AddAccountAsync(Account account)

public async Task LogoutAccountAsync(string userId, bool userInitiated)
{
if (userId == null)
if (string.IsNullOrWhiteSpace(userId))
{
throw new Exception("userId cannot be null");
throw new ArgumentNullException(nameof(userId));
}

await CheckStateAsync();
Expand Down Expand Up @@ -843,6 +865,26 @@ public async Task SetThemeAsync(string value, string userId = null)
await SetValueAsync(key, value, reconciledOptions);
}

public async Task ApplyThemeGloballyAsync(string value)
{
// TODO remove this method (ApplyThemeGlobally) to restore per-account theme support
await CheckStateAsync();
if (_state?.Accounts == null)
{
return;
}
var activeUserId = await GetActiveUserIdAsync();
foreach (var account in _state.Accounts)
{
var uid = account.Value?.Profile?.UserId;
// skip active user (theme already set)
if (uid != null && uid != activeUserId)
{
await SetThemeAsync(value, uid);
}
}
}

public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
Expand Down Expand Up @@ -1225,9 +1267,9 @@ private async Task SaveAccountAsync(Account account, StorageOptions options = nu

private async Task RemoveAccountAsync(string userId, bool userInitiated)
{
if (userId == null)
if (string.IsNullOrWhiteSpace(userId))
{
throw new Exception("userId cannot be null");
throw new ArgumentNullException(nameof(userId));
}

var email = await GetEmailAsync(userId);
Expand Down Expand Up @@ -1470,9 +1512,9 @@ private async Task CheckStateAsync()

private async Task ValidateUserAsync(string userId)
{
if (string.IsNullOrEmpty(userId))
if (string.IsNullOrWhiteSpace(userId))
{
throw new Exception("userId cannot be null or empty");
throw new ArgumentNullException(nameof(userId));
}
await CheckStateAsync();
var accounts = _state?.Accounts;
Expand Down