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 @@