mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 19:08:32 +03:00
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:
parent
19c393842f
commit
5803635f44
18 changed files with 119 additions and 329 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
42
src/Core/Pages/Vault/CipherItemViewModel.cs
Normal file
42
src/Core/Pages/Vault/CipherItemViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue