mirror of
https://github.com/bitwarden/mobile
synced 2025-12-15 07:43:37 +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"
|
StyleClass="list-row, list-row-platform"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="0"
|
ColumnSpacing="0"
|
||||||
x:DataType="pages:AuthenticatorPageListItem">
|
x:DataType="pages:GroupingsPageTOTPListItem">
|
||||||
|
|
||||||
<Grid.Resources>
|
<Grid.Resources>
|
||||||
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||||
|
|||||||
@@ -53,6 +53,14 @@
|
|||||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
||||||
</DataTemplate>
|
</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"
|
<DataTemplate x:Key="groupTemplate"
|
||||||
x:DataType="pages:GroupingsPageListItem">
|
x:DataType="pages:GroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
@@ -104,6 +112,7 @@
|
|||||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||||
HeaderTemplate="{StaticResource headerTemplate}"
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
CipherTemplate="{StaticResource cipherTemplate}"
|
CipherTemplate="{StaticResource cipherTemplate}"
|
||||||
|
AuthenticatorTemplate="{StaticResource authenticatorTemplate}"
|
||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
@@ -131,6 +140,29 @@
|
|||||||
AutomationProperties.Name="{u:I18n Filter}" />
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
</StackLayout>
|
</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
|
<StackLayout
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Padding="20, 0"
|
Padding="20, 0"
|
||||||
|
|||||||
@@ -296,5 +296,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _accountListOverlay.HideAsync();
|
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
|
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)
|
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)
|
bool doUpper = true, bool first = false)
|
||||||
{
|
{
|
||||||
AddRange(groupItems);
|
AddRange(groupItems);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace Bit.App.Pages
|
|||||||
public DataTemplate HeaderTemplate { get; set; }
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate CipherTemplate { get; set; }
|
public DataTemplate CipherTemplate { get; set; }
|
||||||
public DataTemplate GroupTemplate { get; set; }
|
public DataTemplate GroupTemplate { get; set; }
|
||||||
|
public DataTemplate AuthenticatorTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
@@ -15,10 +16,16 @@ namespace Bit.App.Pages
|
|||||||
return HeaderTemplate;
|
return HeaderTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is GroupingsPageTOTPListItem)
|
||||||
|
{
|
||||||
|
return AuthenticatorTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
if (item is GroupingsPageListItem listItem)
|
if (item is GroupingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
|
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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 _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
private bool _showVaultFilter;
|
private bool _showVaultFilter;
|
||||||
|
private bool _showTOTPFilter;
|
||||||
|
private bool _totpFilterEnable;
|
||||||
private string _vaultFilterSelection;
|
private string _vaultFilterSelection;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
private List<Organization> _organizations;
|
private List<Organization> _organizations;
|
||||||
@@ -81,6 +83,9 @@ namespace Bit.App.Pages
|
|||||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
onException: ex => _logger.Exception(ex),
|
onException: ex => _logger.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
TOTPFilterCommand = new AsyncCommand(TOTPFilterAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
@@ -158,6 +163,17 @@ namespace Bit.App.Pages
|
|||||||
get => _showVaultFilter;
|
get => _showVaultFilter;
|
||||||
set => SetProperty(ref _showVaultFilter, value);
|
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
|
public string VaultFilterDescription
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -177,6 +193,7 @@ namespace Bit.App.Pages
|
|||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
public ICommand VaultFilterCommand { get; }
|
public ICommand VaultFilterCommand { get; }
|
||||||
|
public ICommand TOTPFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -211,13 +228,14 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
}
|
}
|
||||||
|
|
||||||
_doingLoad = true;
|
_doingLoad = true;
|
||||||
LoadedOnce = true;
|
LoadedOnce = true;
|
||||||
ShowNoData = false;
|
ShowNoData = false;
|
||||||
Loading = true;
|
Loading = true;
|
||||||
ShowList = false;
|
ShowList = false;
|
||||||
ShowAddCipherButton = !Deleted;
|
ShowAddCipherButton = !Deleted;
|
||||||
|
ShowTOTPFilter = Type == CipherType.Login;
|
||||||
|
|
||||||
var groupedItems = new List<GroupingsPageListGroup>();
|
var groupedItems = new List<GroupingsPageListGroup>();
|
||||||
var page = Page as GroupingsPage;
|
var page = Page as GroupingsPage;
|
||||||
|
|
||||||
@@ -297,10 +315,36 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (Ciphers?.Any() ?? false)
|
if (Ciphers?.Any() ?? false)
|
||||||
{
|
{
|
||||||
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
|
if (TOTPFilterEnable)
|
||||||
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
|
{
|
||||||
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted && !string.IsNullOrEmpty(c.Login.Totp))
|
||||||
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
.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)
|
if (ShowNoFolderCipherGroup)
|
||||||
{
|
{
|
||||||
@@ -413,6 +457,11 @@ namespace Bit.App.Pages
|
|||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task TOTPFilterAsync()
|
||||||
|
{
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
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);
|
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">
|
<data name="All" xml:space="preserve">
|
||||||
<value>All</value>
|
<value>All</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DisplayItemsContainingTOTP" xml:space="preserve">
|
||||||
|
<value>Display items containing TOTP</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
Reference in New Issue
Block a user