Warn if accounts dialog is dirty when user attempts to close it. This includes editing items from the Marketplaces child-dialog

This commit is contained in:
Robert McRackan committed 2026-08-31 17:15:56 -04:00
1 parent 3e7191adc7
commit 129486ce89
2 files changed
+94 -3

No files matched your search

@@ -18,10 +18,16 @@ namespace LibationAvalonia.Dialogs;
public partial class AccountsDialog : DialogWindow
{
public AvaloniaList<AccountDto> Accounts { get; } = new();
private bool _isDirty;
private bool _closeConfirmed;
public class AccountDto : ViewModels.ViewModelBase
{
public IReadOnlyList<Locale> Locales => AccountsDialog.Locales;
public bool LibraryScan { get; set; } = true;
public bool LibraryScan
{
get => field;
set => this.RaiseAndSetIfChanged(ref field, value);
} = true;
public string? AccountId
{
get => field;
@@ -43,7 +49,11 @@ public partial class AccountsDialog : DialogWindow
}
}
public string? AccountName { get; set; }
public string? AccountName
{
get => field;
set => this.RaiseAndSetIfChanged(ref field, value);
}
public bool IsDefault => string.IsNullOrEmpty(AccountId);
/// <summary>
@@ -153,7 +163,11 @@ public partial class AccountsDialog : DialogWindow
else if (e.Action is NotifyCollectionChangedAction.Remove && e.OldItems?.Count > 0)
{
foreach (var oldItem in e.OldItems.OfType<AccountDto>())
{
oldItem.PropertyChanged -= AccountDto_PropertyChanged;
if (!oldItem.IsDefault)
_isDirty = true;
}
}
}
@@ -161,6 +175,13 @@ public partial class AccountsDialog : DialogWindow
private void AccountDto_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// only user-editable fields; skip derived props like CanExport / button tooltips
if (e.PropertyName is nameof(AccountDto.LibraryScan)
or nameof(AccountDto.AccountId)
or nameof(AccountDto.SelectedLocale)
or nameof(AccountDto.AccountName))
_isDirty = true;
if (!Accounts.Any(a => a.IsDefault))
addBlankAccount();
}
@@ -214,7 +235,10 @@ public partial class AccountsDialog : DialogWindow
}
if (importResult.Account is { } account)
{
Accounts.Add(new AccountDto(account));
_isDirty = true;
}
}
catch (Exception ex)
{
@@ -252,7 +276,10 @@ public partial class AccountsDialog : DialogWindow
var dialog = new MarketplacesDialog(account, persister.AccountsSettings, acc.AdditionalLocaleNames);
if (await dialog.ShowDialog<DialogResult>(this) == DialogResult.OK)
{
acc.SetAdditionalLocaleNames(dialog.SelectedAdditionalLocaleNames);
_isDirty = true;
}
}
protected override async Task SaveAndCloseAsync()
@@ -282,6 +309,30 @@ public partial class AccountsDialog : DialogWindow
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
protected override async void OnClosing(WindowClosingEventArgs e)
{
if (!_closeConfirmed && _isDirty && DialogResult != DialogResult.OK)
{
e.Cancel = true;
var result = await MessageBox.Show(
this,
"You have unsaved changes. Close without saving?",
"Unsaved Changes",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);
if (result == DialogResult.Yes)
{
_closeConfirmed = true;
Close(DialogResult.Cancel);
}
}
base.OnClosing(e);
}
private void persist(AccountsSettings accountsSettings)
{
var existingAccounts = accountsSettings.Accounts;
@@ -21,6 +21,8 @@ public partial class AccountsDialog : Form
private const string COL_Locale = nameof(Locale);
private const string COL_Marketplaces = nameof(Marketplaces);
private bool _isDirty;
public AccountsDialog()
{
InitializeComponent();
@@ -29,6 +31,7 @@ public partial class AccountsDialog : Form
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged;
dataGridView1.UserAddedRow += DataGridView1_UserAddedRow;
populateDropDown();
@@ -36,6 +39,31 @@ public partial class AccountsDialog : Form
this.SetLibationIcon();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// commit in-progress edits so CellValueChanged can mark dirty before we decide
if (dataGridView1.IsCurrentCellInEditMode)
dataGridView1.EndEdit();
if (dataGridView1.IsCurrentCellDirty)
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
if (_isDirty && DialogResult != DialogResult.OK)
{
var result = MessageBox.Show(
this,
"You have unsaved changes. Close without saving?",
"Unsaved Changes",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);
if (result == DialogResult.No)
e.Cancel = true;
}
base.OnFormClosing(e);
}
private void populateDropDown()
=> (dataGridView1.Columns[COL_Locale] as DataGridViewComboBoxColumn)?.DataSource
= Localization.Locales
@@ -90,6 +118,7 @@ public partial class AccountsDialog : Form
{
if (e.RowIndex < 0 || e.ColumnIndex < 0)
return;
_isDirty = true;
var colName = dataGridView1.Columns[e.ColumnIndex].Name;
if (colName is COL_AccountId or COL_Locale)
UpdateExportCellState(dataGridView1.Rows[e.RowIndex]);
@@ -100,10 +129,14 @@ public partial class AccountsDialog : Form
if (!dataGridView1.IsCurrentCellDirty || dataGridView1.CurrentCell is null)
return;
var colName = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name;
if (colName == COL_Locale)
// combo and checkbox do not commit until leave-cell unless we force it here
if (colName is COL_Locale or COL_LibraryScan)
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void DataGridView1_UserAddedRow(object? sender, DataGridViewRowEventArgs e)
=> _isDirty = true;
private static bool AccountRowCanExport(string? accountId, string? localeName)
{
if (string.IsNullOrWhiteSpace(accountId) || string.IsNullOrWhiteSpace(localeName))
@@ -150,7 +183,10 @@ public partial class AccountsDialog : Form
case COL_Delete:
// if final/edit row: do nothing
if (e.RowIndex < dgv.RowCount - 1)
{
dgv.Rows.Remove(row);
_isDirty = true;
}
break;
case COL_Export:
// if final/edit row: do nothing
@@ -222,6 +258,7 @@ public partial class AccountsDialog : Form
var selected = dialog.SelectedAdditionalLocaleNames.ToList();
row.Tag = selected;
row.Cells[COL_Marketplaces].Value = MarketplacesUi.ButtonText(selected.Count + 1);
_isDirty = true;
}
private void saveBtn_Click(object sender, EventArgs e)
@@ -416,7 +453,10 @@ public partial class AccountsDialog : Form
}
if (importResult.Account is { } account)
{
AddAccountToGrid(account);
_isDirty = true;
}
}
catch (Exception ex)
{