mirror of
https://github.com/bitwarden/mobile
synced 2025-12-18 17:23:18 +00:00
EC-295 Added swipe to copy on vault login items
This commit is contained in:
@@ -20,6 +20,7 @@ using System.Net;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.App.Utilities.Helpers;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
@@ -71,6 +72,15 @@ namespace Bit.Droid
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
|
||||
var cipherHelper = new CipherHelper(
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IEventService>("eventService"),
|
||||
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
||||
ServiceContainer.Resolve<IClipboardService>("clipboardService"),
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService")
|
||||
);
|
||||
ServiceContainer.Register<ICipherHelper>("cipherHelper", cipherHelper);
|
||||
}
|
||||
#if !FDROID
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
<Folder Include="Utilities\AccountManagement\" />
|
||||
<Folder Include="Utilities\Helpers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -422,5 +423,6 @@
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||
<None Remove="Utilities\AccountManagement\" />
|
||||
<None Remove="Utilities\Helpers\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
20
src/App/Controls/IconFontImageSource.cs
Normal file
20
src/App/Controls/IconFontImageSource.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class IconFontImageSource : FontImageSource
|
||||
{
|
||||
public IconFontImageSource()
|
||||
{
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "bwi-font";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "bwi-font.ttf#bwi-font";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.Helpers;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
@@ -25,6 +26,7 @@ namespace Bit.App.Pages
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ICipherHelper _cipherHelper;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private bool _showNoData;
|
||||
@@ -40,6 +42,7 @@ namespace Bit.App.Pages
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_cipherHelper = ServiceContainer.Resolve<ICipherHelper>("cipherHelper");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||
@@ -180,7 +183,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (_deviceActionService.SystemMajorVersion() < 21)
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
await _cipherHelper.ShowCipherOptionsAsync(Page, cipher);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -241,7 +244,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
await _cipherHelper.ShowCipherOptionsAsync(Page, cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities.Helpers;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -25,6 +25,7 @@ namespace Bit.App.Pages
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ICipherHelper _cipherHelper;
|
||||
private CancellationTokenSource _searchCancellationTokenSource;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -42,6 +43,7 @@ namespace Bit.App.Pages
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_cipherHelper = ServiceContainer.Resolve<ICipherHelper>("cipherHelper");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||
@@ -193,7 +195,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (_deviceActionService.SystemMajorVersion() < 21)
|
||||
{
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
await _cipherHelper.ShowCipherOptionsAsync(Page, cipher);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -219,7 +221,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
await _cipherHelper.ShowCipherOptionsAsync(Page, cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,45 @@
|
||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="loginCipherTemplate"
|
||||
x:DataType="pages:GroupingsPageListItem">
|
||||
<SwipeView Threshold="{Binding Source={x:Reference _page}, Path=SwipeThreshold}">
|
||||
<SwipeView.LeftItems>
|
||||
<SwipeItems Mode="Execute">
|
||||
<SwipeItem Text="{u:I18n CopyUsername}"
|
||||
BackgroundColor="{DynamicResource PrimaryColor}"
|
||||
Command="{Binding Source={x:Reference _page}, Path=BindingContext.CopyUsernameItemCommand}"
|
||||
CommandParameter="{Binding .}">
|
||||
<SwipeItem.IconImageSource>
|
||||
<controls:IconFontImageSource
|
||||
Glyph="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Color="{DynamicResource TextColor}"/>
|
||||
</SwipeItem.IconImageSource>
|
||||
</SwipeItem>
|
||||
</SwipeItems>
|
||||
</SwipeView.LeftItems>
|
||||
<SwipeView.RightItems>
|
||||
<SwipeItems Mode="Execute">
|
||||
<SwipeItem Text="{u:I18n CopyPassword}"
|
||||
BackgroundColor="{DynamicResource PrimaryColor}"
|
||||
Command="{Binding Source={x:Reference _page}, Path=BindingContext.CopyPasswordItemCommand}"
|
||||
CommandParameter="{Binding .}">
|
||||
<SwipeItem.IconImageSource>
|
||||
<controls:IconFontImageSource
|
||||
Glyph="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Color="{DynamicResource TextColor}"/>
|
||||
</SwipeItem.IconImageSource>
|
||||
</SwipeItem>
|
||||
</SwipeItems>
|
||||
</SwipeView.RightItems>
|
||||
<controls:CipherViewCell
|
||||
Cipher="{Binding Cipher}"
|
||||
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
|
||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
|
||||
BackgroundColor="{DynamicResource BackgroundColor}"/>
|
||||
</SwipeView>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="groupTemplate"
|
||||
x:DataType="pages:GroupingsPageListItem">
|
||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||
@@ -104,7 +143,8 @@
|
||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||
HeaderTemplate="{StaticResource headerTemplate}"
|
||||
CipherTemplate="{StaticResource cipherTemplate}"
|
||||
GroupTemplate="{StaticResource groupTemplate}" />
|
||||
GroupTemplate="{StaticResource groupTemplate}"
|
||||
LoginCipherTemplate="{StaticResource loginCipherTemplate}" />
|
||||
|
||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||
<StackLayout
|
||||
|
||||
@@ -26,6 +26,19 @@ namespace Bit.App.Pages
|
||||
|
||||
private PreviousPageInfo _previousPage;
|
||||
|
||||
double _swipeThreshold;
|
||||
public double SwipeThreshold
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_swipeThreshold == default)
|
||||
{
|
||||
_swipeThreshold = (Content?.Width ?? 500) / 2;
|
||||
}
|
||||
return _swipeThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
||||
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
|
||||
PreviousPageInfo previousPage = null, bool deleted = false)
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Bit.App.Pages
|
||||
public DataTemplate HeaderTemplate { get; set; }
|
||||
public DataTemplate CipherTemplate { get; set; }
|
||||
public DataTemplate GroupTemplate { get; set; }
|
||||
public DataTemplate LoginCipherTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
@@ -17,7 +18,14 @@ namespace Bit.App.Pages
|
||||
|
||||
if (item is GroupingsPageListItem listItem)
|
||||
{
|
||||
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
|
||||
if (listItem.Cipher is null)
|
||||
{
|
||||
return GroupTemplate;
|
||||
}
|
||||
|
||||
return listItem.Cipher.Type == Core.Enums.CipherType.Login && LoginCipherTemplate != null
|
||||
? LoginCipherTemplate
|
||||
: CipherTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.Helpers;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
@@ -45,9 +46,9 @@ namespace Bit.App.Pages
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ICipherHelper _cipherHelper;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GroupingsPageViewModel()
|
||||
@@ -61,9 +62,9 @@ namespace Bit.App.Pages
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_cipherHelper = ServiceContainer.Resolve<ICipherHelper>("cipherHelper");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
Loading = true;
|
||||
@@ -74,6 +75,8 @@ namespace Bit.App.Pages
|
||||
await LoadAsync();
|
||||
});
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
CopyUsernameItemCommand = new AsyncCommand<IGroupingsPageListItem>(CopyUsernameItemAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||
CopyPasswordItemCommand = new AsyncCommand<IGroupingsPageListItem>(CopyPasswordItemAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||
|
||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||
{
|
||||
@@ -157,6 +160,9 @@ namespace Bit.App.Pages
|
||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||
public Command RefreshCommand { get; set; }
|
||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||
public IAsyncCommand<IGroupingsPageListItem> CopyUsernameItemCommand { get; }
|
||||
public IAsyncCommand<IGroupingsPageListItem> CopyPasswordItemCommand { get; }
|
||||
|
||||
public bool LoadedOnce { get; set; }
|
||||
|
||||
public async Task LoadAsync()
|
||||
@@ -628,7 +634,23 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
await _cipherHelper.ShowCipherOptionsAsync(Page, cipher);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyUsernameItemAsync(IGroupingsPageListItem listItem)
|
||||
{
|
||||
if (listItem is GroupingsPageListItem groupPageListItem && groupPageListItem.Cipher?.Type == CipherType.Login)
|
||||
{
|
||||
await _cipherHelper.CopyUsernameAsync(groupPageListItem.Cipher);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyPasswordItemAsync(IGroupingsPageListItem listItem)
|
||||
{
|
||||
if (listItem is GroupingsPageListItem groupPageListItem && groupPageListItem.Cipher?.Type == CipherType.Login)
|
||||
{
|
||||
await _cipherHelper.CopyPasswordAsync(groupPageListItem.Cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,132 +24,6 @@ namespace Bit.App.Utilities
|
||||
{
|
||||
public static class AppHelpers
|
||||
{
|
||||
public static async Task<string> CipherListOptions(ContentPage page, CipherView cipher, IPasswordRepromptService passwordRepromptService)
|
||||
{
|
||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
|
||||
var options = new List<string> { AppResources.View };
|
||||
if (!cipher.IsDeleted)
|
||||
{
|
||||
options.Add(AppResources.Edit);
|
||||
}
|
||||
if (cipher.Type == Core.Enums.CipherType.Login)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Username))
|
||||
{
|
||||
options.Add(AppResources.CopyUsername);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Password) && cipher.ViewPassword)
|
||||
{
|
||||
options.Add(AppResources.CopyPassword);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Totp))
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var canAccessPremium = await stateService.CanAccessPremiumAsync();
|
||||
if (canAccessPremium || cipher.OrganizationUseTotp)
|
||||
{
|
||||
options.Add(AppResources.CopyTotp);
|
||||
}
|
||||
}
|
||||
if (cipher.Login.CanLaunch)
|
||||
{
|
||||
options.Add(AppResources.Launch);
|
||||
}
|
||||
}
|
||||
else if (cipher.Type == Core.Enums.CipherType.Card)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Card.Number))
|
||||
{
|
||||
options.Add(AppResources.CopyNumber);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Card.Code))
|
||||
{
|
||||
options.Add(AppResources.CopySecurityCode);
|
||||
}
|
||||
}
|
||||
else if (cipher.Type == Core.Enums.CipherType.SecureNote)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Notes))
|
||||
{
|
||||
options.Add(AppResources.CopyNotes);
|
||||
}
|
||||
}
|
||||
var selection = await page.DisplayActionSheet(cipher.Name, AppResources.Cancel, null, options.ToArray());
|
||||
if (await vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
platformUtilsService.ShowToast("info", null, AppResources.VaultIsLocked);
|
||||
}
|
||||
else if (selection == AppResources.View)
|
||||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new ViewPage(cipher.Id)));
|
||||
}
|
||||
else if (selection == AppResources.Edit)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new AddEditPage(cipher.Id)));
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyUsername)
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||
}
|
||||
else if (selection == AppResources.CopyPassword)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyTotp)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
await clipboardService.CopyTextAsync(totp);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.Launch)
|
||||
{
|
||||
platformUtilsService.LaunchUri(cipher.Login.LaunchUri);
|
||||
}
|
||||
else if (selection == AppResources.CopyNumber)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopySecurityCode)
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyNotes)
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Notes);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
public static async Task<string> SendListOptions(ContentPage page, SendView send)
|
||||
{
|
||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
|
||||
178
src/App/Utilities/Helpers/CipherHelper.cs
Normal file
178
src/App/Utilities/Helpers/CipherHelper.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities.Helpers
|
||||
{
|
||||
public class CipherHelper : ICipherHelper
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
|
||||
public CipherHelper(IPlatformUtilsService platformUtilsService,
|
||||
IEventService eventService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IClipboardService clipboardService,
|
||||
IPasswordRepromptService passwordRepromptService)
|
||||
{
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_eventService = eventService;
|
||||
_vaultTimeoutService = vaultTimeoutService;
|
||||
_clipboardService = clipboardService;
|
||||
_passwordRepromptService = passwordRepromptService;
|
||||
}
|
||||
|
||||
public async Task<string> ShowCipherOptionsAsync(Page page, CipherView cipher)
|
||||
{
|
||||
var selection = await page.DisplayActionSheet(cipher.Name, AppResources.Cancel, null, await GetCipherOptionsAsync(cipher));
|
||||
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
_platformUtilsService.ShowToast("info", null, AppResources.VaultIsLocked);
|
||||
}
|
||||
else if (selection == AppResources.View)
|
||||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new ViewPage(cipher.Id)));
|
||||
}
|
||||
else if (selection == AppResources.Edit)
|
||||
{
|
||||
if (await RepromptPasswordIfNeededAsync(cipher))
|
||||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new AddEditPage(cipher.Id)));
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyUsername)
|
||||
{
|
||||
await CopyUsernameAsync(cipher);
|
||||
}
|
||||
else if (selection == AppResources.CopyPassword)
|
||||
{
|
||||
await CopyPasswordAsync(cipher);
|
||||
}
|
||||
else if (selection == AppResources.CopyTotp)
|
||||
{
|
||||
if (await RepromptPasswordIfNeededAsync(cipher))
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(totp);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.Launch)
|
||||
{
|
||||
_platformUtilsService.LaunchUri(cipher.Login.LaunchUri);
|
||||
}
|
||||
else if (selection == AppResources.CopyNumber)
|
||||
{
|
||||
if (await RepromptPasswordIfNeededAsync(cipher))
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopySecurityCode)
|
||||
{
|
||||
if (await RepromptPasswordIfNeededAsync(cipher))
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||
_eventService.CollectAsync(EventType.Cipher_ClientCopiedCardCode, cipher.Id).FireAndForget();
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyNotes)
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(cipher.Notes);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
public async Task CopyUsernameAsync(CipherView cipher)
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(cipher.Login.Username);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||
}
|
||||
|
||||
public async Task CopyPasswordAsync(CipherView cipher)
|
||||
{
|
||||
if (await RepromptPasswordIfNeededAsync(cipher))
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
_eventService.CollectAsync(EventType.Cipher_ClientCopiedPassword, cipher.Id).FireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string[]> GetCipherOptionsAsync(CipherView cipher)
|
||||
{
|
||||
var options = new List<string> { AppResources.View };
|
||||
if (!cipher.IsDeleted)
|
||||
{
|
||||
options.Add(AppResources.Edit);
|
||||
}
|
||||
if (cipher.Type == CipherType.Login)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Username))
|
||||
{
|
||||
options.Add(AppResources.CopyUsername);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Password) && cipher.ViewPassword)
|
||||
{
|
||||
options.Add(AppResources.CopyPassword);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Login.Totp))
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var canAccessPremium = await stateService.CanAccessPremiumAsync();
|
||||
if (canAccessPremium || cipher.OrganizationUseTotp)
|
||||
{
|
||||
options.Add(AppResources.CopyTotp);
|
||||
}
|
||||
}
|
||||
if (cipher.Login.CanLaunch)
|
||||
{
|
||||
options.Add(AppResources.Launch);
|
||||
}
|
||||
}
|
||||
else if (cipher.Type == CipherType.Card)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Card.Number))
|
||||
{
|
||||
options.Add(AppResources.CopyNumber);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Card.Code))
|
||||
{
|
||||
options.Add(AppResources.CopySecurityCode);
|
||||
}
|
||||
}
|
||||
else if (cipher.Type == CipherType.SecureNote)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher.Notes))
|
||||
{
|
||||
options.Add(AppResources.CopyNotes);
|
||||
}
|
||||
}
|
||||
|
||||
return options.ToArray();
|
||||
}
|
||||
|
||||
private async Task<bool> RepromptPasswordIfNeededAsync(CipherView cipher)
|
||||
{
|
||||
return cipher.Reprompt == CipherRepromptType.None || await _passwordRepromptService.ShowPasswordPromptAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/App/Utilities/Helpers/ICipherHelper.cs
Normal file
13
src/App/Utilities/Helpers/ICipherHelper.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities.Helpers
|
||||
{
|
||||
public interface ICipherHelper
|
||||
{
|
||||
Task<string> ShowCipherOptionsAsync(Page page, CipherView cipher);
|
||||
Task CopyUsernameAsync(CipherView cipher);
|
||||
Task CopyPasswordAsync(CipherView cipher);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.App.Utilities.Helpers;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -182,6 +183,15 @@ namespace Bit.iOS.Core.Utilities
|
||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
|
||||
var cipherHelper = new CipherHelper(
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IEventService>("eventService"),
|
||||
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
||||
ServiceContainer.Resolve<IClipboardService>("clipboardService"),
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService")
|
||||
);
|
||||
ServiceContainer.Register<ICipherHelper>("cipherHelper", cipherHelper);
|
||||
|
||||
if (postBootstrapFunc != null)
|
||||
{
|
||||
await postBootstrapFunc.Invoke();
|
||||
|
||||
Reference in New Issue
Block a user