Replace CommunityToolkit.Mvvm with PropertyChanged.SourceGenerator

This commit is contained in:
chylex 2025-03-18 12:24:43 +01:00
parent e30b305eb5
commit 38f79dee7d
No known key found for this signature in database
17 changed files with 112 additions and 119 deletions

2
app/.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.cs]
propertychanged.auto_notify = false

View File

@ -23,7 +23,10 @@
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="999.0.0-build.0.g0d941a6a62" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Dialogs.CheckBox;
class CheckBoxDialogModel : ObservableObject {
partial class CheckBoxDialogModel {
public string Title { get; init; } = "";
private IReadOnlyList<CheckBoxItem> items = [];
@ -28,7 +28,10 @@ class CheckBoxDialogModel : ObservableObject {
private bool pauseCheckEvents = false;
[DependsOn(nameof(Items))]
public bool AreAllSelected => Items.All(static item => item.IsChecked);
[DependsOn(nameof(Items))]
public bool AreNoneSelected => Items.All(static item => !item.IsChecked);
public void SelectAll() => SetAllChecked(true);
@ -46,8 +49,7 @@ class CheckBoxDialogModel : ObservableObject {
}
private void UpdateBulkButtons() {
OnPropertyChanged(nameof(AreAllSelected));
OnPropertyChanged(nameof(AreNoneSelected));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Items)));
}
private void OnItemPropertyChanged(object? sender, PropertyChangedEventArgs e) {

View File

@ -1,12 +1,12 @@
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Dialogs.CheckBox;
partial class CheckBoxItem : ObservableObject {
partial class CheckBoxItem {
public string Title { get; init; } = "";
public object? Item { get; init; } = null;
[ObservableProperty]
[Notify]
private bool isChecked = false;
}

View File

@ -1,30 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Dialogs.Progress;
sealed partial class ProgressItem : ObservableObject {
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(Opacity))]
private bool isVisible = false;
public double Opacity => IsVisible ? 1.0 : 0.0;
sealed partial class ProgressItem {
[Notify]
private string message = "";
public string Message {
get => message;
set {
SetProperty(ref message, value);
IsVisible = !string.IsNullOrEmpty(value);
}
}
[ObservableProperty]
[Notify]
private string items = "";
[ObservableProperty]
[Notify]
private long progress = 0L;
[ObservableProperty]
[Notify]
private bool isIndeterminate;
[DependsOn(nameof(Message))]
public bool IsVisible => !string.IsNullOrEmpty(Message);
[DependsOn(nameof(IsVisible))]
public double Opacity => IsVisible ? 1.0 : 0.0;
}

View File

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Dialogs.TextBox;
class TextBoxDialogModel : ObservableObject {
partial class TextBoxDialogModel {
public string Title { get; init; } = "";
public string Description { get; init; } = "";
@ -27,10 +27,11 @@ class TextBoxDialogModel : ObservableObject {
}
}
[DependsOn(nameof(Items))]
public bool HasErrors => Items.Any(static item => !item.IsValid);
private void OnItemErrorsChanged(object? sender, DataErrorsChangedEventArgs e) {
OnPropertyChanged(nameof(HasErrors));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Items)));
}
}

View File

@ -1,25 +1,22 @@
using System;
using System.Collections;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Dialogs.TextBox;
class TextBoxItem : ObservableObject, INotifyDataErrorInfo {
partial class TextBoxItem : INotifyDataErrorInfo {
public string Title { get; init; } = "";
public object? Item { get; init; } = null;
public Func<string, bool> ValidityCheck { get; init; } = static _ => true;
public bool IsValid => ValidityCheck(Value);
[Notify]
private string value = string.Empty;
public string Value {
get => value;
set {
SetProperty(ref this.value, value);
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value)));
}
private void OnValueChanged() {
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value)));
}
public IEnumerable GetErrors(string? propertyName) {

View File

@ -5,17 +5,17 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Common;
using DHT.Server;
using DHT.Server.Data.Filters;
using DHT.Server.Data.Settings;
using DHT.Utils.Logging;
using DHT.Utils.Tasks;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Controls;
sealed partial class DownloadItemFilterPanelModel : ObservableObject, IAsyncDisposable {
sealed partial class DownloadItemFilterPanelModel : IAsyncDisposable {
private static readonly Log Log = Log.ForType<DownloadItemFilterPanelModel>();
public sealed record Unit(string Name, uint Scale);
@ -32,15 +32,16 @@ sealed partial class DownloadItemFilterPanelModel : ObservableObject, IAsyncDisp
nameof(MaximumSizeUnit),
];
public string FilterStatisticsText { get; private set; } = "";
[Notify(Setter.Private)]
private string filterStatisticsText = "";
[ObservableProperty]
[Notify]
private bool limitSize = false;
[ObservableProperty]
[Notify]
private ulong maximumSize = 0UL;
[ObservableProperty]
[Notify]
private Unit maximumSizeUnit = AllUnits[0];
public IEnumerable<Unit> Units => AllUnits;
@ -151,9 +152,7 @@ sealed partial class DownloadItemFilterPanelModel : ObservableObject, IAsyncDisp
private void UpdateFilterStatisticsText() {
string matchingItemCountStr = matchingItemCount?.Format() ?? "(...)";
string totalItemCountStr = totalItemCount?.Format() ?? "(...)";
FilterStatisticsText = verb + " " + matchingItemCountStr + " out of " + totalItemCountStr + " file" + (totalItemCount is null or 1 ? "." : "s.");
OnPropertyChanged(nameof(FilterStatisticsText));
}
public DownloadItemFilter CreateFilter() {

View File

@ -7,7 +7,6 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.CheckBox;
using DHT.Desktop.Dialogs.Message;
@ -16,10 +15,11 @@ using DHT.Server;
using DHT.Server.Data;
using DHT.Server.Data.Filters;
using DHT.Utils.Tasks;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Controls;
sealed partial class MessageFilterPanelModel : ObservableObject, IDisposable {
sealed partial class MessageFilterPanelModel : IDisposable {
private static readonly HashSet<string> FilterProperties = [
nameof(FilterByDate),
nameof(StartDate),
@ -30,37 +30,38 @@ sealed partial class MessageFilterPanelModel : ObservableObject, IDisposable {
nameof(IncludedUsers),
];
public string FilterStatisticsText { get; private set; } = "";
public event PropertyChangedEventHandler? FilterPropertyChanged;
public bool HasAnyFilters => FilterByDate || FilterByChannel || FilterByUser;
[ObservableProperty]
[Notify]
private string filterStatisticsText = "";
[Notify]
private bool filterByDate = false;
[ObservableProperty]
[Notify]
private DateTime? startDate = null;
[ObservableProperty]
[Notify]
private DateTime? endDate = null;
[ObservableProperty]
[Notify]
private bool filterByChannel = false;
[ObservableProperty]
[Notify]
private HashSet<ulong>? includedChannels = null;
[ObservableProperty]
[Notify]
private bool filterByUser = false;
[ObservableProperty]
[Notify]
private HashSet<ulong>? includedUsers = null;
[ObservableProperty]
[Notify]
private string channelFilterLabel = "";
[ObservableProperty]
[Notify]
private string userFilterLabel = "";
private readonly Window window;
@ -181,9 +182,7 @@ sealed partial class MessageFilterPanelModel : ObservableObject, IDisposable {
private void UpdateFilterStatisticsText() {
string exportedMessageCountStr = exportedMessageCount?.Format() ?? "(...)";
string totalMessageCountStr = totalMessageCount?.Format() ?? "(...)";
FilterStatisticsText = verb + " " + exportedMessageCountStr + " out of " + totalMessageCountStr + " message" + (totalMessageCount is null or 1 ? "." : "s.");
OnPropertyChanged(nameof(FilterStatisticsText));
}
public async Task OpenChannelFilterDialog() {

View File

@ -1,30 +1,30 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Server;
using DHT.Server;
using DHT.Server.Service;
using DHT.Utils.Logging;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Controls;
sealed partial class ServerConfigurationPanelModel : ObservableObject, IDisposable {
sealed partial class ServerConfigurationPanelModel : IDisposable {
private static readonly Log Log = Log.ForType<ServerConfigurationPanelModel>();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasMadeChanges))]
[Notify]
private string inputPort;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasMadeChanges))]
[Notify]
private string inputToken;
[DependsOn(nameof(InputPort), nameof(InputToken))]
public bool HasMadeChanges => ServerConfiguration.Port.ToString() != InputPort || ServerConfiguration.Token != InputToken;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private bool isToggleServerButtonEnabled = true;
public string ToggleServerButtonText => server.IsRunning ? "Stop Server" : "Start Server";
@ -53,7 +53,7 @@ sealed partial class ServerConfigurationPanelModel : ObservableObject, IDisposab
}
private void UpdateServerStatus() {
OnPropertyChanged(nameof(ToggleServerButtonText));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ToggleServerButtonText)));
}
private async Task StartServer() {
@ -106,7 +106,7 @@ sealed partial class ServerConfigurationPanelModel : ObservableObject, IDisposab
ServerConfiguration.Port = port;
ServerConfiguration.Token = inputToken;
OnPropertyChanged(nameof(HasMadeChanges));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(HasMadeChanges)));
await StartServer();
}

View File

@ -2,27 +2,27 @@ using System;
using System.Reactive.Linq;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Server;
using DHT.Server.Service;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Controls;
sealed partial class StatusBarModel : ObservableObject, IDisposable {
[ObservableProperty(Setter = Access.Private)]
sealed partial class StatusBarModel : IDisposable {
[Notify(Setter.Private)]
private long? serverCount;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private long? channelCount;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private long? messageCount;
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(ServerStatusText))]
[Notify(Setter.Private)]
private ServerManager.Status serverStatus;
public string ServerStatusText => serverStatus switch {
[DependsOn(nameof(ServerStatus))]
public string ServerStatusText => ServerStatus switch {
ServerManager.Status.Starting => "STARTING",
ServerManager.Status.Started => "READY",
ServerManager.Status.Stopping => "STOPPING",
@ -45,7 +45,7 @@ sealed partial class StatusBarModel : ObservableObject, IDisposable {
channelCountSubscription = state.Db.Channels.TotalCount.ObserveOn(AvaloniaScheduler.Instance).Subscribe(newChannelCount => ChannelCount = newChannelCount);
messageCountSubscription = state.Db.Messages.TotalCount.ObserveOn(AvaloniaScheduler.Instance).Subscribe(newMessageCount => MessageCount = newMessageCount);
state.Server.StatusChanged += OnServerStatusChanged;
state.Server.StatusChanged += OnStateServerStatusChanged;
serverStatus = state.Server.IsRunning ? ServerManager.Status.Started : ServerManager.Status.Stopped;
}
@ -54,10 +54,10 @@ sealed partial class StatusBarModel : ObservableObject, IDisposable {
channelCountSubscription.Dispose();
messageCountSubscription.Dispose();
state.Server.StatusChanged -= OnServerStatusChanged;
state.Server.StatusChanged -= OnStateServerStatusChanged;
}
private void OnServerStatusChanged(object? sender, ServerManager.Status e) {
private void OnStateServerStatusChanged(object? sender, ServerManager.Status e) {
Dispatcher.UIThread.InvokeAsync(() => ServerStatus = e);
}
}

View File

@ -1,11 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Dialogs;
sealed partial class NewDatabaseSettingsDialogModel : ObservableObject {
[ObservableProperty]
sealed partial class NewDatabaseSettingsDialogModel {
[Notify]
private bool separateFileForDownloads = true;
[ObservableProperty]
[Notify]
private bool downloadsAutoStart = true;
}

View File

@ -3,25 +3,25 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Main.Screens;
using DHT.Desktop.Server;
using DHT.Server;
using DHT.Server.Database;
using DHT.Utils.Logging;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main;
sealed partial class MainWindowModel : ObservableObject, IAsyncDisposable {
sealed partial class MainWindowModel : IAsyncDisposable {
private const string DefaultTitle = "Discord History Tracker";
private static readonly Log Log = Log.ForType<MainWindowModel>();
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private string title = DefaultTitle;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private UserControl currentScreen;
private readonly WelcomeScreen welcomeScreen;

View File

@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.File;
using DHT.Desktop.Dialogs.Message;
@ -20,31 +20,32 @@ using DHT.Server.Data.Settings;
using DHT.Server.Download;
using DHT.Utils.Logging;
using DHT.Utils.Tasks;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Pages;
sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
sealed partial class DownloadsPageModel : IAsyncDisposable {
private static readonly Log Log = Log.ForType<DownloadsPageModel>();
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private bool isToggleDownloadButtonEnabled = true;
[DependsOn(nameof(IsDownloading))]
public string ToggleDownloadButtonText => IsDownloading ? "Stop Downloading" : "Start Downloading";
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(IsRetryFailedOnDownloadsButtonEnabled))]
[Notify(Setter.Private)]
private bool isRetryingFailedDownloads = false;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private bool hasSuccessfulDownloads;
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(IsRetryFailedOnDownloadsButtonEnabled))]
[Notify(Setter.Private)]
private bool hasFailedDownloads;
[DependsOn(nameof(IsRetryingFailedDownloads), nameof(HasFailedDownloads))]
public bool IsRetryFailedOnDownloadsButtonEnabled => !IsRetryingFailedDownloads && HasFailedDownloads;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private string downloadMessage = "";
public DownloadItemFilterPanelModel FilterModel { get; }
@ -145,8 +146,7 @@ sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
private void OnDownloadStateChanged() {
RecomputeDownloadStatistics();
OnPropertyChanged(nameof(ToggleDownloadButtonText));
OnPropertyChanged(nameof(IsDownloading));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsDownloading)));
}
private void OnItemFinished(DownloadItem item) {
@ -281,21 +281,19 @@ sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
HasFailedDownloads = statusStatistics.FailedCount > 0;
}
[ObservableObject]
public sealed partial class StatisticsRow(string state) {
public string State { get; } = state;
[ObservableProperty]
[Notify]
private int items;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SizeText))]
[Notify]
private ulong? size;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SizeText))]
[Notify]
private bool hasFilesWithUnknownSize;
[DependsOn(nameof(Size), nameof(HasFilesWithUnknownSize))]
public string SizeText {
get {
if (size == null) {

View File

@ -4,28 +4,27 @@ using System.Threading.Tasks;
using System.Web;
using Avalonia.Controls;
using Avalonia.Input.Platform;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Discord;
using DHT.Desktop.Server;
using PropertyChanged.SourceGenerator;
using static DHT.Desktop.Program;
namespace DHT.Desktop.Main.Pages;
sealed partial class TrackingPageModel : ObservableObject {
[ObservableProperty(Setter = Access.Private)]
sealed partial class TrackingPageModel {
[Notify(Setter.Private)]
private bool isCopyTrackingScriptButtonEnabled = true;
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(ToggleAppDevToolsButtonText))]
[Notify(Setter.Private)]
private bool? areDevToolsEnabled = null;
[ObservableProperty(Setter = Access.Private)]
[NotifyPropertyChangedFor(nameof(ToggleAppDevToolsButtonText))]
[Notify(Setter.Private)]
private bool isToggleAppDevToolsButtonEnabled = false;
public string OpenDevToolsShortcutText { get; } = OperatingSystem.IsMacOS() ? "Cmd+Shift+I" : "Ctrl+Shift+I";
[DependsOn(nameof(AreDevToolsEnabled), nameof(IsToggleAppDevToolsButtonEnabled))]
public string ToggleAppDevToolsButtonText {
get {
if (!AreDevToolsEnabled.HasValue) {

View File

@ -3,7 +3,6 @@ using System.ComponentModel;
using System.Threading.Tasks;
using System.Web;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Dialogs.Progress;
@ -13,16 +12,17 @@ using DHT.Server;
using DHT.Server.Data.Filters;
using DHT.Server.Service.Viewer;
using DHT.Utils.Logging;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Pages;
sealed partial class ViewerPageModel : ObservableObject, IDisposable {
sealed partial class ViewerPageModel : IDisposable {
private static readonly Log Log = Log.ForType<ViewerPageModel>();
public bool DatabaseToolFilterModeKeep { get; set; } = true;
public bool DatabaseToolFilterModeRemove { get; set; } = false;
[ObservableProperty]
[Notify]
private bool hasFilters = false;
public MessageFilterPanelModel FilterModel { get; }

View File

@ -5,7 +5,6 @@ using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Dialogs.Progress;
@ -14,15 +13,16 @@ using DHT.Server.Database;
using DHT.Server.Database.Sqlite.Schema;
using DHT.Server.Database.Sqlite.Utils;
using DHT.Utils.Logging;
using PropertyChanged.SourceGenerator;
namespace DHT.Desktop.Main.Screens;
sealed partial class WelcomeScreenModel : ObservableObject {
sealed partial class WelcomeScreenModel {
private static readonly Log Log = Log.ForType<WelcomeScreenModel>();
public string Version => Program.Version;
[ObservableProperty(Setter = Access.Private)]
[Notify(Setter.Private)]
private bool isOpenOrCreateDatabaseButtonEnabled = true;
public event EventHandler<IDatabaseFile>? DatabaseSelected;