Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Image-Sort.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0F31C99F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSort.WPF.UiTests", "tests\ImageSort.WPF.UiTests\ImageSort.WPF.UiTests.csproj", "{D72448F9-569A-4BFA-A0C7-79F20BE17F4F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSort.Avalonia", "src\ImageSort.Avalonia\ImageSort.Avalonia.csproj", "{1086BFB7-F980-4B08-956F-555B06C70992}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -222,6 +224,30 @@ Global
{D72448F9-569A-4BFA-A0C7-79F20BE17F4F}.Release|x64.Build.0 = Release|Any CPU
{D72448F9-569A-4BFA-A0C7-79F20BE17F4F}.Release|x86.ActiveCfg = Release|Any CPU
{D72448F9-569A-4BFA-A0C7-79F20BE17F4F}.Release|x86.Build.0 = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|ARM64.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|x64.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|x64.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|x86.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Debug|x86.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|Any CPU.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|Any CPU.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|ARM64.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|ARM64.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|x64.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|x64.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|x86.ActiveCfg = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.MSIX|x86.Build.0 = Debug|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|Any CPU.Build.0 = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|ARM64.ActiveCfg = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|ARM64.Build.0 = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|x64.ActiveCfg = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|x64.Build.0 = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|x86.ActiveCfg = Release|Any CPU
{1086BFB7-F980-4B08-956F-555B06C70992}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -235,6 +261,7 @@ Global
{DF9B003E-4B31-494D-8186-15CE8DD69489} = {D1378C69-4AE6-4045-9CE3-1310C1DAFB2D}
{1E511652-E2A5-44EE-B0F6-E8E53FD6AA8F} = {D1378C69-4AE6-4045-9CE3-1310C1DAFB2D}
{D72448F9-569A-4BFA-A0C7-79F20BE17F4F} = {0F31C99F-8F09-4B2C-9411-26532A69AE62}
{1086BFB7-F980-4B08-956F-555B06C70992} = {D1378C69-4AE6-4045-9CE3-1310C1DAFB2D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0577168B-4A3F-4316-8E17-0E4423B092CC}
Expand Down
26 changes: 26 additions & 0 deletions src/ImageSort.Avalonia/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ImageSort.Avalonia.App"
xmlns:local="using:ImageSort.Avalonia"
xmlns:converters="using:ImageSort.Avalonia.Converters"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

<Application.Resources>
<converters:PathToBitmapConverter x:Key="PathToBitmapConverter"/>
<converters:PathToFilenameConverter x:Key="PathToFilenameConverter"/>
</Application.Resources>

<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>

<Application.Styles>
<FluentTheme />
<!-- Add AdonisUI resources. Note: Paths may need adjustment for Avalonia -->
<!-- <ResourceDictionary Source="avares://AdonisUI/ColorSchemes/Light.xaml" /> -->
<!-- <ResourceDictionary Source="avares://AdonisUI.ClassicTheme/Resources.xaml" /> -->
<!-- Add local styles -->
<StyleInclude Source="avares://ImageSort.Avalonia/Styles/Controls.xaml" />
</Application.Styles>
</Application>
163 changes: 163 additions & 0 deletions src/ImageSort.Avalonia/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using Avalonia.Markup.Xaml;
using ImageSort.Avalonia.ViewModels;
using ImageSort.Avalonia.Views;
using System;
using System.Globalization;
using System.Reactive.Concurrency;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using ImageSort.DependencyManagement;
using ImageSort.FileSystem;
using ImageSort.SettingsManagement;
using ReactiveUI;
using Splat;
using ImageSort.Avalonia.SettingsManagement; // For the new SettingsHelper and Restore extension
using ImageSort.ViewModels; // Added for core ViewModels
using System.IO; // Required for FileSystemWatcher
using ImageSort.Avalonia.FileSystem; // Added for TemporaryRecycleBin

#if !DO_NOT_INCLUDE_UPDATER
#endif

namespace ImageSort.Avalonia;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
#if DEBUG && !DEBUG_LOCALIZATION
Thread.CurrentThread.CurrentCulture = new CultureInfo("en");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
#endif

Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
Locator.CurrentMutable.RegisterManditoryDependencies();
Locator.CurrentMutable.RegisterSettings(settings =>
{
settings.Add(new GeneralSettingsGroupViewModel());
settings.Add(new PinnedFolderSettingsViewModel());
settings.Add(new KeyBindingsSettingsGroupViewModel());
settings.Add(new MetadataPanelSettings());
});
Locator.CurrentMutable.RegisterLazySingleton(() => new SettingsViewModel());
Locator.CurrentMutable.Register<IRecycleBin>(() => new TemporaryRecycleBin()); // Register TemporaryRecycleBin

Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
DisableAvaloniaDataAnnotationValidation();
var fileSystem = Locator.Current.GetService<IFileSystem>();
if (fileSystem == null)
{
throw new InvalidOperationException("IFileSystem service not registered.");
}

var recycleBin = Locator.Current.GetService<IRecycleBin>();
if (recycleBin == null)
{
throw new InvalidOperationException("IRecycleBin service not registered.");
}

var backgroundScheduler = RxApp.TaskpoolScheduler;
var mainThreadScheduler = RxApp.MainThreadScheduler;

var foldersViewModel = new FoldersViewModel(fileSystem, backgroundScheduler);
// Correctly instantiate ImagesViewModel with its actual constructor signature
var imagesViewModel = new ImagesViewModel(fileSystem, null); // Pass fileSystem and null for the optional folderWatcherFactory
// Pass dependencies directly to ActionsViewModel constructor
var actionsViewModel = new ActionsViewModel(imagesViewModel, foldersViewModel, fileSystem);

var mainWindowViewModel = new MainWindowViewModel(
foldersViewModel,
imagesViewModel,
actionsViewModel,
fileSystem,
recycleBin,
backgroundScheduler);

desktop.MainWindow = new MainWindow
{
DataContext = mainWindowViewModel, // Set DataContext
ViewModel = mainWindowViewModel // Explicitly set the ViewModel property
};

OnAppStartup();
}

base.OnFrameworkInitializationCompleted();
}

private void DisableAvaloniaDataAnnotationValidation()
{
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();

foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}

private void CurrentDomain_UnhandledException(object? sender, UnhandledExceptionEventArgs e)
{
System.Diagnostics.Trace.WriteLine(e.ExceptionObject);
}

private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
System.Diagnostics.Trace.WriteLine(e.Exception);
e.SetObserved();
}

#pragma warning disable CS1998
private async void OnAppStartup()
#pragma warning restore CS1998
{
var settings = Locator.Current.GetService<SettingsViewModel>();
var mainWindowVm = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow?.DataContext as MainWindowViewModel;

if (settings != null)
{
settings.Restore(); // This should set CurrentFolder (if saved) and PinnedFolder paths
}

if (mainWindowVm != null && settings != null)
{
var pinnedFolderSettings = settings.GetGroup<PinnedFolderSettingsViewModel>();
if (pinnedFolderSettings != null && pinnedFolderSettings.PinnedFolders != null)
{
mainWindowVm.Folders.AddPinnedFoldersFromPaths(pinnedFolderSettings.PinnedFolders);
}

// After settings are restored and pinned folders loaded,
// if CurrentFolder has been set (e.g., by settings restoration),
// force it to re-notify. This might help ensure DisplayedFolderItems updates correctly.
if (mainWindowVm.Folders.CurrentFolder != null)
{
var tempCurrentFolder = mainWindowVm.Folders.CurrentFolder;
// Temporarily setting to null and back to the original instance
// to ensure property change notifications are raised.
mainWindowVm.Folders.CurrentFolder = null;
mainWindowVm.Folders.CurrentFolder = tempCurrentFolder;
}
}

#if !DO_NOT_INCLUDE_UPDATER
#endif
}
}
Binary file added src/ImageSort.Avalonia/Assets/avalonia-logo.ico
Binary file not shown.
33 changes: 33 additions & 0 deletions src/ImageSort.Avalonia/Converters/PathToBitmapConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Avalonia.Data.Converters;
using Avalonia.Media.Imaging;
using System;
using System.Globalization;
using System.IO;

namespace ImageSort.Avalonia.Converters
{
public class PathToBitmapConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string path && !string.IsNullOrEmpty(path) && File.Exists(path))
{
try
{
return new Bitmap(path);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading image {path}: {ex.Message}");
return null;
}
}
return null;
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
32 changes: 32 additions & 0 deletions src/ImageSort.Avalonia/Converters/PathToFilenameConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Avalonia.Data.Converters;
using System;
using System.Globalization;
using System.IO;

namespace ImageSort.Avalonia.Converters
{
public class PathToFilenameConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string path && !string.IsNullOrEmpty(path))
{
try
{
return Path.GetFileName(path);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting filename from {path}: {ex.Message}");
return path; // Fallback to full path on error
}
}
return string.Empty;
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Loading
Loading