1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

PS-70 Added toggle to quickly filter TOTP cypher items and show their details, Added new text resource

This commit is contained in:
André Bispo
2022-06-07 18:38:48 +01:00
parent c91277bc43
commit 5e2142fba7
9 changed files with 240 additions and 9 deletions

View File

@@ -10,7 +10,7 @@
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="pages:AuthenticatorPageListItem">
x:DataType="pages:GroupingsPageTOTPListItem">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>

View File

@@ -53,6 +53,14 @@
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
</DataTemplate>
<DataTemplate x:Key="authenticatorTemplate"
x:DataType="pages:GroupingsPageTOTPListItem">
<controls:AuthenticatorViewCell
Cipher="{Binding Cipher}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
TotpSec="{Binding TotpSec}"/>
</DataTemplate>
<DataTemplate x:Key="groupTemplate"
x:DataType="pages:GroupingsPageListItem">
<controls:ExtendedStackLayout Orientation="Horizontal"
@@ -104,6 +112,7 @@
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
HeaderTemplate="{StaticResource headerTemplate}"
CipherTemplate="{StaticResource cipherTemplate}"
AuthenticatorTemplate="{StaticResource authenticatorTemplate}"
GroupTemplate="{StaticResource groupTemplate}" />
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
@@ -131,6 +140,29 @@
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<StackLayout
IsVisible="{Binding ShowTOTPFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,10,0">
<Label
Text="{u:I18n DisplayItemsContainingTOTP}"
LineBreakMode="TailTruncation"
Margin="10,0"
StyleClass="text-md, text-muted"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DisplayItemsContainingTOTP}" />
<Switch
IsToggled="{Binding TOTPFilterEnable}"
StyleClass="box-value"
HorizontalOptions="End"
Toggled="Switch_Toggled"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DisplayItemsContainingTOTP}" />
</StackLayout>
<StackLayout
VerticalOptions="CenterAndExpand"
Padding="20, 0"

View File

@@ -296,5 +296,10 @@ namespace Bit.App.Pages
{
await _accountListOverlay.HideAsync();
}
void Switch_Toggled(System.Object sender, Xamarin.Forms.ToggledEventArgs e)
{
_vm.TOTPFilterCommand.Execute(null);
}
}
}

View File

@@ -2,13 +2,13 @@
namespace Bit.App.Pages
{
public class GroupingsPageListGroup : List<GroupingsPageListItem>
public class GroupingsPageListGroup : List<IGroupingsPageListItem>
{
public GroupingsPageListGroup(string name, int count, bool doUpper = true, bool first = false)
: this(new List<GroupingsPageListItem>(), name, count, doUpper, first)
: this(new List<IGroupingsPageListItem>(), name, count, doUpper, first)
{ }
public GroupingsPageListGroup(List<GroupingsPageListItem> groupItems, string name, int count,
public GroupingsPageListGroup(IEnumerable<IGroupingsPageListItem> groupItems, string name, int count,
bool doUpper = true, bool first = false)
{
AddRange(groupItems);

View File

@@ -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 AuthenticatorTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
@@ -15,10 +16,16 @@ namespace Bit.App.Pages
return HeaderTemplate;
}
if (item is GroupingsPageTOTPListItem)
{
return AuthenticatorTemplate;
}
if (item is GroupingsPageListItem listItem)
{
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
}
return null;
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem
{
//private string _totpCode;
private readonly ITotpService _totpService;
//public CipherView Cipher { get; set; }
//public CipherType? Type { get; set; }
//public int interval { get; set; }
//public long TotpSec { get; set; }
//private DateTime? _totpInterval = null;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public int interval { get; set; }
private string _totpSec;
private string _totpCode;
private string _totpCodeFormatted = "938 928";
public GroupingsPageTOTPListItem(CipherView cipherView, bool websiteIconsEnabled)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public async Task TotpTickAsync()
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % interval;
var totpSec = interval - mod;
TotpSec = totpSec.ToString();
//TotpLow = totpSec < 7;
if (mod == 0)
{
await TotpUpdateCodeAsync();
}
}
public async Task TotpUpdateCodeAsync()
{
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
{
if (_totpCode.Length > 4)
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
}
}
}
}

View File

@@ -31,6 +31,8 @@ namespace Bit.App.Pages
private bool _websiteIconsEnabled;
private bool _syncRefreshing;
private bool _showVaultFilter;
private bool _showTOTPFilter;
private bool _totpFilterEnable;
private string _vaultFilterSelection;
private string _noDataText;
private List<Organization> _organizations;
@@ -81,6 +83,9 @@ namespace Bit.App.Pages
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
TOTPFilterCommand = new AsyncCommand(TOTPFilterAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
@@ -158,6 +163,17 @@ namespace Bit.App.Pages
get => _showVaultFilter;
set => SetProperty(ref _showVaultFilter, value);
}
public bool ShowTOTPFilter
{
get => _showTOTPFilter;
set => SetProperty(ref _showTOTPFilter, value);
}
public bool TOTPFilterEnable
{
get => _totpFilterEnable;
set => SetProperty(ref _totpFilterEnable, value);
}
public string VaultFilterDescription
{
get
@@ -177,6 +193,7 @@ namespace Bit.App.Pages
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
public ICommand VaultFilterCommand { get; }
public ICommand TOTPFilterCommand { get; }
public bool LoadedOnce { get; set; }
public async Task LoadAsync()
@@ -211,13 +228,14 @@ namespace Bit.App.Pages
}
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
_doingLoad = true;
LoadedOnce = true;
ShowNoData = false;
Loading = true;
ShowList = false;
ShowAddCipherButton = !Deleted;
ShowTOTPFilter = Type == CipherType.Login;
var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage;
@@ -297,10 +315,36 @@ namespace Bit.App.Pages
}
if (Ciphers?.Any() ?? false)
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
if (TOTPFilterEnable)
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted && !string.IsNullOrEmpty(c.Login.Totp))
.Select(c => new GroupingsPageTOTPListItem(c, true)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
foreach (var item in ciphersListItems)
{
item.TotpUpdateCodeAsync();
}
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (Type != CipherType.Login)
return false;
foreach (var item in ciphersListItems)
{
item.TotpTickAsync();
}
return true;
});
}
else
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
}
if (ShowNoFolderCipherGroup)
{
@@ -413,6 +457,11 @@ namespace Bit.App.Pages
await LoadAsync();
}
public async Task TOTPFilterAsync()
{
await LoadAsync();
}
public async Task SelectCipherAsync(CipherView cipher)
{
var page = new ViewPage(cipher.Id);

View File

@@ -4024,5 +4024,11 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("All", resourceCulture);
}
}
public static string DisplayItemsContainingTOTP {
get {
return ResourceManager.GetString("DisplayItemsContainingTOTP", resourceCulture);
}
}
}
}

View File

@@ -2248,4 +2248,7 @@
<data name="All" xml:space="preserve">
<value>All</value>
</data>
<data name="DisplayItemsContainingTOTP" xml:space="preserve">
<value>Display items containing TOTP</value>
</data>
</root>