Browse Source

Fix file history issues, refactor

Ruben 7 months ago
parent
commit
c8b4897142

+ 2 - 1
src/PicView.Avalonia/Navigation/ErrorHandling.cs

@@ -5,6 +5,7 @@ using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.Calculations;
 using PicView.Core.Gallery;
+using PicView.Core.Navigation;
 using StartUpMenu = PicView.Avalonia.Views.StartUpMenu;
 
 namespace PicView.Avalonia.Navigation;
@@ -76,7 +77,7 @@ public static class ErrorHandling
         
         if (!NavigationManager.CanNavigate(vm))
         {
-            await FileHistoryNavigation.OpenLastFileAsync(vm);
+            await NavigationManager.LoadPicFromStringAsync(FileHistory.GetLastEntry(), vm).ConfigureAwait(false);
             return;
         }
         

+ 0 - 141
src/PicView.Avalonia/Navigation/FileHistoryNavigation.cs

@@ -1,141 +0,0 @@
-using PicView.Avalonia.ViewModels;
-using PicView.Core.Navigation;
-
-namespace PicView.Avalonia.Navigation;
-
-
-// TODO: This file needs to me removed and the FileHistory class needs to use interfaces instead.
-
-
-public static class FileHistoryNavigation
-{
-    private static FileHistory? _fileHistory;
-
-    public static void Add(string file)
-    {
-        _fileHistory ??= new FileHistory();
-        _fileHistory.Add(file);
-    }
-
-    public static void Remove(string file)
-    {
-        _fileHistory ??= new FileHistory();
-        _fileHistory.Remove(file);
-    }
-
-    public static void Rename(string oldPath, string newPath)
-    {
-        _fileHistory ??= new FileHistory();
-        _fileHistory.Rename(oldPath, newPath);
-    }
-
-    public static bool Contains(string file)
-    {
-        _fileHistory ??= new FileHistory();
-        return _fileHistory.Contains(file);
-    }
-
-    public static string GetLastFile()
-    {
-        _fileHistory ??= new FileHistory();
-        return _fileHistory.GetLastFile() ?? string.Empty;
-    }
-
-    public static int GetCount()
-    {
-        return _fileHistory?.GetCount() ?? 0;
-    }
-
-    internal static async Task OpenLastFileAsync(MainViewModel vm)
-    {
-        _fileHistory ??= new FileHistory();
-        
-        if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config/recent.txt")) == false)
-        {
-            if (File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Ruben2776/PicView/Config/recent.txt")) == false)
-            {
-                await LoadLastFileFromSettingsOrNotAsync();
-                return;
-            }
-        }
-   
-        var entry = _fileHistory.GetLastFile();
-
-        if (entry is null)
-        {
-            await LoadLastFileFromSettingsOrNotAsync();
-            return;
-        }
-
-        vm.CurrentView = vm.ImageViewer;
-        await NavigationManager.LoadPicFromStringAsync(entry, vm);
-        return;
-        
-        async Task LoadLastFileFromSettingsOrNotAsync()
-        {
-            if (!string.IsNullOrWhiteSpace(Settings.StartUp.LastFile))
-            {
-                vm.CurrentView = vm.ImageViewer;
-                await NavigationManager.LoadPicFromStringAsync(Settings.StartUp.LastFile, vm);
-            }
-            else
-            {
-                ErrorHandling.ShowStartUpMenu(vm);
-            }
-        }
-    }
-
-    public static async Task NextAsync(MainViewModel vm) => await NextAsyncInternal(vm, true).ConfigureAwait(false);
-
-    public static async Task PrevAsync(MainViewModel vm) => await NextAsyncInternal(vm, false).ConfigureAwait(false);
-    
-    private static async Task NextAsyncInternal(MainViewModel vm, bool next)
-    {
-        if (!NavigationManager.CanNavigate(vm))
-        {
-            if (vm.FileInfo is not null)
-            {
-                var lastFile = Path.GetFileNameWithoutExtension(GetLastFile());
-                if (lastFile == Path.GetFileNameWithoutExtension(vm.FileInfo.Name))
-                {
-                    return;
-                }
-                await OpenLastFileAsync(vm).ConfigureAwait(false);
-            }
-            return;
-        }
-
-        await LoadEntryAsync(vm, NavigationManager.GetCurrentIndex, next).ConfigureAwait(false);
-    }
-
-    public static async Task LoadEntryAsync(MainViewModel vm, int index, bool next)
-    {
-        _fileHistory ??= new FileHistory();
-        string? nextEntry;
-        if (next)
-        {
-            nextEntry = await Task.FromResult(_fileHistory.GetNextEntry(Settings.UIProperties.Looping, index)).ConfigureAwait(false);
-        }
-        else
-        {
-            nextEntry = await Task.FromResult(_fileHistory.GetPreviousEntry(Settings.UIProperties.Looping, index)).ConfigureAwait(false);
-        }
-
-        if (string.IsNullOrWhiteSpace(nextEntry))
-        {
-            return;
-        }
-        await NavigationManager.LoadPicFromStringAsync(nextEntry, vm);
-    }
-    
-    public static void WriteToFile()
-    {
-        _fileHistory?.WriteToFile();
-    }
-
-    public static string GetFileLocation(int i)
-    {
-        _fileHistory ??= new FileHistory();
-        return _fileHistory.GetEntryAt(i) ?? string.Empty;
-    }
-}

+ 3 - 3
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -232,7 +232,7 @@ public class ImageIterator : IAsyncDisposable
                 PreLoader.Resynchronize(ImagePaths);
             }
             
-            FileHistoryNavigation.Remove(e.FullPath);
+            FileHistory.Remove(e.FullPath);
 
         }
         catch (Exception exception)
@@ -310,7 +310,7 @@ public class ImageIterator : IAsyncDisposable
             Resynchronize();
 
             _isRunning = false;
-            FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
+            FileHistory.Rename(e.OldFullPath, e.FullPath);
             await Dispatcher.UIThread.InvokeAsync(() =>
                 GalleryFunctions.RenameGalleryItem(oldIndex, index, Path.GetFileNameWithoutExtension(e.Name), e.FullPath,
                     _vm));
@@ -609,7 +609,7 @@ public class ImageIterator : IAsyncDisposable
             // Add recent files, except when browsing archive
             if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath) && ImagePaths.Count > CurrentIndex)
             {
-                FileHistoryNavigation.Add(ImagePaths[CurrentIndex]);
+                FileHistory.Add(ImagePaths[CurrentIndex]);
             }
         }
         catch (OperationCanceledException)

+ 2 - 2
src/PicView.Avalonia/Navigation/NavigationManager.cs

@@ -579,7 +579,7 @@ public static class NavigationManager
         vm.IsLoading = false;
         vm.FileInfo = fileInfo;
         vm.ExifOrientation = imageModel.EXIFOrientation;
-        FileHistoryNavigation.Add(url);
+        FileHistory.Add(url);
 
         await DisposeImageIteratorAsync();
         
@@ -727,7 +727,7 @@ public static class NavigationManager
         await _imageIterator.DisposeAsync();
     }
     
-    public static bool IsCollectionEmpty => _imageIterator.ImagePaths is null || _imageIterator.ImagePaths.Count < 0;
+    public static bool IsCollectionEmpty => _imageIterator?.ImagePaths is null || _imageIterator?.ImagePaths?.Count < 0;
     public static List<string>? GetCollection => _imageIterator?.ImagePaths;
     
     public static void UpdateFileListAndIndex(List<string> fileList, int index) => _imageIterator?.UpdateFileListAndIndex(fileList, index);

+ 9 - 0
src/PicView.Avalonia/PicViewTheme/Controls/Menu.axaml

@@ -177,6 +177,15 @@
             <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
         </Style>
 
+        <Style Selector="^.active /template/ Border#root">
+            <Setter Property="Background" Value="{DynamicResource AccentColor}" />
+            <Setter Property="BorderBrush" Value="{DynamicResource AccentColor}" />
+        </Style>
+
+        <Style Selector="^.active /template/ ContentPresenter#PART_HeaderPresenter">
+            <Setter Property="Foreground" Value="{StaticResource SecondaryTextColor}" />
+        </Style>
+
 
     </ControlTheme>
 

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

@@ -9,6 +9,7 @@ using PicView.Avalonia.WindowBehavior;
 using PicView.Core.FileHandling;
 using PicView.Core.Gallery;
 using PicView.Core.ImageDecoding;
+using PicView.Core.Navigation;
 
 namespace PicView.Avalonia.StartUp;
 
@@ -152,7 +153,7 @@ public static class QuickLoad
         // Add recent files, except when browsing archive
         if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath))
         {
-            FileHistoryNavigation.Add(fileInfo.FullName);
+            FileHistory.Add(fileInfo.FullName);
         }
 
         NavigationManager.AddToPreloader(NavigationManager.GetCurrentIndex, imageModel);

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

@@ -16,6 +16,7 @@ using PicView.Avalonia.Views;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Calculations;
 using PicView.Core.Gallery;
+using PicView.Core.Navigation;
 using PicView.Core.ProcessHandling;
 
 namespace PicView.Avalonia.StartUp;
@@ -117,6 +118,7 @@ public static class StartUpHelper
         ValidateGallerySettings(vm, settingsExists);
         SetWindowEventHandlers(window);
         MenuManager.AddMenus();
+        FileHistory.Initialize();
 
         Application.Current.Name = "PicView";
 

+ 4 - 3
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -15,6 +15,7 @@ using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.FileHandling;
 using PicView.Core.ImageDecoding;
+using PicView.Core.Navigation;
 using PicView.Core.ProcessHandling;
 
 namespace PicView.Avalonia.UI;
@@ -550,7 +551,7 @@ public static class FunctionsHelper
             return;
         }
 
-        await FileHistoryNavigation.OpenLastFileAsync(Vm).ConfigureAwait(false);
+        await NavigationManager.LoadPicFromStringAsync(FileHistory.GetLastEntry(), Vm).ConfigureAwait(false);
     }
 
     public static async Task OpenPreviousFileHistoryEntry()
@@ -560,7 +561,7 @@ public static class FunctionsHelper
             return;
         }
 
-        await FileHistoryNavigation.PrevAsync(Vm).ConfigureAwait(false);
+        await NavigationManager.LoadPicFromStringAsync(FileHistory.GetPreviousEntry(), Vm).ConfigureAwait(false);
     }
     public static async Task OpenNextFileHistoryEntry()
     {
@@ -569,7 +570,7 @@ public static class FunctionsHelper
             return;
         }
 
-        await FileHistoryNavigation.NextAsync(Vm).ConfigureAwait(false);
+        await NavigationManager.LoadPicFromStringAsync(FileHistory.GetNextEntry(), Vm).ConfigureAwait(false);
     }
     
     public static async Task Print()

+ 50 - 64
src/PicView.Avalonia/Views/MainView.axaml.cs

@@ -1,5 +1,4 @@
-using System.Diagnostics;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
@@ -13,6 +12,7 @@ using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Extensions;
+using PicView.Core.Navigation;
 
 namespace PicView.Avalonia.Views;
 
@@ -48,8 +48,6 @@ public partial class MainView : UserControl
             }
             HideInterfaceLogic.AddHoverButtonEvents(AltButtonsPanel, vm);
             PointerWheelChanged += async (_, e) => await vm.ImageViewer.PreviewOnPointerWheelChanged(this, e);
-            
-            
         };
     }
 
@@ -98,88 +96,76 @@ public partial class MainView : UserControl
         ConversionHelper.DetermineIfOptimizeImageShouldBeEnabled(vm);
 
         // Set source for ChangeCtrlZoomImage
-        // TODO should probably be refactored inside a command (It doesn't update the UI in the zoom view, so should be made into a command)
-        if (!Application.Current.TryGetResource("ScanEyeImage", Application.Current.RequestedThemeVariant, out var scanEyeImage ))
+        if (!Application.Current.TryGetResource("ScanEyeImage", Application.Current.RequestedThemeVariant, out var scanEyeImage))
         {
             return;
         }
-        if (!Application.Current.TryGetResource("LeftRightArrowsImage", Application.Current.RequestedThemeVariant, out var leftRightArrowsImage ))
+        if (!Application.Current.TryGetResource("LeftRightArrowsImage", Application.Current.RequestedThemeVariant, out var leftRightArrowsImage))
         {
             return;
         }
         var isNavigatingWithCtrl = Settings.Zoom.CtrlZoom;
         vm.ChangeCtrlZoomImage = isNavigatingWithCtrl ? leftRightArrowsImage as DrawingImage : scanEyeImage as DrawingImage;
 
-        // Update file history
-        var count = FileHistoryNavigation.GetCount();
-        if (RecentFilesCM.Items.Count < count)
-        {
-            for (var i = RecentFilesCM.Items.Count; i < count; i++)
-            {
-                AddOrReplaceMenuItem(i, vm, isReplace: false);
-            }
-        }
-        else
-        {
-            for (var i = 0; i < count; i++)
-            {
-                AddOrReplaceMenuItem(i, vm, isReplace: true);
-            }
-        }
+        // Update file history menu items
+        UpdateFileHistoryMenuItems(vm);
     }
 
-    private void AddOrReplaceMenuItem(int index, MainViewModel vm, bool isReplace)
+    private void UpdateFileHistoryMenuItems(MainViewModel vm)
     {
-        if (!Application.Current.TryGetResource("SecondaryAccentColor", Application.Current.RequestedThemeVariant, out var secondaryAccentColor))
-        {
-            return;
-        }
-
-        try
+        // Clear existing items 
+        RecentFilesCM.Items.Clear();
+        var currentFilePath = NavigationManager.GetCurrentFileName;
+            
+        // Add menu items for each history entry
+        for (var i = 0; i < FileHistory.Count; i++)
         {
-#if DEBUG
-            Debug.Assert(secondaryAccentColor != null, nameof(secondaryAccentColor) + " != null");
-#endif
-            var secondaryAccentBrush = (SolidColorBrush)secondaryAccentColor;
-            var fileLocation = FileHistoryNavigation.GetFileLocation(index);
-            var selected = NavigationManager.GetCurrentIndex == NavigationManager.GetCollection?.IndexOf(fileLocation);
-            var header = Path.GetFileNameWithoutExtension(fileLocation);
-            header = header.Length > 60 ? header.Shorten(60) : header;
-        
+            var fileLocation = FileHistory.GetEntry(i);
+            if (string.IsNullOrEmpty(fileLocation))
+                continue;
+                
+            var isSelected = fileLocation == currentFilePath;
+            var filename = Path.GetFileNameWithoutExtension(fileLocation);
+            var header = filename.Length > 60 ? filename.Shorten(60) : filename;
+            
             var item = new MenuItem
             {
-                Header = header,
+                Header = header
             };
-
-            if (selected)
+            if (isSelected)
             {
-                item.Foreground = secondaryAccentBrush;
+                item.Classes.Add("active");
             }
-        
+            
+            var filePath = fileLocation; // Local copy for the closure
             item.Click += async delegate
             {
-                await NavigationManager.LoadPicFromStringAsync(fileLocation, vm).ConfigureAwait(false);
+                await NavigationManager.LoadPicFromStringAsync(filePath, vm).ConfigureAwait(false);
             };
-        
+            
             ToolTip.SetTip(item, fileLocation);
-
-            if (isReplace)
-            {
-                RecentFilesCM.Items[index] = item;
-            }
-            else
-            {
-                RecentFilesCM.Items.Insert(index, item);
-            }
-        }
-#if DEBUG
-        catch (Exception e)
-        {
-            Console.WriteLine(e);
+            
+            RecentFilesCM.Items.Add(item);
         }
-#else
-        catch (Exception){}
-#endif
+        
+        // TODO add clear history translations
+        // Add a separator and "Clear history" option if there are items
+        // if (FileHistory.Count <= 0)
+        // {
+        //     return;
+        // }
+        //
+        // RecentFilesCM.Items.Add(new Separator());
+        //     
+        // var clearItem = new MenuItem { Header = TranslationHelper.GetTranslation("ClearHistory") };
+        // clearItem.Click += delegate
+        // {
+        //     FileHistory.Clear();
+        //     FileHistory.SaveToFile();
+        //     RecentFilesCM.Items.Clear();
+        // };
+        //     
+        // RecentFilesCM.Items.Add(clearItem);
     }
 
     private async Task Drop(object? sender, DragEventArgs e)
@@ -203,4 +189,4 @@ public partial class MainView : UserControl
     {
         DragAndDropHelper.DragLeave(e, this);
     }
-}
+}

+ 5 - 4
src/PicView.Avalonia/WindowBehavior/WindowFunctions.cs

@@ -12,6 +12,7 @@ using PicView.Avalonia.ViewModels;
 using PicView.Core.ArchiveHandling;
 using PicView.Core.Calculations;
 using PicView.Core.FileHandling;
+using PicView.Core.Navigation;
 
 // ReSharper disable CompareOfFloatsByEqualityOperator
 
@@ -41,7 +42,7 @@ public static class WindowFunctions
         }
 
         var vm = window.DataContext as MainViewModel;
-        string lastFile;
+        string? lastFile;
         if (NavigationManager.CanNavigate(vm))
         {
             if (!string.IsNullOrEmpty(ArchiveExtraction.LastOpenedArchive))
@@ -50,20 +51,20 @@ public static class WindowFunctions
             }
             else
             {
-                lastFile = vm?.FileInfo?.FullName ?? FileHistoryNavigation.GetLastFile();
+                lastFile = vm?.FileInfo?.FullName ?? FileHistory.GetLastEntry();
             }
         }
         else
         {
             var url = vm?.Title.GetURL();
-            lastFile = !string.IsNullOrWhiteSpace(url) ? url : FileHistoryNavigation.GetLastFile();
+            lastFile = !string.IsNullOrWhiteSpace(url) ? url : FileHistory.GetLastEntry();
         }
 
         Settings.StartUp.LastFile = lastFile;
         await SaveSettingsAsync();
         await KeybindingManager.UpdateKeyBindingsFile(); // Save keybindings
         FileDeletionHelper.DeleteTempFiles();
-        FileHistoryNavigation.WriteToFile();
+        FileHistory.SaveToFile();
         ArchiveExtraction.Cleanup();
         Environment.Exit(0);
     }

+ 222 - 144
src/PicView.Core/Navigation/FileHistory.cs

@@ -5,23 +5,62 @@ namespace PicView.Core.Navigation;
 /// <summary>
 /// Manages the history of recently accessed files.
 /// </summary>
-public class FileHistory
+public static class FileHistory
 {
-    private readonly List<string> _fileHistory;
-    public const short MaxCount = 15;
-    private readonly string _fileLocation;
+    private const int MaxHistoryEntries = 15;
+    private static readonly List<string> Entries = new(MaxHistoryEntries);
+    private static string? _fileLocation;
 
     /// <summary>
-    /// Initializes a new instance of the <see cref="FileHistory"/> class.
+    /// Gets the number of entries in the file history
     /// </summary>
-    public FileHistory()
+    public static int Count => Entries.Count;
+
+    /// <summary>
+    /// Gets all history entries
+    /// </summary>
+    public static IReadOnlyList<string> AllEntries => Entries.AsReadOnly();
+
+    /// <summary>
+    /// Gets or sets the current index position in history
+    /// </summary>
+    public static int CurrentIndex
+    {
+        get;
+        private set => field = Math.Clamp(value, -1, Entries.Count - 1);
+    } = -1;
+
+    /// <summary>
+    /// Indicates whether there is a previous entry available in history (older entry)
+    /// </summary>
+    public static bool HasPrevious => CurrentIndex > 0;
+
+    /// <summary>
+    /// Indicates whether there is a next entry available in history (newer entry)
+    /// </summary>
+    public static bool HasNext => CurrentIndex < Entries.Count - 1 && Entries.Count > 0;
+    
+    /// <summary>
+    /// Gets the current entry at the current index
+    /// </summary>
+    public static string? CurrentEntry => CurrentIndex >= 0 && CurrentIndex < Entries.Count ? Entries[CurrentIndex] : null;
+
+    /// <summary>
+    /// Initializes the file history by loading entries from the history file
+    /// </summary>
+    public static void Initialize()
     {
-        _fileHistory ??= [];
         _fileLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config/recent.txt");
         try
         {
             if (!File.Exists(_fileLocation))
             {
+                var directory = Path.GetDirectoryName(_fileLocation);
+                if (directory != null && !Directory.Exists(directory))
+                {
+                    Directory.CreateDirectory(directory);
+                }
+                
                 using var fs = File.Create(_fileLocation);
                 fs.Seek(0, SeekOrigin.Begin);
             }
@@ -42,102 +81,172 @@ public class FileHistory
             Trace.WriteLine($"{nameof(FileHistory)} exception, \n{e.Message}");
 #endif
         }
-        ReadFromFile();
+        LoadFromFile();
+        CurrentIndex = Entries.Count > 0 ? Entries.Count - 1 : -1;  // Set to most recent entry
     }
 
     /// <summary>
-    /// Reads the file history from the .txt file.
+    /// Adds an entry to the history
     /// </summary>
-    /// <returns>An empty string if successful, otherwise an error message.</returns>
-    public string ReadFromFile()
+    public static void Add(string path)
     {
-        _fileHistory.Clear();
-        try
+        if (string.IsNullOrWhiteSpace(path))
+            return;
+
+        // Check if the entry already exists
+        var existingIndex = Entries.IndexOf(path);
+        
+        if (existingIndex >= 0)
         {
-            using var reader = new StreamReader(_fileLocation);
-            while (reader.Peek() >= 0)
-            {
-                _fileHistory.Add(reader.ReadLine());
-            }
+            // If entry already exists, just update current index to point to it
+            CurrentIndex = existingIndex;
+            return;
         }
-        catch (Exception e)
+
+        // Trim the list if it will exceed the maximum size
+        if (Entries.Count >= MaxHistoryEntries)
         {
-#if DEBUG
-            Trace.WriteLine($"{nameof(FileHistory)}: {nameof(ReadFromFile)} exception,\n{e.Message}");
-#endif
-            return e.Message;
+            // Remove oldest entry (at beginning)
+            Entries.RemoveAt(0);
+            // Adjust current index since we removed an item
+            if (CurrentIndex > 0)
+            {
+                CurrentIndex--;
+            }
         }
-        return string.Empty;
+
+        // Add to the end of the list (newest entry)
+        Entries.Add(path);
+        
+        // Set the current index to the newly added item (last position)
+        CurrentIndex = Entries.Count - 1;
     }
 
     /// <summary>
-    /// Writes the file history to the .txt file.
+    /// Gets the next entry in history (newer entry)
     /// </summary>
-    /// <returns>An empty string if successful, otherwise an error message.</returns>
-    public string WriteToFile()
+    /// <returns>The next entry in history, or null if there is no next entry</returns>
+    public static string? GetNextEntry()
     {
-        try
-        {
-            using var writer = new StreamWriter(_fileLocation);
-            foreach (var item in _fileHistory)
-            {
-                writer.WriteLine(item);
-            }
-        }
-        catch (Exception e)
-        {
-            return e.Message;
-        }
-        return string.Empty;
+        if (!HasNext)
+            return null;
+            
+        CurrentIndex++;
+        return CurrentEntry;
     }
 
     /// <summary>
-    ///  Adds a file to the history.
+    /// Gets the previous entry in history (older entry)
     /// </summary>
-    /// <param name="fileName">The name of the file to add to the history.</param>
-    public void Add(string fileName)
+    /// <returns>The previous entry in history, or null if there is no previous entry</returns>
+    public static string? GetPreviousEntry()
     {
-        try
-        {
-            if (string.IsNullOrWhiteSpace(fileName) || _fileHistory.Exists(e => e is not null && e.EndsWith(fileName)))
-            {
-                return;
-            }
+        if (!HasPrevious)
+            return null;
+            
+        CurrentIndex--;
+        return CurrentEntry;
+    }
 
-            if (_fileHistory.Count >= MaxCount)
-            {
-                _fileHistory.RemoveAt(0);
-            }
+    /// <summary>
+    /// Gets an entry at the specified index
+    /// </summary>
+    public static string? GetEntry(int index)
+    {
+        if (index < 0 || index >= Entries.Count)
+            return null;
 
-            _fileHistory.Add(fileName);
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Trace.WriteLine($"{nameof(FileHistory)}: {nameof(Add)} exception,\n{e.Message}");
-#endif
-        }
+        return Entries[index];
     }
 
-    public void Remove(string fileName)
+    /// <summary>
+    /// Gets the first entry in history (oldest)
+    /// </summary>
+    public static string? GetFirstEntry() => Entries.Count > 0 ? Entries[0] : null;
+
+    /// <summary>
+    /// Gets the last entry in history (newest)
+    /// </summary>
+    public static string? GetLastEntry() => Entries.Count > 0 ? Entries[^1] : null;
+
+    /// <summary>
+    /// Tries to find an entry that matches or contains the given string
+    /// </summary>
+    public static string? GetEntryByString(string searchString)
     {
-        try
+        if (string.IsNullOrWhiteSpace(searchString))
+            return null;
+        
+        // First try exact match
+        var exactMatch = Entries.FirstOrDefault(e => 
+            string.Equals(e, searchString, StringComparison.OrdinalIgnoreCase));
+        
+        if (exactMatch != null)
+            return exactMatch;
+
+        // Then try contains
+        return Entries.FirstOrDefault(e => 
+            e.Contains(searchString, StringComparison.OrdinalIgnoreCase));
+    }
+
+    /// <summary>
+    /// Clears all history entries
+    /// </summary>
+    public static void Clear()
+    {
+        Entries.Clear();
+        CurrentIndex = -1;
+    }
+
+    /// <summary>
+    /// Removes a specific entry from history
+    /// </summary>
+    public static bool Remove(string path)
+    {
+        var index = Entries.IndexOf(path);
+        if (index < 0)
+            return false;
+            
+        Entries.RemoveAt(index);
+        
+        // Adjust current index if necessary
+        if (index <= CurrentIndex)
         {
-            if (string.IsNullOrWhiteSpace(fileName))
-            {
-                return;
-            }
-            _fileHistory.Remove(fileName);
+            CurrentIndex = Math.Max(-1, CurrentIndex - 1);
         }
-        catch (Exception e)
+        
+        return true;
+    }
+
+    /// <summary>
+    /// Removes an entry at the specified index
+    /// </summary>
+    public static bool RemoveAt(int index)
+    {
+        if (index < 0 || index >= Entries.Count)
+            return false;
+
+        Entries.RemoveAt(index);
+        
+        // Adjust current index if necessary
+        if (index <= CurrentIndex)
         {
-#if DEBUG
-            Trace.WriteLine($"{nameof(FileHistory)}: {nameof(Remove)} exception,\n{e.Message}");
-#endif
+            CurrentIndex = Math.Max(-1, CurrentIndex - 1);
         }
+        
+        return true;
     }
-
-    public void Rename(string oldName, string newName)
+    
+    /// <summary>
+    /// Renames a file in the history, replacing the old entry with the new one.
+    /// </summary>
+    /// <param name="oldName">The old name to be replaced.</param>
+    /// <param name="newName">The new name that will replace the old one.</param>
+    /// <remarks>
+    /// This method is case-insensitive and will replace the first entry that matches the old name.
+    /// If no matching entry is found, this method does nothing.
+    /// </remarks>
+    public static void Rename(string oldName, string newName)
     {
         try
         {
@@ -145,12 +254,15 @@ public class FileHistory
             {
                 return;
             }
-            var index = _fileHistory.IndexOf(oldName);
-            if (index < 0)
+
+            var entry = GetEntryByString(oldName);
+            if (string.IsNullOrWhiteSpace(entry) || !Entries.Contains(entry))
             {
                 return;
             }
-            _fileHistory[index] = newName;
+
+            var index = Entries.IndexOf(entry);
+            Entries[index] = newName;
         }
         catch (Exception e)
         {
@@ -159,96 +271,62 @@ public class FileHistory
 #endif
         }
     }
-    
-    public int GetCount() => _fileHistory.Count;
-
-    public bool Contains(string name) => !string.IsNullOrWhiteSpace(name) && _fileHistory.Contains(name);
-
 
     /// <summary>
-    ///  Gets the last file in the history.
+    /// Saves the history to the history file
     /// </summary>
-    /// <returns>The last file entry or null if the history is empty.</returns>
-    public string? GetLastFile() => _fileHistory.Count > 0 ? _fileHistory[^1] : null;
-
-    /// <summary>
-    /// Gets the first file in the history.
-    /// </summary>
-    /// <returns>The first file entry or null if the history is empty.</returns>
-    public string? GetFirstFile() => _fileHistory.Count > 0 ? _fileHistory[0] : null;
-
-    /// <summary>
-    /// Gets the file entry at the specified index.
-    /// </summary>
-    /// <param name="index">The index of the file entry to retrieve.</param>
-    /// <returns>The file entry at the specified index or null if the history is empty.</returns>
-    public string? GetEntryAt(int index)
+    public static void SaveToFile()
     {
-        if (_fileHistory.Count == 0)
-        {
-            return null;
-        }
-
-        if (index < 0)
-        {
-            return _fileHistory[0];
-        }
-
-        return index >= _fileHistory.Count ? _fileHistory[^1] : _fileHistory[index];
-    }
-    
-    public string? GetNextEntry(bool looping, int index)
-    {
-        if (_fileHistory.Count <= 0)
-        {
-            return GetLastFile();
-        }
-
         try
         {
-            var foundIndex = _fileHistory.IndexOf(_fileHistory[index]);
-
-            if (looping)
+            if (_fileLocation == null)
+                return;
+                
+            // Create directory if it doesn't exist
+            var directory = Path.GetDirectoryName(_fileLocation);
+            if (directory != null && !Directory.Exists(directory))
             {
-                return GetEntryAt((foundIndex + 1 + _fileHistory.Count) % _fileHistory.Count);
+                Directory.CreateDirectory(directory);
             }
 
-            foundIndex++;
-            return foundIndex >= MaxCount ? null : GetEntryAt(foundIndex);
+            // Write all entries to file
+            File.WriteAllLines(_fileLocation, Entries);
         }
-        catch (Exception e)
+        catch (Exception ex)
         {
 #if DEBUG
-            Trace.WriteLine($"{nameof(FileHistory)}: {nameof(GetNextEntry)} exception,\n{e.Message}");
+            // Log error but don't throw - this is not critical functionality
+            Debug.WriteLine($"Error saving file history: {ex.Message}");
 #endif
-            return null;
         }
     }
-    
-    public string? GetPreviousEntry(bool looping, int index)
-    {
-        if (_fileHistory.Count <= 0)
-        {
-            return GetFirstFile();
-        }
 
-        try
-        {
-            var foundIndex = _fileHistory.IndexOf(_fileHistory[index]);
-            if (looping)
+    /// <summary>
+    /// Loads the history from the history file
+    /// </summary>
+    private static void LoadFromFile()
+    {
+        try {
+            if (_fileLocation == null || !File.Exists(_fileLocation))
             {
-                return GetEntryAt((foundIndex - 1 + _fileHistory.Count) % _fileHistory.Count);
+                return;
             }
 
-            foundIndex--;
-            return foundIndex < 0 ? null : GetEntryAt(foundIndex);
+            var lines = File.ReadAllLines(_fileLocation);
+            foreach (var line in lines)
+            {
+                if (!string.IsNullOrWhiteSpace(line) && Entries.Count < MaxHistoryEntries)
+                {
+                    Entries.Add(line);
+                }
+            }
         }
-        catch (Exception e)
+        catch (Exception ex)
         {
 #if DEBUG
-            Trace.WriteLine($"{nameof(FileHistory)}: {nameof(GetPreviousEntry)} exception,\n{e.Message}");
+            // Log error but don't throw - we can start with an empty history
+            Debug.WriteLine($"Error loading file history: {ex.Message}");
 #endif
-            return null;
         }
     }
 }

+ 0 - 72
src/PicView.Tests/FileHistoryTest.cs

@@ -1,72 +0,0 @@
-using PicView.Core.FileHandling;
-using PicView.Core.Navigation;
-
-namespace PicView.Tests;
-
-public class FileHistoryTest
-{
-    [Fact]
-    public void TestFileHistory()
-    {
-        var list = new List<string>();
-        var history = new FileHistory();
-        Assert.NotNull(history);
-
-        // Check adding
-        for (var i = 0; i <= FileHistory.MaxCount; i++)
-        {
-            AddRandomFiles(history, list);
-        }
-        Assert.Equal(FileHistory.MaxCount, history.GetCount());
-        AddRandomFiles(history, list);
-        Assert.Equal(FileHistory.MaxCount, history.GetCount());
-
-        // Check removing
-        history.Remove(history.GetLastFile());
-        Assert.Equal(FileHistory.MaxCount - 1, history.GetCount());
-
-        // Check renaming
-        var lastFile = history.GetLastFile();
-        var newFile = Path.GetFileNameWithoutExtension(lastFile);
-        newFile = Path.GetRandomFileName();
-        history.Rename(lastFile, newFile);
-        Assert.Equal(newFile, history.GetLastFile());
-
-        history.Remove(newFile);
-        Assert.False(history.Contains(newFile));
-
-        // Check getting iterations
-        var entry = history.GetEntryAt(1);
-        Assert.NotNull(entry);
-
-        var nextEntry = history.GetNextEntry(looping: true, 2);
-        Assert.NotNull(nextEntry);
-        Assert.True(File.Exists(nextEntry));
-
-        var prevEntry = history.GetPreviousEntry(looping: true, 2);
-        Assert.NotNull(prevEntry);
-        Assert.True(File.Exists(prevEntry));
-
-        foreach (var t in list)
-        {
-            FileDeletionHelper.DeleteFileWithErrorMsg(t, false);
-            Assert.False(File.Exists(t));
-            history.Remove(t);
-        }
-        Assert.Equal(0, history.GetCount());
-    }
-
-    private static void AddRandomFiles(FileHistory history, List<string> list)
-    {
-        var imageFileExtensionArray = new[] { ".jpg", ".png", ".bmp", ".gif", ".tiff", ".webp" };
-        var directory = TempFileHelper.CreateTempDirectory();
-        Assert.True(directory);
-        var randomExtension = imageFileExtensionArray[new Random().Next(0, imageFileExtensionArray.Length)];
-        var randomFileNameWithExtension = TempFileHelper.TempFilePath + randomExtension;
-        var fullPath = Path.Combine(TempFileHelper.TempFilePath, randomFileNameWithExtension);
-        using var fs = File.Create(fullPath);
-        Assert.True(File.Exists(fullPath));
-        history.Add(randomFileNameWithExtension);
-        list.Add(randomFileNameWithExtension);
-    }
-}