Sfoglia il codice sorgente

Refactor/rename, clean up

Ruben 7 mesi fa
parent
commit
cf83e08ef4
25 ha cambiato i file con 443 aggiunte e 662 eliminazioni
  1. 31 135
      src/PicView.Avalonia/Animations/AnimationsHelper.cs
  2. 10 46
      src/PicView.Avalonia/Clipboard/ClipboardHelper.cs
  3. 2 2
      src/PicView.Avalonia/CustomControls/GalleryAnimationControl.cs
  4. 89 1
      src/PicView.Avalonia/FileSystem/FileManager.cs
  5. 50 0
      src/PicView.Avalonia/Gallery/GalleryHelper.cs
  6. 124 96
      src/PicView.Avalonia/ImageHandling/GetImageModel.cs
  7. 0 23
      src/PicView.Avalonia/ImageHandling/ImageControl.cs
  8. 0 46
      src/PicView.Avalonia/ImageHandling/ImageDownloader.cs
  9. 2 1
      src/PicView.Avalonia/ImageHandling/ImageFormatConverter.cs
  10. 16 0
      src/PicView.Avalonia/ImageTransformations/Rotation.cs
  11. 7 4
      src/PicView.Avalonia/Navigation/FileHistoryNavigation.cs
  12. 8 1
      src/PicView.Avalonia/Navigation/Slideshow.cs
  13. 2 1
      src/PicView.Avalonia/StartUp/StartUpHelper.cs
  14. 1 1
      src/PicView.Avalonia/UI/FunctionsHelper.cs
  15. 6 6
      src/PicView.Avalonia/UI/HideInterfaceLogic.cs
  16. 2 2
      src/PicView.Avalonia/ViewModels/ImageCropperViewModel.cs
  17. 17 241
      src/PicView.Avalonia/ViewModels/MainViewModel.cs
  18. 5 4
      src/PicView.Avalonia/Views/GallerySettingsView.axaml
  19. 2 2
      src/PicView.Avalonia/Views/MainView.axaml
  20. 1 1
      src/PicView.Avalonia/Views/UC/Buttons/GalleryShortcut.axaml
  21. 31 9
      src/PicView.Avalonia/Wallpaper/WallpaperManager.cs
  22. 10 0
      src/PicView.Avalonia/Wallpaper/WallpaperStyle.cs
  23. 5 1
      src/PicView.Core/FileHandling/FileDeletionHelper.cs
  24. 18 16
      src/PicView.Core/Http/HttpClientDownloadWithProgress.cs
  25. 4 23
      src/PicView.Core/Http/HttpManager.cs

+ 31 - 135
src/PicView.Avalonia/Animations/AnimationsHelper.cs

@@ -1,9 +1,12 @@
 using Avalonia;
 using Avalonia.Animation;
 using Avalonia.Animation.Easings;
+using Avalonia.Controls.Shapes;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Styling;
+using Avalonia.Threading;
+using PicView.Avalonia.UI;
 
 namespace PicView.Avalonia.Animations;
 public static class AnimationsHelper
@@ -87,145 +90,38 @@ public static class AnimationsHelper
         };
     }
     
-    public static Animation RotationAnimation(object from, object to, double speed)
+    /// <summary>
+    /// Displays a brief animation to indicate a clipboard operation occurred
+    /// </summary>
+    public static async Task CopyAnimation()
     {
-        return new Animation
-        {
-            Duration = TimeSpan.FromSeconds(speed),
-            Easing = new LinearEasing(),
-            FillMode = FillMode.Forward,
-            Children =
-            {
-                new KeyFrame
-                {
-                    Setters =
-                    {
-                        new Setter
-                        {
-                            Property = RotateTransform.AngleProperty,
-                            Value = from
-                        },
-                        new Setter
-                        {
-                            Property = RotateTransform.CenterXProperty,
-                            Value = from
-                        },
-                        new Setter
-                        {
-                            Property = RotateTransform.CenterYProperty,
-                            Value = from
-                        }
-                    },
-                    Cue = new Cue(0d)
-                },
-                new KeyFrame
-                {
-                    Setters =
-                    {
-                        new Setter
-                        {
-                            Property = RotateTransform.AngleProperty,
-                            Value = to
-                        },
-                        new Setter
-                        {
-                            Property = RotateTransform.CenterXProperty,
-                            Value = to
-                        },
-                        new Setter
-                        {
-                            Property = RotateTransform.CenterYProperty,
-                            Value = to
-                        }
-                    },
-                    Cue = new Cue(1d)
-                },
-            }
-        };
-    }
-    
-    public static Animation FlipAnimation(object from, object to, double speed)
-    {
-        var x = ScaleTransform.ScaleXProperty;
-        var y = ScaleTransform.ScaleYProperty;
-        return new Animation
+        const double speed = 0.2;
+        const double opacity = 0.4;
+        var startOpacityAnimation = OpacityAnimation(0, opacity, speed);
+        var endOpacityAnimation = OpacityAnimation(opacity, 0, speed);
+        Rectangle? rectangle = null;
+
+        await Dispatcher.UIThread.InvokeAsync(() =>
         {
-            Duration = TimeSpan.FromSeconds(speed),
-            Easing = new LinearEasing(),
-            FillMode = FillMode.Forward,
-            Children =
+            rectangle = new Rectangle
             {
-                new KeyFrame
-                {
-                    Setters =
-                    {
-                        new Setter
-                        {
-                            Property = x,
-                            Value = from
-                        },
-                        new Setter
-                        {
-                            Property = y,
-                            Value = 1
-                        }
-                    },
-                    Cue = new Cue(0d)
-                },
-                new KeyFrame
-                {
-                    Setters =
-                    {
-                        new Setter
-                        {
-                            Property = x,
-                            Value = to
-                        },
-                        new Setter
-                        {
-                            Property = y,
-                            Value = 1
-                        }
-                    },
-                    Cue = new Cue(1d)
-                },
-            }
-        };
-    }
-    
-    public static Animation ZoomAnimation(double initialZoom, double zoomValue, double oldX, double oldY, double newX, double newY, TimeSpan duration)
-    {
-        return new Animation
+                Width = UIHelper.GetMainView.Width,
+                Height = UIHelper.GetMainView.Height,
+                Opacity = 0,
+                Fill = Brushes.Black,
+                IsHitTestVisible = false
+            };
+            UIHelper.GetMainView.MainGrid.Children.Add(rectangle);
+        });
+
+        await startOpacityAnimation.RunAsync(rectangle);
+        await endOpacityAnimation.RunAsync(rectangle);
+        await Task.Delay(200);
+
+        await Dispatcher.UIThread.InvokeAsync(() =>
         {
-            Duration = duration,
-            FillMode = FillMode.Forward,
-            Easing = new LinearEasing(),
-            Children =
-            {
-                new KeyFrame
-                {
-                    Cue = new Cue(0d),
-                    Setters =
-                    {
-                        new Setter(ScaleTransform.ScaleXProperty, initialZoom),
-                        new Setter(ScaleTransform.ScaleYProperty, initialZoom),
-                        new Setter(TranslateTransform.XProperty, oldX),
-                        new Setter(TranslateTransform.YProperty, oldY)
-                    }
-                },
-                new KeyFrame
-                {
-                    Cue = new Cue(1d),
-                    Setters =
-                    {
-                        new Setter(ScaleTransform.ScaleXProperty, zoomValue),
-                        new Setter(ScaleTransform.ScaleYProperty, zoomValue),
-                        new Setter(TranslateTransform.XProperty, newX),
-                        new Setter(TranslateTransform.YProperty, newY)
-                    }
-                }
-            }
-        };
+            UIHelper.GetMainView.MainGrid.Children.Remove(rectangle);
+        });
     }
     
     public static Animation CenteringAnimation(double fromX, double fromY, double toX, double toY, double speed)

+ 10 - 46
src/PicView.Avalonia/Clipboard/ClipboardHelper.cs

@@ -1,17 +1,14 @@
-using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls.Shapes;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
-using Avalonia.Media;
 using Avalonia.Media.Imaging;
 using Avalonia.Platform.Storage;
-using Avalonia.Threading;
 using PicView.Avalonia.Animations;
 using PicView.Avalonia.ImageHandling;
 using PicView.Avalonia.Navigation;
-using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.FileHandling;
 using PicView.Core.Localization;
@@ -24,39 +21,7 @@ namespace PicView.Avalonia.Clipboard;
 /// </summary>
 public static class ClipboardHelper
 {
-    /// <summary>
-    /// Displays a brief animation to indicate a clipboard operation occurred
-    /// </summary>
-    public static async Task CopyAnimation()
-    {
-        const double speed = 0.2;
-        const double opacity = 0.4;
-        var startOpacityAnimation = AnimationsHelper.OpacityAnimation(0, opacity, speed);
-        var endOpacityAnimation = AnimationsHelper.OpacityAnimation(opacity, 0, speed);
-        Rectangle? rectangle = null;
 
-        await Dispatcher.UIThread.InvokeAsync(() =>
-        {
-            rectangle = new Rectangle
-            {
-                Width = UIHelper.GetMainView.Width,
-                Height = UIHelper.GetMainView.Height,
-                Opacity = 0,
-                Fill = Brushes.Black,
-                IsHitTestVisible = false
-            };
-            UIHelper.GetMainView.MainGrid.Children.Add(rectangle);
-        });
-
-        await startOpacityAnimation.RunAsync(rectangle);
-        await endOpacityAnimation.RunAsync(rectangle);
-        await Task.Delay(200);
-
-        await Dispatcher.UIThread.InvokeAsync(() =>
-        {
-            UIHelper.GetMainView.MainGrid.Children.Remove(rectangle);
-        });
-    }
 
     /// <summary>
     /// Duplicates the current file and navigates to it
@@ -78,7 +43,7 @@ public static class ClipboardHelper
             return;
         }
 
-        await Task.WhenAll(CopyAnimation(), NavigationManager.LoadPicFromFile(duplicatedPath, vm));
+        await Task.WhenAll(AnimationsHelper.CopyAnimation(), NavigationManager.LoadPicFromFile(duplicatedPath, vm));
     }
 
     /// <summary>
@@ -96,7 +61,7 @@ public static class ClipboardHelper
 
         try
         {
-            await Task.WhenAll(clipboard.SetTextAsync(text), CopyAnimation());
+            await Task.WhenAll(clipboard.SetTextAsync(text), AnimationsHelper.CopyAnimation());
             return true;
         }
         catch (Exception)
@@ -123,7 +88,7 @@ public static class ClipboardHelper
             var success = await Task.Run(() => vm.PlatformService.CopyFile(file));
             if (success)
             {
-                await CopyAnimation();
+                await AnimationsHelper.CopyAnimation();
             }
             return success;
         }
@@ -153,7 +118,7 @@ public static class ClipboardHelper
             // Handle for Windows
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
-                await Task.WhenAll(vm.PlatformService.CopyImageToClipboard(bitmap), CopyAnimation());
+                await Task.WhenAll(vm.PlatformService.CopyImageToClipboard(bitmap), AnimationsHelper.CopyAnimation());
                 return false;
             }
 
@@ -162,7 +127,7 @@ public static class ClipboardHelper
 
             var dataObject = new DataObject();
             dataObject.Set("image/png", ms.ToArray());
-            await Task.WhenAll(clipboard.SetDataObjectAsync(dataObject), CopyAnimation());
+            await Task.WhenAll(clipboard.SetDataObjectAsync(dataObject), AnimationsHelper.CopyAnimation());
         }
         catch (Exception)
         {
@@ -224,8 +189,7 @@ public static class ClipboardHelper
                 return false;
             }
 
-            await clipboard.SetTextAsync(base64);
-            await CopyAnimation();
+            await Task.WhenAll(clipboard.SetTextAsync(base64), AnimationsHelper.CopyAnimation());
             return true;
         }
         catch (Exception)
@@ -252,7 +216,7 @@ public static class ClipboardHelper
             var success = await Task.Run(() => vm.PlatformService.CutFile(path));
             if (success)
             {
-                await CopyAnimation();
+                await AnimationsHelper.CopyAnimation();
             }
             return success;
         }
@@ -299,7 +263,7 @@ public static class ClipboardHelper
         catch (Exception ex)
         {
             // Log or handle the exception
-            System.Diagnostics.Debug.WriteLine($"Paste operation failed: {ex.Message}");
+            Debug.WriteLine($"Paste operation failed: {ex.Message}");
         }
     }
 

+ 2 - 2
src/PicView.Avalonia/CustomControls/GalleryAnimationControl.cs

@@ -148,7 +148,7 @@ public class GalleryAnimationControl : UserControl
         {
             _isAnimating = true;
 
-            ViewModel.SetGalleryItemStretch(Settings.Gallery.FullGalleryStretchMode);
+            GalleryHelper.SetGalleryItemStretch(Settings.Gallery.FullGalleryStretchMode, ViewModel);
 
             // Setup initial state
             await Dispatcher.UIThread.InvokeAsync(() =>
@@ -240,7 +240,7 @@ public class GalleryAnimationControl : UserControl
             _isAnimating = true;
 
             // Setup gallery properties
-            ViewModel.SetGalleryItemStretch(Settings.Gallery.BottomGalleryStretchMode);
+            GalleryHelper.SetGalleryItemStretch(Settings.Gallery.BottomGalleryStretchMode, ViewModel);
 
             // Setup initial state
             await Dispatcher.UIThread.InvokeAsync(() =>

+ 89 - 1
src/PicView.Avalonia/FileSystem/FileManager.cs

@@ -1,4 +1,5 @@
-using PicView.Avalonia.UI;
+using PicView.Avalonia.Animations;
+using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views.UC.PopUps;
 using PicView.Core.FileHandling;
@@ -33,4 +34,91 @@ public static class FileManager
             await TooltipHelper.ShowTooltipMessageAsync(errorMsg, true);
         }
     }
+    
+    public static async Task DuplicateFile(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+
+        vm.IsLoading = true;
+        if (path == vm.FileInfo.FullName)
+        {
+            await FunctionsHelper.DuplicateFile();
+        }
+        else
+        {
+            var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(path);
+            if (!string.IsNullOrWhiteSpace(duplicatedPath))
+            {
+                await AnimationsHelper.CopyAnimation();
+            }
+        }
+        vm.IsLoading = false;
+    }
+    
+    public static async Task ShowFileProperties(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+        if (vm.PlatformService is null)
+        {
+            return;
+        }
+        await Task.Run(() =>
+        {
+            vm.PlatformService.ShowFileProperties(path);
+        });
+    }
+    
+    public static async Task Print(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+        if (vm.PlatformService is null)
+        {
+            return;
+        }
+        await Task.Run(() =>
+        {
+            vm.PlatformService?.Print(path);
+        });
+    }
+    
+    public static async Task LocateOnDisk(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+        if (vm.PlatformService is null)
+        {
+            return;
+        }
+        await Task.Run(() =>
+        {
+            vm.PlatformService?.LocateOnDisk(path);
+        });
+    }
+    
+    public static async Task OpenWith(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+        if (vm.PlatformService is null)
+        {
+            return;
+        }
+        await Task.Run(() =>
+        {
+            vm.PlatformService?.OpenWith(path);
+        });
+    }
 }

+ 50 - 0
src/PicView.Avalonia/Gallery/GalleryHelper.cs

@@ -0,0 +1,50 @@
+using Avalonia.Media;
+using PicView.Avalonia.ViewModels;
+
+namespace PicView.Avalonia.Gallery;
+
+public static class GalleryHelper
+{
+    public static void SetGalleryItemStretch(string value, MainViewModel vm)
+    {
+        if (value.Equals("Square", StringComparison.OrdinalIgnoreCase))
+        {
+            if (GalleryFunctions.IsFullGalleryOpen)
+            {
+                GalleryStretchMode.ChangeFullGalleryStretchSquare(vm);
+            }
+            else
+            {
+                GalleryStretchMode.ChangeBottomGalleryStretchSquare(vm);
+            }
+
+            return;
+        }
+
+        if (value.Equals("FillSquare", StringComparison.OrdinalIgnoreCase))
+        {
+            if (GalleryFunctions.IsFullGalleryOpen)
+            {
+                GalleryStretchMode.ChangeFullGalleryStretchSquareFill(vm);
+            }
+            else
+            {
+                GalleryStretchMode.ChangeBottomGalleryStretchSquareFill(vm);
+            }
+
+            return;
+        }
+
+        if (Enum.TryParse<Stretch>(value, out var stretch))
+        {
+            if (GalleryFunctions.IsFullGalleryOpen)
+            {
+                GalleryStretchMode.ChangeFullGalleryItemStretch(vm, stretch);
+            }
+            else
+            {
+                GalleryStretchMode.ChangeBottomGalleryItemStretch(vm, stretch);
+            }
+        }
+    }
+}

+ 124 - 96
src/PicView.Avalonia/ImageHandling/GetImageModel.cs

@@ -6,146 +6,174 @@ namespace PicView.Avalonia.ImageHandling;
 
 public static class GetImageModel
 {
+    // Group extensions by how they should be handled
+    private static readonly Dictionary<string, ImageHandler> ExtensionHandlers = new()
+    {
+        { ".webp", new AnimatedWebpHandler() },
+        { ".gif", new AnimatedGifHandler() },
+        { ".png", new StandardBitmapHandler() },
+        { ".jpg", new StandardBitmapHandler() },
+        { ".jpeg", new StandardBitmapHandler() },
+        { ".jpe", new StandardBitmapHandler() },
+        { ".bmp", new StandardBitmapHandler() },
+        { ".jfif", new StandardBitmapHandler() },
+        { ".ico", new StandardBitmapHandler() },
+        { ".wbmp", new StandardBitmapHandler() },
+        { ".svg", new SvgHandler() },
+        { ".svgz", new SvgHandler() },
+        { ".b64", new Base64Handler() }
+    };
+
     public static async Task<ImageModel?> GetImageModelAsync(FileInfo fileInfo)
     {
         if (fileInfo is null)
         {
-#if DEBUG
-            Console.WriteLine($"Error: {nameof(GetImageModel)}:{nameof(GetImageModelAsync)}: fileInfo is null");
-#endif
-            return new ImageModel
-            {
-                FileInfo = null,
-                ImageType = ImageType.Invalid,
-                Image = null, // TODO replace with error image
-                PixelHeight = 0,
-                PixelWidth = 0,
-                EXIFOrientation = EXIFHelper.EXIFOrientation.None
-            };
+            LogError("fileInfo is null");
+            return CreateErrorImageModel(null);
         }
 
         var imageModel = new ImageModel { FileInfo = fileInfo };
 
         try
         {
+            // Get extension and prepare MagickImage for metadata
             var ext = fileInfo.Extension.ToLower();
             using var magickImage = new MagickImage();
             magickImage.Ping(fileInfo);
+            
+            // If extension is missing, use format from MagickImage
             if (string.IsNullOrEmpty(ext))
             {
-                ext = magickImage.Format.ToString().ToLower();
+                ext = $".{magickImage.Format.ToString().ToLower()}";
             }
+            
+            // Extract EXIF orientation early
             imageModel.EXIFOrientation = EXIFHelper.GetImageOrientation(magickImage);
 
-            switch (ext)
+            // Process the image based on extension
+            if (ExtensionHandlers.TryGetValue(ext, out var handler))
             {
-                case ".webp":
-                    await AddImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    if (ImageAnalyzer.IsAnimated(fileInfo))
-                    {
-                        imageModel.ImageType = ImageType.AnimatedWebp;
-                    }
-
-                    break;
-                case ".gif":
-                    await AddImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    if (ImageAnalyzer.IsAnimated(fileInfo))
-                    {
-                        imageModel.ImageType = ImageType.AnimatedGif;
-                    }
-
-                    break;
-                case ".png":
-                case ".jpg":
-                case ".jpeg":
-                case ".jpe":
-                case ".bmp":
-                case ".jfif":
-                case ".ico":
-                case ".wbmp":
-                    await AddImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-
-                    break;
-
-                case ".svg":
-                case ".svgz":
-                    AddSvgImage(fileInfo, imageModel);
-                    break;
-
-                case ".b64":
-                    await AddBase64ImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    break;
-
-                default:
-                    await AddDefaultImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    break;
+                await handler.ProcessImageAsync(fileInfo, imageModel).ConfigureAwait(false);
             }
+            else
+            {
+                // Unknown format - try default handler
+                await new DefaultImageHandler().ProcessImageAsync(fileInfo, imageModel).ConfigureAwait(false);
+            }
+
+            return imageModel;
         }
         catch (Exception e)
         {
-#if DEBUG
-            Console.WriteLine($"Error: {nameof(GetImageModel)}:{nameof(GetImageModelAsync)}: \n{e}");
-#endif
-            return new ImageModel
-            {
-                FileInfo = fileInfo,
-                ImageType = ImageType.Invalid,
-                Image = null, // TODO replace with error image
-                PixelHeight = 0,
-                PixelWidth = 0,
-                EXIFOrientation = EXIFHelper.EXIFOrientation.None
-            };
+            LogError($"Error processing {fileInfo.Name}: {e.Message}");
+            return CreateErrorImageModel(fileInfo);
         }
-
-        return imageModel;
     }
 
-    private static async Task AddDefaultImageAsync(FileInfo fileInfo, ImageModel imageModel)
+    private static void LogError(string message)
     {
-        var bitmap = await GetImage.GetDefaultBitmapAsync(fileInfo).ConfigureAwait(false);
-        SetModel(bitmap, fileInfo, imageModel);
+#if DEBUG
+        Console.WriteLine($"Error: {nameof(GetImageModel)}:{nameof(GetImageModelAsync)}: {message}");
+#endif
     }
 
+    private static ImageModel CreateErrorImageModel(FileInfo? fileInfo)
+    {
+        return new ImageModel
+        {
+            FileInfo = fileInfo,
+            ImageType = ImageType.Invalid,
+            Image = null, // TODO replace with error image
+            PixelHeight = 0,
+            PixelWidth = 0,
+            EXIFOrientation = EXIFHelper.EXIFOrientation.None
+        };
+    }
 
-    #region Bitmap
+    #region Image Handlers
 
-    private static async Task AddImageAsync(FileInfo fileInfo, ImageModel imageModel)
+    // Base abstract class for all image handlers
+    private abstract class ImageHandler
     {
-        var bitmap = await GetImage.GetStandardBitmapAsync(fileInfo).ConfigureAwait(false);
-        SetModel(bitmap, fileInfo, imageModel);
+        public abstract Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel);
+
+        protected static void SetBitmapModel(Bitmap bitmap, FileInfo fileInfo, ImageModel imageModel, ImageType imageType = ImageType.Bitmap)
+        {
+            imageModel.Image = bitmap;
+            imageModel.PixelWidth = bitmap?.PixelSize.Width ?? 0;
+            imageModel.PixelHeight = bitmap?.PixelSize.Height ?? 0;
+            imageModel.ImageType = imageType;
+            imageModel.EXIFOrientation = EXIFHelper.GetImageOrientation(fileInfo);
+        }
     }
 
-    private static void SetModel(Bitmap bitmap, FileInfo fileInfo, ImageModel imageModel)
+    // Handler for standard bitmap formats
+    private class StandardBitmapHandler : ImageHandler
     {
-        imageModel.Image = bitmap;
-        imageModel.PixelWidth = bitmap?.PixelSize.Width ?? 0;
-        imageModel.PixelHeight = bitmap?.PixelSize.Height ?? 0;
-        imageModel.ImageType = ImageType.Bitmap;
-        imageModel.EXIFOrientation = EXIFHelper.GetImageOrientation(fileInfo);
+        public override async Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            var bitmap = await GetImage.GetStandardBitmapAsync(fileInfo).ConfigureAwait(false);
+            SetBitmapModel(bitmap, fileInfo, imageModel);
+        }
     }
 
-    #endregion
+    // Handler for animated WebP
+    private class AnimatedWebpHandler : ImageHandler
+    {
+        public override async Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            var bitmap = await GetImage.GetStandardBitmapAsync(fileInfo).ConfigureAwait(false);
+            SetBitmapModel(bitmap, fileInfo, imageModel, 
+                ImageAnalyzer.IsAnimated(fileInfo) ? ImageType.AnimatedWebp : ImageType.Bitmap);
+        }
+    }
 
-    #region SVG
+    // Handler for animated GIF
+    private class AnimatedGifHandler : ImageHandler
+    {
+        public override async Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            var bitmap = await GetImage.GetStandardBitmapAsync(fileInfo).ConfigureAwait(false);
+            SetBitmapModel(bitmap, fileInfo, imageModel, 
+                ImageAnalyzer.IsAnimated(fileInfo) ? ImageType.AnimatedGif : ImageType.Bitmap);
+        }
+    }
 
-    private static void AddSvgImage(FileInfo fileInfo, ImageModel imageModel)
+    // Handler for SVG images
+    private class SvgHandler : ImageHandler
     {
-        var svg = new MagickImage();
-        svg.Ping(fileInfo.FullName);
-        imageModel.PixelWidth = (int)svg.Width;
-        imageModel.PixelHeight = (int)svg.Height;
-        imageModel.ImageType = ImageType.Svg;
-        imageModel.Image = fileInfo.FullName;
+        public override Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            using var svg = new MagickImage();
+            svg.Ping(fileInfo.FullName);
+            
+            imageModel.PixelWidth = (int)svg.Width;
+            imageModel.PixelHeight = (int)svg.Height;
+            imageModel.ImageType = ImageType.Svg;
+            imageModel.Image = fileInfo.FullName;
+            
+            return Task.CompletedTask;
+        }
     }
 
-    #endregion
-    
-    #region Base64
+    // Handler for Base64 encoded images
+    private class Base64Handler : ImageHandler
+    {
+        public override async Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            var bitmap = await GetImage.GetBase64ImageAsync(fileInfo).ConfigureAwait(false);
+            SetBitmapModel(bitmap, fileInfo, imageModel);
+        }
+    }
 
-    private static async Task AddBase64ImageAsync(FileInfo fileInfo, ImageModel imageModel)
+    // Default handler for unknown formats
+    private class DefaultImageHandler : ImageHandler
     {
-        var base64 = await GetImage.GetBase64ImageAsync(fileInfo).ConfigureAwait(false);
-        SetModel(base64, fileInfo, imageModel);
+        public override async Task ProcessImageAsync(FileInfo fileInfo, ImageModel imageModel)
+        {
+            var bitmap = await GetImage.GetDefaultBitmapAsync(fileInfo).ConfigureAwait(false);
+            SetBitmapModel(bitmap, fileInfo, imageModel);
+        }
     }
 
     #endregion

+ 0 - 23
src/PicView.Avalonia/ImageHandling/ImageControl.cs

@@ -1,23 +0,0 @@
-using Avalonia.Threading;
-using PicView.Avalonia.ViewModels;
-
-namespace PicView.Avalonia.ImageHandling;
-
-public static class ImageControl
-{
-    public static void Flip(MainViewModel vm)
-    {
-        if (vm.ScaleX == 1)
-        {
-            vm.ScaleX = -1;
-            vm.GetIsFlippedTranslation = vm.UnFlip;
-        }
-        else
-        {
-            vm.ScaleX = 1;
-            vm.GetIsFlippedTranslation = vm.Flip;
-        }
-
-        Dispatcher.UIThread.Invoke(() => { vm.ImageViewer.Flip(true); });
-    }
-}

+ 0 - 46
src/PicView.Avalonia/ImageHandling/ImageDownloader.cs

@@ -1,46 +0,0 @@
-using System.Diagnostics;
-
-namespace PicView.Avalonia.ImageHandling;
-
-/// <summary>
-/// Handles downloading images from URLs
-/// </summary>
-public static class ImageDownloader
-{
-    /// <summary>
-    /// Downloads an image from a URL to a local temporary file
-    /// </summary>
-    /// <param name="url">URL of the image to download</param>
-    /// <returns>Local path to the downloaded image or empty string if download failed</returns>
-    public static async Task<string> DownloadImageFromUrlAsync(string url)
-    {
-        if (string.IsNullOrWhiteSpace(url))
-        {
-            return string.Empty;
-        }
-        
-        try
-        {
-            var extension = Path.GetExtension(url);
-            var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + extension);
-
-            using var client = new HttpClient();
-            var response = await client.GetAsync(url);
-            if (response.IsSuccessStatusCode)
-            {
-                await using var fs = new FileStream(tempPath, FileMode.Create);
-                await response.Content.CopyToAsync(fs);
-                return tempPath;
-            }
-        }
-        catch (Exception ex)
-        {
-#if DEBUG
-            Debug.WriteLine($"{nameof(DownloadImageFromUrlAsync)} Error downloading image: {ex.Message}");
-#endif
-            
-        }
-
-        return string.Empty;
-    }
-}

+ 2 - 1
src/PicView.Avalonia/ImageHandling/ImageFormatConverter.cs

@@ -2,6 +2,7 @@
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.FileHandling;
+using PicView.Core.Http;
 using PicView.Core.ImageDecoding;
 
 namespace PicView.Avalonia.ImageHandling;
@@ -79,7 +80,7 @@ public static class ImageFormatConverter
         var url = path.GetURL();
         if (!string.IsNullOrWhiteSpace(url))
         {
-            path = await ImageDownloader.DownloadImageFromUrlAsync(url);
+            path = await HttpManager.DownloadFileAsync(url).ConfigureAwait(false);
             if (string.IsNullOrEmpty(path))
             {
                 return string.Empty;

+ 16 - 0
src/PicView.Avalonia/ImageTransformations/Rotation.cs

@@ -114,4 +114,20 @@ public static class Rotation
         }
         await MoveCursorAfterRotation(vm, rotationButton);
     }
+    
+    public static void Flip(MainViewModel vm)
+    {
+        if (vm.ScaleX == 1)
+        {
+            vm.ScaleX = -1;
+            vm.GetIsFlippedTranslation = vm.UnFlip;
+        }
+        else
+        {
+            vm.ScaleX = 1;
+            vm.GetIsFlippedTranslation = vm.Flip;
+        }
+
+        Dispatcher.UIThread.Invoke(() => { vm.ImageViewer.Flip(true); });
+    }
 }

+ 7 - 4
src/PicView.Avalonia/Navigation/FileHistoryNavigation.cs

@@ -93,12 +93,15 @@ public static class FileHistoryNavigation
     {
         if (!NavigationManager.CanNavigate(vm))
         {
-            var lastFile = Path.GetFileNameWithoutExtension(GetLastFile());
-            if (lastFile == Path.GetFileNameWithoutExtension(vm.FileInfo.Name))
+            if (vm.FileInfo is not null)
             {
-                return;
+                var lastFile = Path.GetFileNameWithoutExtension(GetLastFile());
+                if (lastFile == Path.GetFileNameWithoutExtension(vm.FileInfo.Name))
+                {
+                    return;
+                }
+                await OpenLastFileAsync(vm).ConfigureAwait(false);
             }
-            await OpenLastFileAsync(vm).ConfigureAwait(false);
             return;
         }
 

+ 8 - 1
src/PicView.Avalonia/Navigation/Slideshow.cs

@@ -32,7 +32,14 @@ public static class Slideshow
             return;
         }
 
-        await Start(vm, milliseconds);
+        if (milliseconds <= 0)
+        {
+            await StartSlideshow(vm);
+        }
+        else
+        {
+            await Start(vm, milliseconds);
+        }
     }
     
     public static void StopSlideshow(MainViewModel vm)

+ 2 - 1
src/PicView.Avalonia/StartUp/StartUpHelper.cs

@@ -309,7 +309,8 @@ public static class StartUpHelper
         vm.GetSlideshowSpeed = Settings.UIProperties.SlideShowTimer;
         vm.GetZoomSpeed = Settings.Zoom.ZoomSpeed;
         vm.IsShowingSideBySide = Settings.ImageScaling.ShowImageSideBySide;
-        vm.IsGalleryShown = Settings.Gallery.ShowBottomGalleryInHiddenUI;
+        vm.IsBottomGalleryShown = Settings.Gallery.IsBottomGalleryShown;
+        vm.IsBottomGalleryShownInHiddenUI = Settings.Gallery.ShowBottomGalleryInHiddenUI;
         vm.IsAvoidingZoomingOut  = Settings.Zoom.AvoidZoomingOut;
         vm.IsUIShown  = Settings.UIProperties.ShowInterface;
         vm.IsTopToolbarShown  = Settings.UIProperties.ShowInterface;

+ 1 - 1
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -737,7 +737,7 @@ public static class FunctionsHelper
 
     public static Task Flip()
     {
-        ImageControl.Flip(Vm);
+        Rotation.Flip(Vm);
         return Task.CompletedTask;
     }
 

+ 6 - 6
src/PicView.Avalonia/UI/HideInterfaceLogic.cs

@@ -39,11 +39,11 @@ public static class HideInterfaceLogic
                             vm.GalleryMode = GalleryMode.BottomToClosed;
                         }
                     });
-                    vm.IsGalleryShown = false;
+                    vm.IsBottomGalleryShown = false;
                 }
                 else
                 {
-                    vm.IsGalleryShown = Settings.Gallery.ShowBottomGalleryInHiddenUI;
+                    vm.IsBottomGalleryShown = Settings.Gallery.ShowBottomGalleryInHiddenUI;
                 }
             }
         }
@@ -76,11 +76,11 @@ public static class HideInterfaceLogic
                         _ = GalleryLoad.LoadGallery(vm, vm.FileInfo.DirectoryName);
                     }
 
-                    vm.IsGalleryShown = true;
+                    vm.IsBottomGalleryShown = true;
                 }
                 else
                 {
-                    vm.IsGalleryShown = false;
+                    vm.IsBottomGalleryShown = false;
                 }
             }
         }
@@ -271,11 +271,11 @@ public static class HideInterfaceLogic
             if (!Settings.UIProperties.ShowInterface && !Settings.Gallery
                     .ShowBottomGalleryInHiddenUI)
             {
-                vm.IsGalleryShown = false;
+                vm.IsBottomGalleryShown = false;
             }
             else
             {
-                vm.IsGalleryShown = Settings.Gallery.IsBottomGalleryShown;
+                vm.IsBottomGalleryShown = Settings.Gallery.IsBottomGalleryShown;
             }
         }
         

+ 2 - 2
src/PicView.Avalonia/ViewModels/ImageCropperViewModel.cs

@@ -2,7 +2,7 @@
 using Avalonia;
 using Avalonia.Media.Imaging;
 using ImageMagick;
-using PicView.Avalonia.Clipboard;
+using PicView.Avalonia.Animations;
 using PicView.Avalonia.Crop;
 using PicView.Avalonia.FileSystem;
 using PicView.Avalonia.ImageHandling;
@@ -156,7 +156,7 @@ public class ImageCropperViewModel : ViewModelBase
 
         if (bitmap is not null)
         {
-            await Task.WhenAll(vm.PlatformService.CopyImageToClipboard(bitmap), ClipboardHelper.CopyAnimation());
+            await Task.WhenAll(vm.PlatformService.CopyImageToClipboard(bitmap), AnimationsHelper.CopyAnimation());
         }
     }
 

+ 17 - 241
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -7,6 +7,7 @@ using Avalonia.Layout;
 using Avalonia.Media;
 using PicView.Avalonia.Clipboard;
 using PicView.Avalonia.Converters;
+using PicView.Avalonia.FileSystem;
 using PicView.Avalonia.Gallery;
 using PicView.Avalonia.ImageEffects;
 using PicView.Avalonia.ImageHandling;
@@ -115,7 +116,7 @@ public class MainViewModel : ViewModelBase
         set => this.RaiseAndSetIfChanged(ref field, value);
     }
 
-    public bool IsGalleryShown
+    public bool IsBottomGalleryShown
     {
         get;
         set => this.RaiseAndSetIfChanged(ref field, value);
@@ -1244,179 +1245,27 @@ public class MainViewModel : ViewModelBase
         }
     }
     
-    private async Task CopyFileTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CopyFileToClipboard(path, this);
-    }
+    private async Task CopyFileTask(string path) => await ClipboardHelper.CopyFileToClipboard(path, this);
     
-    private async Task CopyFilePathTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CopyTextToClipboard(path);
-    }
+    private async Task CopyFilePathTask(string path) => await ClipboardHelper.CopyTextToClipboard(path);
     
-    private async Task CopyBase64Task(string path)
-    {
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CopyBase64ToClipboard(path, this);
-    }
+    private async Task CopyBase64Task(string path) => await ClipboardHelper.CopyBase64ToClipboard(path, this);
     
-    private async Task CutFileTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CutFile(path, this);
-    }
+    private async Task CutFileTask(string path) => await ClipboardHelper.CutFile(path, this);
     
-    private async Task DeleteFileTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            try
-            {
-                var errorMsg = FileDeletionHelper.DeleteFileWithErrorMsg(path, recycle: false);
-                if (!string.IsNullOrWhiteSpace(errorMsg))
-                {
-                    _ = TooltipHelper.ShowTooltipMessageAsync(errorMsg);
-                }
-            }
-            catch (Exception e)
-            {
-                _ = TooltipHelper.ShowTooltipMessageAsync(e);
-#if DEBUG
-                Console.WriteLine(e);
-#endif
-            }
-        });
-        
-    }
-    
-    private async Task RecycleFileTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            FileDeletionHelper.DeleteFileWithErrorMsg(path, recycle: true);
-        });
-    }
+    private async Task DeleteFileTask(string path)=> await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, recycle: false));
     
-    private async Task DuplicateFileTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
+    private static async Task RecycleFileTask(string path) => await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, recycle: true));
 
-        IsLoading = true;
-        if (path == FileInfo.FullName)
-        {
-            await FunctionsHelper.DuplicateFile();
-        }
-        else
-        {
-            var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(path);
-            if (!string.IsNullOrWhiteSpace(duplicatedPath))
-            {
-                await ClipboardHelper.CopyAnimation();
-            }
-        }
-        IsLoading = false;
-    }
+    private async Task DuplicateFileTask(string path) => await FileManager.DuplicateFile(path, this).ConfigureAwait(false);
     
-    private async Task ShowFilePropertiesTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            PlatformService.ShowFileProperties(path);
-        });
-    }
+    private async Task ShowFilePropertiesTask(string path) => await FileManager.ShowFileProperties(path, this).ConfigureAwait(false);
 
-    private async Task PrintTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            PlatformService?.Print(path);
-        });
-    }
+    private async Task PrintTask(string path) => await FileManager.Print(path, this).ConfigureAwait(false);
     
-    private async Task OpenWithTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            PlatformService?.OpenWith(path);
-        });
-    }
+    private async Task OpenWithTask(string path) => await FileManager.Print(path, this).ConfigureAwait(false);
     
-    private async Task LocateOnDiskTask(string path)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        if (PlatformService is null)
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            PlatformService?.LocateOnDisk(path);
-        });
-    }
+    private async Task LocateOnDiskTask(string path) => await FileManager.LocateOnDisk(path, this).ConfigureAwait(false);
     
     public async Task SetAsWallpaperTask(string path) => await SetAsWallpaperTask(path, WallpaperStyle.Fit).ConfigureAwait(false);
     
@@ -1427,33 +1276,9 @@ public class MainViewModel : ViewModelBase
     public async Task SetAsWallpaperStretchedTask(string path) => await SetAsWallpaperTask(path, WallpaperStyle.Stretch).ConfigureAwait(false);
     
     public async Task SetAsWallpaperCenteredTask(string path) => await SetAsWallpaperTask(path, WallpaperStyle.Center).ConfigureAwait(false);
-    
-    public async Task SetAsWallpaperTask(string path, WallpaperStyle style)
-    {
-        if (PlatformService is null)
-        {
-            return;
-        }
-        
-        IsLoading = true;
-        try
-        {
-            var file = await ImageFormatConverter.ConvertToCommonSupportedFormatAsync(path, this).ConfigureAwait(false);
 
-            PlatformService?.SetAsWallpaper(file, WallpaperManager.GetWallpaperStyle(style));
-        }
-        catch (Exception e)
-        {
-            await TooltipHelper.ShowTooltipMessageAsync(e.Message, true);
-#if DEBUG
-            Console.WriteLine(e);   
-#endif
-        }
-        finally
-        {
-            IsLoading = false;
-        }
-    }
+    public async Task SetAsWallpaperTask(string path, WallpaperStyle style) =>
+        await WallpaperManager.SetAsWallpaper(path, style, this).ConfigureAwait(false);
     
     private async Task SetAsLockScreenTask(string path)
     {
@@ -1500,58 +1325,9 @@ public class MainViewModel : ViewModelBase
         }
     }
 
-    public void SetGalleryItemStretch(string value)
-    {
-        if (value.Equals("Square", StringComparison.OrdinalIgnoreCase))
-        {
-            if (GalleryFunctions.IsFullGalleryOpen)
-            {
-                GalleryStretchMode.ChangeFullGalleryStretchSquare(this);
-            }
-            else
-            {
-                GalleryStretchMode.ChangeBottomGalleryStretchSquare(this);
-            }
-            return;
-        }
-        
-        if (value.Equals("FillSquare", StringComparison.OrdinalIgnoreCase))
-        {
-            if (GalleryFunctions.IsFullGalleryOpen)
-            {
-                GalleryStretchMode.ChangeFullGalleryStretchSquareFill(this);
-            }
-            else
-            {
-                GalleryStretchMode.ChangeBottomGalleryStretchSquareFill(this);
-            }
-            return;
-        }
-
-        if (Enum.TryParse<Stretch>(value, out var stretch))
-        {
-            if (GalleryFunctions.IsFullGalleryOpen)
-            {
-                GalleryStretchMode.ChangeFullGalleryItemStretch(this, stretch);
-            }
-            else
-            {
-                GalleryStretchMode.ChangeBottomGalleryItemStretch(this, stretch);
-            }
-        }
-    }
+    private void SetGalleryItemStretch(string value) => GalleryHelper.SetGalleryItemStretch(value, this);
 
-    public async Task StartSlideShowTask(int milliseconds)
-    {
-        if (milliseconds <= 0)
-        {
-            await Avalonia.Navigation.Slideshow.StartSlideshow(this);
-        }
-        else
-        {
-            await Avalonia.Navigation.Slideshow.StartSlideshow(this, milliseconds);
-        }
-    }
+    public async Task StartSlideShowTask(int milliseconds) => await Avalonia.Navigation.Slideshow.StartSlideshow(this, milliseconds);
 
     #endregion Methods
 

+ 5 - 4
src/PicView.Avalonia/Views/GallerySettingsView.axaml

@@ -28,17 +28,18 @@
             BorderThickness="0"
             Classes="altHover"
             Command="{CompiledBinding ToggleBottomGalleryCommand}"
-            IsChecked="{CompiledBinding IsGalleryShown}"
+            IsChecked="{CompiledBinding IsBottomGalleryShown}"
             Margin="0,0,0,10"
-            ToolTip.Tip="{CompiledBinding GetIsShowingBottomGalleryTranslation,
+            ToolTip.Tip="{CompiledBinding ShowBottomGallery,
                                           Mode=OneWay}"
-            Width="300">
+            Width="300"
+            x:Name="ToggleBottomGalleryButton">
             <TextBlock
                 Classes="txt"
                 Margin="0"
                 MaxWidth="240"
                 Padding="0,1,5,0"
-                Text="{CompiledBinding GetIsShowingBottomGalleryTranslation,
+                Text="{CompiledBinding ShowBottomGallery,
                                        Mode=OneWay}" />
         </ToggleButton>
 

+ 2 - 2
src/PicView.Avalonia/Views/MainView.axaml

@@ -360,7 +360,7 @@
                     Command="{CompiledBinding ToggleBottomGalleryCommand}"
                     Header="{CompiledBinding GetIsShowingBottomGalleryTranslation,
                                              Mode=OneWay}"
-                    IsChecked="{CompiledBinding IsGalleryShown}">
+                    IsChecked="{CompiledBinding IsBottomGalleryShown}">
                     <MenuItem.Icon>
                         <Image Height="12" Width="12">
                             <DrawingImage>
@@ -1155,7 +1155,7 @@
             GalleryMode="{CompiledBinding GalleryMode,
                                           Mode=OneWay}"
             Height="NaN"
-            IsVisible="{CompiledBinding IsGalleryShown,
+            IsVisible="{CompiledBinding IsBottomGalleryShown,
                                         Mode=OneWay}"
             VerticalAlignment="{CompiledBinding GalleryVerticalAlignment,
                                                 Mode=OneWay}"

+ 1 - 1
src/PicView.Avalonia/Views/UC/Buttons/GalleryShortcut.axaml

@@ -1,7 +1,7 @@
 <UserControl
     Background="Transparent"
     Height="210"
-    IsEnabled="{CompiledBinding !IsGalleryShown}"
+    IsEnabled="{CompiledBinding !IsBottomGalleryShown}"
     IsVisible="{CompiledBinding !IsUIShown}"
     Width="210"
     d:DesignHeight="450"

+ 31 - 9
src/PicView.Avalonia/Wallpaper/WallpaperManager.cs

@@ -1,18 +1,40 @@
 using System.Runtime.InteropServices;
+using PicView.Avalonia.ImageHandling;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
 
 namespace PicView.Avalonia.Wallpaper;
 
-public enum WallpaperStyle
-{
-    Tile,
-    Center,
-    Stretch,
-    Fit,
-    Fill
-}
-
 public static class WallpaperManager
 {
+    
+    public static async Task SetAsWallpaper(string path, WallpaperStyle style, MainViewModel vm)
+    {
+        if (vm.PlatformService is null)
+        {
+            return;
+        }
+        
+        vm.IsLoading = true;
+        try
+        {
+            var file = await ImageFormatConverter.ConvertToCommonSupportedFormatAsync(path, vm).ConfigureAwait(false);
+
+            vm.PlatformService?.SetAsWallpaper(file, GetWallpaperStyle(style));
+        }
+        catch (Exception e)
+        {
+            await TooltipHelper.ShowTooltipMessageAsync(e.Message, true);
+#if DEBUG
+            Console.WriteLine(e);   
+#endif
+        }
+        finally
+        {
+            vm.IsLoading = false;
+        }
+    }
+    
     public static int GetWallpaperStyle(WallpaperStyle style)
     {
         switch (style)

+ 10 - 0
src/PicView.Avalonia/Wallpaper/WallpaperStyle.cs

@@ -0,0 +1,10 @@
+namespace PicView.Avalonia.Wallpaper;
+
+public enum WallpaperStyle
+{
+    Tile,
+    Center,
+    Stretch,
+    Fit,
+    Fill
+}

+ 5 - 1
src/PicView.Core/FileHandling/FileDeletionHelper.cs

@@ -8,7 +8,11 @@ public static class FileDeletionHelper
 {
     public static string DeleteFileWithErrorMsg(string file, bool recycle)
     {
-        if (File.Exists(file) == false)
+        if (string.IsNullOrWhiteSpace(file))
+        {
+            return string.Empty;
+        }
+        if ( File.Exists(file) == false)
         {
             return string.Empty;
         }

+ 18 - 16
src/PicView.Core/Http/HttpClientDownloadWithProgress.cs

@@ -105,23 +105,25 @@ public sealed class HttpClientDownloadWithProgress : IDisposable
         
         do
         {
-            bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
-            
-            if (bytesRead > 0)
+            bytesRead = await contentStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+
+            if (bytesRead <= 0)
+            {
+                continue;
+            }
+
+            await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
+            totalBytesRead += bytesRead;
+
+            if (totalDownloadSize.HasValue)
+            {
+                var progressPercentage = (double)totalBytesRead / totalDownloadSize.Value * 100;
+                OnProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
+            }
+            else
             {
-                await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
-                totalBytesRead += bytesRead;
-
-                if (totalDownloadSize.HasValue)
-                {
-                    var progressPercentage = (double)totalBytesRead / totalDownloadSize.Value * 100;
-                    OnProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
-                }
-                else
-                {
-                    // If we don't know the total size, just report bytes downloaded
-                    OnProgressChanged(null, totalBytesRead, null);
-                }
+                // If we don't know the total size, just report bytes downloaded
+                OnProgressChanged(null, totalBytesRead, null);
             }
         } while (bytesRead > 0 && !cancellationToken.IsCancellationRequested);
         

+ 4 - 23
src/PicView.Core/Http/HttpManager.cs

@@ -1,4 +1,5 @@
-using PicView.Core.FileHandling;
+using PicView.Core.Extensions;
+using PicView.Core.FileHandling;
 using PicView.Core.Localization;
 
 namespace PicView.Core.Http;
@@ -89,32 +90,12 @@ public static class HttpManager
             return string.Empty;
 
         var percentComplete = TranslationHelper.Translation.PercentComplete;
-        var downloadedMb = FormatFileSize(totalBytesDownloaded.Value);
-        var totalMb = FormatFileSize(totalFileSize.Value);
+        var downloadedMb = totalBytesDownloaded.Value.GetReadableFileSize();
+        var totalMb = totalFileSize.Value.GetReadableFileSize();
         
         return $"{downloadedMb}/{totalMb} ({(int)progressPercentage.Value}% {percentComplete})";
     }
     
-    /// <summary>
-    /// Formats file size to a human-readable format
-    /// </summary>
-    /// <param name="bytes">Size in bytes</param>
-    /// <returns>Formatted file size</returns>
-    private static string FormatFileSize(long bytes)
-    {
-        string[] sizes = ["B", "KB", "MB", "GB", "TB"];
-        var order = 0;
-        double size = bytes;
-        
-        while (size >= 1024 && order < sizes.Length - 1)
-        {
-            order++;
-            size /= 1024;
-        }
-        
-        return $"{size:0.##} {sizes[order]}";
-    }
-    
     /// <summary>
     /// Downloads a file from a URL and returns the local file path
     /// </summary>