Skip to content

Commit

Permalink
Feature: Add confirmation when the app closes.
Browse files Browse the repository at this point in the history
  • Loading branch information
deanthecoder committed Jan 21, 2024
1 parent 4442fdc commit f6581ad
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
88 changes: 88 additions & 0 deletions Speculator/CSharp.Utils/UI/AppCloseHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Code authored by Dean Edis (DeanTheCoder).
// Anyone is free to copy, modify, use, compile, or distribute this software,
// either in source code form or as a compiled binary, for any non-commercial
// purpose.
//
// If you modify the code, please retain this copyright header,
// and consider contributing back to the repository or letting us know
// about your modifications. Your contributions are valued!
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.

using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Material.Icons;

namespace CSharp.Utils.UI;

/// <summary>
/// Closing an app, especially on the Mac, is a bit of a mess.
/// Closing the main app window triggers close events that can be cancelled.
/// Closing using the Mac app menu or Command-Q will close the app window
/// and kill the UI thread, preventing the window from triggering a confirmation
/// dialog.
///
/// This class ensures both types of closure play nicely with each other.
/// </summary>
public class AppCloseHandler
{
private bool m_isCloseConfirmed;
private bool m_isConfirmationDialogActive;
private IClassicDesktopStyleApplicationLifetime m_desktop;

public static AppCloseHandler Instance { get; } = new AppCloseHandler();

/// <summary>
/// Call from App.axaml.cs as early as possible.
/// </summary>
public void Init(IClassicDesktopStyleApplicationLifetime desktop)
{
m_desktop = desktop;

// Triggered when the user hits Command-Q or uses the Mac app menu.
desktop.ShutdownRequested += (_, args) =>
{
if (m_isCloseConfirmed)
return;

args.Cancel = true;
PromptForConfirmationAsync();
};
}

/// <summary>
/// Subscribe this to the main window's Closing event.
/// </summary>
public void OnMainWindowClosing(WindowClosingEventArgs args)
{
if (m_isCloseConfirmed)
return; // Allow the close.

args.Cancel = true;
if (!m_isConfirmationDialogActive)
{
// User hasn't seen the confirmation dialog yet, so show it.
PromptForConfirmationAsync();
}
}

private void PromptForConfirmationAsync()
{
m_isConfirmationDialogActive = true;
DialogService.Instance.Warn(
"Confirm Exit?",
"Any unsaved changes will be lost.",
"CANCEL",
"EXIT",
confirmed =>
{
m_isConfirmationDialogActive = false;
if (confirmed)
{
m_isCloseConfirmed = true;
m_desktop.Shutdown(); // Repeat the shutdown request, confirmed this time.
}
},
MaterialIconKind.CloseBold);
}
}
3 changes: 3 additions & 0 deletions Speculator/Speculator/Views/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using CSharp.Utils.UI;
using Speculator.ViewModels;

namespace Speculator.Views;
Expand All @@ -37,6 +38,8 @@ public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
AppCloseHandler.Instance.Init(desktop);

var viewModel = new MainWindowViewModel();
desktop.MainWindow = new MainWindow
{
Expand Down
4 changes: 3 additions & 1 deletion Speculator/Speculator/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using CSharp.Utils.UI;
using Material.Icons.Avalonia;
using Speculator.ViewModels;
// ReSharper disable UnusedParameter.Local
Expand All @@ -30,6 +31,7 @@ public MainWindow()
{
InitializeComponent();

Closing += (_, args) => AppCloseHandler.Instance.OnMainWindowClosing(args);
Closed += (_, _) => (DataContext as IDisposable)?.Dispose();
}

Expand Down Expand Up @@ -75,4 +77,4 @@ private void OnKeyboardIconLoaded(object sender, RoutedEventArgs e)
};
icon.PointerExited += (_, _) => Keyboard.Opacity = 0.0;
}
}
}

0 comments on commit f6581ad

Please sign in to comment.