diff --git a/SolutionParityChecker.App/App.axaml b/SolutionParityChecker.App/App.axaml
index 1c2e7ad..2c7915e 100644
--- a/SolutionParityChecker.App/App.axaml
+++ b/SolutionParityChecker.App/App.axaml
@@ -2,8 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SolutionParityChecker.App.App"
RequestedThemeVariant="Default">
-
-
diff --git a/SolutionParityChecker.App/App.axaml.cs b/SolutionParityChecker.App/App.axaml.cs
index 7d9302c..b39a4d6 100644
--- a/SolutionParityChecker.App/App.axaml.cs
+++ b/SolutionParityChecker.App/App.axaml.cs
@@ -1,6 +1,8 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
+using SolutionParityChecker.App.ViewModels;
+using SolutionParityChecker.App.Views;
namespace SolutionParityChecker.App;
diff --git a/SolutionParityChecker.App/Assets/avalonia-logo.ico b/SolutionParityChecker.App/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..da8d49f
Binary files /dev/null and b/SolutionParityChecker.App/Assets/avalonia-logo.ico differ
diff --git a/SolutionParityChecker.App/MainWindow.axaml b/SolutionParityChecker.App/MainWindow.axaml
deleted file mode 100644
index d6a5f57..0000000
--- a/SolutionParityChecker.App/MainWindow.axaml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
- Welcome to Avalonia!
-
-
-
-
-
-
-
diff --git a/SolutionParityChecker.App/MainWindowViewModel.cs b/SolutionParityChecker.App/MainWindowViewModel.cs
deleted file mode 100644
index e866bd3..0000000
--- a/SolutionParityChecker.App/MainWindowViewModel.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace SolutionParityChecker.App;
-
-public class MainWindowViewModel
-{
- public string SolutionFolderPath { get; set; } = string.Empty;
- public string SolutionFilePath { get; set; } = string.Empty;
-}
diff --git a/SolutionParityChecker.App/Models/AppState.cs b/SolutionParityChecker.App/Models/AppState.cs
deleted file mode 100644
index d8c628c..0000000
--- a/SolutionParityChecker.App/Models/AppState.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace SolutionParityChecker.App.Models;
-
-public class AppState
-{
-
-}
\ No newline at end of file
diff --git a/SolutionParityChecker.App/Program.cs b/SolutionParityChecker.App/Program.cs
index 160e9f3..49a281a 100644
--- a/SolutionParityChecker.App/Program.cs
+++ b/SolutionParityChecker.App/Program.cs
@@ -1,5 +1,5 @@
-using Avalonia;
-using System;
+using System;
+using Avalonia;
namespace SolutionParityChecker.App;
@@ -9,13 +9,10 @@ class Program
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
- public static void Main(string[] args) => BuildAvaloniaApp()
- .StartWithClassicDesktopLifetime(args);
+ public static void Main(string[] args) =>
+ BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
- public static AppBuilder BuildAvaloniaApp()
- => AppBuilder.Configure()
- .UsePlatformDetect()
- .WithInterFont()
- .LogToTrace();
-}
\ No newline at end of file
+ public static AppBuilder BuildAvaloniaApp() =>
+ AppBuilder.Configure().UsePlatformDetect().WithInterFont().LogToTrace();
+}
diff --git a/SolutionParityChecker.App/Services/DialogService.cs b/SolutionParityChecker.App/Services/DialogService.cs
deleted file mode 100644
index 37492a4..0000000
--- a/SolutionParityChecker.App/Services/DialogService.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace SolutionParityChecker.App.Services;
-
-public class DialogService
-{
-
-}
\ No newline at end of file
diff --git a/SolutionParityChecker.App/SolutionParityChecker.App.csproj b/SolutionParityChecker.App/SolutionParityChecker.App.csproj
index f602f90..081513a 100644
--- a/SolutionParityChecker.App/SolutionParityChecker.App.csproj
+++ b/SolutionParityChecker.App/SolutionParityChecker.App.csproj
@@ -8,13 +8,22 @@
true
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs b/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..dac9045
--- /dev/null
+++ b/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,121 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace SolutionParityChecker.App.ViewModels;
+
+public partial class MainWindowViewModel : ViewModelBase
+{
+ public string SolutionFolderPath { get; set; } = string.Empty;
+ public string SolutionFilePath { get; set; } = string.Empty;
+
+ [ObservableProperty]
+ private string? _fileText;
+
+ [RelayCommand]
+ private async Task OpenFile(CancellationToken token)
+ {
+ ErrorMessages?.Clear();
+ try
+ {
+ var file = await DoOpenFilePickerAsync();
+ if (file is null)
+ return;
+
+ // Limit the text file to 1MB so that the demo won't lag.
+ if ((await file.GetBasicPropertiesAsync()).Size <= 1024 * 1024 * 1)
+ {
+ await using var readStream = await file.OpenReadAsync();
+ using var reader = new StreamReader(readStream);
+ FileText = await reader.ReadToEndAsync(token);
+ }
+ else
+ {
+ throw new Exception("File exceeded 1MB limit.");
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorMessages?.Add(e.Message);
+ }
+ }
+
+ [RelayCommand]
+ private async Task SaveFile()
+ {
+ ErrorMessages?.Clear();
+ try
+ {
+ var file = await DoSaveFilePickerAsync();
+ if (file is null)
+ return;
+
+ // Limit the text file to 1MB so that the demo won't lag.
+ if (FileText?.Length <= 1024 * 1024 * 1)
+ {
+ var stream = new MemoryStream(Encoding.Default.GetBytes((string)FileText));
+ await using var writeStream = await file.OpenWriteAsync();
+ await stream.CopyToAsync(writeStream);
+ }
+ else
+ {
+ throw new Exception("File exceeded 1MB limit.");
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorMessages?.Add(e.Message);
+ }
+ }
+
+ private async Task DoOpenFilePickerAsync()
+ {
+ // For learning purposes, we opted to directly get the reference
+ // for StorageProvider APIs here inside the ViewModel.
+
+ // For your real-world apps, you should follow the MVVM principles
+ // by making service classes and locating them with DI/IoC.
+
+ // See IoCFileOps project for an example of how to accomplish this.
+ if (
+ Application.Current?.ApplicationLifetime
+ is not IClassicDesktopStyleApplicationLifetime desktop
+ || desktop.MainWindow?.StorageProvider is not { } provider
+ )
+ throw new NullReferenceException("Missing StorageProvider instance.");
+
+ var files = await provider.OpenFilePickerAsync(
+ new FilePickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
+ );
+
+ return files?.Count >= 1 ? files[0] : null;
+ }
+
+ private async Task DoSaveFilePickerAsync()
+ {
+ // For learning purposes, we opted to directly get the reference
+ // for StorageProvider APIs here inside the ViewModel.
+
+ // For your real-world apps, you should follow the MVVM principles
+ // by making service classes and locating them with DI/IoC.
+
+ // See DepInject project for a sample of how to accomplish this.
+ if (
+ Application.Current?.ApplicationLifetime
+ is not IClassicDesktopStyleApplicationLifetime desktop
+ || desktop.MainWindow?.StorageProvider is not { } provider
+ )
+ throw new NullReferenceException("Missing StorageProvider instance.");
+
+ return await provider.SaveFilePickerAsync(
+ new FilePickerSaveOptions() { Title = "Save Text File" }
+ );
+ }
+}
diff --git a/SolutionParityChecker.App/ViewModels/ViewModelBase.cs b/SolutionParityChecker.App/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..ebf744e
--- /dev/null
+++ b/SolutionParityChecker.App/ViewModels/ViewModelBase.cs
@@ -0,0 +1,15 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace SolutionParityChecker.App.ViewModels;
+
+public partial class ViewModelBase : ObservableObject
+{
+ protected ViewModelBase()
+ {
+ ErrorMessages = new ObservableCollection();
+ }
+
+ [ObservableProperty]
+ private ObservableCollection? _errorMessages;
+}
\ No newline at end of file
diff --git a/SolutionParityChecker.App/Views/MainWindow.axaml b/SolutionParityChecker.App/Views/MainWindow.axaml
new file mode 100644
index 0000000..639da74
--- /dev/null
+++ b/SolutionParityChecker.App/Views/MainWindow.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Welcome to Avalonia!
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SolutionParityChecker.App/MainWindow.axaml.cs b/SolutionParityChecker.App/Views/MainWindow.axaml.cs
similarity index 91%
rename from SolutionParityChecker.App/MainWindow.axaml.cs
rename to SolutionParityChecker.App/Views/MainWindow.axaml.cs
index 5f79b9d..53ebbc5 100644
--- a/SolutionParityChecker.App/MainWindow.axaml.cs
+++ b/SolutionParityChecker.App/Views/MainWindow.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
-namespace SolutionParityChecker.App;
+namespace SolutionParityChecker.App.Views;
public partial class MainWindow : Window
{
diff --git a/SolutionParityChecker.App/app.manifest b/SolutionParityChecker.App/app.manifest
index e2aac00..c459245 100644
--- a/SolutionParityChecker.App/app.manifest
+++ b/SolutionParityChecker.App/app.manifest
@@ -1,7 +1,7 @@