mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-28 12:35:40 +01:00
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
This commit is contained in:
parent
9db32ca019
commit
583fd6ba1e
@ -99,7 +99,6 @@
|
|||||||
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
|
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
||||||
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
|
||||||
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
|
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
|
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
|
||||||
|
@ -18,17 +18,16 @@
|
|||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
</controls:BaseCipherViewCell.Resources>
|
</controls:BaseCipherViewCell.Resources>
|
||||||
|
|
||||||
<controls:CachedImage
|
<Image
|
||||||
x:Name="_iconImage"
|
x:Name="_iconImage"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
BitmapOptimizations="True"
|
IsOpaque="True"
|
||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
WidthRequest="22"
|
WidthRequest="22"
|
||||||
HeightRequest="22"
|
HeightRequest="22"
|
||||||
Success="Icon_Success"
|
Loaded="Image_OnLoaded"
|
||||||
Error="Icon_Error"
|
|
||||||
AutomationProperties.IsInAccessibleTree="False" />
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
|
@ -7,8 +7,24 @@
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override CachedImage Icon => _iconImage;
|
protected override Image Icon => _iconImage;
|
||||||
|
|
||||||
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
namespace Bit.App.Controls
|
|
||||||
{
|
|
||||||
#if !UT
|
|
||||||
public class CachedImage : FFImageLoading.Maui.CachedImage
|
|
||||||
{
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public abstract class BaseCipherViewCell : ExtendedGrid
|
public abstract class BaseCipherViewCell : ExtendedGrid
|
||||||
{
|
{
|
||||||
protected virtual CachedImage Icon { get; }
|
protected virtual Image Icon { get; }
|
||||||
|
|
||||||
protected virtual IconLabel IconPlaceholder { get; }
|
protected virtual IconLabel IconPlaceholder { get; }
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ namespace Bit.App.Controls
|
|||||||
// the app would crash because there can't be any lock files by the app when it gets suspended.
|
// 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
|
// 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
|
// 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()
|
protected override void OnBindingContextChanged()
|
||||||
{
|
{
|
||||||
@ -47,8 +47,7 @@ namespace Bit.App.Controls
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !UT
|
public void Icon_Success(object sender, EventArgs e)
|
||||||
public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (BindingContext is CipherItemViewModel cipherItemVM)
|
if (BindingContext is CipherItemViewModel cipherItemVM)
|
||||||
{
|
{
|
||||||
@ -62,7 +61,7 @@ namespace Bit.App.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)
|
public void Icon_Error(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (BindingContext is CipherItemViewModel cipherItemVM)
|
if (BindingContext is CipherItemViewModel cipherItemVM)
|
||||||
{
|
{
|
||||||
@ -74,38 +73,5 @@ namespace Bit.App.Controls
|
|||||||
IconPlaceholder.IsVisible = true;
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,18 +29,17 @@
|
|||||||
<ColumnDefinition Width="60" />
|
<ColumnDefinition Width="60" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<controls:CachedImage
|
<Image
|
||||||
x:Name="_iconImage"
|
x:Name="_iconImage"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
BitmapOptimizations="True"
|
IsOpaque="True"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Margin="9"
|
Margin="9"
|
||||||
WidthRequest="22"
|
WidthRequest="22"
|
||||||
HeightRequest="22"
|
HeightRequest="22"
|
||||||
Aspect="AspectFit"
|
Aspect="AspectFit"
|
||||||
Success="Icon_Success"
|
Loaded="Image_OnLoaded"
|
||||||
Error="Icon_Error"
|
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
AutomationId="CipherWebsiteIcon" />
|
AutomationId="CipherWebsiteIcon" />
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ namespace Bit.App.Controls
|
|||||||
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override CachedImage Icon => _iconImage;
|
protected override Image Icon => _iconImage;
|
||||||
|
|
||||||
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
||||||
|
|
||||||
@ -40,5 +40,21 @@ namespace Bit.App.Controls
|
|||||||
ButtonCommand?.Execute(cipherItem.Cipher);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||||
<!-- HACK: When running Unit Tests we cannot load FFImageLoading because it doesn't support "raw" net8.0 -->
|
<!-- HACK: When running Unit Tests we cannot load FFImageLoading because it doesn't support "raw" net8.0 -->
|
||||||
<PackageReference Condition="!$(CustomConstants.Contains(UT))" Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
using Bit.App.Controls;
|
|
||||||
using Camera.MAUI;
|
using Camera.MAUI;
|
||||||
using CommunityToolkit.Maui;
|
using CommunityToolkit.Maui;
|
||||||
#if !UT
|
|
||||||
using FFImageLoading.Maui;
|
|
||||||
#endif
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Maui.Controls.Compatibility.Hosting;
|
using Microsoft.Maui.Controls.Compatibility.Hosting;
|
||||||
using Microsoft.Maui.Handlers;
|
|
||||||
using SkiaSharp.Views.Maui.Controls.Hosting;
|
using SkiaSharp.Views.Maui.Controls.Hosting;
|
||||||
using AppEffects = Bit.App.Effects;
|
using AppEffects = Bit.App.Effects;
|
||||||
|
|
||||||
@ -26,9 +22,6 @@ public static class MauiProgram
|
|||||||
.UseMauiCompatibility()
|
.UseMauiCompatibility()
|
||||||
.UseMauiCameraView()
|
.UseMauiCameraView()
|
||||||
.UseSkiaSharp()
|
.UseSkiaSharp()
|
||||||
#if !UT
|
|
||||||
.UseFFImageLoading()
|
|
||||||
#endif
|
|
||||||
.ConfigureEffects(effects =>
|
.ConfigureEffects(effects =>
|
||||||
{
|
{
|
||||||
#if ANDROID
|
#if ANDROID
|
||||||
@ -63,13 +56,6 @@ public static class MauiProgram
|
|||||||
builder.Logging.AddDebug();
|
builder.Logging.AddDebug();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ExplicitlyPreventThingsGetRemovedBecauseOfLinker();
|
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker()
|
|
||||||
{
|
|
||||||
StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ namespace Bit.App.Pages
|
|||||||
public class CipherItemViewModel : ExtendedViewModel, IGroupingsPageListItem
|
public class CipherItemViewModel : ExtendedViewModel, IGroupingsPageListItem
|
||||||
{
|
{
|
||||||
private readonly bool _websiteIconsEnabled;
|
private readonly bool _websiteIconsEnabled;
|
||||||
private string _iconImageSource = string.Empty;
|
private UriImageSource _iconImageSource = null;
|
||||||
|
|
||||||
public CipherItemViewModel(CipherView cipherView, bool websiteIconsEnabled, bool fuzzyAutofill = false)
|
public CipherItemViewModel(CipherView cipherView, bool websiteIconsEnabled, bool fuzzyAutofill = false)
|
||||||
{
|
{
|
||||||
@ -27,14 +27,22 @@ namespace Bit.App.Pages
|
|||||||
&& IconImageSource != null;
|
&& IconImageSource != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string IconImageSource
|
public UriImageSource IconImageSource
|
||||||
{
|
{
|
||||||
get
|
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;
|
return _iconImageSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user