فهرست منبع

Clipboard refactor

Ruben 7 ماه پیش
والد
کامیت
8b697c8b72

+ 183 - 0
src/PicView.Avalonia/Clipboard/ClipboardFileOperations.cs

@@ -0,0 +1,183 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Avalonia.Platform.Storage;
+using PicView.Avalonia.Animations;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.FileHandling;
+using PicView.Core.Localization;
+using PicView.Core.ProcessHandling;
+
+namespace PicView.Avalonia.Clipboard;
+
+/// <summary>
+/// Handles clipboard operations related to files
+/// </summary>
+public static class ClipboardFileOperations
+{
+    /// <summary>
+    /// Duplicates the specified file, either the current file or another one specified by path.
+    /// If the current file is being duplicated, the view model will navigate to the duplicated file.
+    /// </summary>
+    /// <param name="path">Path to the file to duplicate, or null to duplicate the current file.</param>
+    /// <param name="vm">The main view model</param>
+    public static async Task Duplicate(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            return;
+        }
+        
+        try
+        {
+            vm.IsLoading = true;
+            
+            if (path == vm.FileInfo?.FullName)
+            {
+                await DuplicateCurrentFile(vm);
+            }
+            else
+            {
+                await DuplicateFile(path);
+            }
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"{nameof(ClipboardFileOperations)} {nameof(Duplicate)} {ex.StackTrace}");
+#endif
+            await TooltipHelper.ShowTooltipMessageAsync(TranslationHelper.Translation.UnexpectedError);
+        }
+        finally
+        {
+            vm.IsLoading = false;
+        }
+    }
+
+    /// <summary>
+    /// Duplicates the current file and navigates to it
+    /// </summary>
+    /// <param name="vm">The main view model</param>
+    private static async Task DuplicateCurrentFile(MainViewModel vm)
+    {
+        if (!NavigationManager.CanNavigate(vm))
+        {
+            return;
+        }
+
+        var oldPath = vm.FileInfo.FullName;
+        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(oldPath, vm.FileInfo);
+
+        if (string.IsNullOrWhiteSpace(duplicatedPath) || !File.Exists(duplicatedPath))
+        {
+            return;
+        }
+
+        await Task.WhenAll(
+            AnimationsHelper.CopyAnimation(), 
+            NavigationManager.LoadPicFromFile(duplicatedPath, vm)
+        );
+    }
+    
+    /// <summary>
+    /// Duplicates the specified file and plays a copy animation when done. The original file is not navigated away from.
+    /// </summary>
+    /// <param name="path">Path to the file to duplicate</param>
+    private static async Task DuplicateFile(string path)
+    {
+        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(path);
+        if (!string.IsNullOrWhiteSpace(duplicatedPath))
+        {
+            await AnimationsHelper.CopyAnimation();
+        }
+    }
+
+    /// <summary>
+    /// Copies a file to the clipboard
+    /// </summary>
+    /// <param name="filePath">Path to the file</param>
+    /// <param name="vm">The main view model</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static Task<bool> CopyFileToClipboard(string? filePath, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(filePath))
+        {
+            return Task.FromResult(false);
+        }
+
+        return ClipboardService.ExecuteClipboardOperation(
+            () => Task.Run(() => vm.PlatformService.CopyFile(filePath))
+        );
+    }
+
+    /// <summary>
+    /// Cuts a file to the clipboard (copy + mark for deletion on paste)
+    /// </summary>
+    /// <param name="filePath">Path to the file</param>
+    /// <param name="vm">The main view model</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static Task<bool> CutFile(string filePath, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(filePath))
+        {
+            return Task.FromResult(false);
+        }
+
+        return ClipboardService.ExecuteClipboardOperation(
+            () => Task.Run(() => vm.PlatformService.CutFile(filePath))
+        );
+    }
+    
+    /// <summary>
+    /// Handles pasting files from the clipboard
+    /// </summary>
+    public static async Task PasteFiles(object files, MainViewModel vm)
+    {
+        try
+        {
+            if (files is IEnumerable<IStorageItem> items)
+            {
+                await ProcessStorageItems(items.ToArray(), vm);
+            }
+            else if (files is IStorageItem singleFile)
+            {
+                var path = GetLocalPath(singleFile.Path);
+                await NavigationManager.LoadPicFromStringAsync(path, vm);
+            }
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"{nameof(ClipboardFileOperations)} {nameof(PasteFiles)} {ex.StackTrace}");
+#endif
+        }
+    }
+    
+    private static async Task ProcessStorageItems(IStorageItem[] storageItems, MainViewModel vm)
+    {
+        if (storageItems.Length == 0)
+        {
+            return;
+        }
+
+        // Load the first file
+        var firstFile = storageItems[0];
+        var firstPath = GetLocalPath(firstFile.Path);
+        await NavigationManager.LoadPicFromStringAsync(firstPath, vm);
+
+        // Open consecutive files in a new process
+        foreach (var file in storageItems.Skip(1))
+        {
+            var path = GetLocalPath(file.Path);
+            ProcessHelper.StartNewProcess(path);
+        }
+    }
+    
+    private static string GetLocalPath(Uri uri)
+    {
+        return RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
+            ? uri.AbsolutePath
+            : uri.LocalPath;
+    }
+}

+ 0 - 431
src/PicView.Avalonia/Clipboard/ClipboardHelper.cs

@@ -1,431 +0,0 @@
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Input;
-using Avalonia.Input.Platform;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform.Storage;
-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;
-using PicView.Core.ProcessHandling;
-
-namespace PicView.Avalonia.Clipboard;
-
-/// <summary>
-/// Helper class for clipboard operations
-/// </summary>
-public static class ClipboardHelper
-{
-    /// <summary>
-    /// Duplicates the current file and navigates to it
-    /// </summary>
-    /// <param name="vm">The main view model</param>
-    public static async Task DuplicateCurrentFile(MainViewModel vm)
-    {
-        if (!NavigationManager.CanNavigate(vm))
-        {
-            return;
-        }
-
-        vm.IsLoading = true;
-        var oldPath = vm.FileInfo.FullName;
-        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(oldPath, vm.FileInfo);
-
-        if (string.IsNullOrWhiteSpace(duplicatedPath) || !File.Exists(duplicatedPath))
-        {
-            return;
-        }
-
-        await Task.WhenAll(AnimationsHelper.CopyAnimation(), NavigationManager.LoadPicFromFile(duplicatedPath, vm));
-    }
-    
-    /// <summary>
-    /// Duplicates the specified file and plays a copy animation when done. The original file is not navigated away from.
-    /// </summary>
-    /// <param name="path">Path to the file to duplicate</param>
-    public static async Task DuplicateFile(string path)
-    {
-        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(path);
-        if (!string.IsNullOrWhiteSpace(duplicatedPath))
-        {
-            await AnimationsHelper.CopyAnimation();
-        }
-    }
-    
-    /// <summary>
-    /// Duplicates the specified file, either the current file or another one specified by path.
-    /// If the current file is being duplicated, the view model will navigate to the duplicated file.
-    /// </summary>
-    /// <param name="path">Path to the file to duplicate, or null to duplicate the current file.</param>
-    /// <param name="vm">The main view model</param>
-    public static async Task Duplicate(string path, MainViewModel vm)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return;
-        }
-        try
-        {
-            vm.IsLoading = true;
-            
-            if (path == vm.FileInfo?.FullName)
-            {
-                await DuplicateCurrentFile(vm);
-            }
-            else
-            {
-                await DuplicateFile(path);
-            }
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Debug.WriteLine($"{nameof(ClipboardHelper)} {nameof(Duplicate)} {e.StackTrace}");
-#endif
-            await TooltipHelper.ShowTooltipMessageAsync(TranslationHelper.Translation.UnexpectedError);
-        }
-        finally
-        {
-            vm.IsLoading = false;
-        }
-    }
-
-    /// <summary>
-    /// Copies text to the clipboard
-    /// </summary>
-    /// <param name="text">The text to copy</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task<bool> CopyTextToClipboard(string text)
-    {
-        var clipboard = GetClipboard();
-        if (clipboard == null || string.IsNullOrWhiteSpace(text))
-        {
-            return false;
-        }
-
-        try
-        {
-            await Task.WhenAll(clipboard.SetTextAsync(text), AnimationsHelper.CopyAnimation());
-            return true;
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Debug.WriteLine($"{nameof(ClipboardHelper)} {nameof(CopyTextToClipboard)} {e.StackTrace}");
-#endif
-            await TooltipHelper.ShowTooltipMessageAsync(TranslationHelper.Translation.UnexpectedError);
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// Copies a file to the clipboard
-    /// </summary>
-    /// <param name="file">Path to the file</param>
-    /// <param name="vm">The main view model</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task<bool> CopyFileToClipboard(string? file, MainViewModel vm)
-    {
-        if (string.IsNullOrWhiteSpace(file))
-        {
-            return false;
-        }
-
-        try
-        {
-            var success = await Task.Run(() => vm.PlatformService.CopyFile(file));
-            if (success)
-            {
-                await AnimationsHelper.CopyAnimation();
-            }
-            return success;
-        }
-        catch (Exception)
-        {
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// Copies the current image to the clipboard
-    /// </summary>
-    /// <param name="vm">The main view model</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task<bool> CopyImageToClipboard(MainViewModel vm)
-    {
-        var clipboard = GetClipboard();
-        if (clipboard == null || vm.ImageSource is not Bitmap bitmap)
-        {
-            return false;
-        }
-
-        try
-        {
-            await clipboard.ClearAsync();
-
-            // Handle for Windows
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                await Task.WhenAll(vm.PlatformService.CopyImageToClipboard(bitmap), AnimationsHelper.CopyAnimation());
-                return false;
-            }
-
-            using var ms = new MemoryStream();
-            bitmap.Save(ms);
-
-            var dataObject = new DataObject();
-            dataObject.Set("image/png", ms.ToArray());
-            await Task.WhenAll(clipboard.SetDataObjectAsync(dataObject), AnimationsHelper.CopyAnimation());
-        }
-        catch (Exception)
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    /// <summary>
-    /// Copies an image as base64 string to the clipboard
-    /// </summary>
-    /// <param name="path">Optional path to the image file</param>
-    /// <param name="vm">The main view model</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task<bool> CopyBase64ToClipboard(string path, MainViewModel vm)
-    {
-        var clipboard = GetClipboard();
-        if (clipboard == null)
-        {
-            return false;
-        }
-
-        try
-        {
-            string base64;
-            if (string.IsNullOrWhiteSpace(path))
-            {
-                switch (vm.ImageType)
-                {
-                    case ImageType.AnimatedGif:
-                    case ImageType.AnimatedWebp:
-                        throw new ArgumentOutOfRangeException(nameof(vm.ImageType), "Animated images are not supported");
-                    case ImageType.Bitmap:
-                        if (vm.ImageSource is not Bitmap bitmap)
-                        {
-                            return false;
-                        }
-
-                        using (var stream = new MemoryStream())
-                        {
-                            bitmap.Save(stream, quality: 100);
-                            base64 = Convert.ToBase64String(stream.ToArray());
-                        }
-                        break;
-                    case ImageType.Svg:
-                        return false;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(vm.ImageType), $"Unsupported image type: {vm.ImageType}");
-                }
-            }
-            else
-            {
-                base64 = Convert.ToBase64String(await File.ReadAllBytesAsync(path));
-            }
-
-            if (string.IsNullOrEmpty(base64))
-            {
-                return false;
-            }
-
-            await Task.WhenAll(clipboard.SetTextAsync(base64), AnimationsHelper.CopyAnimation());
-            return true;
-        }
-        catch (Exception)
-        {
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// Cuts a file to the clipboard (copy + mark for deletion on paste)
-    /// </summary>
-    /// <param name="path">Path to the file</param>
-    /// <param name="vm">The main view model</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task<bool> CutFile(string path, MainViewModel vm)
-    {
-        if (string.IsNullOrWhiteSpace(path))
-        {
-            return false;
-        }
-
-        try
-        {
-            var success = await Task.Run(() => vm.PlatformService.CutFile(path));
-            if (success)
-            {
-                await AnimationsHelper.CopyAnimation();
-            }
-            return success;
-        }
-        catch (Exception)
-        {
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// Pastes content from the clipboard
-    /// </summary>
-    /// <param name="vm">The main view model</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task Paste(MainViewModel vm)
-    {
-        var clipboard = GetClipboard();
-        if (clipboard == null)
-        {
-            return;
-        }
-
-        try
-        {
-            // Try to paste files first
-            var files = await clipboard.GetDataAsync(DataFormats.Files);
-            if (files != null)
-            {
-                await PasteFiles(files, vm);
-                return;
-            }
-
-            // Try to paste text (URLs, file paths)
-            var text = await clipboard.GetTextAsync();
-            if (!string.IsNullOrWhiteSpace(text))
-            {
-                await NavigationManager.LoadPicFromStringAsync(text, vm).ConfigureAwait(false);
-                return;
-            }
-
-            // Try to paste image data
-            await PasteClipboardImage(vm, clipboard);
-        }
-        catch (Exception ex)
-        {
-            Debug.WriteLine($"Paste operation failed: {ex.Message}");
-        }
-    }
-
-    /// <summary>
-    /// Pastes an image from the clipboard
-    /// </summary>
-    /// <param name="vm">The main view model</param>
-    /// <param name="clipboard">The clipboard instance</param>
-    /// <returns>A task representing the asynchronous operation</returns>
-    public static async Task PasteClipboardImage(MainViewModel vm, IClipboard clipboard)
-    {
-        var name = TranslationHelper.Translation.ClipboardImage;
-        var imageType = ImageType.Bitmap;
-
-        // List of formats to try
-        string[] formats = new[]
-        {
-            "PNG", "image/jpeg", "image/png", "image/bmp", "BMP",
-            "JPG", "JPEG", "image/tiff", "GIF", "image/gif"
-        };
-
-        foreach (var format in formats)
-        {
-            var bitmap = await GetBitmapFromBytes(format);
-            if (bitmap != null)
-            {
-                await UpdateImage.SetSingleImageAsync(bitmap, imageType, name, vm);
-                return;
-            }
-        }
-
-        // Windows-specific clipboard handling
-        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-        {
-            var bitmap = await vm.PlatformService.GetImageFromClipboard();
-            if (bitmap != null)
-            {
-                await UpdateImage.SetSingleImageAsync(bitmap, imageType, name, vm);
-            }
-        }
-
-        async Task<Bitmap?> GetBitmapFromBytes(string format)
-        {
-            try
-            {
-                var data = await clipboard.GetDataAsync(format);
-                if (data is byte[] dataBytes)
-                {
-                    using var memoryStream = new MemoryStream(dataBytes);
-                    return new Bitmap(memoryStream);
-                }
-            }
-            catch (Exception)
-            {
-                // Ignore format errors and try next format
-            }
-            return null;
-        }
-    }
-
-    /// <summary>
-    /// Handles pasting files from the clipboard
-    /// </summary>
-    private static async Task PasteFiles(object files, MainViewModel vm)
-    {
-        if (files is IEnumerable<IStorageItem> items)
-        {
-            var storageItems = items.ToArray();
-            if (storageItems.Length > 0)
-            {
-                // Load the first file
-                var firstFile = storageItems[0];
-                var firstPath = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
-                    ? firstFile.Path.AbsolutePath
-                    : firstFile.Path.LocalPath;
-
-                await NavigationManager.LoadPicFromStringAsync(firstPath, vm).ConfigureAwait(false);
-
-                // Open consecutive files in a new process
-                foreach (var file in storageItems.Skip(1))
-                {
-                    var path = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
-                        ? file.Path.AbsolutePath
-                        : file.Path.LocalPath;
-
-                    ProcessHelper.StartNewProcess(path);
-                }
-            }
-        }
-        else if (files is IStorageItem singleFile)
-        {
-            var path = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
-                ? singleFile.Path.AbsolutePath
-                : singleFile.Path.LocalPath;
-
-            await NavigationManager.LoadPicFromStringAsync(path, vm).ConfigureAwait(false);
-        }
-    }
-
-    /// <summary>
-    /// Gets the clipboard instance from the current application
-    /// </summary>
-    /// <returns>The clipboard instance or null if not available</returns>
-    private static IClipboard? GetClipboard()
-    {
-        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
-        {
-            return desktop.MainWindow.Clipboard;
-        }
-        return null;
-    }
-}

+ 185 - 0
src/PicView.Avalonia/Clipboard/ClipboardImageOperations.cs

@@ -0,0 +1,185 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Media.Imaging;
+using PicView.Avalonia.ImageHandling;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.Localization;
+
+namespace PicView.Avalonia.Clipboard;
+
+/// <summary>
+/// Handles clipboard operations related to images
+/// </summary>
+public static class ClipboardImageOperations
+{
+    /// <summary>
+    /// Copies the current image to the clipboard
+    /// </summary>
+    /// <param name="vm">The main view model</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static async Task<bool> CopyImageToClipboard(MainViewModel vm)
+    {
+        var clipboard = ClipboardService.GetClipboard();
+        if (clipboard == null || vm.ImageSource is not Bitmap bitmap)
+        {
+            return false;
+        }
+
+        return await ClipboardService.ExecuteClipboardOperation(async () =>
+        {
+            await clipboard.ClearAsync();
+
+            // Handle for Windows
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                await vm.PlatformService.CopyImageToClipboard(bitmap);
+                return true;
+            }
+
+            // Handle for other platforms
+            using var ms = new MemoryStream();
+            bitmap.Save(ms);
+
+            var dataObject = new DataObject();
+            dataObject.Set("image/png", ms.ToArray());
+            await clipboard.SetDataObjectAsync(dataObject);
+            return true;
+        });
+    }
+
+    /// <summary>
+    /// Copies an image as base64 string to the clipboard
+    /// </summary>
+    /// <param name="path">Optional path to the image file</param>
+    /// <param name="vm">The main view model</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static async Task<bool> CopyBase64ToClipboard(string path, MainViewModel vm)
+    {
+        var clipboard = ClipboardService.GetClipboard();
+        if (clipboard == null)
+        {
+            return false;
+        }
+
+        return await ClipboardService.ExecuteClipboardOperation(async () =>
+        {
+            var base64 = await GetBase64String(path, vm);
+            
+            if (string.IsNullOrEmpty(base64))
+            {
+                return false;
+            }
+
+            await clipboard.SetTextAsync(base64);
+            return true;
+        });
+    }
+    
+    private static async Task<string> GetBase64String(string path, MainViewModel vm)
+    {
+        if (string.IsNullOrWhiteSpace(path))
+        {
+            switch (vm.ImageType)
+            {
+                case ImageType.AnimatedGif:
+                case ImageType.AnimatedWebp:
+                case ImageType.Bitmap:
+                    if (vm.ImageSource is not Bitmap bitmap)
+                    {
+                        return string.Empty;
+                    }
+
+                    using (var stream = new MemoryStream())
+                    {
+                        bitmap.Save(stream, quality: 100);
+                        return Convert.ToBase64String(stream.ToArray());
+                    }
+
+                case ImageType.Svg:
+                    return string.Empty;
+                    
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(vm.ImageType), $"Unsupported image type: {vm.ImageType}");
+            }
+        }
+        else
+        {
+            return Convert.ToBase64String(await File.ReadAllBytesAsync(path));
+        }
+    }
+    
+    /// <summary>
+    /// Pastes an image from the clipboard
+    /// </summary>
+    /// <param name="vm">The main view model</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static async Task PasteClipboardImage(MainViewModel vm)
+    {
+        var clipboard = ClipboardService.GetClipboard();
+        if (clipboard == null)
+        {
+            return;
+        }
+
+        try
+        {
+            var name = TranslationHelper.Translation.ClipboardImage;
+            var imageType = ImageType.Bitmap;
+
+            // Try standard image formats
+            var bitmap = await TryGetBitmapFromClipboard(clipboard);
+            
+            // Try Windows-specific clipboard handling if needed
+            if (bitmap == null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                bitmap = await vm.PlatformService.GetImageFromClipboard();
+            }
+
+            // Set the image if we got one
+            if (bitmap != null)
+            {
+                await UpdateImage.SetSingleImageAsync(bitmap, imageType, name, vm);
+            }
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"{nameof(ClipboardImageOperations)} {nameof(PasteClipboardImage)} error: {ex.Message}");
+#endif
+        }
+    }
+    
+    private static async Task<Bitmap?> TryGetBitmapFromClipboard(IClipboard clipboard)
+    {
+        // List of formats to try
+        var formats = new[]
+        {
+            "PNG", "image/jpeg", "image/png", "image/bmp", "BMP",
+            "JPG", "JPEG", "image/tiff", "GIF", "image/gif"
+        };
+
+        foreach (var format in formats)
+        {
+            try
+            {
+                var data = await clipboard.GetDataAsync(format);
+                if (data is not byte[] dataBytes)
+                {
+                    continue;
+                }
+
+                using var memoryStream = new MemoryStream(dataBytes);
+                return new Bitmap(memoryStream);
+            }
+            catch (Exception)
+            {
+                // Ignore format errors and try next format
+            }
+        }
+        
+        return null;
+    }
+}

+ 50 - 0
src/PicView.Avalonia/Clipboard/ClipboardPasteOperations.cs

@@ -0,0 +1,50 @@
+using System.Diagnostics;
+using Avalonia.Input;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.ViewModels;
+
+namespace PicView.Avalonia.Clipboard;
+
+public static class ClipboardPasteOperations
+{
+    /// <summary>
+    /// Pastes content from the clipboard
+    /// </summary>
+    /// <param name="vm">The main view model</param>
+    public static async Task Paste(MainViewModel vm)
+    {
+        var clipboard = ClipboardService.GetClipboard();
+        if (clipboard == null)
+        {
+            return;
+        }
+
+        try
+        {
+            // Try to paste files first
+            var files = await clipboard.GetDataAsync(DataFormats.Files);
+            if (files != null)
+            {
+                await ClipboardFileOperations.PasteFiles(files, vm);
+                return;
+            }
+
+            // Try to paste text (URLs, file paths)
+            var text = await clipboard.GetTextAsync();
+            if (!string.IsNullOrWhiteSpace(text))
+            {
+                await NavigationManager.LoadPicFromStringAsync(text, vm).ConfigureAwait(false);
+                return;
+            }
+
+            // Try to paste image data
+            await ClipboardImageOperations.PasteClipboardImage(vm);
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"Paste operation failed: {ex.Message}");
+#endif
+        }
+    }
+}

+ 54 - 0
src/PicView.Avalonia/Clipboard/ClipboardService.cs

@@ -0,0 +1,54 @@
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Input.Platform;
+using PicView.Avalonia.Animations;
+
+namespace PicView.Avalonia.Clipboard;
+
+/// <summary>
+/// Base service for clipboard operations
+/// </summary>
+public static class ClipboardService
+{
+    /// <summary>
+    /// Gets the clipboard instance from the current application
+    /// </summary>
+    /// <returns>The clipboard instance or null if not available</returns>
+    public static IClipboard? GetClipboard()
+    {
+        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            return desktop.MainWindow?.Clipboard;
+        }
+        return null;
+    }
+    
+    /// <summary>
+    /// Executes a clipboard operation with standard error handling and animation
+    /// </summary>
+    /// <param name="operation">The clipboard operation to perform</param>
+    /// <param name="showAnimation">Whether to show the copy animation</param>
+    /// <returns>True if the operation was successful, false otherwise</returns>
+    public static async Task<bool> ExecuteClipboardOperation(Func<Task<bool>> operation, bool showAnimation = true)
+    {
+        try
+        {
+            var success = await operation();
+            
+            if (success && showAnimation)
+            {
+                await AnimationsHelper.CopyAnimation();
+            }
+            
+            return success;
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"{nameof(ClipboardService)} operation failed: {ex.Message}\n{ex.StackTrace}");
+#endif
+            return false;
+        }
+    }
+}

+ 32 - 0
src/PicView.Avalonia/Clipboard/ClipboardTextOperations.cs

@@ -0,0 +1,32 @@
+namespace PicView.Avalonia.Clipboard;
+
+/// <summary>
+/// Handles clipboard operations related to text
+/// </summary>
+public static class ClipboardTextOperations
+{
+    /// <summary>
+    /// Copies text to the clipboard
+    /// </summary>
+    /// <param name="text">The text to copy</param>
+    /// <returns>A task representing the asynchronous operation</returns>
+    public static async Task<bool> CopyTextToClipboard(string text)
+    {
+        if (string.IsNullOrWhiteSpace(text))
+        {
+            return false;
+        }
+        
+        var clipboard = ClipboardService.GetClipboard();
+        if (clipboard == null)
+        {
+            return false;
+        }
+
+        return await ClipboardService.ExecuteClipboardOperation(async () =>
+        {
+            await clipboard.SetTextAsync(text);
+            return true;
+        }, showAnimation: true);
+    }
+}

+ 1 - 7
src/PicView.Avalonia/Navigation/NavigationManager.cs

@@ -1,6 +1,5 @@
 using Avalonia;
 using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Media.Imaging;
 using Avalonia.Threading;
 using ImageMagick;
@@ -817,12 +816,7 @@ public static class NavigationManager
             }
             else 
             {
-                if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-                {
-                    return;
-                }
-                var clipboard = desktop.MainWindow.Clipboard;
-                await ClipboardHelper.PasteClipboardImage(vm, clipboard);
+                await ClipboardImageOperations.PasteClipboardImage(vm);
             }
             return;
         }

+ 14 - 41
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -639,53 +639,26 @@ public static class FunctionsHelper
 
     #region Copy and Paste functions
 
-    public static async Task CopyFile()
-    {
-        await ClipboardHelper.CopyFileToClipboard(Vm?.FileInfo?.FullName, Vm);
-    }
+    public static async Task CopyFile() =>
+        await ClipboardFileOperations.CopyFileToClipboard(Vm?.FileInfo?.FullName, Vm).ConfigureAwait(false);
 
-    public static async Task CopyFilePath()
-    {
-        await ClipboardHelper.CopyTextToClipboard(Vm?.FileInfo?.FullName);
-    }
+    public static async Task CopyFilePath() => 
+        await ClipboardTextOperations.CopyTextToClipboard(Vm?.FileInfo?.FullName).ConfigureAwait(false);
 
-    public static async Task CopyImage()
-    {
-        if (Vm is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CopyImageToClipboard(Vm);
-    }
+    public static async Task CopyImage() => 
+        await ClipboardImageOperations.CopyImageToClipboard(Vm).ConfigureAwait(false);
 
-    public static async Task CopyBase64()
-    {
-        if (Vm is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CopyBase64ToClipboard(Vm.FileInfo?.FullName, vm: Vm);
-    }
+    public static async Task CopyBase64() =>
+        await ClipboardImageOperations.CopyBase64ToClipboard(Vm.FileInfo?.FullName, vm: Vm).ConfigureAwait(false);
 
-    public static async Task DuplicateFile() => await ClipboardHelper.DuplicateCurrentFile(Vm).ConfigureAwait(false);
+    public static async Task DuplicateFile() => 
+        await ClipboardFileOperations.Duplicate(Vm.FileInfo?.FullName, Vm).ConfigureAwait(false);
 
-    public static async Task CutFile()
-    {
-        if (Vm is null)
-        {
-            return;
-        }
-        await ClipboardHelper.CutFile(Vm.FileInfo.FullName, Vm);
-    }
+    public static async Task CutFile() =>
+        await ClipboardFileOperations.CutFile(Vm.FileInfo.FullName, Vm).ConfigureAwait(false);
 
-    public static async Task Paste()
-    {
-        if (Vm is null)
-        {
-            return;
-        }
-        await ClipboardHelper.Paste(Vm);
-    }
+    public static async Task Paste() =>
+        await ClipboardPasteOperations.Paste(Vm).ConfigureAwait(false);
     
     #endregion
 

+ 11 - 7
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -1477,22 +1477,26 @@ public class MainViewModel : ViewModelBase
     private async Task ConvertFileExtension(int index) =>
         await ConversionHelper.ConvertFileExtension(index, this).ConfigureAwait(false);
 
-    private async Task CopyFileTask(string path) => await ClipboardHelper.CopyFileToClipboard(path, this);
+    private async Task CopyFileTask(string path) => 
+        await ClipboardFileOperations.CopyFileToClipboard(path, this).ConfigureAwait(false);
 
-    private static async Task CopyFilePathTask(string path) => await ClipboardHelper.CopyTextToClipboard(path);
+    private static async Task CopyFilePathTask(string path) => 
+        await ClipboardTextOperations.CopyTextToClipboard(path).ConfigureAwait(false);
 
-    private async Task CopyBase64Task(string path) => await ClipboardHelper.CopyBase64ToClipboard(path, this);
+    private async Task CopyBase64Task(string path) =>
+        await ClipboardImageOperations.CopyBase64ToClipboard(path, this).ConfigureAwait(false);
 
-    private async Task CutFileTask(string path) => await ClipboardHelper.CutFile(path, this);
+    private async Task CutFileTask(string path) =>
+        await ClipboardFileOperations.CutFile(path, this).ConfigureAwait(false);
 
     private static async Task DeleteFileTask(string path) =>
-        await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, false));
+        await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, false)).ConfigureAwait(false);
 
     private static async Task RecycleFileTask(string path) =>
-        await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, true));
+        await Task.Run(() => FileDeletionHelper.DeleteFileWithErrorMsg(path, true)).ConfigureAwait(false);
 
     private async Task DuplicateFileTask(string path) =>
-        await ClipboardHelper.Duplicate(path, this).ConfigureAwait(false);
+        await ClipboardFileOperations.Duplicate(path, this).ConfigureAwait(false);
 
     private async Task ShowFilePropertiesTask(string path) =>
         await FileManager.ShowFileProperties(path, this).ConfigureAwait(false);