Add PowerKit and replace custom utility extensions (#1525)

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2026-04-19 23:10:45 +03:00
committed by GitHub
parent 757daa7a60
commit 7456f0fe3a
75 changed files with 133 additions and 603 deletions

View File

@@ -7,13 +7,13 @@ using Avalonia.Platform;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils;
using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels;
using DiscordChatExporter.Gui.ViewModels.Components;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
using Material.Styles.Themes;
using Microsoft.Extensions.DependencyInjection;
using PowerKit;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui;
@@ -22,7 +22,7 @@ public class App : Application, IDisposable
private readonly ServiceProvider _services;
private readonly SettingsService _settingsService;
private readonly DisposableCollector _eventRoot = new();
private readonly IDisposable _eventSubscription;
private bool _isDisposed;
@@ -54,7 +54,7 @@ public class App : Application, IDisposable
_settingsService = _services.GetRequiredService<SettingsService>();
// Re-initialize the theme when the user changes it
_eventRoot.Add(
_eventSubscription = Disposable.Merge(
_settingsService.WatchProperty(
o => o.Theme,
v =>
@@ -131,7 +131,7 @@ public class App : Application, IDisposable
_isDisposed = true;
_eventRoot.Dispose();
_eventSubscription.Dispose();
_services.Dispose();
}
}

View File

@@ -41,6 +41,7 @@
<PackageReference Include="Material.Icons.Avalonia" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Onova" />
<PackageReference Include="PowerKit" PrivateAssets="all" />
<PackageReference Include="ThisAssembly.Project" PrivateAssets="all" />
</ItemGroup>

View File

@@ -3,20 +3,19 @@ using System.Globalization;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils;
using DiscordChatExporter.Gui.Utils.Extensions;
using PowerKit;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager : ObservableObject, IDisposable
{
private readonly DisposableCollector _eventRoot = new();
private readonly IDisposable _eventSubscription;
public LocalizationManager(SettingsService settingsService)
{
_eventRoot.Add(settingsService.WatchProperty(o => o.Language, v => Language = v, true));
_eventRoot.Add(
_eventSubscription = Disposable.Merge(
settingsService.WatchProperty(o => o.Language, v => Language = v, true),
this.WatchProperty(
o => o.Language,
_ =>
@@ -66,7 +65,7 @@ public partial class LocalizationManager : ObservableObject, IDisposable
return $"Missing localization for '{key}'";
}
public void Dispose() => _eventRoot.Dispose();
public void Dispose() => _eventSubscription.Dispose();
}
public partial class LocalizationManager

View File

@@ -1,10 +0,0 @@
using System;
namespace DiscordChatExporter.Gui.Utils;
internal class Disposable(Action dispose) : IDisposable
{
public static IDisposable Create(Action dispose) => new Disposable(dispose);
public void Dispose() => dispose();
}

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using DiscordChatExporter.Gui.Utils.Extensions;
namespace DiscordChatExporter.Gui.Utils;
internal class DisposableCollector : IDisposable
{
private readonly object _lock = new();
private readonly List<IDisposable> _items = [];
public void Add(IDisposable item)
{
lock (_lock)
{
_items.Add(item);
}
}
public void Dispose()
{
lock (_lock)
{
_items.DisposeAll();
_items.Clear();
}
}
}

View File

@@ -1,15 +0,0 @@
using System.Windows.Input;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class CommandExtensions
{
extension(ICommand command)
{
public void ExecuteIfCan(object? parameter = null)
{
if (command.CanExecute(parameter))
command.Execute(parameter);
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class DisposableExtensions
{
extension(IEnumerable<IDisposable> disposables)
{
public void DisposeAll()
{
var exceptions = default(List<Exception>);
foreach (var disposable in disposables)
{
try
{
disposable.Dispose();
}
catch (Exception ex)
{
(exceptions ??= []).Add(ex);
}
}
if (exceptions?.Any() == true)
throw new AggregateException(exceptions);
}
}
}

View File

@@ -1,55 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class NotifyPropertyChangedExtensions
{
extension<TOwner>(TOwner owner)
where TOwner : INotifyPropertyChanged
{
public IDisposable WatchProperty<TProperty>(
Expression<Func<TOwner, TProperty>> propertyExpression,
Action<TProperty> callback,
bool watchInitialValue = false
)
{
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression?.Member is not PropertyInfo property)
throw new ArgumentException("Provided expression must reference a property.");
var getValue = propertyExpression.Compile();
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
{
if (
string.IsNullOrWhiteSpace(args.PropertyName)
|| string.Equals(args.PropertyName, property.Name, StringComparison.Ordinal)
)
{
callback(getValue(owner));
}
}
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValue)
callback(getValue(owner));
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
}
public IDisposable WatchAllProperties(Action callback, bool watchInitialValues = false)
{
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) => callback();
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValues)
callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
}
}
}

View File

@@ -1,17 +0,0 @@
using System.Diagnostics;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class ProcessExtensions
{
extension(Process)
{
public static void StartShellExecute(string path)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo(path) { UseShellExecute = true };
process.Start();
}
}
}

View File

@@ -10,15 +10,14 @@ using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exceptions;
using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Models;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils;
using DiscordChatExporter.Gui.Utils.Extensions;
using Gress;
using Gress.Completable;
using PowerKit;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Components;
@@ -29,7 +28,7 @@ public partial class DashboardViewModel : ViewModelBase
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
private readonly DisposableCollector _eventRoot = new();
private readonly IDisposable _eventSubscription;
private readonly AutoResetProgressMuxer _progressMuxer;
private DiscordClient? _discord;
@@ -50,14 +49,11 @@ public partial class DashboardViewModel : ViewModelBase
_progressMuxer = Progress.CreateMuxer().WithAutoReset();
_eventRoot.Add(
_eventSubscription = Disposable.Merge(
Progress.WatchProperty(
o => o.Current,
_ => OnPropertyChanged(nameof(IsProgressIndeterminate))
)
);
_eventRoot.Add(
),
SelectedChannels.WatchProperty(
o => o.Count,
_ => ExportCommand.NotifyCanExecuteChanged()
@@ -332,7 +328,7 @@ public partial class DashboardViewModel : ViewModelBase
{
if (disposing)
{
_eventRoot.Dispose();
_eventSubscription.Dispose();
}
base.Dispose(disposing);

View File

@@ -10,10 +10,10 @@ using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning;
using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;

View File

@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Models;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils;
using DiscordChatExporter.Gui.Utils.Extensions;
using PowerKit;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
@@ -15,7 +14,7 @@ public class SettingsViewModel : DialogViewModelBase
{
private readonly SettingsService _settingsService;
private readonly DisposableCollector _eventRoot = new();
private readonly IDisposable _eventSubscription;
public SettingsViewModel(
SettingsService settingsService,
@@ -25,7 +24,9 @@ public class SettingsViewModel : DialogViewModelBase
_settingsService = settingsService;
LocalizationManager = localizationManager;
_eventRoot.Add(_settingsService.WatchAllProperties(OnAllPropertiesChanged));
_eventSubscription = Disposable.Merge(
_settingsService.WatchAllProperties(OnAllPropertiesChanged)
);
}
public LocalizationManager LocalizationManager { get; }
@@ -140,7 +141,7 @@ public class SettingsViewModel : DialogViewModelBase
{
if (disposing)
{
_eventRoot.Dispose();
_eventSubscription.Dispose();
}
base.Dispose(disposing);

View File

@@ -7,6 +7,7 @@ using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels.Components;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels;

View File

@@ -4,8 +4,8 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels.Components;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Views.Components;

View File

@@ -3,7 +3,7 @@ using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using DiscordChatExporter.Gui.Utils.Extensions;
using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Views.Controls;