Files
Libation/Source/LibationAvalonia/MessageBox.cs
2025-12-06 18:55:38 -07:00

211 lines
8.7 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DataLayer;
using Dinah.Core.Logging;
using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels.Dialogs;
using LibationUiBase.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform;
#nullable enable
namespace LibationAvalonia
{
public class MessageBox
{
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
=> ShowCoreAsync(null, text, caption, buttons, icon, defaultButton);
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, bool saveAndRestorePosition = true)
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, saveAndRestorePosition);
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons)
=> ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(string text, string caption)
=> ShowCoreAsync(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(string text)
=> ShowCoreAsync(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(Window? owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true)
=> ShowCoreAsync(owner, text, caption, buttons, icon, defaultButton, saveAndRestorePosition);
public static Task<DialogResult> Show(Window? owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
=> ShowCoreAsync(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(Window? owner, string text, string caption, MessageBoxButtons buttons)
=> ShowCoreAsync(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(Window? owner, string text, string caption)
=> ShowCoreAsync(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static Task<DialogResult> Show(Window? owner, string text)
=> ShowCoreAsync(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static async Task VerboseLoggingWarning_ShowIfTrue()
{
// when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured
if (Serilog.Log.Logger.IsVerboseEnabled())
await Show("""
Warning: verbose logging is enabled.
This should be used for debugging only. It creates many
more logs and debug files, neither of which are as
strictly anonymous.
When you are finished debugging, it's highly recommended
to set your debug MinimumLevel to Information and restart
Libation.
""", "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
/// <summary>
/// Note: the format field should use {0} and NOT use the `$` string interpolation. Formatting is done inside this method.
/// </summary>
public static async Task<DialogResult> ShowConfirmationDialog(Window? owner, IEnumerable<LibraryBook> libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
{
if (libraryBooks is null || !libraryBooks.Any())
return DialogResult.Cancel;
var count = libraryBooks.Count();
string thisThese = count > 1 ? "these" : "this";
string bookBooks = count > 1 ? "books" : "book";
string titlesAgg = libraryBooks.AggregateTitles();
var message
= string.Format(format, $"{thisThese} {count} {bookBooks}")
+ $"\r\n\r\n{titlesAgg}";
return await ShowCoreAsync(owner,
message,
title,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
defaultButton);
}
/// <summary>
/// Logs error. Displays a message box dialog with specified text and caption.
/// </summary>
/// <param name="synchronizeInvoke">Form calling this method.</param>
/// <param name="text">The text to display in the message box.</param>
/// <param name="caption">The text to display in the title bar of the message box.</param>
/// <param name="exception">Exception to log.</param>
public static async Task ShowAdminAlert(Window? owner, string text, string caption, Exception exception)
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
// for development and debugging, show me what broke!
if (System.Diagnostics.Debugger.IsAttached)
//Wrap the exception to preserve its stack trace.
throw new Exception("An unhandled exception was encountered", exception);
try
{
Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption });
}
catch { }
var form = new MessageBoxAlertAdminDialog(text, caption, exception);
await DisplayWindow(form, owner);
});
private static async Task<DialogResult> ShowCoreAsync(Window? owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true)
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
owner = owner?.IsLoaded is true ? owner : null;
var dialog = CreateMessageBox(owner, message, caption, buttons, icon, defaultButton, saveAndRestorePosition);
return await DisplayWindow(dialog, owner);
});
private static MessageBoxWindow CreateMessageBox(Window? owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true)
{
var dialog = new MessageBoxWindow(saveAndRestorePosition);
var vm = new MessageBoxViewModel(message, caption, buttons, icon, defaultButton);
dialog.DataContext = vm;
dialog.ControlToFocusOnShow = dialog.FindControl<Control>(defaultButton.ToString());
dialog.CanResize = false;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
var tbx = dialog.messageTextBlock;
tbx.MinWidth = vm.TextBlockMinWidth;
tbx.Text = message;
var desiredMax = GetMaxMessageBoxSizeFromOwner(owner);
tbx.Measure(desiredMax);
tbx.Height = tbx.DesiredSize.Height;
tbx.Width = tbx.DesiredSize.Width;
var absoluteHeight = vm.FormHeightFromTboxHeight((int)tbx.DesiredSize.Height);
dialog.MinHeight = absoluteHeight;
dialog.MinWidth = vm.FormWidthFromTboxWidth((int)tbx.DesiredSize.Width);
dialog.MaxHeight = absoluteHeight;
dialog.MaxWidth = dialog.MinWidth;
dialog.Height = absoluteHeight;
dialog.Width = dialog.MinWidth;
return dialog;
}
private static Size GetMaxMessageBoxSizeFromOwner(TopLevel? owner)
{
if (owner is null && App.Current is IClassicDesktopStyleApplicationLifetime lt)
{
//The Windows enumeration will only contain active (non-disposed) windows.
//If none are available, the last disposed window may still be in MainWindow
//Just be careful what you use it for. It will still have Screens, but
//ScreenFromTopLevel can't be used on macOS.
owner = lt.Windows.FirstOrDefault() ?? lt.MainWindow;
}
if (owner?.Screens is Screens screens)
{
var mainScreen = owner?.PlatformImpl is null ? screens.Primary : screens.ScreenFromTopLevel(owner);
if (mainScreen is not null)
return new Size(0.20 * mainScreen.Bounds.Width, 0.9 * mainScreen.Bounds.Height - 55);
}
return owner?.ClientSize ?? new Size(800, 600);
}
private static async Task<DialogResult> DisplayWindow(DialogWindow toDisplay, Window? owner)
{
if (owner is null)
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (desktop.MainWindow?.IsLoaded is true)
return await toDisplay.ShowDialog<DialogResult>(desktop.MainWindow);
else
{
var tcs = new TaskCompletionSource<DialogResult>();
desktop.MainWindow = toDisplay;
toDisplay.Closed += (_, _) => tcs.SetResult(toDisplay.DialogResult);
toDisplay.Show();
return await tcs.Task;
}
}
else
{
var window = new Window
{
IsVisible = false,
Height = 1,
Width = 1,
SystemDecorations = SystemDecorations.None,
ShowInTaskbar = false
};
window.Show();
var result = await toDisplay.ShowDialog<DialogResult>(window);
window.Close();
return result;
}
}
else
{
return await toDisplay.ShowDialog<DialogResult>(owner);
}
}
}
}