Compare commits
5 Commits
3d016fb8fd
...
63f5518f3f
Author | SHA1 | Date |
---|---|---|
Dinis Vieira | 63f5518f3f | |
Dinis Vieira | 9de3bdf0b9 | |
Dinis Vieira | 01ee126c6f | |
Dinis Vieira | 6b15bfce12 | |
Dinis Vieira | 583fd6ba1e |
|
@ -99,7 +99,6 @@
|
|||
<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.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="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
|
||||
|
|
|
@ -18,17 +18,16 @@
|
|||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</controls:BaseCipherViewCell.Resources>
|
||||
|
||||
<controls:CachedImage
|
||||
<Image
|
||||
x:Name="_iconImage"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2"
|
||||
BitmapOptimizations="True"
|
||||
IsOpaque="True"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Success="Icon_Success"
|
||||
Error="Icon_Error"
|
||||
Loaded="Image_OnLoaded"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<controls:IconLabel
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace Bit.App.Controls
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AuthenticatorViewCell : BaseCipherViewCell
|
||||
{
|
||||
|
@ -7,8 +9,36 @@
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override CachedImage Icon => _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; }
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
Icon_Error(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
|
||||
{
|
||||
protected virtual CachedImage Icon { get; }
|
||||
protected virtual Image Icon { 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.
|
||||
// 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 @@ namespace Bit.App.Controls
|
|||
});
|
||||
}
|
||||
|
||||
#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 @@ 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)
|
||||
{
|
||||
|
@ -74,38 +73,5 @@ namespace Bit.App.Controls
|
|||
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" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:CachedImage
|
||||
<Image
|
||||
x:Name="_iconImage"
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
IsOpaque="True"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="9"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Aspect="AspectFit"
|
||||
Success="Icon_Success"
|
||||
Error="Icon_Error"
|
||||
Loaded="Image_OnLoaded"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherWebsiteIcon" />
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -23,7 +24,7 @@ namespace Bit.App.Controls
|
|||
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
||||
}
|
||||
|
||||
protected override CachedImage Icon => _iconImage;
|
||||
protected override Image Icon => _iconImage;
|
||||
|
||||
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
||||
|
||||
|
@ -40,5 +41,33 @@ namespace Bit.App.Controls
|
|||
ButtonCommand?.Execute(cipherItem.Cipher);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Image_OnLoaded(object sender, EventArgs e)
|
||||
{
|
||||
if (Handler?.MauiContext == null) { return; }
|
||||
if (_iconImage?.Source == null) { return; }
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
Icon_Error(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<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 -->
|
||||
<PackageReference Condition="!$(CustomConstants.Contains(UT))" Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
|
|
|
@ -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 class MauiProgram
|
|||
.UseMauiCompatibility()
|
||||
.UseMauiCameraView()
|
||||
.UseSkiaSharp()
|
||||
#if !UT
|
||||
.UseFFImageLoading()
|
||||
#endif
|
||||
.ConfigureEffects(effects =>
|
||||
{
|
||||
#if ANDROID
|
||||
|
@ -63,13 +56,6 @@ public static class MauiProgram
|
|||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
ExplicitlyPreventThingsGetRemovedBecauseOfLinker();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker()
|
||||
{
|
||||
StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,23 @@ namespace Bit.App.Pages
|
|||
&& 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),
|
||||
CachingEnabled = true
|
||||
};
|
||||
}
|
||||
|
||||
return _iconImageSource;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue