diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs index 308d7f84a..297dc0e4b 100644 --- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs +++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs @@ -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; - } - - } } } diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml index 8acc3c4c6..87dc6abd4 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml @@ -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"> diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs index 111de479a..bea341a46 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs @@ -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); } } } diff --git a/src/Core/Controls/CipherViewCell/CipherViewCellViewModel.cs b/src/Core/Controls/CipherViewCell/CipherViewCellViewModel.cs deleted file mode 100644 index 0ef7754db..000000000 --- a/src/Core/Controls/CipherViewCell/CipherViewCellViewModel.cs +++ /dev/null @@ -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; - } - - } - } -} diff --git a/src/Core/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/Core/Pages/Vault/AutofillCiphersPageViewModel.cs index ae56879b6..a13d61f32 100644 --- a/src/Core/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/Core/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -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(); 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; } diff --git a/src/Core/Pages/Vault/CipherItemViewModel.cs b/src/Core/Pages/Vault/CipherItemViewModel.cs new file mode 100644 index 000000000..1ee850a23 --- /dev/null +++ b/src/Core/Pages/Vault/CipherItemViewModel.cs @@ -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; + } + } + } +} diff --git a/src/Core/Pages/Vault/CipherSelectionPage.xaml b/src/Core/Pages/Vault/CipherSelectionPage.xaml index e9b91bbe2..77cb06bdd 100644 --- a/src/Core/Pages/Vault/CipherSelectionPage.xaml +++ b/src/Core/Pages/Vault/CipherSelectionPage.xaml @@ -28,7 +28,6 @@ - + ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}" /> 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(); } diff --git a/src/Core/Pages/Vault/CiphersPage.xaml b/src/Core/Pages/Vault/CiphersPage.xaml index 8e089d6be..77e4b1e65 100644 --- a/src/Core/Pages/Vault/CiphersPage.xaml +++ b/src/Core/Pages/Vault/CiphersPage.xaml @@ -18,8 +18,6 @@ - - + Margin="{OnPlatform Android='0,5,0,0', iOS='0,15'}"> diff --git a/src/Core/Pages/Vault/CiphersPage.xaml.cs b/src/Core/Pages/Vault/CiphersPage.xaml.cs index 0ae1561a8..ed2227b0f 100644 --- a/src/Core/Pages/Vault/CiphersPage.xaml.cs +++ b/src/Core/Pages/Vault/CiphersPage.xaml.cs @@ -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); } } diff --git a/src/Core/Pages/Vault/CiphersPageViewModel.cs b/src/Core/Pages/Vault/CiphersPageViewModel.cs index b9ba2ba02..44c786fd4 100644 --- a/src/Core/Pages/Vault/CiphersPageViewModel.cs +++ b/src/Core/Pages/Vault/CiphersPageViewModel.cs @@ -42,7 +42,7 @@ namespace Bit.App.Pages _policyService = ServiceContainer.Resolve("policyService"); _logger = ServiceContainer.Resolve("logger"); - Ciphers = new ExtendedObservableCollection(); + Ciphers = new ExtendedObservableCollection(); CipherOptionsCommand = CreateDefaultAsyncRelayCommand(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 Ciphers { get; set; } + public ExtendedObservableCollection Ciphers { get; set; } public Func 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 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; }); diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml b/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml index 530a939c7..f48cab556 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml @@ -32,8 +32,6 @@ - - + x:DataType="pages:CipherItemViewModel"> + ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}" /> - + + Margin="{OnPlatform Android='0,5,0,0', iOS='0,15'}">