PM-3349 PM-3350 Refactored cipher bindings to have a simpler approach reusing a new CipherItemViewModel to avoid unwanted issues in the app

This commit is contained in:
Federico Maccaroni 2023-12-07 23:59:06 -03:00
parent 19c393842f
commit 5803635f44
No known key found for this signature in database
GPG Key ID: 5D233F8F2B034536
18 changed files with 119 additions and 329 deletions

View File

@ -1,68 +1,10 @@
using System;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Controls
namespace Bit.App.Controls
{
public partial class AuthenticatorViewCell : ExtendedGrid
{
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
public AuthenticatorViewCell()
{
InitializeComponent();
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => GetValue(CipherProperty) as CipherView;
set => SetValue(CipherProperty, value);
}
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
}
public long TotpSec
{
get => (long)GetValue(TotpSecProperty);
set => SetValue(TotpSecProperty, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled ?? false
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
private string _iconImageSource = string.Empty;
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
}
}

View File

@ -3,13 +3,14 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
xmlns:core="clr-namespace:Bit.Core"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:CipherViewCellViewModel"
x:DataType="pages:CipherItemViewModel"
AutomationId="CipherCell">
<Grid.Resources>

View File

@ -1,7 +1,6 @@
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Pages;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
@ -11,12 +10,6 @@ namespace Bit.App.Controls
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(ICommand), typeof(CipherViewCell));
@ -30,51 +23,17 @@ namespace Bit.App.Controls
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
}
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
}
public CipherView Cipher
{
get => GetValue(CipherProperty) as CipherView;
set => SetValue(CipherProperty, value);
}
public ICommand ButtonCommand
{
get => GetValue(ButtonCommandProperty) as ICommand;
set => SetValue(ButtonCommandProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (BindingContext is CipherViewCellViewModel cipherViewCellViewModel && propertyName == WebsiteIconsEnabledProperty.PropertyName)
{
cipherViewCellViewModel.WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
}
}
private void MoreButton_Clicked(object sender, EventArgs e)
{
// WORKAROUND: Added a temporary workaround so that the MoreButton still works in all pages even if it uses GroupingsPageListItem instead of CipherViewCellViewModel.
// Ideally this should be fixed so that even Groupings Page uses CipherViewCellViewModel
CipherView cipherView = null;
if (BindingContext is CipherViewCellViewModel cipherViewCellViewModel)
if (BindingContext is CipherItemViewModel cipherItem)
{
cipherView = cipherViewCellViewModel.Cipher;
}
else if (BindingContext is GroupingsPageListItem groupingsPageListItem)
{
cipherView = groupingsPageListItem.Cipher;
}
if (cipherView != null)
{
ButtonCommand?.Execute(cipherView);
ButtonCommand?.Execute(cipherItem.Cipher);
}
}
}

View File

@ -1,66 +0,0 @@
using System.Globalization;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public class CipherViewToCipherViewCellViewModelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is CipherView cipher)
{
return new CipherViewCellViewModel(cipher, false);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
public class CipherViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public CipherViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
}
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetIconImage(Cipher);
}
return _iconImageSource;
}
}
}
}

View File

@ -1,16 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.Core.Resources.Localization;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -48,7 +43,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);
var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
var matching = ciphers.Item1?.Select(c => new CipherItemViewModel(c, WebsiteIconsEnabled)).ToList();
var hasMatching = matching?.Any() ?? false;
if (matching?.Any() ?? false)
{
@ -57,7 +52,7 @@ namespace Bit.App.Pages
}
var fuzzy = ciphers.Item2?.Select(c =>
new GroupingsPageListItem { Cipher = c, FuzzyAutofill = true }).ToList();
new CipherItemViewModel(c, WebsiteIconsEnabled, true)).ToList();
if (fuzzy?.Any() ?? false)
{
groupedItems.Add(
@ -70,7 +65,7 @@ namespace Bit.App.Pages
protected override async Task SelectCipherAsync(IGroupingsPageListItem item)
{
if (!(item is GroupingsPageListItem listItem) || listItem.Cipher is null)
if (!(item is CipherItemViewModel listItem) || listItem.Cipher is null)
{
return;
}

View File

@ -0,0 +1,42 @@
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class CipherItemViewModel : ExtendedViewModel, IGroupingsPageListItem
{
private readonly bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public CipherItemViewModel(CipherView cipherView, bool websiteIconsEnabled, bool fuzzyAutofill = false)
{
Cipher = cipherView;
_websiteIconsEnabled = websiteIconsEnabled;
FuzzyAutofill = fuzzyAutofill;
}
public CipherView Cipher { get; }
public bool FuzzyAutofill { get; }
public bool ShowIconImage
{
get => _websiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetIconImage(Cipher);
}
return _iconImageSource;
}
}
}
}

View File

@ -28,7 +28,6 @@
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<controls:SelectionChangedEventArgsConverter x:Key="SelectionChangedEventArgsConverter" />
<controls:CipherViewToCipherViewCellViewModelConverter x:Key="cipherViewToCipherViewCellViewModel" />
<ToolbarItem
x:Name="_closeItem"
@ -46,9 +45,7 @@
<DataTemplate x:Key="cipherTemplate"
x:DataType="pages:GroupingsPageListItem">
<controls:CipherViewCell
BindingContext="{Binding Cipher, Converter={StaticResource cipherViewToCipherViewCellViewModel}}"
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}" />
</DataTemplate>
<DataTemplate

View File

@ -1,14 +1,10 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -37,12 +33,10 @@ namespace Bit.App.Pages
InitializeComponent();
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_closeItem);
ToolbarItems.Add(_addItem);
}
#if IOS
ToolbarItems.Add(_closeItem);
ToolbarItems.Add(_addItem);
#endif
SetActivityIndicator(_mainContent);
_vm = BindingContext as CipherSelectionPageViewModel;
@ -88,12 +82,12 @@ namespace Bit.App.Pages
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
MainThread.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
@ -130,11 +124,10 @@ namespace Bit.App.Pages
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
_appOptions.Uri = null;
}
#if ANDROID
_appOptions.Uri = null;
#endif
return base.OnBackButtonPressed();
}

View File

@ -18,8 +18,6 @@
<ContentPage.Resources>
<ResourceDictionary>
<controls:CipherViewToCipherViewCellViewModelConverter x:Key="cipherViewToCipherViewCellViewModel" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<StackLayout
@ -55,7 +53,7 @@
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
Margin="{OnPlatform Android='0,5,0,0', iOS='0,15'}">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"
@ -112,10 +110,7 @@
<!--Binding context is not applied if the cell is the direct child, check for context https://github.com/dotnet/maui/issues/9131-->
<Grid>
<controls:CipherViewCell
BindingContext="{Binding ., Converter={StaticResource cipherViewToCipherViewCellViewModel}}"
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
/>
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>

View File

@ -1,8 +1,8 @@
using Bit.App.Controls;
using Bit.App.Models;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
namespace Bit.App.Pages
@ -120,9 +120,9 @@ namespace Bit.App.Pages
return;
}
if (e.CurrentSelection?.FirstOrDefault() is CipherView cipher)
if (e.CurrentSelection?.FirstOrDefault() is CipherItemViewModel cipherIteemVM)
{
await _vm.SelectCipherAsync(cipher);
await _vm.SelectCipherAsync(cipherIteemVM.Cipher);
}
}

View File

@ -42,7 +42,7 @@ namespace Bit.App.Pages
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>();
Ciphers = new ExtendedObservableCollection<CipherItemViewModel>();
CipherOptionsCommand = CreateDefaultAsyncRelayCommand<CipherView>(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
@ -53,7 +53,7 @@ namespace Bit.App.Pages
public ICommand CipherOptionsCommand { get; }
public ICommand AddCipherCommand { get; }
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
public ExtendedObservableCollection<CipherItemViewModel> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public string AutofillUrl { get; set; }
public bool Deleted { get; set; }
@ -87,12 +87,6 @@ namespace Bit.App.Pages
public bool ShowAddCipher => ShowNoData && _appOptions?.OtpData != null;
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
internal void Prepare(Func<CipherView, bool> filter, bool deleted, AppOptions appOptions)
{
Filter = filter;
@ -105,7 +99,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
await InitVaultFilterAsync(true);
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
_websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true;
PerformSearchIfPopulated();
}
@ -157,7 +151,7 @@ namespace Bit.App.Pages
}
MainThread.BeginInvokeOnMainThread(() =>
{
Ciphers.ResetWithRange(ciphers);
Ciphers.ResetWithRange(ciphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList());
ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0;
ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData;
});

View File

@ -32,8 +32,6 @@
<ContentPage.Resources>
<ResourceDictionary>
<controls:CipherViewToCipherViewCellViewModelConverter x:Key="cipherViewToCipherViewCellViewModel" />
<ToolbarItem x:Name="_syncItem" x:Key="syncItem" Text="{u:I18n Sync}"
Clicked="Sync_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_lockItem" x:Key="lockItem" Text="{u:I18n Lock}"
@ -45,19 +43,14 @@
SemanticProperties.Description="{u:I18n AddItem}" />
<DataTemplate x:Key="cipherTemplate"
x:DataType="pages:GroupingsPageListItem">
x:DataType="pages:CipherItemViewModel">
<controls:CipherViewCell
BindingContext="{Binding Cipher, Converter={StaticResource cipherViewToCipherViewCellViewModel}}"
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, 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}" />
<controls:AuthenticatorViewCell />
</DataTemplate>
<DataTemplate x:Key="groupTemplate"
@ -124,7 +117,7 @@
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
Margin="{OnPlatform Android='0,5,0,0', iOS='0,15'}">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"

View File

@ -215,13 +215,21 @@ namespace Bit.App.Pages
return;
}
if (e.CurrentSelection?.FirstOrDefault() is GroupingsPageTOTPListItem totpItem)
var selection = e.CurrentSelection?.FirstOrDefault();
if (selection is GroupingsPageTOTPListItem totpItem)
{
await _vm.SelectCipherAsync(totpItem.Cipher);
return;
}
if (!(e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item))
if (selection is CipherItemViewModel cipherItemVM)
{
await _vm.SelectCipherAsync(cipherItemVM.Cipher);
return;
}
if (!(selection is GroupingsPageListItem item))
{
return;
}
@ -234,10 +242,6 @@ namespace Bit.App.Pages
{
await _vm.SelectTotpCodesAsync();
}
else if (item.Cipher != null)
{
await _vm.SelectCipherAsync(item.Cipher);
}
else if (item.Folder != null)
{
await _vm.SelectFolderAsync(item.Folder);

View File

@ -1,8 +1,8 @@
using Bit.Core.Resources.Localization;
using Bit.App.Utilities.Automation;
using Bit.App.Utilities.Automation;
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using CollectionView = Bit.Core.Models.View.CollectionView;
namespace Bit.App.Pages
@ -14,10 +14,8 @@ namespace Bit.App.Pages
public FolderView Folder { get; set; }
public CollectionView Collection { get; set; }
public CipherView Cipher { get; set; }
public CipherType? Type { get; set; }
public string ItemCount { get; set; }
public bool FuzzyAutofill { get; set; }
public bool IsTrash { get; set; }
public bool IsTotpCode { get; set; }

View File

@ -1,7 +1,4 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
namespace Bit.App.Pages
{
public class GroupingsPageListItemSelector : DataTemplateSelector
{
@ -12,19 +9,24 @@ namespace Bit.App.Pages
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is GroupingsPageHeaderListItem)
{
return HeaderTemplate;
}
if (item is GroupingsPageTOTPListItem)
{
return AuthenticatorTemplate;
}
if (item is GroupingsPageListItem listItem)
if (item is CipherItemViewModel)
{
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
return CipherTemplate;
}
if (item is GroupingsPageHeaderListItem)
{
return HeaderTemplate;
}
if (item is GroupingsPageListItem)
{
return GroupTemplate;
}
return null;

View File

@ -1,56 +1,34 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem
public class GroupingsPageTOTPListItem : CipherItemViewModel, IGroupingsPageListItem
{
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
private double _progress;
private string _totpSec;
private string _totpCodeFormatted;
private TotpHelper _totpTickHelper;
private readonly TotpHelper _totpTickHelper;
public GroupingsPageTOTPListItem(CipherView cipherView, bool websiteIconsEnabled)
:base(cipherView, websiteIconsEnabled)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>();
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
CopyCommand = CreateDefaultAsyncRelayCommand(CopyToClipboardAsync,
onException: ex => _logger.Value.Exception(ex),
onException: _logger.Value.Exception,
allowsMultipleExecutions: false);
_totpTickHelper = new TotpHelper(cipherView);
}
public AsyncRelayCommand CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
@ -72,31 +50,6 @@ namespace Bit.App.Pages
get => _progress;
set => SetProperty(ref _progress, 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 string TotpCodeFormattedStart => TotpCodeFormatted?.Split(' ')[0];
@ -105,7 +58,7 @@ namespace Bit.App.Pages
public async Task CopyToClipboardAsync()
{
await _clipboardService.CopyTextAsync(TotpCodeFormatted?.Replace(" ", string.Empty));
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
_platformUtilsService.Value.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
}
public async Task TotpTickAsync()

View File

@ -25,7 +25,6 @@ namespace Bit.App.Pages
private bool _websiteIconsEnabled;
private bool _syncRefreshing;
private bool _showTotpFilter;
private bool _totpFilterEnable;
private string _noDataText;
private List<CipherView> _allCiphers;
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
@ -150,11 +149,6 @@ namespace Bit.App.Pages
get => _showList;
set => SetProperty(ref _showList, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowTotp
{
get => _showTotpFilter;
@ -206,7 +200,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage;
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
_websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true;
try
{
await LoadDataAsync();
@ -225,7 +219,7 @@ namespace Bit.App.Pages
var hasFavorites = FavoriteCiphers?.Any() ?? false;
if (hasFavorites)
{
var favListItems = FavoriteCiphers.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
var favListItems = FavoriteCiphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList();
groupedItems.Add(new GroupingsPageListGroup(favListItems, AppResources.Favorites,
favListItems.Count, uppercaseGroupNames, true));
}
@ -282,7 +276,7 @@ namespace Bit.App.Pages
if (ShowNoFolderCipherGroup)
{
var noFolderCiphersListItems = NoFolderCiphers.Select(
c => new GroupingsPageListItem { Cipher = c }).ToList();
c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList();
groupedItems.Add(new GroupingsPageListGroup(noFolderCiphersListItems, AppResources.FolderNone,
noFolderCiphersListItems.Count, uppercaseGroupNames, false));
}
@ -399,7 +393,7 @@ namespace Bit.App.Pages
_totpTickCts?.Cancel();
if (ShowTotp)
{
var ciphersListItems = TOTPCiphers.Select(c => new GroupingsPageTOTPListItem(c, true)).ToList();
var ciphersListItems = TOTPCiphers.Select(c => new GroupingsPageTOTPListItem(c, _websiteIconsEnabled)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
@ -408,7 +402,7 @@ namespace Bit.App.Pages
else
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}

View File

@ -1,13 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -42,7 +36,7 @@ namespace Bit.App.Pages
if (ciphers?.Any() ?? false)
{
groupedItems.Add(
new GroupingsPageListGroup(ciphers.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(),
new GroupingsPageListGroup(ciphers.Select(c => new CipherItemViewModel(c, WebsiteIconsEnabled)).ToList(),
AppResources.MatchingItems,
ciphers.Count,
false,
@ -54,7 +48,7 @@ namespace Bit.App.Pages
protected override async Task SelectCipherAsync(IGroupingsPageListItem item)
{
if (!(item is GroupingsPageListItem listItem) || listItem.Cipher is null)
if (!(item is CipherItemViewModel listItem) || listItem.Cipher is null)
{
return;
}