Преглед изворни кода

Refactor update manager to use interface for platform specific updating.

Ruben пре 7 месеци
родитељ
комит
fadb25badd

+ 39 - 332
src/PicView.Avalonia.Win32/App.axaml.cs

@@ -1,23 +1,19 @@
 using System.Runtime;
 using Avalonia;
-using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media.Imaging;
 using Avalonia.Threading;
 using Clowd.Clipboard;
 using PicView.Avalonia.ColorManagement;
-using PicView.Avalonia.Functions;
 using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.StartUp;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Win32.Views;
-using PicView.Avalonia.WindowBehavior;
 using PicView.Core.FileHandling;
 using PicView.Core.Localization;
 using PicView.Core.ProcessHandling;
-using PicView.Core.ViewModels;
 using PicView.Core.WindowsNT;
 using PicView.Core.WindowsNT.FileAssociation;
 using PicView.Core.WindowsNT.FileHandling;
@@ -28,19 +24,12 @@ using Win32Clipboard = PicView.Core.WindowsNT.Copy.Win32Clipboard;
 
 namespace PicView.Avalonia.Win32;
 
-public partial class App : Application, IPlatformSpecificService
+public class App : Application, IPlatformSpecificService
 {
-    private WinMainWindow? _mainWindow;
-    private ExifWindow? _exifWindow;
-    private SettingsWindow? _settingsWindow;
-    private KeybindingsWindow? _keybindingsWindow;
-    private AboutWindow? _aboutWindow;
-    private SingleImageResizeWindow? _singleImageResizeWindow;
-    private BatchResizeWindow? _batchResizeWindow;
-    private EffectsWindow? _effectsWindow;
-    private MainViewModel? _vm;
-    
+    private static WinMainWindow? _mainWindow;
+    private static WindowManager? _windowManager;
     private TaskbarProgress? _taskbarProgress;
+    private MainViewModel? _vm;
 
     public override void Initialize()
     {
@@ -71,7 +60,7 @@ public partial class App : Application, IPlatformSpecificService
             {
                 return;
             }
-        
+
             TranslationManager.Init();
 
             await Dispatcher.UIThread.InvokeAsync(() =>
@@ -80,54 +69,57 @@ public partial class App : Application, IPlatformSpecificService
 
                 _mainWindow = new WinMainWindow();
                 desktop.MainWindow = _mainWindow;
-            },DispatcherPriority.Send);
-        
+            }, DispatcherPriority.Send);
+
             _vm = new MainViewModel(this);
-        
+
             await Dispatcher.UIThread.InvokeAsync(() =>
             {
                 _mainWindow.DataContext = _vm;
                 StartUpHelper.StartWithArguments(_vm, settingsExists, desktop, _mainWindow);
-            },DispatcherPriority.Send);
+                _windowManager = new WindowManager();
+            }, DispatcherPriority.Send);
         }
         catch (Exception e)
         {
-            #if DEBUG
+#if DEBUG
             Console.WriteLine(e);
-            #endif
+#endif
         }
     }
-    
+
     #region Interface Implementations
-    
+
     public void SetTaskbarProgress(ulong progress, ulong maximum)
     {
         if (_taskbarProgress is null)
         {
             var handle = _mainWindow?.TryGetPlatformHandle()?.Handle;
-    
+
             // Ensure the handle is valid before proceeding
             if (handle == IntPtr.Zero || handle is null)
             {
                 return;
             }
+
             _taskbarProgress = new TaskbarProgress(handle.Value);
         }
 
         _taskbarProgress.SetProgress(progress, maximum);
     }
-    
+
     public void StopTaskbarProgress()
     {
         var handle = _mainWindow?.TryGetPlatformHandle()?.Handle;
-    
+
         // Ensure the handle is valid before proceeding
         if (handle == IntPtr.Zero || handle is null)
         {
             return;
         }
+
         _taskbarProgress?.StopProgress();
-        
+
         _taskbarProgress = null;
     }
 
@@ -163,311 +155,26 @@ public partial class App : Application, IPlatformSpecificService
         FileExplorer.ShowFileProperties(path);
     }
 
-    public void ShowAboutWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-
-            if (_aboutWindow is null)
-            {
-                _aboutWindow = new AboutWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _aboutWindow.Show(desktop.MainWindow);
-                _aboutWindow.Closing += (s, e) => _aboutWindow = null;
-            }
-            else
-            {
-                if (_aboutWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_aboutWindow);
-                }
-                else
-                {
-                    _aboutWindow.Show();
-                }       
-            }
-
-            _ = FunctionsMapper.CloseMenus();
-        }
-    }
-
-    public void ShowExifWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
+    public void ShowAboutWindow() =>
+        _windowManager?.ShowAboutWindow(_vm);
 
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
+    public void ShowExifWindow() =>
+        _windowManager?.ShowExifWindow(_vm);
 
-            if (_exifWindow is null)
-            {
-                _exifWindow = new ExifWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _exifWindow.Show(desktop.MainWindow);
-                _exifWindow.Closing += (s, e) => _exifWindow = null;
-            }
-            else
-            {
-                if (_exifWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_exifWindow);
-                }
-                else
-                {
-                    _exifWindow.Show();
-                }       
-            }
+    public void ShowKeybindingsWindow() =>
+        _windowManager?.ShowKeybindingsWindow(_vm);
 
-            _ = FunctionsMapper.CloseMenus();
-        }
-    }
+    public void ShowSettingsWindow() =>
+        _windowManager?.ShowSettingsWindow(_vm);
 
-    public void ShowKeybindingsWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
+    public void ShowSingleImageResizeWindow() =>
+        _windowManager?.ShowSingleImageResizeWindow(_vm);
 
-            if (_keybindingsWindow is null)
-            {
-                _keybindingsWindow = new KeybindingsWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _keybindingsWindow.Show(desktop.MainWindow);
-                _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
-            }
-            else
-            {
-                if (_keybindingsWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_keybindingsWindow);
-                }
-                else
-                {
-                    _keybindingsWindow.Show();
-                }       
-            }
+    public void ShowBatchResizeWindow() =>
+        _windowManager?.ShowBatchResizeWindow(_vm);
 
-            _ = FunctionsMapper.CloseMenus();
-        }
-    }
-
-    public void ShowSettingsWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-            if (_settingsWindow is null)
-            {
-                _vm.AssociationsViewModel ??= new FileAssociationsViewModel();
-                _vm.SettingsViewModel ??= new SettingsViewModel();
-                _settingsWindow = new SettingsWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _settingsWindow.Show(desktop.MainWindow);
-                _settingsWindow.Closing += (s, e) => _settingsWindow = null;
-            }
-            else
-            {
-                if (_settingsWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_settingsWindow);
-                }
-                else
-                {
-                    _settingsWindow.Show();
-                }         
-            }
-            _= FunctionsMapper.CloseMenus();
-            
-        }
-    }
-
-    public void ShowSingleImageResizeWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-            if (_singleImageResizeWindow is null)
-            {
-                _singleImageResizeWindow = new SingleImageResizeWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _singleImageResizeWindow.Show(desktop.MainWindow);
-                _singleImageResizeWindow.Closing += (s, e) => _singleImageResizeWindow = null;
-            }
-            else
-            {
-                if (_singleImageResizeWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_singleImageResizeWindow);
-                }
-                else
-                {
-                    _singleImageResizeWindow.Show();
-                }         
-            }
-            _= FunctionsMapper.CloseMenus();
-        }
-    }
-    
-    public void ShowBatchResizeWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-            if (_batchResizeWindow is null)
-            {
-                _batchResizeWindow = new BatchResizeWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                };
-                _batchResizeWindow.Show(desktop.MainWindow);
-                _batchResizeWindow.Closing += (s, e) => _batchResizeWindow = null;
-            }
-            else
-            {
-                if (_batchResizeWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_batchResizeWindow);
-                }
-                else
-                {
-                    _batchResizeWindow.Show();
-                }
-            }
-            _= FunctionsMapper.CloseMenus();
-        }   
-    }
-    
-    public void ShowEffectsWindow()
-    {
-        if (Dispatcher.UIThread.CheckAccess())
-        {
-            Set();
-        }
-        else
-        {
-            Dispatcher.UIThread.InvokeAsync(Set);
-        }
-        return;
-        void Set()
-        {
-            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-            if (_effectsWindow is null)
-            {
-                _effectsWindow = new EffectsWindow
-                {
-                    DataContext = _vm,
-                    WindowStartupLocation = WindowStartupLocation.CenterOwner,    
-                };
-                _effectsWindow.Show(desktop.MainWindow);
-                _effectsWindow.Closing += (s, e) => _effectsWindow = null;
-            }
-            else
-            {
-                if (_effectsWindow.WindowState == WindowState.Minimized)
-                {
-                    WindowFunctions.ShowMinimizedWindow(_effectsWindow);
-                }
-                else
-                {
-                    _effectsWindow.Show();
-                }
-            }
-            _= FunctionsMapper.CloseMenus();
-        }
-    }
+    public void ShowEffectsWindow() =>
+        _windowManager?.ShowEffectsWindow(_vm);
 
     public void Print(string path)
     {
@@ -482,7 +189,7 @@ public partial class App : Application, IPlatformSpecificService
             WallpaperHelper.SetDesktopWallpaper(path, style);
         });
     }
-    
+
     public bool SetAsLockScreen(string path)
     {
         return false;
@@ -493,7 +200,7 @@ public partial class App : Application, IPlatformSpecificService
     {
         return Win32Clipboard.CopyFileToClipboard(false, path);
     }
-    
+
     public bool CutFile(string path)
     {
         return Win32Clipboard.CopyFileToClipboard(true, path);
@@ -508,7 +215,7 @@ public partial class App : Application, IPlatformSpecificService
     {
         return await ClipboardAvalonia.GetImageAsync().ConfigureAwait(false);
     }
-    
+
     public async Task<bool> ExtractWithLocalSoftwareAsync(string path, string tempDirectory)
     {
         return await ArchiveExtractionHelper.ExtractWithLocalSoftwareAsync(path, tempDirectory);
@@ -529,11 +236,11 @@ public partial class App : Application, IPlatformSpecificService
     {
         NativeMethods.DisableScreensaver();
     }
-    
+
     public void EnableScreensaver()
     {
         NativeMethods.EnableScreensaver();
     }
-    
+
     #endregion
 }

+ 164 - 0
src/PicView.Avalonia.Win32/PlatformUpdate/WinUpdateHelper.cs

@@ -0,0 +1,164 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
+using PicView.Avalonia.Update;
+using PicView.Avalonia.WindowBehavior;
+using PicView.Core.DebugTools;
+
+namespace PicView.Avalonia.Win32.PlatformUpdate;
+
+/// <summary>
+///     Handles Windows-specific update logic
+/// </summary>
+public static class WinUpdateHelper
+{
+    private const string ExecutableName = "PicView.exe";
+
+    public static async Task HandleWindowsUpdate(UpdateInfo updateInfo, string tempPath)
+    {
+        // Determine if application is installed or portable
+        var isInstalled = IsApplicationInstalled();
+
+        // Determine architecture
+        var architecture = RuntimeInformation.ProcessArchitecture switch
+        {
+            Architecture.X64 => isInstalled ? InstalledArchitecture.X64Install : InstalledArchitecture.X64Portable,
+            Architecture.Arm64 => isInstalled
+                ? InstalledArchitecture.Arm64Install
+                : InstalledArchitecture.Arm64Portable,
+            _ => InstalledArchitecture.X64Install
+        };
+
+        // Apply update based on architecture and installation type
+        switch (architecture)
+        {
+            case InstalledArchitecture.Arm64Install:
+                await InstallWindowsUpdate(updateInfo.Arm64Install, tempPath);
+                break;
+
+            case InstalledArchitecture.Arm64Portable:
+                await OpenDownloadInBrowser(updateInfo.Arm64Portable);
+                break;
+
+            case InstalledArchitecture.X64Install:
+                await InstallWindowsUpdate(updateInfo.X64Install, tempPath);
+                break;
+
+            case InstalledArchitecture.X64Portable:
+                await OpenDownloadInBrowser(updateInfo.X64Portable);
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     Downloads and runs the installer for Windows
+    /// </summary>
+    private static async Task InstallWindowsUpdate(string downloadUrl, string tempPath)
+    {
+        var fileName = Path.GetFileName(downloadUrl);
+        var tempFileDownloadPath = Path.Combine(tempPath, fileName);
+
+        await UpdateManager.DownloadUpdateFile(downloadUrl, tempFileDownloadPath);
+
+        var process = new Process
+        {
+            StartInfo = new ProcessStartInfo
+            {
+                UseShellExecute = true,
+                FileName = tempFileDownloadPath
+            }
+        };
+
+        process.Start();
+        await WindowFunctions.WindowClosingBehavior();
+    }
+
+    /// <summary>
+    ///     Opens a download link in the browser for portable versions
+    /// </summary>
+    private static async Task OpenDownloadInBrowser(string downloadUrl)
+    {
+        var process = new Process
+        {
+            StartInfo = new ProcessStartInfo(downloadUrl)
+            {
+                UseShellExecute = true,
+                Verb = "open"
+            }
+        };
+
+        process.Start();
+        await process.WaitForExitAsync();
+    }
+
+    /// <summary>
+    ///     Checks if the application is installed or running as portable
+    /// </summary>
+    private static bool IsApplicationInstalled()
+    {
+        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+        {
+            return false;
+        }
+
+        // Check if executable exists in Program Files
+        var x64Path = Path.Combine(
+            Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
+            "PicView",
+            ExecutableName);
+
+        return File.Exists(x64Path) || CheckRegistryForInstallation();
+    }
+
+    /// <summary>
+    ///     Checks Windows registry to determine if the application is installed
+    /// </summary>
+    private static bool CheckRegistryForInstallation()
+    {
+        const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
+        const string registryKey64 = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
+
+        try
+        {
+            // Check 32-bit registry path
+            return CheckRegistryKeyForInstallation(Registry.LocalMachine, registryKey) ||
+                   // Check 64-bit registry path
+                   CheckRegistryKeyForInstallation(Registry.LocalMachine, registryKey64);
+        }
+        catch (Exception e)
+        {
+            DebugHelper.LogDebug(nameof(WinUpdateHelper), nameof(CheckRegistryForInstallation), e);
+            return false;
+        }
+    }
+
+    /// <summary>
+    ///     Checks a specific registry key for PicView installation
+    /// </summary>
+    private static bool CheckRegistryKeyForInstallation(RegistryKey baseKey, string keyPath)
+    {
+        using var key = baseKey.OpenSubKey(keyPath);
+        if (key == null)
+        {
+            return false;
+        }
+
+        foreach (var subKeyName in key.GetSubKeyNames())
+        {
+            using var subKey = key.OpenSubKey(subKeyName);
+            var installDir = subKey?.GetValue("InstallLocation")?.ToString();
+
+            if (string.IsNullOrWhiteSpace(installDir))
+            {
+                continue;
+            }
+
+            if (Path.Exists(Path.Combine(installDir, ExecutableName)))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 2 - 1
src/PicView.Avalonia.Win32/Views/AboutWindow.axaml

@@ -86,7 +86,8 @@
                 Focusable="True"
                 Margin="0"
                 Padding="10,2,5,10"
-                PointerPressed="MoveWindow" />
+                PointerPressed="MoveWindow"
+                x:Name="AboutView" />
         </StackPanel>
     </Border>
 </Window>

+ 17 - 4
src/PicView.Avalonia.Win32/Views/AboutWindow.axaml.cs

@@ -4,15 +4,19 @@ using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using PicView.Avalonia.Input;
+using PicView.Avalonia.Interfaces;
+using PicView.Avalonia.Update;
+using PicView.Avalonia.Win32.PlatformUpdate;
 using PicView.Core.Localization;
 
 namespace PicView.Avalonia.Win32.Views;
 
-public partial class AboutWindow : Window
+public partial class AboutWindow : Window, IPlatformSpecificUpdate
 {
     public AboutWindow()
     {
         InitializeComponent();
+        AboutView.PlatformUpdate = this;
         if (Settings.Theme.GlassTheme)
         {
             IconBorder.Background = Brushes.Transparent;
@@ -23,7 +27,7 @@ public partial class AboutWindow : Window
             CloseButton.BorderThickness = new Thickness(0);
             BorderRectangle.Height = 0;
             TitleText.Background = Brushes.Transparent;
-            
+
             if (!Application.Current.TryGetResource("SecondaryTextColor",
                     Application.Current.RequestedThemeVariant, out var textColor))
             {
@@ -34,11 +38,12 @@ public partial class AboutWindow : Window
             {
                 return;
             }
-            
+
             TitleText.Foreground = new SolidColorBrush(color);
             MinimizeButton.Foreground = new SolidColorBrush(color);
             CloseButton.Foreground = new SolidColorBrush(color);
         }
+
         Loaded += delegate
         {
             MinWidth = MaxWidth = Width;
@@ -55,9 +60,17 @@ public partial class AboutWindow : Window
         };
     }
 
+    public async Task HandlePlatofrmUpdate(UpdateInfo updateInfo, string tempPath)
+    {
+        await WinUpdateHelper.HandleWindowsUpdate(updateInfo, tempPath);
+    }
+
     private void MoveWindow(object? sender, PointerPressedEventArgs e)
     {
-        if (VisualRoot is null) { return; }
+        if (VisualRoot is null)
+        {
+            return;
+        }
 
         var hostWindow = (Window)VisualRoot;
         hostWindow?.BeginMoveDrag(e);

+ 337 - 0
src/PicView.Avalonia.Win32/WindowHelper.cs

@@ -0,0 +1,337 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using PicView.Avalonia.Functions;
+using PicView.Avalonia.Interfaces;
+using PicView.Avalonia.Update;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Win32.PlatformUpdate;
+using PicView.Avalonia.Win32.Views;
+using PicView.Avalonia.WindowBehavior;
+using PicView.Core.ViewModels;
+
+namespace PicView.Avalonia.Win32;
+
+public class WindowManager : IPlatformSpecificUpdate
+{
+    private ExifWindow? _exifWindow;
+    private SettingsWindow? _settingsWindow;
+    private  KeybindingsWindow? _keybindingsWindow;
+    private AboutWindow? _aboutWindow;
+    private SingleImageResizeWindow? _singleImageResizeWindow;
+    private BatchResizeWindow? _batchResizeWindow;
+    private EffectsWindow? _effectsWindow;
+    
+    public   void ShowAboutWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_aboutWindow is null)
+            {
+                _aboutWindow = new AboutWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _aboutWindow.Show(desktop.MainWindow);
+                _aboutWindow.Closing += (s, e) => _aboutWindow = null;
+                
+            }
+            else
+            {
+                if (_aboutWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_aboutWindow);
+                }
+                else
+                {
+                    _aboutWindow.Show();
+                }       
+            }
+
+            _ = FunctionsMapper.CloseMenus();
+        }
+    }
+
+    public   void ShowExifWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_exifWindow is null)
+            {
+                _exifWindow = new ExifWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _exifWindow.Show(desktop.MainWindow);
+                _exifWindow.Closing += (s, e) => _exifWindow = null;
+            }
+            else
+            {
+                if (_exifWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_exifWindow);
+                }
+                else
+                {
+                    _exifWindow.Show();
+                }       
+            }
+
+            _ = FunctionsMapper.CloseMenus();
+        }
+    }
+
+    public   void ShowKeybindingsWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_keybindingsWindow is null)
+            {
+                _keybindingsWindow = new KeybindingsWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _keybindingsWindow.Show(desktop.MainWindow);
+                _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
+            }
+            else
+            {
+                if (_keybindingsWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_keybindingsWindow);
+                }
+                else
+                {
+                    _keybindingsWindow.Show();
+                }       
+            }
+
+            _ = FunctionsMapper.CloseMenus();
+        }
+    }
+
+    public   void ShowSettingsWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_settingsWindow is null)
+            {
+                vm.AssociationsViewModel ??= new FileAssociationsViewModel();
+                vm.SettingsViewModel ??= new SettingsViewModel();
+                _settingsWindow = new SettingsWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _settingsWindow.Show(desktop.MainWindow);
+                _settingsWindow.Closing += (s, e) => _settingsWindow = null;
+            }
+            else
+            {
+                if (_settingsWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_settingsWindow);
+                }
+                else
+                {
+                    _settingsWindow.Show();
+                }         
+            }
+            _= FunctionsMapper.CloseMenus();
+            
+        }
+    }
+
+    public   void ShowSingleImageResizeWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_singleImageResizeWindow is null)
+            {
+                _singleImageResizeWindow = new SingleImageResizeWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _singleImageResizeWindow.Show(desktop.MainWindow);
+                _singleImageResizeWindow.Closing += (s, e) => _singleImageResizeWindow = null;
+            }
+            else
+            {
+                if (_singleImageResizeWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_singleImageResizeWindow);
+                }
+                else
+                {
+                    _singleImageResizeWindow.Show();
+                }         
+            }
+            _= FunctionsMapper.CloseMenus();
+        }
+    }
+    
+    public   void ShowBatchResizeWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_batchResizeWindow is null)
+            {
+                _batchResizeWindow = new BatchResizeWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _batchResizeWindow.Show(desktop.MainWindow);
+                _batchResizeWindow.Closing += (s, e) => _batchResizeWindow = null;
+            }
+            else
+            {
+                if (_batchResizeWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_batchResizeWindow);
+                }
+                else
+                {
+                    _batchResizeWindow.Show();
+                }
+            }
+            _= FunctionsMapper.CloseMenus();
+        }   
+    }
+    
+    public   void ShowEffectsWindow(MainViewModel vm)
+    {
+        if (Dispatcher.UIThread.CheckAccess())
+        {
+            Set();
+        }
+        else
+        {
+            Dispatcher.UIThread.InvokeAsync(Set);
+        }
+        return;
+        void Set()
+        {
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_effectsWindow is null)
+            {
+                _effectsWindow = new EffectsWindow
+                {
+                    DataContext = vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,    
+                };
+                _effectsWindow.Show(desktop.MainWindow);
+                _effectsWindow.Closing += (s, e) => _effectsWindow = null;
+            }
+            else
+            {
+                if (_effectsWindow.WindowState == WindowState.Minimized)
+                {
+                    WindowFunctions.ShowMinimizedWindow(_effectsWindow);
+                }
+                else
+                {
+                    _effectsWindow.Show();
+                }
+            }
+            _= FunctionsMapper.CloseMenus();
+        }
+    }
+
+    public async Task HandlePlatofrmUpdate(UpdateInfo updateInfo, string tempPath)
+    {
+        await WinUpdateHelper.HandleWindowsUpdate(updateInfo, tempPath);
+    }
+}

+ 11 - 11
src/PicView.Avalonia/Functions/FunctionsMapper.cs

@@ -1,4 +1,4 @@
-using Avalonia;
+using Avalonia;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Threading;
 using PicView.Avalonia.Clipboard;
@@ -493,7 +493,7 @@ public static class FunctionsMapper
     public static async Task NormalWindowAndStretch() =>
         await WindowFunctions.NormalWindowStretch(Vm).ConfigureAwait(false);
 
-    /// <inheritdoc cref="WindowFunctions.ToggleFullscreen(MainViewModel)" />
+    /// <inheritdoc cref="WindowFunctions.ToggleFullscreen" />
     public static async Task ToggleFullscreen() =>
         await WindowFunctions.ToggleFullscreen(Vm).ConfigureAwait(false);
     
@@ -644,39 +644,39 @@ public static class FunctionsMapper
 
     #region Sorting
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesByName() =>
         await FileListManager.UpdateFileList(Vm.PlatformService, Vm, FileListHelper.SortFilesBy.Name).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesByCreationTime() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.CreationTime).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesByLastAccessTime() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.LastAccessTime).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesByLastWriteTime() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.LastWriteTime).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesBySize() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.FileSize).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesByExtension() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.Extension).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, FileListHelper.SortFilesBy)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, FileListHelper.SortFilesBy)" />
     public static async Task SortFilesRandomly() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, FileListHelper.SortFilesBy.Random).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, bool)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, bool)" />
     public static async Task SortFilesAscending() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, ascending: true).ConfigureAwait(false);
 
-    /// <inheritdoc cref="FileListManager.UpdateFileList(IPlatformService, MainViewModel, bool)" />
+    /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, bool)" />
     public static async Task SortFilesDescending() =>
         await FileListManager.UpdateFileList(Vm?.PlatformService, Vm, ascending: false).ConfigureAwait(false);
 

+ 8 - 0
src/PicView.Avalonia/Interfaces/IPlatformSpecificUpdate.cs

@@ -0,0 +1,8 @@
+using PicView.Avalonia.Update;
+
+namespace PicView.Avalonia.Interfaces;
+
+public interface IPlatformSpecificUpdate
+{
+    public Task HandlePlatofrmUpdate(UpdateInfo updateInfo, string tempPath);
+}

+ 28 - 2
src/PicView.Avalonia/Navigation/FileListManager.cs

@@ -8,10 +8,19 @@ using PicView.Core.FileHandling;
 
 namespace PicView.Avalonia.Navigation;
 
+/// <summary>
+/// Manages file list sorting and updating within the application.
+/// </summary>
 public static class FileListManager
 {
     private static CancellationTokenSource? _cancellationTokenSource;
-    
+
+    /// <summary>
+    /// Sorts an enumerable collection of file paths based on the specified sort order and platform-specific string comparison.
+    /// </summary>
+    /// <param name="files">The collection of file paths to sort.</param>
+    /// <param name="platformService">Platform-specific service for string comparison.</param>
+    /// <returns>A sorted list of file paths.</returns>
     public static List<string> SortIEnumerable(IEnumerable<string> files, IPlatformSpecificService? platformService)
     {
         var sortFilesBy = FileListHelper.GetSortOrder();
@@ -67,7 +76,13 @@ public static class FileListManager
                 return files.OrderBy(f => Guid.NewGuid()).ToList();
         }
     }
-    
+
+    /// <summary>
+    /// Updates the file list in the view model based on the specified sort order.
+    /// </summary>
+    /// <param name="platformSpecificService">Platform-specific service for file retrieval and sorting.</param>
+    /// <param name="vm">The main view model instance.</param>
+    /// <param name="sortFilesBy">The sort order to apply.</param>
     public static async Task UpdateFileList(IPlatformSpecificService? platformSpecificService, MainViewModel vm, FileListHelper.SortFilesBy sortFilesBy)
     {
         Settings.Sorting.SortPreference = (int)sortFilesBy;
@@ -79,6 +94,12 @@ public static class FileListManager
         await UpdateFileList(platformSpecificService, vm);
     }
 
+    /// <summary>
+    /// Updates the file list in the view model based on the specified sorting direction (ascending/descending).
+    /// </summary>
+    /// <param name="platformSpecificService">Platform-specific service for file retrieval and sorting.</param>
+    /// <param name="vm">The main view model instance.</param>
+    /// <param name="ascending">Whether to sort in ascending order (true) or descending order (false).</param>
     public static async Task UpdateFileList(IPlatformSpecificService? platformSpecificService, MainViewModel vm, bool ascending)
     {
         Settings.Sorting.Ascending = ascending;
@@ -89,6 +110,11 @@ public static class FileListManager
         await UpdateFileList(platformSpecificService, vm);
     }
 
+    /// <summary>
+    /// Updates the file list in the view model, refreshing the gallery and UI as necessary.
+    /// </summary>
+    /// <param name="platformSpecificService">Platform-specific service for file retrieval and sorting.</param>
+    /// <param name="vm">The main view model instance.</param>
     private static async Task UpdateFileList(IPlatformSpecificService? platformSpecificService, MainViewModel vm)
     {
         if (_cancellationTokenSource is not null)

+ 19 - 262
src/PicView.Avalonia/Update/UpdateManager.cs

@@ -1,13 +1,11 @@
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using Microsoft.Win32;
+using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
-using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Config;
+using PicView.Core.DebugTools;
 using PicView.Core.Http;
 #if RELEASE
 using PicView.Core.Config;
@@ -25,28 +23,22 @@ public partial class UpdateSourceGenerationContext : JsonSerializerContext;
 /// <summary>
 ///     Handles application update operations
 /// </summary>
-[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
 public static class UpdateManager
 {
     private const string PrimaryUpdateUrl = "https://picview.org/update.json";
     private const string FallbackUpdateUrl = "https://picview.netlify.app/update.json";
-    private const string ExecutableName = "PicView.exe";
-    private const string UpdateArgument = "update";
-    
-    #if DEBUG
+
+
+#if DEBUG
     // ReSharper disable once ConvertToConstant.Local
     private static readonly bool ForceUpdate = true;
-    #endif
-    
+#endif
+
     /// <summary>
     ///     Checks for updates and installs if a newer version is available
     /// </summary>
-    /// <param name="vm">The main view model</param>
-    public static async Task<bool> UpdateCurrentVersion(MainViewModel vm)
+    public static async Task<bool> UpdateCurrentVersion(IPlatformSpecificUpdate platformUpdate)
     {
-        // TODO Add support for other OS
-        // TODO add UI
-        
         // Create temporary directory for update files
         var tempPath = CreateTemporaryDirectory();
         var tempJsonPath = Path.Combine(tempPath, "update.json");
@@ -54,18 +46,18 @@ public static class UpdateManager
         // Check if update is needed
         Version? currentVersion;
 #if DEBUG
-        currentVersion = ForceUpdate ? new Version("3.0.0.3") : VersionHelper.GetAssemblyVersion();  
+        currentVersion = ForceUpdate ? new Version("3.0.0.3") : VersionHelper.GetAssemblyVersion();
+        Debug.Assert(currentVersion != null);
 #else
         currentVersion = VersionHelper.GetAssemblyVersion();
 #endif
-        Debug.Assert(currentVersion != null);
         
         var updateInfo = await DownloadAndParseUpdateInfo(tempJsonPath);
         if (updateInfo == null)
         {
             return false;
         }
-        
+
         var remoteVersion = new Version(updateInfo.Version);
         if (remoteVersion <= currentVersion)
         {
@@ -73,33 +65,10 @@ public static class UpdateManager
         }
 
         // Handle update based on platform and installation type
-        await HandlePlatformSpecificUpdate(vm, updateInfo, tempPath);
+        await platformUpdate?.HandlePlatofrmUpdate(updateInfo, tempPath);
         return true;
     }
 
-    #region Utilities
-
-    /// <summary>
-    ///     Logs debug information in debug builds
-    /// </summary>
-    private static void LogDebug(object message)
-    {
-#if DEBUG
-        if (message is Exception e)
-        {
-            Console.WriteLine(e);
-        }
-        else
-        {
-            Trace.WriteLine(message);
-        }
-#endif
-    }
-
-    #endregion
-
-    #region Update Info
-
     /// <summary>
     ///     Creates a temporary directory for update files
     /// </summary>
@@ -142,7 +111,7 @@ public static class UpdateManager
         }
         catch (Exception e)
         {
-            LogDebug(e);
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(DownloadUpdateJson), e);
             return false;
         }
     }
@@ -165,225 +134,18 @@ public static class UpdateManager
 
             await TooltipHelper.ShowTooltipMessageAsync("Update information is missing or corrupted.");
             return null;
-
         }
         catch (Exception e)
         {
-            LogDebug(e);
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(ParseUpdateJson), e);
             await TooltipHelper.ShowTooltipMessageAsync("Failed to parse update information: \n" + e.Message);
             return null;
         }
     }
-
-    #endregion
-
-    #region Platform-specific Updates
-
-    /// <summary>
-    ///     Handles updates based on platform and installation type
-    /// </summary>
-    private static async Task HandlePlatformSpecificUpdate(
-        MainViewModel vm,
-        UpdateInfo updateInfo,
-        string tempPath)
-    {
-        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-        {
-            await HandleWindowsUpdate(vm, updateInfo, tempPath);
-        }
-        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-        {
-            // TODO: Implement macOS update logic
-        }
-        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-        {
-            // TODO: Implement Linux update logic
-        }
-    }
-
-    /// <summary>
-    ///     Handles Windows-specific update logic
-    /// </summary>
-    private static async Task HandleWindowsUpdate(
-        MainViewModel vm,
-        UpdateInfo updateInfo,
-        string tempPath)
-    {
-        // Determine if application is installed or portable
-        var isInstalled = IsApplicationInstalled();
-
-        // Determine architecture
-        var architecture = RuntimeInformation.ProcessArchitecture switch
-        {
-            Architecture.X64 => isInstalled ? InstalledArchitecture.X64Install : InstalledArchitecture.X64Portable,
-            Architecture.Arm64 => isInstalled
-                ? InstalledArchitecture.Arm64Install
-                : InstalledArchitecture.Arm64Portable,
-            _ => InstalledArchitecture.X64Install
-        };
-
-        // Apply update based on architecture and installation type
-        await ApplyWindowsUpdate(vm, updateInfo, architecture, tempPath);
-    }
-
-    /// <summary>
-    ///     Applies Windows update based on architecture and installation type
-    /// </summary>
-    private static async Task ApplyWindowsUpdate(
-        MainViewModel vm,
-        UpdateInfo updateInfo,
-        InstalledArchitecture architecture,
-        string tempPath)
-    {
-        switch (architecture)
-        {
-            case InstalledArchitecture.Arm64Install:
-                await InstallWindowsUpdate(vm, updateInfo.Arm64Install, tempPath);
-                break;
-
-            case InstalledArchitecture.Arm64Portable:
-                await OpenDownloadInBrowser(updateInfo.Arm64Portable);
-                break;
-
-            case InstalledArchitecture.X64Install:
-                await InstallWindowsUpdate(vm, updateInfo.X64Install, tempPath);
-                break;
-
-            case InstalledArchitecture.X64Portable:
-                await OpenDownloadInBrowser(updateInfo.X64Portable);
-                break;
-        }
-    }
-
-    /// <summary>
-    ///     Downloads and runs the installer for Windows
-    /// </summary>
-    private static async Task InstallWindowsUpdate(MainViewModel vm, string downloadUrl, string tempPath)
-    {
-        var fileName = Path.GetFileName(downloadUrl);
-        var tempFileDownloadPath = Path.Combine(tempPath, fileName);
-
-        await DownloadUpdateFile(vm, downloadUrl, tempFileDownloadPath);
-
-        var process = new Process
-        {
-            StartInfo = new ProcessStartInfo
-            {
-                UseShellExecute = true,
-                FileName = tempFileDownloadPath
-            }
-        };
-
-        process.Start();
-        await WindowFunctions.WindowClosingBehavior();
-    }
-
-    /// <summary>
-    ///     Opens a download link in the browser for portable versions
-    /// </summary>
-    private static async Task OpenDownloadInBrowser(string downloadUrl)
-    {
-        var process = new Process
-        {
-            StartInfo = new ProcessStartInfo(downloadUrl)
-            {
-                UseShellExecute = true,
-                Verb = "open"
-            }
-        };
-
-        process.Start();
-        await process.WaitForExitAsync();
-    }
-
-    #endregion
-
-    #region Installation Detection
-
-    /// <summary>
-    ///     Checks if the application is installed or running as portable
-    /// </summary>
-    private static bool IsApplicationInstalled()
-    {
-        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-        {
-            return false;
-        }
-
-        // Check if executable exists in Program Files
-        var x64Path = Path.Combine(
-            Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
-            "PicView",
-            ExecutableName);
-
-        return File.Exists(x64Path) || CheckRegistryForInstallation();
-    }
-
-    /// <summary>
-    ///     Checks Windows registry to determine if the application is installed
-    /// </summary>
-    private static bool CheckRegistryForInstallation()
-    {
-        const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
-        const string registryKey64 = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
-
-        try
-        {
-            // Check 32-bit registry path
-            if (CheckRegistryKeyForInstallation(Registry.LocalMachine, registryKey))
-            {
-                return true;
-            }
-
-            // Check 64-bit registry path
-            return CheckRegistryKeyForInstallation(Registry.LocalMachine, registryKey64);
-        }
-        catch (Exception e)
-        {
-            LogDebug($"{nameof(CheckRegistryForInstallation)} exception, \n {e.Message}");
-            return false;
-        }
-    }
-
-    /// <summary>
-    ///     Checks a specific registry key for PicView installation
-    /// </summary>
-    private static bool CheckRegistryKeyForInstallation(RegistryKey baseKey, string keyPath)
-    {
-        using var key = baseKey.OpenSubKey(keyPath);
-        if (key == null)
-        {
-            return false;
-        }
-
-        foreach (var subKeyName in key.GetSubKeyNames())
-        {
-            using var subKey = key.OpenSubKey(subKeyName);
-            var installDir = subKey?.GetValue("InstallLocation")?.ToString();
-
-            if (string.IsNullOrWhiteSpace(installDir))
-            {
-                continue;
-            }
-
-            if (Path.Exists(Path.Combine(installDir, ExecutableName)))
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    #endregion
-
-    #region Download Helpers
-
-    /// <summary>
-    ///     Downloads update file with progress reporting
-    /// </summary>
-    private static async Task DownloadUpdateFile(MainViewModel vm, string downloadUrl, string tempPath)
+    
+    public static async Task DownloadUpdateFile(string downloadUrl, string tempPath)
     {
+        var vm = UIHelper.GetMainView.DataContext as MainViewModel;
         vm.PlatformService.StopTaskbarProgress();
 
         using var downloader = new HttpClientDownloadWithProgress(downloadUrl, tempPath);
@@ -396,7 +158,7 @@ public static class UpdateManager
         }
         catch (Exception e)
         {
-            LogDebug(e);
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(DownloadUpdateFile), e);
             await TooltipHelper.ShowTooltipMessageAsync(e.Message);
         }
         finally
@@ -404,10 +166,7 @@ public static class UpdateManager
             vm.PlatformService.StopTaskbarProgress();
         }
     }
-
-    /// <summary>
-    ///     Updates download progress in the taskbar
-    /// </summary>
+    
     private static void UpdateDownloadProgress(
         MainViewModel vm,
         long? totalFileSize,
@@ -421,6 +180,4 @@ public static class UpdateManager
 
         vm.PlatformService.SetTaskbarProgress((ulong)totalBytesDownloaded.Value, (ulong)totalFileSize.Value);
     }
-
-    #endregion
 }

+ 14 - 26
src/PicView.Avalonia/Views/AboutView.axaml.cs

@@ -1,25 +1,25 @@
-using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Media;
 using Avalonia.Styling;
+using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.Update;
-using PicView.Avalonia.ViewModels;
 using PicView.Core.Config;
 using PicView.Core.Localization;
-using PicView.Core.ProcessHandling;
 
 namespace PicView.Avalonia.Views;
 
 public partial class AboutView : UserControl
 {
+    public IPlatformSpecificUpdate PlatformUpdate;
+
     public AboutView()
     {
         InitializeComponent();
         Loaded += (_, _) =>
         {
             AppVersion.Text = VersionHelper.GetCurrentVersion();
-            
+
             if (!Settings.Theme.Dark && !Settings.Theme.GlassTheme)
             {
                 if (!Application.Current.TryGetResource("MainTextColor",
@@ -37,14 +37,8 @@ public partial class AboutView : UserControl
                 UpdateButton.Foreground = new SolidColorBrush(color);
                 UpdateButton.Classes.Remove("altHover");
                 UpdateButton.Classes.Add("accentHover");
-                UpdateButton.PointerEntered += (_, _) =>
-                {
-                    UpdateButton.Foreground = Brushes.White;
-                };
-                UpdateButton.PointerExited += (_, _) =>
-                {
-                    UpdateButton.Foreground = new SolidColorBrush(color);
-                };
+                UpdateButton.PointerEntered += (_, _) => { UpdateButton.Foreground = Brushes.White; };
+                UpdateButton.PointerExited += (_, _) => { UpdateButton.Foreground = new SolidColorBrush(color); };
             }
 
             KofiImage.PointerEntered += (_, _) =>
@@ -71,28 +65,22 @@ public partial class AboutView : UserControl
                     KofiImage.Source = drawingImage;
                 }
             };
-            
+
             UpdateButton.Click += async (_, _) =>
             {
-                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-                {
-                    // TODO: replace with auto update service
-                    ProcessHelper.OpenLink("https://PicView.org/avalonia-download");
-                    return;
-                }
                 //Set loading and prevent user from interacting with UI
                 ParentContainer.Opacity = .1;
                 ParentContainer.IsHitTestVisible = false;
                 SpinWaiter.IsVisible = true;
                 try
                 {
-                   var checkIfNewUpdate = await UpdateManager.UpdateCurrentVersion(DataContext as MainViewModel);
-                   UpdateButton.IsVisible = false;
-                   UpdateStatus.IsVisible = true;
-                   if (!checkIfNewUpdate)
-                   {
-                       UpdateStatus.Text = TranslationManager.Translation.NoUpdateFound;
-                   }
+                    var checkIfNewUpdate = await UpdateManager.UpdateCurrentVersion(PlatformUpdate);
+                    UpdateButton.IsVisible = false;
+                    UpdateStatus.IsVisible = true;
+                    if (!checkIfNewUpdate)
+                    {
+                        UpdateStatus.Text = TranslationManager.Translation.NoUpdateFound;
+                    }
                 }
                 finally
                 {

+ 12 - 0
src/PicView.Core/DebugTools/DebugHelper.cs

@@ -0,0 +1,12 @@
+using System.Diagnostics;
+
+namespace PicView.Core.DebugTools;
+
+public static class DebugHelper
+{
+    public static void LogDebug(string className, string methodName, Exception exception)
+    {
+        Debug.WriteLine($"{className}:{methodName} exception {exception.Message}");
+        Debug.WriteLine(Environment.NewLine + exception.StackTrace);
+    }
+}