Просмотр исходного кода

Change infrastructure loading, work in progress

Ruben 4 дней назад
Родитель
Сommit
5b6cdd0426
28 измененных файлов с 1255 добавлено и 125 удалено
  1. 1 0
      src/PicView.Avalonia.MacOS/PlatformUpdate/MacUpdateHelper.cs
  2. 1 1
      src/PicView.Avalonia.MacOS/Views/AboutWindow.axaml.cs
  3. 1 8
      src/PicView.Avalonia.MacOS/Views/MacMainWindow2.axaml.cs
  4. 1 1
      src/PicView.Avalonia.MacOS/Views/MacOSTitlebar.axaml
  5. 1 0
      src/PicView.Avalonia.MacOS/WindowImpl/WindowInitializer.cs
  6. 2 0
      src/PicView.Avalonia.Win32/PlatformUpdate/WinUpdateHelper.cs
  7. 1 0
      src/PicView.Avalonia.Win32/Views/AboutWindow.axaml.cs
  8. 57 68
      src/PicView.Avalonia.Win32/Views/WinMainWindow2.axaml.cs
  9. 12 11
      src/PicView.Avalonia.Win32/Views/WinTitleBar2.axaml
  10. 1 0
      src/PicView.Avalonia.Win32/WindowImpl/WindowInitializer.cs
  11. 832 0
      src/PicView.Avalonia/Functions/FunctionsMapper2.cs
  12. 1 0
      src/PicView.Avalonia/Interfaces/IPlatformSpecificUpdate.cs
  13. 2 14
      src/PicView.Avalonia/UI/MenuManager.cs
  14. 1 0
      src/PicView.Avalonia/Update/UpdateManager.cs
  15. 1 2
      src/PicView.Avalonia/ViewModels/MainViewModel.cs
  16. 1 6
      src/PicView.Avalonia/ViewModels/MainWindowViewModel.cs
  17. 11 0
      src/PicView.Avalonia/ViewModels/TopTitlebarViewModel.cs
  18. 8 8
      src/PicView.Avalonia/Views/UC/Menus/DropDownMenu.axaml
  19. 17 4
      src/PicView.Avalonia/Views/UC/Menus/DropDownMenu.axaml.cs
  20. 43 0
      src/PicView.Core/IPlatform/IPlatformSpecificService.cs
  21. 8 0
      src/PicView.Core/IPlatform/IPlatformSpecificUpdate.cs
  22. 50 0
      src/PicView.Core/IPlatform/IPlatformWindowService.cs
  23. 1 0
      src/PicView.Core/IPlatform/readme.md
  24. 1 1
      src/PicView.Core/Update/InstalledArchitecture.cs
  25. 1 1
      src/PicView.Core/Update/UpdateInfo.cs
  26. 174 0
      src/PicView.Core/Update/UpdateManager.cs
  27. 23 0
      src/PicView.Core/ViewModels/CoreViewModel.cs
  28. 2 0
      src/PicView.Core/ViewModels/DropDownMenuViewModel.cs

+ 1 - 0
src/PicView.Avalonia.MacOS/PlatformUpdate/MacUpdateHelper.cs

@@ -2,6 +2,7 @@ using System.Diagnostics;
 using PicView.Avalonia.Update;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.DebugTools;
+using PicView.Core.Update;
 
 namespace PicView.Avalonia.MacOS.PlatformUpdate;
 

+ 1 - 1
src/PicView.Avalonia.MacOS/Views/AboutWindow.axaml.cs

@@ -4,8 +4,8 @@ using Avalonia.Media;
 using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.MacOS.PlatformUpdate;
 using PicView.Avalonia.UI;
-using PicView.Avalonia.Update;
 using PicView.Avalonia.ViewModels;
+using PicView.Core.Update;
 
 namespace PicView.Avalonia.MacOS.Views;
 

+ 1 - 8
src/PicView.Avalonia.MacOS/Views/MacMainWindow2.axaml.cs

@@ -112,7 +112,7 @@ public partial class MacMainWindow2 : Window
             {
                 if (!dropDownMenu.IsPointerOver)
                 {
-                    vm.MainWindow.IsDropDownMenuVisible.Value = false;
+                    vm.MainWindow.TopTitlebarViewModel.DropDownMenu.IsDropDownMenuVisible.Value = false;
                 }
 
                 if (vm.MainWindow.IsEditableTitlebarOpen.Value && !Titlebar.IsPointerOver)
@@ -213,13 +213,6 @@ public partial class MacMainWindow2 : Window
             {
                 control.DataContext = tab;
             }
-                
-            // Close the source window if it's empty
-            if (parentVm.Tabs.Tabs.Value.Count == 0)
-            {
-                // Close();
-            }
-            // Logic handled, return
             return;
         }
 

+ 1 - 1
src/PicView.Avalonia.MacOS/Views/MacOSTitlebar.axaml

@@ -27,7 +27,7 @@
             BorderBrush="{DynamicResource MainBorderColor}"
             BorderThickness="0"
             Classes="altHover"
-            Command="{CompiledBinding MainWindow.ToggleTabsCommand}"
+            Command="{CompiledBinding MainWindow.TopTitlebarViewModel.ToggleMenu}"
             Data="{StaticResource FilledArrowDownGeometry}"
             DockPanel.Dock="Right"
             Foreground="{DynamicResource MainTextColor}"

+ 1 - 0
src/PicView.Avalonia.MacOS/WindowImpl/WindowInitializer.cs

@@ -14,6 +14,7 @@ using PicView.Avalonia.Update;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Config;
+using PicView.Core.Update;
 using PicView.Core.ViewModels;
 using R3;
 

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

@@ -4,6 +4,8 @@ using Microsoft.Win32;
 using PicView.Avalonia.Update;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.DebugTools;
+using PicView.Core.Update;
+using UpdateManager = PicView.Avalonia.Update.UpdateManager;
 
 namespace PicView.Avalonia.Win32.PlatformUpdate;
 

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

@@ -8,6 +8,7 @@ using PicView.Avalonia.UI;
 using PicView.Avalonia.Update;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Win32.PlatformUpdate;
+using PicView.Core.Update;
 
 namespace PicView.Avalonia.Win32.Views;
 

+ 57 - 68
src/PicView.Avalonia.Win32/Views/WinMainWindow2.axaml.cs

@@ -34,10 +34,9 @@ namespace PicView.Avalonia.Win32.Views;
 
 public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatformWindowService
 {
-    private readonly AvaloniaRenderingFrameProvider _frameProvider;
-    private readonly CompositeDisposable _disposables = new();
-    
     private static WindowInitializer? _windowInitializer;
+    private readonly CompositeDisposable _disposables = new();
+    private readonly AvaloniaRenderingFrameProvider _frameProvider;
     private TaskbarProgress? _taskbarProgress;
     private MainViewModel? _vm;
 
@@ -46,10 +45,10 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
         // initialize RenderingFrameProvider
         _frameProvider = new AvaloniaRenderingFrameProvider(GetTopLevel(this)!);
         UIHelper.SetFrameProvider(_frameProvider);
-        
+
         Initialization();
     }
-    
+
     public WinMainWindow2(bool mainWindowAlreadyExists)
     {
         if (mainWindowAlreadyExists)
@@ -57,17 +56,29 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
             // initialize RenderingFrameProvider
             _frameProvider = new AvaloniaRenderingFrameProvider(GetTopLevel(this)!);
             UIHelper.SetFrameProvider(_frameProvider);
-            
+
+            _vm = new MainViewModel(this, this);
+            DataContext = _vm;
+
+            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            ThemeManager.DetermineTheme(Application.Current, true);
+            StartUpHelper2.StartUpBlank(_vm, true, false, desktop, this);
+            _windowInitializer = new WindowInitializer();
+
             Initialization();
             return;
         }
-        
+
         var settingsExists = LoadSettings();
-        
+
         // initialize RenderingFrameProvider
         _frameProvider = new AvaloniaRenderingFrameProvider(GetTopLevel(this)!);
         UIHelper.SetFrameProvider(_frameProvider);
-        
+
         Initialization();
         WindowInitialization(settingsExists);
     }
@@ -75,16 +86,10 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
     private void WindowInitialization(bool settingsExists)
     {
         TranslationManager.Init();
-        
-        _vm = new MainViewModel(this, this)
-        {
-            MainWindow =
-            {
-                TopTitlebarViewModel = new TopTitlebarViewModel()
-            }
-        };
+
+        _vm = new MainViewModel(this, this);
         DataContext = _vm;
-        
+
         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
         {
             return;
@@ -110,7 +115,7 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
             {
                 return;
             }
-            
+
             Observable.EveryValueChanged(MainTabControl.Items, x => x.Count).Subscribe(count =>
             {
                 vm.Tabs.IsTabPanelVisible.Value = count > 1;
@@ -162,11 +167,11 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
                         break;
                 }
             });
-            
+
             MainTabControl.TabDetached += MainTabControlOnTabDetached;
             MainTabControl.TabCreated += MainTabControlOnTabCreated;
             MainTabControl.SelectionChanged += MainTabControlOnSelectionChanged;
-            
+
             var dropDownMenu = new DropDownMenu
             {
                 Name = "DropDownMenu",
@@ -177,15 +182,10 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
                 ZIndex = 2
             };
             MainPanel.Children.Add(dropDownMenu);
-            
+
             // Close tabMenu when clicking outside of it
             PointerPressed += (_, _) =>
             {
-                if (!dropDownMenu.IsPointerOver)
-                {
-                    vm.MainWindow.IsDropDownMenuVisible.Value = false;
-                }
-
                 if (vm.MainWindow.IsEditableTitlebarOpen.Value && !Titlebar.IsPointerOver)
                 {
                     Titlebar.EditableTitlebar.CloseTitlebar();
@@ -193,7 +193,7 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
             };
         };
     }
-    
+
     private void MainTabControlOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
     {
         if (DataContext is not MainViewModel vm)
@@ -268,45 +268,31 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
 
             // Need to properly remove it from the previous location
             parentVm.Tabs.RemoveTab(tab);
-            
+
             // Add to new window (if not already added by drag preview)
             if (!targetVm.Tabs.Tabs.Value.Contains(tab))
             {
                 targetVm.Tabs.Tabs.Value.Add(tab);
             }
+
             targetVm.Tabs.SelectTab(tab);
-                
+
             // Update context
             tab.ParentWindowContext = targetVm;
-                
+
             // Refresh bindings
             if (tab.CurrentView.CurrentValue is Control control)
             {
                 control.DataContext = tab;
             }
-                
-            // Close the source window if it's empty
-            if (parentVm.Tabs.Tabs.Value.Count == 0)
-            {
-                // Close();
-            }
-            // Logic handled, return
+
             return;
         }
 
         // 3. Fallback: Create a new window (Detaching behavior)
         Task.Run(() =>
         {
-            var newVm = new MainViewModel(parentVm.PlatformService, parentVm.PlatformWindowService)
-            {
-                Tabs = new TabOverviewViewModel(tab),
-                MainWindow =
-                {
-                    TopTitlebarViewModel = new TopTitlebarViewModel()
-                }
-            };
-            tab.ParentWindowContext = newVm;
-
+            MainViewModel? newVm = null;
             Dispatcher.UIThread.Invoke(() =>
             {
                 // Create a new window with the detached tab
@@ -314,19 +300,20 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
                 {
                     Position = new PixelPoint(e.ScreenPosition.X - 100, e.ScreenPosition.Y - 50),
                     Width = Width,
-                    Height = Height,
-                    DataContext = newVm
+                    Height = Height
                 };
 
-                StartUpHelper2.StartUpBlank(newVm, settingsExists: true, setPos: false, desktop, newWindow);
+                newVm = newWindow.DataContext as MainViewModel;
+
                 // Fix null DataContext
                 if (tab.CurrentView.CurrentValue is Control control)
                 {
                     control.DataContext = tab;
                 }
-
             }, DispatcherPriority.Send);
 
+            newVm.Tabs.Tabs.Value[0] = tab;
+
             // Initialize the NEW window's tabs with the OLD window's services
             // This ensures both windows share the same memory cache
             if (parentVm.Tabs.SharedCache is not { } cache ||
@@ -340,7 +327,8 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
 
             if (newVm.Tabs.ActiveTab.CurrentValue.ImageIterator?.Files?.Count > 0)
             {
-                newVm.Tabs.LoadAndInitializeFromPath(newVm.Tabs.ActiveTab.CurrentValue.ImageIterator.Files, gallery, nav,
+                newVm.Tabs.LoadAndInitializeFromPath(newVm.Tabs.ActiveTab.CurrentValue.ImageIterator.Files, gallery,
+                    nav,
                     cache, thumb, fileWatcher);
             }
             else
@@ -386,16 +374,17 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
         _disposables.Dispose();
         base.OnClosed(e);
     }
-    
-        #region Interface Implementations
-        
-        public int CombinedTitleButtonsWidth
-        {
-            get => (int)(Settings.WindowProperties.Maximized && !Settings.WindowProperties.Fullscreen
-                ? OffScreenMargin.Left + OffScreenMargin.Right + field : field);
-            set;
-        } = 185;
-    
+
+    #region Interface Implementations
+
+    public int CombinedTitleButtonsWidth
+    {
+        get => (int)(Settings.WindowProperties.Maximized && !Settings.WindowProperties.Fullscreen
+            ? OffScreenMargin.Left + OffScreenMargin.Right + field
+            : field);
+        set;
+    } = 185;
+
     public Task<bool> DeleteFile(string path, bool recycle) =>
         Task.Run(() => WinFileHelper.DeleteFile(path, recycle));
 
@@ -539,7 +528,7 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
     #endregion
 
     #region Window interface implementations
-    
+
     public void ShowAboutWindow() =>
         _windowInitializer?.ShowAboutWindow(_vm);
 
@@ -556,7 +545,7 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
         _windowInitializer?.ShowSingleImageResizeWindow(_vm);
 
     public async Task ShowBatchResizeWindow() =>
-       await _windowInitializer?.ShowBatchResizeWindow(_vm);
+        await _windowInitializer?.ShowBatchResizeWindow(_vm);
 
     public void ShowEffectsWindow() =>
         _windowInitializer?.ShowEffectsWindow(_vm);
@@ -567,7 +556,7 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
     /// <inheritdoc />
     public async Task Maximize(bool saveSetting = true) =>
         await Win32Window.Maximize(this, _vm, saveSetting);
-    
+
     /// <inheritdoc />
     public async Task MaximizeRestore(bool saveSetting = true) =>
         await Win32Window.ToggleMaximize(this, _vm, saveSetting);
@@ -575,11 +564,11 @@ public partial class WinMainWindow2 : Window, IPlatformSpecificService, IPlatfor
     /// <inheritdoc />
     public async Task Fullscreen(bool saveSetting = true) =>
         await Win32Window.Fullscreen(this, _vm, saveSetting);
-    
+
     /// <inheritdoc />
     public async Task ToggleFullscreen(bool saveSetting = true) =>
         await Win32Window.ToggleFullscreen(this, _vm, saveSetting);
-    
+
     /// <inheritdoc />
     public async Task Restore() =>
         await Win32Window.Restore(this, _vm);

+ 12 - 11
src/PicView.Avalonia.Win32/Views/WinTitleBar2.axaml

@@ -1051,7 +1051,6 @@
             </Menu>
             <DockPanel>
 
-
                 <Border
                     Background="{DynamicResource WindowButtonBackgroundColor}"
                     BorderBrush="{DynamicResource MainBorderColor}"
@@ -1081,22 +1080,23 @@
                     x:Name="MenuButton" />
 
 
-                <customControls:IconButton
-                    Background="{DynamicResource WindowButtonBackgroundColor}"
+                <Button
+                    Background="Transparent"
                     BorderBrush="{DynamicResource MainBorderColor}"
-                    BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding Tools.ShowSearchCommand}"
+                    CornerRadius="6"
                     DockPanel.Dock="Left"
                     Foreground="{DynamicResource MainTextColor}"
-                    Icon="{StaticResource SearchImage}"
-                    IconHeight="13"
-                    IconWidth="13"
-                    IsRepeatEnabled="False"
                     IsTabStop="False"
-                    Margin="0"
+                    Margin="2,3,7,3"
                     Width="30"
-                    x:Name="SearchButton" />
+                    x:Name="SearchButton">
+                    <Image
+                        Height="13"
+                        Source="{StaticResource SearchImage}"
+                        Width="13" />
+                </Button>
 
                 <StackPanel
                     DockPanel.Dock="Right"
@@ -1172,7 +1172,8 @@
                     Background="Transparent"
                     BorderBrush="{DynamicResource MainBorderColor}"
                     Classes="noBorderHover"
-                    Command="{CompiledBinding MainWindow.ToggleTabsCommand}"
+                    Command="{CompiledBinding MainWindow.TopTitlebarViewModel.ToggleDropDownMenuCommand}"
+                    CommandParameter="{CompiledBinding}"
                     CornerRadius="6"
                     DockPanel.Dock="Right"
                     IsTabStop="False"

+ 1 - 0
src/PicView.Avalonia.Win32/WindowImpl/WindowInitializer.cs

@@ -14,6 +14,7 @@ using PicView.Avalonia.Win32.Printing;
 using PicView.Avalonia.Win32.Views;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Config;
+using PicView.Core.Update;
 using PicView.Core.ViewModels;
 using R3;
 

+ 832 - 0
src/PicView.Avalonia/Functions/FunctionsMapper2.cs

@@ -0,0 +1,832 @@
+// using Avalonia;
+// using Avalonia.Controls.ApplicationLifetimes;
+// using Avalonia.Threading;
+// using PicView.Avalonia.Clipboard;
+// using PicView.Avalonia.ColorManagement;
+// using PicView.Avalonia.Crop;
+// using PicView.Avalonia.FileSystem;
+// using PicView.Avalonia.Gallery;
+// using PicView.Avalonia.ImageHandling;
+// using PicView.Avalonia.ImageTransformations.Rotation;
+// using PicView.Avalonia.Navigation;
+// using PicView.Avalonia.SettingsManagement;
+// using PicView.Avalonia.UI;
+// using PicView.Avalonia.ViewModels;
+// using PicView.Avalonia.Views.UC;
+// using PicView.Avalonia.Input;
+// using PicView.Avalonia.WindowBehavior;
+// using PicView.Core.FileHistory;
+// using PicView.Core.FileSorting;
+// using PicView.Core.Keybindings;
+// using PicView.Core.Navigation;
+// using PicView.Core.ProcessHandling;
+//
+// namespace PicView.Avalonia.Functions;
+//
+// /// <summary>
+// /// Used to map functions to their names, used for keyboard shortcuts
+// /// </summary>
+// public class FunctionsMapper2
+// {
+//
+//     public FunctionsMapper2(MainViewModel? vm)
+//     {
+//         _vm = vm;
+//     }
+//
+//     private static MainViewModel? _vm;
+//
+//     public static Func<ValueTask>? GetFunctionByName(string functionName)
+//     {
+//         // Remember to have exact matching names, or it will be null
+//         return functionName switch
+//         {
+//             // Navigation values
+//             "Next" => Next,
+//             "Prev" => Prev,
+//             
+//             "NextFolder" => NextFolder,
+//             "PrevFolder" => PrevFolder,
+//             
+//             "Up" => Up,
+//             "Down" => Down,
+//             
+//             "Last" => Last,
+//             "First" => First,
+//             
+//             "Next10" => Next10,
+//             "Prev10" => Prev10,
+//             
+//             "Next100" => Next100,
+//             "Prev100" => Prev100,
+//
+//             "Search" => Search,
+//             
+//             // Rotate
+//             "RotateLeft" => RotateLeft,
+//             "RotateRight" => RotateRight,
+//
+//             // Scroll
+//             "ScrollUp" => ScrollUp,
+//             "ScrollDown" => ScrollDown,
+//             "ScrollToTop" => ScrollToTop,
+//             "ScrollToBottom" => ScrollToBottom,
+//
+//             // Zoom
+//             "ZoomIn" => ZoomIn,
+//             "ZoomOut" => ZoomOut,
+//             "ResetZoom" => ResetZoom,
+//             "ChangeCtrlZoom" => ChangeCtrlZoom,
+//
+//             // Toggles
+//             "ToggleScroll" => ToggleScroll,
+//             "ToggleLooping" => ToggleLooping,
+//             "ToggleGallery" => ToggleGallery,
+//
+//             // Scale Window
+//             "AutoFitWindow" => AutoFitWindow,
+//             "NormalWindow" => NormalWindow,
+//
+//             // Window functions
+//             "Fullscreen" => Fullscreen,
+//             "ToggleFullscreen" => ToggleFullscreen,
+//             "SetTopMost" => SetTopMost,
+//             "Close" => Close,
+//             "ToggleInterface" => ToggleInterface,
+//             "NewWindow" => NewWindow,
+//             "Center" => Center,
+//             "Maximize" => Maximize,
+//             "Restore" => Restore,
+//
+//             // Windows
+//             "AboutWindow" => AboutWindow,
+//             "EffectsWindow" => EffectsWindow,
+//             "ImageInfoWindow" => ImageInfoWindow,
+//             "ResizeWindow" => ResizeWindow,
+//             "SettingsWindow" => SettingsWindow,
+//             "KeybindingsWindow" => KeybindingsWindow,
+//             "BatchResizeWindow" => BatchResizeWindow,
+//             "ConvertWindow" => ConvertWindow,
+//
+//             // Open functions
+//             "Open" => Open,
+//             "OpenWith" => OpenWith,
+//             "OpenInExplorer" => OpenInExplorer,
+//             "Save" => Save,
+//             "SaveAs" => SaveAs,
+//             "Print" => Print,
+//             "Reload" => Reload,
+//
+//             // Copy functions
+//             "CopyFile" => CopyFile,
+//             "CopyFilePath" => CopyFilePath,
+//             "CopyImage" => CopyImage,
+//             "CopyBase64" => CopyBase64,
+//             "DuplicateFile" => DuplicateFile,
+//             "CutFile" => CutFile,
+//             "Paste" => Paste,
+//
+//             // File functions
+//             "DeleteFile" => DeleteFile,
+//             "DeleteFilePermanently" => DeleteFilePermanently,
+//             "Rename" => Rename,
+//             "ShowFileProperties" => ShowFileProperties,
+//             "ShowSettingsFile" => ShowSettingsFile,
+//             "ShowKeybindingsFile" => ShowKeybindingsFile,
+//             
+//             // Sorting functions
+//             "SortFilesByName" => SortFilesByName,
+//             "SortFilesByCreationTime" => SortFilesByCreationTime,
+//             "SortFilesByLastAccessTime" => SortFilesByLastAccessTime,
+//             "SortFilesByLastWriteTime" => SortFilesByLastWriteTime,
+//             "SortFilesBySize" => SortFilesBySize,
+//             "SortFilesByExtension" => SortFilesByExtension,
+//             "SortFilesRandomly" => SortFilesRandomly,
+//             
+//             "SortFilesAscending" => SortFilesAscending,
+//             "SortFilesDescending" => SortFilesDescending,
+//             
+//             // Image functions
+//             "ResizeImage" => ResizeImage,
+//             "Crop" => Crop,
+//             "Flip" => Flip,
+//             "OptimizeImage" => OptimizeImage,
+//             "Stretch" => Stretch,
+//
+//             // Set stars
+//             "Set0Star" => Set0Star,
+//             "Set1Star" => Set1Star,
+//             "Set2Star" => Set2Star,
+//             "Set3Star" => Set3Star,
+//             "Set4Star" => Set4Star,
+//             "Set5Star" => Set5Star,
+//             
+//             // Background and lock screen image
+//             "SetAsLockScreen" => SetAsLockScreen,
+//             "SetAsLockscreenCentered" => SetAsLockscreenCentered,
+//             "SetAsWallpaper" => SetAsWallpaper,
+//             "SetAsWallpaperFitted" => SetAsWallpaperFitted,
+//             "SetAsWallpaperStretched" => SetAsWallpaperStretched,
+//             "SetAsWallpaperFilled" => SetAsWallpaperFilled,
+//             "SetAsWallpaperCentered" => SetAsWallpaperCentered,
+//             "SetAsWallpaperTiled" => SetAsWallpaperTiled,
+//             
+//             // Tabs
+//             "NewTab" => NewTab,
+//             "CloseTab" => CloseTab,
+//
+//             // Misc
+//             "ChangeBackground" => ChangeBackground,
+//             "SideBySide" => SideBySide,
+//             "GalleryClick" => GalleryClick,
+//             "Slideshow" => Slideshow,
+//             "ColorPicker" => ColorPicker,
+//             "Restart" => Restart,
+//             "OpenDrowDownMenu" => OpenDropDownMenu,
+//
+//             _ => null
+//         };
+//     }
+//
+//     #region Functions
+//
+//     #region Navigation, zoom and rotation
+//
+//     /// <inheritdoc cref="Core.ViewModels.TabOverviewViewModel.NextFile()" />
+//     public static async ValueTask Next() =>
+//         await _vm.Tabs.NavigateDirectionalAsync(MainKeyboardShortcuts.IsKeyHeldDown,
+//             NavigateTo.Next).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="NavigationManager.NavigateBetweenDirectories(bool, MainViewModel)" />
+//     public static async ValueTask NextFolder() =>
+//         await NavigationManager.NavigateBetweenDirectories(true, _vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="NavigationManager.NavigateFirstOrLast(bool, MainViewModel)" />
+//     public static async ValueTask Last() =>
+//         await _vm.Tabs.FirstFile().ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="Core.ViewModels.TabOverviewViewModel.PrevFile()" />
+//     public static async ValueTask Prev() =>
+//         await _vm.Tabs.NavigateDirectionalAsync(MainKeyboardShortcuts.IsKeyHeldDown,
+//             NavigateTo.Previous).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="NavigationManager.NavigateBetweenDirectories(bool, MainViewModel)" />
+//     public static async ValueTask PrevFolder() =>
+//         await NavigationManager.NavigateBetweenDirectories(false, _vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="NavigationManager.NavigateFirstOrLast(bool, MainViewModel)" />
+//     public static async ValueTask First() =>
+//         await _vm.Tabs.FirstFile().ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="Core.ViewModels.TabOverviewViewModel.Next10()" />
+//     public static async ValueTask Next10() =>
+//         await _vm.Tabs.Next10().ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="Core.ViewModels.TabOverviewViewModel.Next100()" />
+//     public static async ValueTask Next100() =>
+//         await _vm.Tabs.Next100().ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="NavigationManager.Prev10(MainViewModel)" />
+//     public static async ValueTask Prev10() =>
+//         await _vm.Tabs.Prev10().ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="NavigationManager.Prev100(MainViewModel)" />
+//     public static async ValueTask Prev100() =>
+//         await NavigationManager.Prev100(_vm).ConfigureAwait(false);
+//     
+//     public static void StopRepeatedNavigation()
+//     {
+//         _vm?.Tabs?.StopRepeatedNavigation();
+//     }
+//
+//     public static async ValueTask Search() =>
+//         await Dispatcher.UIThread.InvokeAsync(DialogManager.AddFileSearchDialog);
+//     
+//
+//     /// <inheritdoc cref="RotationNaRotationNavigationp(MainViewModel)" />
+//     public static async ValueTask Up() =>
+//         await RotationNavigation.NavigateUp(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="RotationNavigation.RotateRight(MainViewModel)" />
+//     public static async ValueTask RotateRight() =>
+//         await RotationNavigation.RotateRight(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="RotationNavigation.RotateLeft(MainViewModel)" />
+//     public static async ValueTask RotateLeft() =>
+//         await RotationNavigation.RotateLeft(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="RotationNavigation.NavigateDown(MainViewModel)" />
+//     public static async ValueTask Down() =>
+//         await RotationNavigation.NavigateDown(_vm).ConfigureAwait(false);
+//     
+//     public static async ValueTask ScrollDown()
+//     {
+//         // TODO: ImageViewer Needs refactor
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             _vm.ImageViewer.ImageScrollViewer.LineDown();
+//         });
+//     }
+//     
+//     public static async ValueTask ScrollUp()
+//     {
+//         // TODO: ImageViewer Needs refactor
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             _vm.ImageViewer.ImageScrollViewer.LineUp();
+//         });
+//     }
+//
+//     public static async ValueTask ScrollToTop()
+//     {
+//         // TODO: ImageViewer Needs refactor
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             _vm.ImageViewer.ImageScrollViewer.ScrollToHome();
+//         });
+//     }
+//
+//     public static async ValueTask ScrollToBottom()
+//     {
+//         // TODO: ImageViewer Needs refactor
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             _vm.ImageViewer.ImageScrollViewer.ScrollToEnd();
+//         });
+//     }
+//
+//     public static ValueTask ZoomIn()
+//     {
+//         if (_vm.Tabs.ActiveTab.CurrentValue.CurrentView.CurrentValue is ImageViewer2 imageViewer)
+//         {
+//             imageViewer.ZoomIn();
+//         }
+//         return ValueTask.CompletedTask;
+//     }
+//
+//     public static ValueTask ZoomOut()
+//     {
+//         if (_vm.Tabs.ActiveTab.CurrentValue.CurrentView.CurrentValue is ImageViewer2 imageViewer)
+//         {
+//             imageViewer.ZoomOut();
+//         }
+//         return ValueTask.CompletedTask;
+//     }
+//
+//     public static async ValueTask ResetZoom()
+//     {
+//         // TODO: ImageViewer Needs refactor
+//         if (_vm is null)
+//         {
+//             return;
+//         }
+//
+//         await Dispatcher.UIThread.InvokeAsync(() => _vm.ImageViewer.ResetZoom(Settings.Zoom.IsZoomAnimated));
+//     }
+//     
+//     #endregion
+//
+//     #region Toggle UI functions
+//
+//     /// <inheritdoc cref="SettingsUpdater.ToggleScroll(MainViewModel)" />
+//     public static async ValueTask ToggleScroll() =>
+//         await SettingsUpdater.ToggleScroll(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="SettingsUpdater.ToggleCtrlZoom(MainViewModel)" />
+//     public static async ValueTask ChangeCtrlZoom() =>
+//         await SettingsUpdater.ToggleCtrlZoom(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="SettingsUpdater.ToggleLooping(MainViewModel)" />
+//     public static async ValueTask ToggleLooping() =>
+//         await SettingsUpdater.ToggleLooping(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="HideInterfaceLogic.ToggleUI(MainViewModel)" />
+//     public static async ValueTask ToggleInterface() =>
+//         await HideInterfaceLogic.ToggleUI(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="SettingsUpdater.ToggleSubdirectories(MainViewModel)" />
+//     public static async ValueTask ToggleSubdirectories() =>
+//         await SettingsUpdater.ToggleSubdirectories(vm: _vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="HideInterfaceLogic.ToggleBottomToolbar(MainViewModel)" />
+//     public static async ValueTask ToggleBottomToolbar() =>
+//         await HideInterfaceLogic.ToggleBottomToolbar(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="SettingsUpdater.ToggleValueTaskbarProgress(MainViewModel)" />
+//     public static async ValueTask ToggleTaskbarProgress() =>
+//         await SettingsUpdater.ToggleTaskbarProgress(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="SettingsUpdater.ToggleConstrainBackgroundColor(MainViewModel)" />
+//     public static async ValueTask ToggleConstrainBackgroundColor() =>
+//         await SettingsUpdater.ToggleConstrainBackgroundColor(_vm).ConfigureAwait(false);
+//
+//     public static ValueTask OpenDropDownMenu()
+//     {
+//         _vm.MainWindow.TopTitlebarViewModel.OpenMenu();
+//         return ValueTask.CompletedTask;
+//     }
+//     
+//     public static ValueTask ToggleDropDownMenu()
+//     {
+//         _vm.MainWindow.TopTitlebarViewModel.ToggleMenu();
+//         return ValueTask.CompletedTask;
+//     }
+//         
+//     
+//     #endregion
+//
+//     #region Gallery functions
+//
+//     /// <inheritdoc cref="GalleryFunctions.ToggleGallery(MainViewModel)" />
+//     public static async ValueTask ToggleGallery() =>
+//         await Task.Run(() => GalleryFunctions.ToggleGallery(_vm));
+//
+//     /// <inheritdoc cref="GalleryFunctions.OpenCloseBottomGallery(MainViewModel)" />
+//     public static async ValueTask OpenCloseBottomGallery() =>
+//         await Task.Run(() => GalleryFunctions.OpenCloseBottomGallery(_vm));
+//     
+//     /// <inheritdoc cref="GalleryFunctions.CloseGallery(MainViewModel)" />
+//     public static ValueTask CloseGallery()
+//     {
+//         GalleryFunctions.CloseGallery(_vm);
+//         return ValueTask.CompletedTask;
+//     }
+//
+//     /// <inheritdoc cref="GalleryNavigation.GalleryClick(MainViewModel)" />
+//     public static async ValueTask GalleryClick() =>
+//         await GalleryNavigation.GalleryClick(_vm).ConfigureAwait(false);
+//
+//     #endregion
+//     
+//     #region Windows and window functions
+//
+//     public static async Task ShowStartUpMenu()
+//     {
+//         //TODO: Needs refactor, add async overload for ShowStartUpMenu
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             ErrorHandling.ShowStartUpMenu(_vm);
+//         });
+//     }
+//     
+//     /// <inheritdoc cref="DialogManager.HandleShouldClosing" />
+//     public static async ValueTask Close() =>
+//         await DialogManager.HandleShouldClosing(_vm).ConfigureAwait(false);
+//
+//     public static async ValueTask Center() =>
+//         await UIHelper.CenterAsync(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="Interfaces.IPlatformWindowService.MaximizeRestore" />
+//     public static async ValueTask Maximize()
+//     {
+//         await _vm.PlatformWindowService.MaximizeRestore();
+//     }
+//     
+//     /// <inheritdoc cref="Interfaces.IPlatformWindowService.Restore" />
+//     public static async ValueTask Restore()
+//     {
+//         await _vm.PlatformWindowService.Restore();
+//     }
+//
+//     /// <inheritdoc cref="ProcessHelper.StartNewProcess()" />
+//     public static async ValueTask NewWindow() =>
+//         await Task.Run(ProcessHelper.StartNewProcess).ConfigureAwait(false);
+//
+//     public static async ValueTask AboutWindow() =>
+//         await Dispatcher.UIThread.InvokeAsync(() => _vm?.PlatformWindowService?.ShowAboutWindow());
+//
+//     public static async ValueTask ConvertWindow() =>
+//         await Dispatcher.UIThread.InvokeAsync(() => _vm?.PlatformWindowService?.ShowConvertWindow());
+//
+//     public static async ValueTask KeybindingsWindow() =>
+//         await Dispatcher.UIThread.InvokeAsync(() => _vm?.PlatformWindowService?.ShowKeybindingsWindow());
+//
+//     public static async ValueTask EffectsWindow() =>
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//             _vm?.PlatformWindowService?.ShowEffectsWindow());
+//
+//     public static async ValueTask ImageInfoWindow() =>
+//         await _vm?.PlatformWindowService?.ShowImageInfoWindow();
+//
+//     public static async ValueTask ResizeWindow() =>
+//         await Dispatcher.UIThread.InvokeAsync(() => _vm?.PlatformWindowService?.ShowSingleImageResizeWindow());
+//
+//     public static async ValueTask BatchResizeWindow() =>
+//         await _vm?.PlatformWindowService?.ShowBatchResizeWindow();
+//
+//     public static async ValueTask SettingsWindow() =>
+//         await _vm?.PlatformWindowService?.ShowSettingsWindow();
+//
+//     #endregion Windows
+//
+//     #region Image Scaling and Window Behavior
+//     
+//     /// <inheritdoc cref="WindowFunctions.Stretch(MainViewModel)" />
+//     public static async ValueTask Stretch() =>
+//         await WindowFunctions.Stretch(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="WindowFunctions.ToggleAutoFit(MainViewModel)" />
+//     public static async ValueTask AutoFitWindow() =>
+//         await WindowFunctions.ToggleAutoFit(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="WindowFunctions.NormalWindow(MainViewModel)" />
+//     public static async ValueTask NormalWindow() =>
+//         await WindowFunctions.NormalWindow(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="Interfaces.IPlatformWindowService.ToggleFullscreen" />
+//     public static async ValueTask ToggleFullscreen() =>
+//         await _vm.PlatformWindowService.ToggleFullscreen().ConfigureAwait(false);
+//     
+//     // This shouldn't be here, but keep as alias and backwards compatibility.
+//     public static ValueTask Fullscreen() => ToggleFullscreen();
+//
+//     /// <inheritdoc cref="WindowFunctions.ToggleTopMost(MainViewModel)" />
+//     public static async ValueTask SetTopMost() =>
+//
+//         await WindowFunctions.ToggleTopMost(_vm).ConfigureAwait(false);
+//
+//     #endregion
+//
+//     #region File funnctions
+//
+//     /// <inheritdoc cref="NavigationManager.LoadPicFromStringAsync(string, MainViewModel)" />
+//     public static async Task OpenLastFile() =>
+//         await NavigationManager.LoadLastFileAsync(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="NavigationManager.LoadPicFromStringAsync(string, MainViewModel)" />
+//     public static async Task OpenPreviousFileHistoryEntry() =>
+//         await NavigationManager.LoadPicFromStringAsync(FileHistoryManager.GetPreviousEntry(), _vm).ConfigureAwait(false);
+//    
+//     /// <inheritdoc cref="NavigationManager.LoadPicFromStringAsync(string, MainViewModel)" />
+//     public static async Task OpenNextFileHistoryEntry() =>
+//         await NavigationManager.LoadPicFromStringAsync(FileHistoryManager.GetNextEntry(), _vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileManager.Print(string, MainViewModel)" />
+//     public static async ValueTask Print() =>
+//         await FileManager.Print(_vm.PicViewer.FileInfo?.CurrentValue?.FullName, _vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FilePicker.SelectAndLoadFile(MainViewModel)" />
+//     public static async ValueTask Open() =>
+//         await FilePicker.SelectAndLoadFile(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileManager.OpenWith(string, MainViewModel)" />
+//     public static async ValueTask OpenWith() =>
+//         await Task.Run(() => _vm?.PlatformService?.OpenWith(_vm.PicViewer.FileInfo?.CurrentValue?.FullName))
+//             .ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileManager.LocateOnDisk(string, MainViewModel)" />
+//     public static async ValueTask OpenInExplorer()=>
+//         await Task.Run(() => _vm?.PlatformService?.LocateOnDisk(_vm.Tabs.ActiveTab.CurrentValue.Model.CurrentValue.FileInfo?.FullName))
+//             .ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileSaverHelper.SaveCurrentFile(MainViewModel)" />
+//     public static async ValueTask Save() =>
+//         await FileSaverHelper.SaveCurrentFile(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileSaverHelper.SaveFileAs(MainViewModel)" />
+//     public static async ValueTask SaveAs() =>
+//         await FileSaverHelper.SaveFileAs(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
+//     public static async ValueTask DeleteFile() =>
+//         await FileManager
+//             .DeleteFileWithOptionalDialog(true, _vm.PicViewer?.FileInfo?.CurrentValue?.FullName, _vm.PlatformService)
+//             .ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
+//     public static async ValueTask DeleteFilePermanently() =>
+//         await FileManager
+//             .DeleteFileWithOptionalDialog(false, _vm.PicViewer?.FileInfo?.CurrentValue?.FullName, _vm.PlatformService)
+//             .ConfigureAwait(false);
+//
+//     public static async ValueTask Rename()
+//     {
+//         // TODO: Needs refactor for selecting file name
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             UIHelper.GetEditableTitlebar.SelectFileName();
+//         });
+//     }
+//     
+//     /// <inheritdoc cref="FileManager.ShowFileProperties(string, MainViewModel)" />
+//     public static async ValueTask ShowFileProperties() =>
+//         await Task.Run(() => _vm?.PlatformService?.ShowFileProperties(_vm.PicViewer.FileInfo?.CurrentValue.FullName)).ConfigureAwait(false);
+//     
+//     #endregion
+//
+//     #region Copy and Paste functions
+//
+//     /// <inheritdoc cref="ClipboardFileOperations.CopyFileToClipboard(string, MainViewModel)" />
+//     public static async ValueTask CopyFile() =>
+//         await ClipboardFileOperations.CopyFileToClipboard(_vm?.PicViewer.FileInfo?.CurrentValue.FullName).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="ClipboardTextOperations.CopyTextToClipboard(string)" />
+//     public static async ValueTask CopyFilePath() => 
+//         await ClipboardTextOperations.CopyTextToClipboard(_vm?.PicViewer.FileInfo?.CurrentValue.FullName).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="ClipboardImageOperations.CopyImageToClipboard(MainViewModel)" />
+//     public static async ValueTask CopyImage() => 
+//         await ClipboardImageOperations.CopyImageToClipboard(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="ClipboardImageOperations.CopyBase64ToClipboard(string, MainViewModel)" />
+//     public static async ValueTask CopyBase64() =>
+//         await ClipboardImageOperations.CopyBase64ToClipboard(_vm.PicViewer.FileInfo?.CurrentValue.FullName, vm: _vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="ClipboardFileOperations.Duplicate(string, MainViewModel)" />
+//     public static async ValueTask DuplicateFile() => 
+//         await ClipboardFileOperations.Duplicate(_vm.PicViewer.FileInfo?.CurrentValue.FullName, _vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="ClipboardFileOperations.CutFile(string, MainViewModel)" />
+//     public static async ValueTask CutFile() =>
+//         await ClipboardFileOperations.CutFile(_vm.PicViewer.FileInfo.CurrentValue.FullName, _vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="ClipboardPasteOperations.Paste(MainViewModel)" />
+//     public static async ValueTask Paste() =>
+//         await ClipboardPasteOperations.Paste(_vm).ConfigureAwait(false);
+//     
+//     #endregion
+//
+//     #region Image Functions
+//     
+//     /// <inheritdoc cref="BackgroundManager.ChangeBackground(MainViewModel)" />
+//     public static async ValueTask ChangeBackground() =>
+//         await BackgroundManager.ChangeBackgroundAsync(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="SettingsUpdater.ToggleSideBySide(MainViewModel)" />
+//     public static async ValueTask SideBySide() =>
+//         await SettingsUpdater.ToggleSideBySide(_vm).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="ErrorHandling.ReloadAsync(MainViewModel)" />
+//     public static async ValueTask Reload() =>
+//         await ErrorHandling.ReloadAsync(_vm).ConfigureAwait(false);
+//
+//     public static async ValueTask ResizeImage() =>
+//         await ResizeWindow();
+//
+//     /// <inheritdoc cref="CropFunctions.StartCropControl(MainViewModel)" />
+//     public static async ValueTask Crop() =>
+//         await CropFunctions.StartCropControlAsync(_vm).ConfigureAwait(false);
+//
+//     public static async ValueTask Flip() =>
+//         await Dispatcher.UIThread.InvokeAsync(() => RotationNavigation.Flip(_vm));
+//
+//     /// <inheritdoc cref="ImageOptimizer.OptimizeImageAsync(MainViewModel)" />
+//     public static async ValueTask OptimizeImage() =>
+//         await ImageOptimizer.OptimizeImageAsync(_vm).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="Navigation.Slideshow.StartSlideshow(MainViewModel)" />
+//     public static async ValueTask Slideshow() =>
+//         await Navigation.Slideshow.StartSlideshow(_vm).ConfigureAwait(false);
+//
+//     public static ValueTask ColorPicker()
+//     {
+//         throw new NotImplementedException();
+//     }
+//     
+//     #endregion
+//
+//     #region Sorting
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesByName() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.Name).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesByCreationTime() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.CreationTime).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesByLastAccessTime() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.LastAccessTime).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesByLastWriteTime() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.LastWriteTime).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesBySize() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.FileSize).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesByExtension() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.Extension).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, SortFilesBy)" />
+//     public static async ValueTask SortFilesRandomly() =>
+//         await _vm.Tabs.SortAsync(SortFilesBy.Random).ConfigureAwait(false);
+//
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, bool)" />
+//     public static async ValueTask SortFilesAscending() =>
+//         await _vm.Tabs.SortAsync(ascending: true).ConfigureAwait(false);
+//     
+//     /// <inheritdoc cref="FileListManager.UpdateFileList(PicView.Avalonia.Interfaces.IPlatformSpecificService, MainViewModel, bool)" />
+//     public static async ValueTask SortFilesDescending() =>
+//         await _vm.Tabs.SortAsync(ascending: false).ConfigureAwait(false);
+//
+//     #endregion Sorting
+//
+//     #region Rating
+//
+//     public static async ValueTask Set0Star()
+//         => await SetExifRatingHelper.Set0Star(_vm);
+//
+//     public static async ValueTask Set1Star()
+//         => await SetExifRatingHelper.Set1Star(_vm);
+//
+//     public static async ValueTask Set2Star()
+//         => await SetExifRatingHelper.Set2Star(_vm);
+//
+//     public static async ValueTask Set3Star()
+//         => await SetExifRatingHelper.Set3Star(_vm);
+//
+//     public static async ValueTask Set4Star()
+//         => await SetExifRatingHelper.Set4Star(_vm);
+//
+//     public static async ValueTask Set5Star()
+//         => await SetExifRatingHelper.Set5Star(_vm);
+//
+//     #endregion
+//
+//     #region Open GPS link
+//
+//     public static async Task OpenGoogleMaps()
+//     {
+//         // TODO: Needs refactoring into its own method
+//         if (_vm is null)
+//         {
+//             return;
+//         }
+//         if (string.IsNullOrEmpty(_vm.Exif.GoogleLink.CurrentValue))
+//         {
+//             return;
+//         }
+//
+//         await Task.Run(() => ProcessHelper.OpenLink(_vm.Exif.GoogleLink.CurrentValue));
+//     }
+//     
+//     public static async Task OpenBingMaps()
+//     {
+//         // TODO: Needs refactoring into its own method
+//         if (_vm is null)
+//         {
+//             return;
+//         }
+//         if (string.IsNullOrEmpty(_vm.Exif.BingLink.CurrentValue))
+//         {
+//             return;
+//         }
+//
+//         await Task.Run(() => ProcessHelper.OpenLink(_vm.Exif.BingLink.CurrentValue));
+//     }
+//
+//     #endregion
+//
+//     #region Wallpaper and lockscreen image
+//
+//     public static async ValueTask SetAsWallpaper() =>
+//         await SetAsWallpaperFilled();
+//
+//     public static async ValueTask SetAsWallpaperTiled() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsWallpaper(_vm.PicViewer.FileInfo.CurrentValue.FullName, 0)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsWallpaperCentered() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsWallpaper(_vm.PicViewer.FileInfo.CurrentValue.FullName, 1)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsWallpaperStretched() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsWallpaper(_vm.PicViewer.FileInfo.CurrentValue.FullName, 2)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsWallpaperFitted() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsWallpaper(_vm.PicViewer.FileInfo.CurrentValue.FullName, 3)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsWallpaperFilled() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsWallpaper(_vm.PicViewer.FileInfo.CurrentValue.FullName, 4)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsLockscreenCentered() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsLockScreen(_vm.PicViewer.FileInfo.CurrentValue.FullName)).ConfigureAwait(false);
+//     
+//     public static async ValueTask SetAsLockScreen() =>
+//         await Task.Run(() => _vm.PlatformService.SetAsLockScreen(_vm.PicViewer.FileInfo.CurrentValue.FullName)).ConfigureAwait(false);
+//
+//     #endregion
+//
+//     #region Tabs
+//
+//     public static ValueTask NewTab()
+//     {
+//         _vm.Tabs.CreateTab();
+//         return ValueTask.CompletedTask;
+//     }
+//     
+//     public static async ValueTask CloseTab()
+//     {
+//         await _vm.Tabs.CloseTabAsync();
+//     }
+//
+//     #endregion
+//
+//     #region Other settings
+//
+//     /// <inheritdoc cref="SettingsUpdater.ResetSettings(MainViewModel)" />
+//     public static async ValueTask ResetSettings() =>
+//         await SettingsUpdater.ResetSettings(_vm).ConfigureAwait(false);
+//     
+//     public static async ValueTask Restart()
+//     {
+//         // TODO: Needs refactoring into its own method
+//         var openFile = string.Empty;
+//         var getFromArgs = false;
+//         if (_vm?.PicViewer.FileInfo is not null)
+//         {
+//             if (_vm.PicViewer.FileInfo.CurrentValue.Exists)
+//             {
+//                 openFile = _vm.PicViewer.FileInfo.CurrentValue.FullName;
+//             }
+//             else
+//             {
+//                 getFromArgs = true;
+//             }
+//         }
+//         else
+//         {
+//             getFromArgs = true;
+//         }
+//         if (getFromArgs)
+//         {
+//             var args = Environment.GetCommandLineArgs();
+//             if (args is not null && args.Length > 0)
+//             {
+//                 openFile = args[1];
+//             }
+//         }
+//         ProcessHelper.RestartApp(openFile);
+//
+//         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+//         {
+//             Environment.Exit(0);
+//             return;
+//         }
+//         await Dispatcher.UIThread.InvokeAsync(() =>
+//         {
+//             desktop.MainWindow?.Close();
+//         });
+//     }
+//     
+//     public static async ValueTask ShowSettingsFile() =>
+//         await Task.Run(() => _vm?.PlatformService?.OpenWith(CurrentSettingsPath)).ConfigureAwait(false);
+//     
+//     public static async ValueTask ShowKeybindingsFile() =>
+//         await Task.Run(() => _vm?.PlatformService?.OpenWith(KeybindingFunctions.CurrentKeybindingsPath)).ConfigureAwait(false);
+//     
+//     public static async ValueTask ShowRecentHistoryFile() =>
+//         await Task.Run(() => _vm?.PlatformService?.OpenWith(FileHistoryManager.CurrentFileHistoryFile)).ConfigureAwait(false);
+//     
+//     public static async ValueTask ToggleOpeningInSameWindow() =>
+//         await SettingsUpdater.ToggleOpeningInSameWindow(_vm).ConfigureAwait(false);
+//     
+//     public static async ValueTask ToggleFileHistory() =>
+//         await SettingsUpdater.ToggleFileHistory(_vm).ConfigureAwait(false);
+//
+//     #endregion
+//     
+//     #endregion
+// }

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

@@ -1,4 +1,5 @@
 using PicView.Avalonia.Update;
+using PicView.Core.Update;
 
 namespace PicView.Avalonia.Interfaces;
 

+ 2 - 14
src/PicView.Avalonia/UI/MenuManager.cs

@@ -1,5 +1,4 @@
-using System.Runtime.InteropServices;
-using Avalonia;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Layout;
 using PicView.Avalonia.ViewModels;
@@ -47,7 +46,6 @@ public static class MenuManager
         vm.MainWindow.IsImageMenuVisible.Value = false;
         vm.MainWindow.IsSettingsMenuVisible.Value = false;
         vm.MainWindow.IsToolsMenuVisible.Value = false;
-        vm.MainWindow.IsDropDownMenuVisible.Value = false;
     }
 
     /// <summary>
@@ -58,8 +56,7 @@ public static class MenuManager
         return vm.MainWindow.IsFileMenuVisible.CurrentValue ||
                vm.MainWindow.IsImageMenuVisible.CurrentValue ||
                vm.MainWindow.IsSettingsMenuVisible.CurrentValue ||
-               vm.MainWindow.IsToolsMenuVisible.CurrentValue ||
-               vm.MainWindow.IsDropDownMenuVisible.CurrentValue;
+               vm.MainWindow.IsToolsMenuVisible.CurrentValue;
     }
 
     /// <summary>
@@ -81,11 +78,6 @@ public static class MenuManager
     /// Toggles the tools menu
     /// </summary>
     public static void ToggleToolsMenu(MainViewModel vm) => ToggleMenu(vm, MenuType.Tools);
-    
-    /// <summary>
-    /// Toggles the drop down menu
-    /// </summary>
-    public static void ToggleDropDownMenu(MainViewModel vm) => ToggleMenu(vm, MenuType.Tabs);
 
     private static void ToggleMenu(MainViewModel vm, MenuType menuType)
     {
@@ -116,7 +108,6 @@ public static class MenuManager
             MenuType.Image => vm.MainWindow.IsImageMenuVisible.CurrentValue,
             MenuType.Settings => vm.MainWindow.IsSettingsMenuVisible.CurrentValue,
             MenuType.Tools => vm.MainWindow.IsToolsMenuVisible.CurrentValue,
-            MenuType.Tabs => vm.MainWindow.IsDropDownMenuVisible.CurrentValue,
             _ => false
         };
     }
@@ -137,9 +128,6 @@ public static class MenuManager
             case MenuType.Tools:
                 vm.MainWindow.IsToolsMenuVisible.Value = state;
                 break;
-            case MenuType.Tabs:
-                vm.MainWindow.IsDropDownMenuVisible.Value = state;
-                break;
         }
     }
 

+ 1 - 0
src/PicView.Avalonia/Update/UpdateManager.cs

@@ -7,6 +7,7 @@ using PicView.Avalonia.ViewModels;
 using PicView.Core.Config;
 using PicView.Core.DebugTools;
 using PicView.Core.Http;
+using PicView.Core.Update;
 
 namespace PicView.Avalonia.Update;
 

+ 1 - 2
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -31,8 +31,7 @@ public class MainViewModel
     public BatchResizeViewModel? BatchResizeViewModel { get; set; }
     public KeybindingsViewModel? Keybindings { get; set; }
 
-    public TabOverviewViewModel Tabs { get; set; } = new();
-    public DropDownMenuViewModel? DropDownMenu { get; set; }
+    public TabOverviewViewModel Tabs { get; } = new();
 
     public MainViewModel(IPlatformSpecificService? platformSpecificService, IPlatformWindowService? platformWindowService)
     {

+ 1 - 6
src/PicView.Avalonia/ViewModels/MainWindowViewModel.cs

@@ -13,7 +13,7 @@ namespace PicView.Avalonia.ViewModels;
 
 public class MainWindowViewModel : IDisposable
 {
-    public TopTitlebarViewModel? TopTitlebarViewModel { get; set; }
+    public TopTitlebarViewModel TopTitlebarViewModel { get; }  = new();
     
     public bool IsNavigationButtonLeftClicked { get; set; }
     public bool IsNavigationButtonRightClicked { get; set; }
@@ -56,8 +56,6 @@ public class MainWindowViewModel : IDisposable
     public BindableReactiveProperty<bool> CanResize { get; } = new();
 
     public BindableReactiveProperty<UserControl?> CurrentView { get; } = new();
-    
-    public BindableReactiveProperty<bool> IsDropDownMenuVisible { get; } = new();
 
     public BindableReactiveProperty<bool> IsFileMenuVisible { get; } = new();
 
@@ -214,7 +212,6 @@ public class MainWindowViewModel : IDisposable
     #region Menus
 
     public ReactiveCommand CloseMenuCommand { get; } = new(CloseMenus);
-    public ReactiveCommand ToggleTabsCommand { get; } = new(ToggleTabsMenu);
     public ReactiveCommand ToggleFileMenuCommand { get; } = new(ToggleFileMenu);
     public ReactiveCommand ToggleImageMenuCommand { get; } = new(ToggleImageMenu);
     public ReactiveCommand ToggleSettingsMenuCommand { get; } = new(ToggleSettingsMenu);
@@ -222,8 +219,6 @@ public class MainWindowViewModel : IDisposable
 
     private static void CloseMenus(Unit unit) =>
         MenuManager.CloseMenus(UIHelper.GetMainView.DataContext as MainViewModel);
-    private static void ToggleTabsMenu(Unit unit) =>
-        MenuManager.ToggleDropDownMenu(UIHelper.GetMainView.DataContext as MainViewModel);
 
     private static void ToggleFileMenu(Unit unit) =>
         MenuManager.ToggleFileMenu(UIHelper.GetMainView.DataContext as MainViewModel);

+ 11 - 0
src/PicView.Avalonia/ViewModels/TopTitlebarViewModel.cs

@@ -1,12 +1,15 @@
 using PicView.Avalonia.Crop;
 using PicView.Avalonia.UI;
 using PicView.Core.Conversion;
+using PicView.Core.ViewModels;
 using R3;
 
 namespace PicView.Avalonia.ViewModels;
 
 public class TopTitlebarViewModel
 {
+    public DropDownMenuViewModel DropDownMenu { get; } = new();
+    
     public BindableReactiveProperty<bool> IsMainMenuVisible { get; } = new();
     public BindableReactiveProperty<bool> IsEditableTitlebarVisible { get; } = new(true);
     public BindableReactiveProperty<bool> IsGalleryButtonVisible { get; } = new(true);
@@ -15,11 +18,19 @@ public class TopTitlebarViewModel
 
     public ReactiveCommand? ToggleMenuCommand { get; private set; }
     public ReactiveCommand? OpenMenuCommand { get; private set; }
+    
+    public ReactiveCommand? ToggleDropDownMenuCommand { get; private set; }
 
     public TopTitlebarViewModel()
     {
         ToggleMenuCommand = new ReactiveCommand(ToggleMenu);
         OpenMenuCommand = new ReactiveCommand(OpenMenu);
+        ToggleDropDownMenuCommand = new ReactiveCommand(ToggleDropDownMenu);
+    }
+
+    private void ToggleDropDownMenu(Unit unit)
+    {
+        DropDownMenu?.IsDropDownMenuVisible.Value = !DropDownMenu.IsDropDownMenuVisible.CurrentValue;
     }
 
     public void ToggleMenu(Unit unit)

+ 8 - 8
src/PicView.Avalonia/Views/UC/Menus/DropDownMenu.axaml

@@ -1,5 +1,5 @@
 <customControls:AnimatedMenu
-    IsOpen="{CompiledBinding MainWindow.IsDropDownMenuVisible.Value}"
+    IsOpen="{CompiledBinding MainWindow.TopTitlebarViewModel.DropDownMenu.IsDropDownMenuVisible.Value}"
     mc:Ignorable="d"
     x:Class="PicView.Avalonia.Views.UC.Menus.DropDownMenu"
     x:DataType="viewModels:MainViewModel"
@@ -35,7 +35,7 @@
                             BorderBrush="{DynamicResource MainBorderColor}"
                             BorderThickness="0,0,1,0"
                             Classes="noBorderHover"
-                            Command="{CompiledBinding DropDownMenu.OpenGalleryOptions}"
+                            Command="{CompiledBinding MainWindow.TopTitlebarViewModel.DropDownMenu.OpenGalleryOptions}"
                             CornerRadius="0,8,0,0"
                             Data="{StaticResource FilledArrowRightGeometry}"
                             DockPanel.Dock="Right"
@@ -65,7 +65,7 @@
                                 BorderBrush="{DynamicResource MainBorderColor}"
                                 BorderThickness="0,0,1,0"
                                 Classes="noBorderHover"
-                                Command="{CompiledBinding DropDownMenu.CloseGalleryOptions}"
+                                Command="{CompiledBinding MainWindow.TopTitlebarViewModel.DropDownMenu.CloseGalleryOptions}"
                                 CornerRadius="8,0,0,0"
                                 Data="{StaticResource FilledArrowLeftGeometry}"
                                 Foreground="{DynamicResource MainTextColor}"
@@ -151,7 +151,7 @@
                 IconHeight="12"
                 IconMargin="0,0,7,0"
                 IconWidth="12"
-                IsVisible="{CompiledBinding !DropDownMenu.IsGalleryOptionsVisible.Value}"
+                IsVisible="{CompiledBinding !MainWindow.TopTitlebarViewModel.DropDownMenu.IsGalleryOptionsVisible.Value}"
                 Padding="15,7,15,7"
                 Text="{CompiledBinding Translation.ResizeImage.Value}"
                 VerticalAlignment="Top" />
@@ -164,7 +164,7 @@
                 IconHeight="12"
                 IconMargin="0,0,7,0"
                 IconWidth="12"
-                IsVisible="{CompiledBinding !DropDownMenu.IsGalleryOptionsVisible.Value}"
+                IsVisible="{CompiledBinding !MainWindow.TopTitlebarViewModel.DropDownMenu.IsGalleryOptionsVisible.Value}"
                 Padding="15,7,15,7"
                 Text="{CompiledBinding Translation.Effects.Value}"
                 VerticalAlignment="Top" />
@@ -177,7 +177,7 @@
                 IconHeight="12"
                 IconMargin="0,0,7,0"
                 IconWidth="12"
-                IsVisible="{CompiledBinding !DropDownMenu.IsGalleryOptionsVisible.Value}"
+                IsVisible="{CompiledBinding !MainWindow.TopTitlebarViewModel.DropDownMenu.IsGalleryOptionsVisible.Value}"
                 Padding="15,7,15,7"
                 Text="{CompiledBinding Translation.ApplicationShortcuts.Value}"
                 VerticalAlignment="Top" />
@@ -190,7 +190,7 @@
                 IconHeight="12"
                 IconMargin="0,0,7,0"
                 IconWidth="12"
-                IsVisible="{CompiledBinding !DropDownMenu.IsGalleryOptionsVisible.Value}"
+                IsVisible="{CompiledBinding !MainWindow.TopTitlebarViewModel.DropDownMenu.IsGalleryOptionsVisible.Value}"
                 Padding="15,7,15,7"
                 Text="{CompiledBinding Translation.Settings.Value}"
                 VerticalAlignment="Top" />
@@ -203,7 +203,7 @@
                 IconHeight="12"
                 IconMargin="0,0,7,0"
                 IconWidth="12"
-                IsVisible="{CompiledBinding !DropDownMenu.IsGalleryOptionsVisible.Value}"
+                IsVisible="{CompiledBinding !MainWindow.TopTitlebarViewModel.DropDownMenu.IsGalleryOptionsVisible.Value}"
                 Padding="15,7,15,7"
                 Text="{CompiledBinding Translation.About.Value}"
                 VerticalAlignment="Top" />

+ 17 - 4
src/PicView.Avalonia/Views/UC/Menus/DropDownMenu.axaml.cs

@@ -1,3 +1,5 @@
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
 using PicView.Avalonia.CustomControls;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
@@ -10,16 +12,27 @@ public partial class DropDownMenu : AnimatedMenu
 {
     public DropDownMenu()
     {
-        if (UIHelper.GetMainView.DataContext is not MainViewModel vm)
+        InitializeComponent();
+        Loaded += OnLoaded;
+
+    }
+
+    private void OnLoaded(object? sender, RoutedEventArgs e)
+    {
+        if (DataContext is not MainViewModel vm)
         {
             return;
         }
-        vm.DropDownMenu ??= new DropDownMenuViewModel();
-        InitializeComponent();
-        Observable.EveryValueChanged(vm.DropDownMenu, x => x.MenuCarouselIndex.CurrentValue, UIHelper.GetFrameProvider)
+        Observable.EveryValueChanged(vm.MainWindow.TopTitlebarViewModel.DropDownMenu, x => x.MenuCarouselIndex.CurrentValue, UIHelper.GetFrameProvider)
             .Subscribe(i =>
             {
                 MainCarousel.SelectedIndex = i;
             });
     }
+
+    protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromLogicalTree(e);
+        Loaded -= OnLoaded;
+    }
 }

+ 43 - 0
src/PicView.Core/IPlatform/IPlatformSpecificService.cs

@@ -0,0 +1,43 @@
+namespace PicView.Core.IPlatform;
+
+public interface IPlatformSpecificService
+{
+    void SetTaskbarProgress(ulong progress, ulong maximum);
+    void StopTaskbarProgress();
+    void SetCursorPos(int x, int y);
+    
+    void DisableScreensaver();
+    void EnableScreensaver();
+
+    List<FileInfo> GetFiles(FileInfo fileInfo);
+
+    int CompareStrings(string str1, string str2);
+
+    void OpenWith(string path);
+
+    void LocateOnDisk(string path);
+    
+    void ShowFileProperties(string path);
+    
+    void Print(string path);
+    
+    Task SetAsWallpaper(string path, int wallpaperStyle);
+    
+    bool SetAsLockScreen(string path);
+    
+    bool CopyFile(string path);
+    
+    bool CutFile(string path);
+    
+    Task CopyImageToClipboard(object bitmap);
+    
+    Task<object?> GetImageFromClipboard();
+    
+    Task<bool> ExtractWithLocalSoftwareAsync(string path, string tempDirectory);
+
+    string DefaultJsonKeyMap();
+
+    void InitiateFileAssociationService();
+    
+    Task<bool> DeleteFile(string path, bool recycle);
+}

+ 8 - 0
src/PicView.Core/IPlatform/IPlatformSpecificUpdate.cs

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

+ 50 - 0
src/PicView.Core/IPlatform/IPlatformWindowService.cs

@@ -0,0 +1,50 @@
+namespace PicView.Core.IPlatform;
+
+public interface IPlatformWindowService
+{
+    /// <summary>
+    /// Gets or sets the width of the buttons on the respective title bar implementation.
+    /// </summary>
+    int CombinedTitleButtonsWidth { get; set; }
+    
+    /// <summary>
+    /// Platform specific maximize implementation.
+    /// </summary>
+    Task Maximize(bool saveSetting = true);
+    
+    /// <summary>
+    /// Platform specific toggle between maximize and restore implementation.
+    /// </summary>
+    Task MaximizeRestore(bool saveSettings = true);
+    
+    /// <summary>
+    /// Platform specific fullscreen implementation.
+    /// </summary>
+    Task Fullscreen(bool saveSetting = true);
+    
+    /// <summary>
+    /// Platform specific toggle between fullscreen and restore implementation.
+    /// </summary>
+    Task ToggleFullscreen(bool saveSettings = true);
+    
+    /// <summary>
+    /// Platform specific restore implementation.
+    /// </summary>
+    Task Restore();
+    
+    void ShowAboutWindow();
+
+    Task ShowImageInfoWindow();
+
+    Task ShowKeybindingsWindow();
+
+    Task ShowSettingsWindow();
+    
+    void ShowEffectsWindow();
+    
+    void ShowSingleImageResizeWindow();
+    
+    Task ShowBatchResizeWindow();
+
+    void ShowConvertWindow();
+}

+ 1 - 0
src/PicView.Core/IPlatform/readme.md

@@ -0,0 +1 @@
+Directory containing interfaces, that requires platform specific solutions.

+ 1 - 1
src/PicView.Avalonia/Update/InstalledArchitecture.cs → src/PicView.Core/Update/InstalledArchitecture.cs

@@ -1,4 +1,4 @@
-namespace PicView.Avalonia.Update;
+namespace PicView.Core.Update;
 
 public enum InstalledArchitecture
 {

+ 1 - 1
src/PicView.Avalonia/Update/UpdateInfo.cs → src/PicView.Core/Update/UpdateInfo.cs

@@ -1,4 +1,4 @@
-namespace PicView.Avalonia.Update;
+namespace PicView.Core.Update;
 
 public class UpdateInfo
 {

+ 174 - 0
src/PicView.Core/Update/UpdateManager.cs

@@ -0,0 +1,174 @@
+using System.Diagnostics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using PicView.Core.Config;
+using PicView.Core.DebugTools;
+using PicView.Core.Http;
+using PicView.Core.IPlatform;
+using PicView.Core.ViewModels;
+
+namespace PicView.Core.Update;
+
+/// <summary>
+///     JSON source generation for UpdateInfo deserialization
+/// </summary>
+[JsonSourceGenerationOptions(AllowTrailingCommas = true)]
+[JsonSerializable(typeof(UpdateInfo))]
+public partial class UpdateSourceGenerationContext : JsonSerializerContext;
+
+/// <summary>
+///     Handles application update operations
+/// </summary>
+public static class UpdateManager
+{
+    private const string PrimaryUpdateUrl = "https://picview.org/update.json";
+    private const string FallbackUpdateUrl = "https://picview.netlify.app/update.json";
+
+
+#if DEBUG
+    // ReSharper disable once ConvertToConstant.Local
+    private static readonly bool ForceUpdate = false;
+#endif
+
+    /// <summary>
+    ///     Checks for updates and installs if a newer version is available
+    /// </summary>
+    public static async Task<bool> UpdateCurrentVersion(IPlatformSpecificUpdate platformUpdate)
+    {
+        // Create temporary directory for update files
+        var tempPath = CreateTemporaryDirectory();
+        var tempJsonPath = Path.Combine(tempPath, "update.json");
+
+        // Check if update is needed
+        Version? currentVersion;
+#if DEBUG
+        currentVersion = ForceUpdate ? new Version("3.0.0.3") : VersionHelper.GetAssemblyVersion();
+        Debug.Assert(currentVersion != null);
+#else
+        currentVersion = VersionHelper.GetAssemblyVersion();
+#endif
+        
+        var updateInfo = await DownloadAndParseUpdateInfo(tempJsonPath);
+        if (updateInfo == null)
+        {
+            return false;
+        }
+
+        var remoteVersion = new Version(updateInfo.Version);
+        if (remoteVersion <= currentVersion)
+        {
+            return false;
+        }
+
+        // Handle update based on platform and installation type
+        await platformUpdate?.HandlePlatofrmUpdate(updateInfo, tempPath);
+        return true;
+    }
+
+    /// <summary>
+    ///     Creates a temporary directory for update files
+    /// </summary>
+    private static string CreateTemporaryDirectory()
+    {
+        var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+        Directory.CreateDirectory(tempPath);
+        return tempPath;
+    }
+
+    /// <summary>
+    ///     Downloads and parses the update information
+    /// </summary>
+    private static async Task<UpdateInfo?> DownloadAndParseUpdateInfo(string tempJsonPath)
+    {
+        // Try primary URL first, fallback to secondary if needed
+        if (await DownloadUpdateJson(PrimaryUpdateUrl, tempJsonPath))
+        {
+            return await ParseUpdateJson(tempJsonPath);
+        }
+
+        if (!await DownloadUpdateJson(FallbackUpdateUrl, tempJsonPath))
+        {
+            return null;
+        }
+
+        return await ParseUpdateJson(tempJsonPath);
+    }
+
+    /// <summary>
+    ///     Downloads the update JSON file
+    /// </summary>
+    private static async Task<bool> DownloadUpdateJson(string url, string destinationPath)
+    {
+        try
+        {
+            using var downloader = new HttpClientDownloadWithProgress(url, destinationPath);
+            await downloader.StartDownloadAsync();
+            return true;
+        }
+        catch (Exception e)
+        {
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(DownloadUpdateJson), e);
+            return false;
+        }
+    }
+
+    /// <summary>
+    ///     Parses the update JSON file
+    /// </summary>
+    private static async Task<UpdateInfo?> ParseUpdateJson(string jsonFilePath)
+    {
+        try
+        {
+            var jsonString = await File.ReadAllTextAsync(jsonFilePath);
+
+            if (JsonSerializer.Deserialize(
+                    jsonString, typeof(UpdateInfo),UpdateSourceGenerationContext.Default) is UpdateInfo updateInfo)
+            {
+                return updateInfo;
+            }
+
+            return null;
+        }
+        catch (Exception e)
+        {
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(ParseUpdateJson), e);
+            return null;
+        }
+    }
+    
+    public static async Task DownloadUpdateFile(CoreViewModel vm, string downloadUrl, string tempPath)
+    {
+        vm.PlatformService.StopTaskbarProgress();
+
+        using var downloader = new HttpClientDownloadWithProgress(downloadUrl, tempPath);
+        try
+        {
+            downloader.ProgressChanged += (size, downloaded, percentage) =>
+                UpdateDownloadProgress(vm, size, downloaded, percentage);
+
+            await downloader.StartDownloadAsync();
+        }
+        catch (Exception e)
+        {
+            DebugHelper.LogDebug(nameof(UpdateManager), nameof(DownloadUpdateFile), e);
+        }
+        finally
+        {
+            vm.PlatformService.StopTaskbarProgress();
+        }
+    }
+    
+    private static void UpdateDownloadProgress(
+        CoreViewModel vm,
+        long? totalFileSize,
+        long? totalBytesDownloaded,
+        double? progressPercentage)
+    {
+        if (!totalFileSize.HasValue || !totalBytesDownloaded.HasValue || !progressPercentage.HasValue)
+        {
+            return;
+        }
+
+        vm.PlatformService.SetTaskbarProgress((ulong)totalBytesDownloaded.Value, (ulong)totalFileSize.Value);
+    }
+}

+ 23 - 0
src/PicView.Core/ViewModels/CoreViewModel.cs

@@ -0,0 +1,23 @@
+using PicView.Core.IPlatform;
+
+namespace PicView.Core.ViewModels;
+
+public class CoreViewModel
+{
+    public readonly IPlatformSpecificService? PlatformService;
+    public readonly IPlatformWindowService? PlatformWindowService;
+    
+    // Shared view models
+    public TranslationViewModel Translation { get; } = new();
+    public GlobalSettingsViewModel GlobalSettings { get; } = new();
+    public SettingsViewModel? SettingsViewModel { get; set; }
+    //public GalleryViewModel SharedGallery { get; } = new();
+    public ExifViewModel? Exif { get; set; }
+    public KeybindingsViewModel? Keybindings { get; set; }
+    //public WindowViewModel Window { get; } = new();
+
+
+    // Collection view models
+    public List<TabOverviewViewModel> TabsCollection { get; } = [];
+
+}

+ 2 - 0
src/PicView.Core/ViewModels/DropDownMenuViewModel.cs

@@ -4,6 +4,8 @@ namespace PicView.Core.ViewModels;
 
 public class DropDownMenuViewModel
 {
+    public BindableReactiveProperty<bool> IsDropDownMenuVisible { get; } = new();
+    
     public BindableReactiveProperty<int> MenuCarouselIndex { get; } = new(0);
     public BindableReactiveProperty<bool> IsGalleryOptionsVisible { get; } = new(false);