Browse Source

Merge pull request #159 from ionite34/fix-imageloader

pull/55/head
Ionite 1 year ago committed by GitHub
parent
commit
5d4a96ae0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      StabilityMatrix.Avalonia/App.axaml.cs
  2. 16
      StabilityMatrix.Avalonia/DesignData/MockNotificationService.cs
  3. 70
      StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
  4. 4
      StabilityMatrix.Avalonia/Program.cs
  5. 22
      StabilityMatrix.Avalonia/Services/INotificationService.cs
  6. 19
      StabilityMatrix.Avalonia/Services/NotificationService.cs
  7. 2
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  8. 36
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

1
StabilityMatrix.Avalonia/App.axaml.cs

@ -63,6 +63,7 @@ public sealed class App : Application
[NotNull] public static IServiceProvider? Services { get; private set; }
[NotNull] public static Visual? VisualRoot { get; private set; }
[NotNull] public static IStorageProvider? StorageProvider { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
[NotNull] public static IConfiguration? Config { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global

16
StabilityMatrix.Avalonia/DesignData/MockNotificationService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.Notifications;
using StabilityMatrix.Avalonia.Services;
@ -29,7 +30,18 @@ public class MockNotificationService : INotificationService
return Task.FromResult(new TaskResult<bool>(true));
}
public void Show(string title, string message, NotificationType appearance = NotificationType.Information)
public void Show(
string title,
string message,
NotificationType appearance = NotificationType.Information,
TimeSpan? expiration = null)
{
}
public void ShowPersistent(
string title,
string message,
NotificationType appearance = NotificationType.Information)
{
}
}

70
StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs

@ -0,0 +1,70 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader.Loaders;
using Avalonia.Media.Imaging;
namespace StabilityMatrix.Avalonia;
public readonly record struct ImageLoadFailedEventArgs(string Url, Exception Exception);
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class FallbackRamCachedWebImageLoader : RamCachedWebImageLoader
{
private readonly WeakEventManager<ImageLoadFailedEventArgs> loadFailedEventManager = new();
public event EventHandler<ImageLoadFailedEventArgs> LoadFailed
{
add => loadFailedEventManager.AddEventHandler(value);
remove => loadFailedEventManager.RemoveEventHandler(value);
}
protected void OnLoadFailed(string url, Exception exception) => loadFailedEventManager.RaiseEvent(
this, new ImageLoadFailedEventArgs(url, exception), nameof(LoadFailed));
/// <summary>
/// Attempts to load bitmap
/// </summary>
/// <param name="url">Target url</param>
/// <returns>Bitmap</returns>
protected override async Task<Bitmap?> LoadAsync(string url)
{
// Try to load from local file first
if (File.Exists(url))
{
try
{
return new Bitmap(url);
}
catch (Exception e)
{
OnLoadFailed(url, e);
return null;
}
}
var internalOrCachedBitmap =
await LoadFromInternalAsync(url).ConfigureAwait(false)
?? await LoadFromGlobalCache(url).ConfigureAwait(false);
if (internalOrCachedBitmap != null) return internalOrCachedBitmap;
try
{
var externalBytes = await LoadDataFromExternalAsync(url).ConfigureAwait(false);
if (externalBytes == null) return null;
using var memoryStream = new MemoryStream(externalBytes);
var bitmap = new Bitmap(memoryStream);
await SaveToGlobalCache(url, externalBytes).ConfigureAwait(false);
return bitmap;
}
catch (Exception)
{
return null;
}
}
}

4
StabilityMatrix.Avalonia/Program.cs

@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@ -67,6 +68,9 @@ public class Program
public static AppBuilder BuildAvaloniaApp()
{
IconProvider.Current.Register<FontAwesomeIconProvider>();
// Use our custom image loader for custom local load error handling
ImageLoader.AsyncImageLoader.Dispose();
ImageLoader.AsyncImageLoader = new FallbackRamCachedWebImageLoader();
return AppBuilder.Configure<App>()
.UsePlatformDetect()

22
StabilityMatrix.Avalonia/Services/INotificationService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.Notifications;
using StabilityMatrix.Core.Models;
@ -41,6 +42,23 @@ public interface INotificationService
string? message = null,
NotificationType appearance = NotificationType.Error);
void Show(string title, string message,
/// <summary>
/// Show a notification with the given parameters.
/// </summary>
void Show(
string title,
string message,
NotificationType appearance = NotificationType.Information,
TimeSpan? expiration = null);
/// <summary>
/// Show a notification that will not auto-dismiss.
/// </summary>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="appearance"></param>
void ShowPersistent(
string title,
string message,
NotificationType appearance = NotificationType.Information);
}

19
StabilityMatrix.Avalonia/Services/NotificationService.cs

@ -14,7 +14,7 @@ public class NotificationService : INotificationService
public void Initialize(
Visual? visual,
NotificationPosition position = NotificationPosition.BottomRight,
int maxItems = 3)
int maxItems = 4)
{
if (notificationManager is not null) return;
notificationManager = new WindowNotificationManager(TopLevel.GetTopLevel(visual))
@ -29,12 +29,23 @@ public class NotificationService : INotificationService
notificationManager?.Show(notification);
}
public void Show(string title, string message,
NotificationType appearance = NotificationType.Information)
public void Show(
string title,
string message,
NotificationType appearance = NotificationType.Information,
TimeSpan? expiration = null)
{
Show(new Notification(title, message, appearance));
Show(new Notification(title, message, appearance, expiration));
}
public void ShowPersistent(
string title,
string message,
NotificationType appearance = NotificationType.Information)
{
Show(new Notification(title, message, appearance, TimeSpan.Zero));
}
/// <inheritdoc />
public async Task<TaskResult<T>> TryAsync<T>(
Task<T> task,

2
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.0.0-avalonia11-preview6" />
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.0.0" />
<PackageReference Include="Avalonia" Version="11.0.0" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0" />

36
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -1,8 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using AsyncAwaitBestPractices;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -54,9 +57,27 @@ public partial class MainWindow : AppWindowBase
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
// Initialize notification service using this window as the visual root
NotificationService?.Initialize(this);
// Attach error notification handler for image loader
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
{
loader.LoadFailed += OnImageLoadFailed;
}
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
// Detach error notification handler for image loader
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
{
loader.LoadFailed -= OnImageLoadFailed;
}
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
if (IsWindows11)
@ -73,6 +94,19 @@ public partial class MainWindow : AppWindowBase
}
}
private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
var fileName = Path.GetFileName(e.Url);
var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName;
NotificationService?.ShowPersistent(
"Failed to load image",
$"Could not load '{displayName}'\n({e.Exception.Message})",
NotificationType.Warning);
});
}
private void TryEnableMicaEffect()
{
TransparencyBackgroundFallback = Brushes.Transparent;

Loading…
Cancel
Save