From 0a4da1e989881700a9372854368f4782208a7f44 Mon Sep 17 00:00:00 2001 From: Jason Barden Date: Wed, 1 Apr 2026 23:48:46 +0100 Subject: [PATCH 1/3] refactor: remove unused using directives and simplify constructors --- .../Services/FileDeleteService.cs | 1 - .../ViewModels/DeletePendingViewModel.cs | 2 -- .../ViewModels/MainWindowViewModel.cs | 3 +-- .../ViewModels/ScannedFileDisplayItem.cs | 24 +++++++++++++++---- .../Views/DeletePendingWindow.axaml.cs | 5 +--- .../Views/MainWindow.axaml.cs | 2 -- .../Views/ViewWindow.axaml.cs | 2 -- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/AStar.Dev.File.App/Services/FileDeleteService.cs b/src/AStar.Dev.File.App/Services/FileDeleteService.cs index a035a19..c15e95f 100644 --- a/src/AStar.Dev.File.App/Services/FileDeleteService.cs +++ b/src/AStar.Dev.File.App/Services/FileDeleteService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; diff --git a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs index f4bbd2b..c733a52 100644 --- a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs @@ -1,11 +1,9 @@ using AStar.Dev.File.App.Data; -using AStar.Dev.File.App.Models; using AStar.Dev.File.App.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; diff --git a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs index 8df6c80..640e51d 100644 --- a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs @@ -235,9 +235,8 @@ private async Task TogglePendingDelete(ScannedFileDisplayItem? item) { file.PendingDelete = !file.PendingDelete; await db.SaveChangesAsync(); + item.PendingDelete = file.PendingDelete; } - - await LoadScannedFilesAsync(); } [RelayCommand] diff --git a/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs b/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs index 9784ae4..617b565 100644 --- a/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs +++ b/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs @@ -1,10 +1,11 @@ using AStar.Dev.File.App.Models; -using System; using System.IO; namespace AStar.Dev.File.App.ViewModels; -public class ScannedFileDisplayItem +using System.ComponentModel; + +public class ScannedFileDisplayItem : INotifyPropertyChanged { public int Id { get; } public string FullPath { get; } @@ -17,7 +18,20 @@ public class ScannedFileDisplayItem public string FileType { get; } public string LastModified { get; } public string LastViewed { get; } - public bool PendingDelete { get; } + + private bool _pendingDelete; + public bool PendingDelete + { + get => _pendingDelete; + set + { + if (_pendingDelete != value) + { + _pendingDelete = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PendingDelete))); + } + } + } public ScannedFileDisplayItem(ScannedFile file) { @@ -34,9 +48,11 @@ public ScannedFileDisplayItem(ScannedFile file) LastViewed = file.LastViewed.HasValue ? file.LastViewed.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") : "—"; - PendingDelete = file.PendingDelete; + _pendingDelete = file.PendingDelete; } + public event PropertyChangedEventHandler? PropertyChanged; + public static string FormatSize(long bytes) { if (bytes >= 1_073_741_824L) diff --git a/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml.cs b/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml.cs index e5d743f..f0d60d2 100644 --- a/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml.cs +++ b/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml.cs @@ -4,8 +4,5 @@ namespace AStar.Dev.File.App.Views; public partial class DeletePendingWindow : Window { - public DeletePendingWindow() - { - InitializeComponent(); - } + public DeletePendingWindow() => InitializeComponent(); } diff --git a/src/AStar.Dev.File.App/Views/MainWindow.axaml.cs b/src/AStar.Dev.File.App/Views/MainWindow.axaml.cs index 33442d3..064ae20 100644 --- a/src/AStar.Dev.File.App/Views/MainWindow.axaml.cs +++ b/src/AStar.Dev.File.App/Views/MainWindow.axaml.cs @@ -2,9 +2,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media.Imaging; -using Microsoft.Extensions.DependencyInjection; using System.Collections.Specialized; -using System.IO; namespace AStar.Dev.File.App.Views; diff --git a/src/AStar.Dev.File.App/Views/ViewWindow.axaml.cs b/src/AStar.Dev.File.App/Views/ViewWindow.axaml.cs index b59a16e..305617c 100644 --- a/src/AStar.Dev.File.App/Views/ViewWindow.axaml.cs +++ b/src/AStar.Dev.File.App/Views/ViewWindow.axaml.cs @@ -1,7 +1,5 @@ using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Media.Imaging; -using AStar.Dev.File.App.ViewModels; namespace AStar.Dev.File.App.Views; From e11ed159782777e645d68b26a1ddb69ae5fc5cdf Mon Sep 17 00:00:00 2001 From: Jason Barden Date: Wed, 1 Apr 2026 23:57:46 +0100 Subject: [PATCH 2/3] feat: add FileViewerService and integrate file viewing functionality --- src/AStar.Dev.File.App/App.axaml.cs | 1 + .../Services/FileViewerService.cs | 43 ++++++++++++++ .../Services/IFileViewerService.cs | 22 +++++++ .../ViewModels/DeletePendingViewModel.cs | 15 ++++- .../ViewModels/MainWindowViewModel.cs | 20 +++---- .../Views/DeletePendingWindow.axaml | 58 ++++++++++++------- .../Views/DeletePendingWindow.axaml.cs | 36 +++++++++++- 7 files changed, 159 insertions(+), 36 deletions(-) create mode 100644 src/AStar.Dev.File.App/Services/FileViewerService.cs create mode 100644 src/AStar.Dev.File.App/Services/IFileViewerService.cs diff --git a/src/AStar.Dev.File.App/App.axaml.cs b/src/AStar.Dev.File.App/App.axaml.cs index 44e4649..7db5b0a 100644 --- a/src/AStar.Dev.File.App/App.axaml.cs +++ b/src/AStar.Dev.File.App/App.axaml.cs @@ -62,6 +62,7 @@ private static IServiceProvider BuildServices() services.AddSingleton(); services.AddSingleton(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/AStar.Dev.File.App/Services/FileViewerService.cs b/src/AStar.Dev.File.App/Services/FileViewerService.cs new file mode 100644 index 0000000..86e5a68 --- /dev/null +++ b/src/AStar.Dev.File.App/Services/FileViewerService.cs @@ -0,0 +1,43 @@ +using AStar.Dev.File.App.Data; +using AStar.Dev.File.App.ViewModels; +using Microsoft.EntityFrameworkCore; +using System; +using System.Threading.Tasks; + +namespace AStar.Dev.File.App.Services; + +/// +/// Service responsible for file viewing operations, including updating view history. +/// +public class FileViewerService : IFileViewerService +{ + private readonly IDbContextFactory _dbContextFactory; + + public event Action? FileViewRequested; + + public FileViewerService(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + /// + /// Processes a file view request by updating the last viewed timestamp in the database + /// and raising the FileViewRequested event. + /// + /// The file to view. If null, this method returns without action. + public async Task ViewFileAsync(ScannedFileDisplayItem? item) + { + if (item is null) + return; + + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var file = await db.ScannedFiles.FindAsync(item.Id); + if (file is not null) + { + file.LastViewed = DateTime.UtcNow; + await db.SaveChangesAsync(); + } + + FileViewRequested?.Invoke(item); + } +} diff --git a/src/AStar.Dev.File.App/Services/IFileViewerService.cs b/src/AStar.Dev.File.App/Services/IFileViewerService.cs new file mode 100644 index 0000000..274b137 --- /dev/null +++ b/src/AStar.Dev.File.App/Services/IFileViewerService.cs @@ -0,0 +1,22 @@ +using AStar.Dev.File.App.ViewModels; +using System.Threading.Tasks; + +namespace AStar.Dev.File.App.Services; + +/// +/// Service for handling file viewing operations including updating view history. +/// +public interface IFileViewerService +{ + /// + /// Raised when a file is requested to be viewed. + /// + event System.Action? FileViewRequested; + + /// + /// Processes a file view request, updating the last viewed timestamp and raising the view event. + /// + /// The file to view + /// A task representing the asynchronous operation + Task ViewFileAsync(ScannedFileDisplayItem? item); +} diff --git a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs index c733a52..1291e90 100644 --- a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs @@ -14,6 +14,7 @@ public partial class DeletePendingViewModel : ViewModelBase { private readonly IDbContextFactory _dbContextFactory; private readonly IFileDeleteService _fileDeleteService; + private readonly IFileViewerService _fileViewerService; [ObservableProperty] private bool _isDeleting; @@ -26,12 +27,18 @@ public partial class DeletePendingViewModel : ViewModelBase public ObservableCollection PendingDeleteFiles { get; } = []; + public event Action? ViewFileRequested; + public DeletePendingViewModel( IDbContextFactory dbContextFactory, - IFileDeleteService fileDeleteService) + IFileDeleteService fileDeleteService, + IFileViewerService fileViewerService) { _dbContextFactory = dbContextFactory; _fileDeleteService = fileDeleteService; + _fileViewerService = fileViewerService; + _fileViewerService.FileViewRequested += item => ViewFileRequested?.Invoke(item); + _ = LoadPendingFilesAsync(); } @@ -109,6 +116,12 @@ private async Task ClearMarkings() await LoadPendingFilesAsync(); } + [RelayCommand] + private async Task ViewFile(ScannedFileDisplayItem? item) + { + await _fileViewerService.ViewFileAsync(item); + } + private async Task LoadPendingFilesAsync() { try diff --git a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs index 640e51d..b593680 100644 --- a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs @@ -18,9 +18,9 @@ public partial class MainWindowViewModel : ViewModelBase { private readonly IFileScannerService _fileScannerService; private readonly IFolderPickerService _folderPickerService; + private readonly IFileViewerService _fileViewerService; private readonly IDbContextFactory _dbContextFactory; private CancellationTokenSource? _cts; - // Guards against cascading reloads when programmatically resetting CurrentPage private bool _suppressPageReload; public static IReadOnlyList PageSizes { get; } = [25, 50, 75, 100, 125, 150, 175, 200]; @@ -104,11 +104,17 @@ private void ToggleDuplicatesOnly() public MainWindowViewModel( IFileScannerService fileScannerService, IFolderPickerService folderPickerService, + IFileViewerService fileViewerService, IDbContextFactory dbContextFactory) { _fileScannerService = fileScannerService; _folderPickerService = folderPickerService; + _fileViewerService = fileViewerService; _dbContextFactory = dbContextFactory; + + // Subscribe to file viewer service events + _fileViewerService.FileViewRequested += item => ViewFileRequested?.Invoke(item); + _ = InitializeAsync(); } @@ -242,17 +248,7 @@ private async Task TogglePendingDelete(ScannedFileDisplayItem? item) [RelayCommand] private async Task ViewFile(ScannedFileDisplayItem? item) { - if (item is null) return; - - await using var db = await _dbContextFactory.CreateDbContextAsync(); - var file = await db.ScannedFiles.FindAsync(item.Id); - if (file is not null) - { - file.LastViewed = DateTime.UtcNow; - await db.SaveChangesAsync(); - } - - ViewFileRequested?.Invoke(item); + await _fileViewerService.ViewFileAsync(item); } [RelayCommand(CanExecute = nameof(IsScanning))] diff --git a/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml b/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml index 505c89c..e73f514 100644 --- a/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml +++ b/src/AStar.Dev.File.App/Views/DeletePendingWindow.axaml @@ -72,21 +72,27 @@ - - - - - - + @@ -106,15 +112,23 @@ Width="110" /> - + -