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:
@@ -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"/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
129
src/App/Pages/Vault/GroupingsPage/GroupingsPageOTPListItem.cs
Normal file
129
src/App/Pages/Vault/GroupingsPage/GroupingsPageOTPListItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
6
src/App/Resources/AppResources.Designer.cs
generated
6
src/App/Resources/AppResources.Designer.cs
generated
@@ -4024,5 +4024,11 @@ namespace Bit.App.Resources {
|
||||
return ResourceManager.GetString("All", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DisplayItemsContainingTOTP {
|
||||
get {
|
||||
return ResourceManager.GetString("DisplayItemsContainingTOTP", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user