Migrate to Avalonia (#1220)

This commit is contained in:
Oleksii Holub
2024-04-27 04:17:46 +03:00
committed by GitHub
parent 74f99b4e59
commit b9c1c47474
89 changed files with 2467 additions and 2810 deletions

View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,34 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.VisualTree;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class AvaloniaExtensions
{
public static Window? TryGetMainWindow(this IApplicationLifetime lifetime) =>
lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime
? desktopLifetime.MainWindow
: null;
public static TopLevel? TryGetTopLevel(this IApplicationLifetime lifetime) =>
lifetime.TryGetMainWindow()
?? (lifetime as ISingleViewApplicationLifetime)?.MainView?.GetVisualRoot() as TopLevel;
public static bool TryShutdown(this IApplicationLifetime lifetime, int exitCode = 0)
{
if (lifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
return desktopLifetime.TryShutdown(exitCode);
}
if (lifetime is IControlledApplicationLifetime controlledLifetime)
{
controlledLifetime.Shutdown(exitCode);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class DisposableExtensions
{
public static void DisposeAll(this IEnumerable<IDisposable> disposables)
{
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

@@ -0,0 +1,60 @@
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
namespace DiscordChatExporter.Gui.Utils.Extensions;
internal static class NotifyPropertyChangedExtensions
{
public static IDisposable WatchProperty<TOwner, TProperty>(
this TOwner owner,
Expression<Func<TOwner, TProperty>> propertyExpression,
Action callback,
bool watchInitialValue = true
)
where TOwner : INotifyPropertyChanged
{
var memberExpression =
propertyExpression.Body as MemberExpression
// Property value might be boxed inside a conversion expression, if the types don't match
?? (propertyExpression.Body as UnaryExpression)?.Operand as MemberExpression;
if (memberExpression?.Member is not PropertyInfo property)
throw new ArgumentException("Provided expression must reference a property.");
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
{
if (
string.IsNullOrWhiteSpace(args.PropertyName)
|| string.Equals(args.PropertyName, property.Name, StringComparison.Ordinal)
)
{
callback();
}
}
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValue)
callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
}
public static IDisposable WatchAllProperties<TOwner>(
this TOwner owner,
Action callback,
bool watchInitialValues = true
)
where TOwner : INotifyPropertyChanged
{
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) => callback();
owner.PropertyChanged += OnPropertyChanged;
if (watchInitialValues)
callback();
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
}
}

View File

@@ -7,4 +7,6 @@ internal static class Internationalization
public static bool Is24Hours =>
string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator)
&& string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator);
public static string AvaloniaClockIdentifier => Is24Hours ? "24HourClock" : "12HourClock";
}

View File

@@ -1,8 +0,0 @@
using System.Windows.Media;
namespace DiscordChatExporter.Gui.Utils;
internal static class MediaColor
{
public static Color FromHex(string hex) => (Color)ColorConverter.ConvertFromString(hex);
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace DiscordChatExporter.Gui.Utils;
internal static class NativeMethods
{
public static class Windows
{
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBox(nint hWnd, string text, string caption, uint type);
}
}

View File

@@ -6,10 +6,8 @@ internal static class ProcessEx
{
public static void StartShellExecute(string path)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo { FileName = path, UseShellExecute = true }
};
using var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = path, UseShellExecute = true };
process.Start();
}