From 583fd6ba1eab719b8537c9f103751819b94b8502 Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Wed, 27 Mar 2024 21:10:33 +0000 Subject: [PATCH 1/3] Removed FFImageLoading dependencies and replaced the CachedImage with the MAUI Image Control Added UriImageSource for MAUI Image to support Cached icons for 90 days Removed Stubs used to avoid issues with Unit Tests and FFImageLoading --- src/App/App.csproj | 1 - .../AuthenticatorViewCell.xaml | 7 ++-- .../AuthenticatorViewCell.xaml.cs | 18 +++++++- src/Core/Controls/CachedImage.cs | 42 ------------------- .../CipherViewCell/BaseCipherViewCell.cs | 42 ++----------------- .../CipherViewCell/CipherViewCell.xaml | 7 ++-- .../CipherViewCell/CipherViewCell.xaml.cs | 18 +++++++- src/Core/Core.csproj | 1 - src/Core/MauiProgram.cs | 16 +------ src/Core/Pages/Vault/CipherItemViewModel.cs | 16 +++++-- 10 files changed, 57 insertions(+), 111 deletions(-) delete mode 100644 src/Core/Controls/CachedImage.cs diff --git a/src/App/App.csproj b/src/App/App.csproj index dc29cf125..8444c1524 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -99,7 +99,6 @@ - diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml index 99c56791c..b93603bea 100644 --- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml +++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml @@ -18,17 +18,16 @@ - _iconImage; + protected override Image Icon => _iconImage; protected override IconLabel IconPlaceholder => _iconPlaceholderImage; + + private async void Image_OnLoaded(object sender, EventArgs e) + { + if (Handler?.MauiContext == null) { return; } + if (_iconImage?.Source == null) { return; } + + var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); + if (result == null) + { + Icon_Error(sender, e); + } + else + { + Icon_Success(sender, e); + } + } } } diff --git a/src/Core/Controls/CachedImage.cs b/src/Core/Controls/CachedImage.cs deleted file mode 100644 index 3f95dc1bf..000000000 --- a/src/Core/Controls/CachedImage.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Bit.App.Controls -{ -#if !UT - public class CachedImage : FFImageLoading.Maui.CachedImage - { - } -#else - /// - /// Given that FFImageLoading package doesn't support net8.0 then for Unit tests projects to build and run correctly - /// we need to not include the reference to FFImageLoading and therefore wrap this class - /// to provide a stub one that does nothing so this project doesn't break and we can run the tests. - /// - public class CachedImage : View - { - public static readonly BindableProperty SourceProperty = BindableProperty.Create( - nameof(Source), typeof(ImageSource), typeof(CachedImage)); - - public static readonly BindableProperty AspectProperty = BindableProperty.Create( - nameof(Aspect), typeof(Aspect), typeof(CachedImage)); - - public bool BitmapOptimizations { get; set; } - public string ErrorPlaceholder { get; set; } - public string LoadingPlaceholder { get; set; } - - public ImageSource Source - { - get { return (ImageSource)GetValue(SourceProperty); } - set { SetValue(SourceProperty, value); } - } - public Aspect Aspect - { - get { return (Aspect)GetValue(AspectProperty); } - set { SetValue(AspectProperty, value); } - } - - public bool IsLoading { get; set; } - - public event EventHandler Success; - public event EventHandler Error; - } -#endif -} diff --git a/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs b/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs index 78dbf82ad..baa952127 100644 --- a/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs +++ b/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs @@ -4,7 +4,7 @@ namespace Bit.App.Controls { public abstract class BaseCipherViewCell : ExtendedGrid { - protected virtual CachedImage Icon { get; } + protected virtual Image Icon { get; } protected virtual IconLabel IconPlaceholder { get; } @@ -15,7 +15,7 @@ public abstract class BaseCipherViewCell : ExtendedGrid // the app would crash because there can't be any lock files by the app when it gets suspended. // So, the approach has changed to reuse the IconLabel default icon to use it for these placeholders // as well. In order to do that both icon controls change their visibility dynamically here reacting to - // CachedImage events and binding context changes. + // Image OnLoaded event and binding context changes. protected override void OnBindingContextChanged() { @@ -47,8 +47,7 @@ private void UpdateIconImages(bool showIcon) }); } -#if !UT - public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e) + public void Icon_Success(object sender, EventArgs e) { if (BindingContext is CipherItemViewModel cipherItemVM) { @@ -62,7 +61,7 @@ public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.Su } } - public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e) + public void Icon_Error(object sender, EventArgs e) { if (BindingContext is CipherItemViewModel cipherItemVM) { @@ -74,38 +73,5 @@ public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.Erro IconPlaceholder.IsVisible = true; }); } -#else - private void Icon_Success(object sender, EventArgs e) {} - private void Icon_Error(object sender, EventArgs e) {} -#endif - } - - public class StubBaseCipherViewCellSoLinkerDoesntRemoveMethods : BaseCipherViewCell - { - protected override CachedImage Icon => new CachedImage(); - protected override IconLabel IconPlaceholder => new IconLabel(); - - public static void CallThisSoLinkerDoesntRemoveMethods() - { -#if !UT - var stub = new StubBaseCipherViewCellSoLinkerDoesntRemoveMethods(); - - try - { - stub.Icon_Success(stub, new FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs(new FFImageLoading.Work.ImageInformation(), FFImageLoading.Work.LoadingResult.Disk)); - } - catch (Exception) - { - } - - try - { - stub.Icon_Error(stub, new FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs(new InvalidOperationException("stub"))); - } - catch (Exception) - { - } -#endif - } } } diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml index bbfdd41ee..dbff0e473 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml @@ -29,18 +29,17 @@ - diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs index fd163bce3..404e475a6 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs @@ -23,7 +23,7 @@ public CipherViewCell() _iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale; } - protected override CachedImage Icon => _iconImage; + protected override Image Icon => _iconImage; protected override IconLabel IconPlaceholder => _iconPlaceholderImage; @@ -40,5 +40,21 @@ private void MoreButton_Clicked(object sender, EventArgs e) ButtonCommand?.Execute(cipherItem.Cipher); } } + + private async void Image_OnLoaded(object sender, EventArgs e) + { + if (Handler?.MauiContext == null) { return; } + if (_iconImage?.Source == null) { return; } + + var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); + if (result == null) + { + Icon_Error(sender, e); + } + else + { + Icon_Success(sender, e); + } + } } } diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index aa4fba0f9..a35737d6d 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -47,7 +47,6 @@ - diff --git a/src/Core/MauiProgram.cs b/src/Core/MauiProgram.cs index 43fdfd843..5020dd82a 100644 --- a/src/Core/MauiProgram.cs +++ b/src/Core/MauiProgram.cs @@ -1,12 +1,8 @@ -using Bit.App.Controls; + using Camera.MAUI; using CommunityToolkit.Maui; -#if !UT -using FFImageLoading.Maui; -#endif using Microsoft.Extensions.Logging; using Microsoft.Maui.Controls.Compatibility.Hosting; -using Microsoft.Maui.Handlers; using SkiaSharp.Views.Maui.Controls.Hosting; using AppEffects = Bit.App.Effects; @@ -26,9 +22,6 @@ public static MauiAppBuilder ConfigureMauiAppBuilder(Action cus .UseMauiCompatibility() .UseMauiCameraView() .UseSkiaSharp() -#if !UT - .UseFFImageLoading() -#endif .ConfigureEffects(effects => { #if ANDROID @@ -63,13 +56,6 @@ public static MauiAppBuilder ConfigureMauiAppBuilder(Action cus builder.Logging.AddDebug(); #endif - ExplicitlyPreventThingsGetRemovedBecauseOfLinker(); - return builder; } - - private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker() - { - StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods(); - } } diff --git a/src/Core/Pages/Vault/CipherItemViewModel.cs b/src/Core/Pages/Vault/CipherItemViewModel.cs index 28dee6780..41aa4ee82 100644 --- a/src/Core/Pages/Vault/CipherItemViewModel.cs +++ b/src/Core/Pages/Vault/CipherItemViewModel.cs @@ -7,7 +7,7 @@ namespace Bit.App.Pages public class CipherItemViewModel : ExtendedViewModel, IGroupingsPageListItem { private readonly bool _websiteIconsEnabled; - private string _iconImageSource = string.Empty; + private UriImageSource _iconImageSource = null; public CipherItemViewModel(CipherView cipherView, bool websiteIconsEnabled, bool fuzzyAutofill = false) { @@ -27,14 +27,22 @@ public bool ShowIconImage && IconImageSource != null; } - public string IconImageSource + public UriImageSource IconImageSource { get { - if (_iconImageSource == string.Empty) // default value since icon source can return null + if (_iconImageSource == null) // default value since icon source can return null { - _iconImageSource = IconImageHelper.GetIconImage(Cipher); + var iconImageStr = IconImageHelper.GetIconImage(Cipher); + if (string.IsNullOrWhiteSpace(iconImageStr)) { return null; } + + _iconImageSource = new UriImageSource + { + Uri = new Uri(iconImageStr), + CacheValidity = TimeSpan.FromDays(90) + }; } + return _iconImageSource; } } From 6b15bfce124e69a722e8caef9e5f14cc1577accb Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Wed, 27 Mar 2024 21:45:14 +0000 Subject: [PATCH 2/3] Added try catch and logging to Image.OnLoaded methods --- .../AuthenticatorViewCell.xaml.cs | 24 +++++++++++++++---- .../CipherViewCell/CipherViewCell.xaml.cs | 21 ++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs index 38c7d5d38..34c24a937 100644 --- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs +++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs @@ -1,4 +1,6 @@ -namespace Bit.App.Controls +using Bit.Core.Services; + +namespace Bit.App.Controls { public partial class AuthenticatorViewCell : BaseCipherViewCell { @@ -16,14 +18,26 @@ private async void Image_OnLoaded(object sender, EventArgs e) if (Handler?.MauiContext == null) { return; } if (_iconImage?.Source == null) { return; } - var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); - if (result == null) + try + { + var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); + if (result == null) + { + Icon_Error(sender, e); + } + else + { + Icon_Success(sender, e); + } + } + catch (InvalidOperationException) //Can occur with incorrect/malformed uris { Icon_Error(sender, e); } - else + catch(Exception ex) { - Icon_Success(sender, e); + LoggerHelper.LogEvenIfCantBeResolved(ex); + Icon_Error(sender, e); } } } diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs index 404e475a6..52adb36a6 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs @@ -1,6 +1,7 @@ using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Pages; +using Bit.Core.Services; using Bit.Core.Utilities; namespace Bit.App.Controls @@ -46,14 +47,26 @@ private async void Image_OnLoaded(object sender, EventArgs e) if (Handler?.MauiContext == null) { return; } if (_iconImage?.Source == null) { return; } - var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); - if (result == null) + try + { + var result = await _iconImage.Source.GetPlatformImageAsync(Handler.MauiContext); + if (result == null) + { + Icon_Error(sender, e); + } + else + { + Icon_Success(sender, e); + } + } + catch (InvalidOperationException) //Can occur with incorrect/malformed uris { Icon_Error(sender, e); } - else + catch(Exception ex) { - Icon_Success(sender, e); + LoggerHelper.LogEvenIfCantBeResolved(ex); + Icon_Error(sender, e); } } } From 01ee126c6f08e09fd4462b00b3f1eae32810949f Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Wed, 27 Mar 2024 22:01:37 +0000 Subject: [PATCH 3/3] minor change missing in last commit --- .../AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs | 2 +- src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs | 2 +- src/Core/Pages/Vault/CipherItemViewModel.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs index 34c24a937..a8137ad24 100644 --- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs +++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs @@ -34,7 +34,7 @@ private async void Image_OnLoaded(object sender, EventArgs e) { Icon_Error(sender, e); } - catch(Exception ex) + catch (Exception ex) { LoggerHelper.LogEvenIfCantBeResolved(ex); Icon_Error(sender, e); diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs index 52adb36a6..ed6b78339 100644 --- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs +++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs @@ -63,7 +63,7 @@ private async void Image_OnLoaded(object sender, EventArgs e) { Icon_Error(sender, e); } - catch(Exception ex) + catch (Exception ex) { LoggerHelper.LogEvenIfCantBeResolved(ex); Icon_Error(sender, e); diff --git a/src/Core/Pages/Vault/CipherItemViewModel.cs b/src/Core/Pages/Vault/CipherItemViewModel.cs index 41aa4ee82..a5570d2d1 100644 --- a/src/Core/Pages/Vault/CipherItemViewModel.cs +++ b/src/Core/Pages/Vault/CipherItemViewModel.cs @@ -39,7 +39,8 @@ public UriImageSource IconImageSource _iconImageSource = new UriImageSource { Uri = new Uri(iconImageStr), - CacheValidity = TimeSpan.FromDays(90) + CacheValidity = TimeSpan.FromDays(90), + CachingEnabled = true }; }