From 591a6d15940eb8d75e89cbedb9b3a88b8d9b8460 Mon Sep 17 00:00:00 2001 From: rmcrackan Date: Tue, 12 May 2026 12:05:11 -0400 Subject: [PATCH] Account 'export' should be disabled when the account has no keys/tokens --- .../Dialogs/AccountsDialog.axaml | 4 +- .../Dialogs/AccountsDialog.axaml.cs | 49 +++++++++++++- .../Dialogs/AccountsDialog.cs | 64 ++++++++++++++++++- 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml index 5a25d5d3..c0cf27b4 100644 --- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml @@ -47,8 +47,8 @@ VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" - IsEnabled="{Binding !IsDefault}" - ToolTip.Tip="Export account authorization to audible-cli" + IsEnabled="{Binding CanExport}" + ToolTip.Tip="{Binding ExportButtonToolTip}" Click="ExportButton_Clicked" /> diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs index a38e4a38..b55dc3d8 100644 --- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs @@ -28,20 +28,65 @@ public partial class AccountsDialog : DialogWindow { this.RaiseAndSetIfChanged(ref field, value); this.RaisePropertyChanged(nameof(IsDefault)); + RefreshCanExport(); + } + } + + public Locale? SelectedLocale + { + get => field; + set + { + this.RaiseAndSetIfChanged(ref field, value); + RefreshCanExport(); } } - public Locale? SelectedLocale { get; set; } public string? AccountName { get; set; } public bool IsDefault => string.IsNullOrEmpty(AccountId); - public AccountDto() { } + public bool CanExport + { + get => field; + private set => this.RaiseAndSetIfChanged(ref field, value); + } + + public string ExportButtonToolTip => + CanExport + ? "Export account authorization to audible-cli" + : "Authenticate this account (e.g. library scan) before exporting to audible-cli."; + + public AccountDto() => RefreshCanExport(); + public AccountDto(Account account) { LibraryScan = account.LibraryScan; AccountId = account.AccountId; SelectedLocale = Locales.Single(l => l.Name == account.Locale?.Name); AccountName = account.AccountName; + RefreshCanExportFromAccount(account); + } + + private void RefreshCanExportFromAccount(Account account) + { + CanExport = account.IdentityTokens?.IsValid == true; + this.RaisePropertyChanged(nameof(ExportButtonToolTip)); + } + + private void RefreshCanExport() + { + if (string.IsNullOrEmpty(AccountId) || SelectedLocale is null) + { + CanExport = false; + this.RaisePropertyChanged(nameof(ExportButtonToolTip)); + return; + } + + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var account = persister.AccountsSettings.Accounts.FirstOrDefault(a => + a.AccountId == AccountId && a.Locale?.Name == SelectedLocale.Name); + CanExport = account?.IdentityTokens?.IsValid == true; + this.RaisePropertyChanged(nameof(ExportButtonToolTip)); } } diff --git a/Source/LibationWinForms/Dialogs/AccountsDialog.cs b/Source/LibationWinForms/Dialogs/AccountsDialog.cs index 9cbb97a6..e1ad0748 100644 --- a/Source/LibationWinForms/Dialogs/AccountsDialog.cs +++ b/Source/LibationWinForms/Dialogs/AccountsDialog.cs @@ -2,9 +2,11 @@ using AudibleUtilities; using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; namespace LibationWinForms.Dialogs; @@ -23,6 +25,9 @@ public partial class AccountsDialog : Form dataGridView1.EnableHeadersVisualStyles = !Application.IsDarkModeEnabled; dataGridView1.Columns[COL_AccountName]?.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; + dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged; + populateDropDown(); populateGridValues(); @@ -60,12 +65,55 @@ public partial class AccountsDialog : Form account.AccountName ?? ""); dataGridView1[COL_Export, row].ToolTipText = "Export account authorization to audible-cli"; + UpdateExportCellState(dataGridView1.Rows[row]); } private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) { e.Row.Cells[COL_Delete].Value = "X"; e.Row.Cells[COL_LibraryScan].Value = true; + e.Row.Cells[COL_Export].ReadOnly = true; + } + + private void DataGridView1_CellValueChanged(object? sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0 || e.ColumnIndex < 0) + return; + var colName = dataGridView1.Columns[e.ColumnIndex].Name; + if (colName is COL_AccountId or COL_Locale) + UpdateExportCellState(dataGridView1.Rows[e.RowIndex]); + } + + private void DataGridView1_CurrentCellDirtyStateChanged(object? sender, EventArgs e) + { + if (!dataGridView1.IsCurrentCellDirty || dataGridView1.CurrentCell is null) + return; + var colName = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name; + if (colName == COL_Locale) + dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); + } + + private static bool AccountRowCanExport(string? accountId, string? localeName) + { + if (string.IsNullOrWhiteSpace(accountId) || string.IsNullOrWhiteSpace(localeName)) + return false; + + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var account = persister.AccountsSettings.Accounts.FirstOrDefault(a => + a.AccountId == accountId && a.Locale?.Name == localeName); + return account?.IdentityTokens?.IsValid == true; + } + + private void UpdateExportCellState(DataGridViewRow row) + { + if (row.IsNewRow || !dataGridView1.Columns.Contains(COL_Export)) + return; + + var canExport = AccountRowCanExport(GetAccountId(row), GetLocale(row)); + row.Cells[COL_Export].ReadOnly = !canExport; + row.Cells[COL_Export].ToolTipText = canExport + ? "Export account authorization to audible-cli" + : "Authenticate this account (e.g. library scan) before exporting to audible-cli."; } private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) @@ -85,7 +133,9 @@ public partial class AccountsDialog : Form break; case COL_Export: // if final/edit row: do nothing - if (e.RowIndex < dgv.RowCount - 1 && RowToAccountDto(row) is AccountDto accountDto) + if (e.RowIndex < dgv.RowCount - 1 + && !row.Cells[COL_Export].ReadOnly + && RowToAccountDto(row) is AccountDto accountDto) Export(accountDto); break; //case COL_MoveUp: @@ -353,6 +403,18 @@ public partial class AccountsDialog : Form { ToolTipText = AccessibilityName; } + + protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object? value, object? formattedValue, string? errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) + { + if (ReadOnly) + { + base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts ^ (DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground)); + var caption = formattedValue?.ToString() ?? Convert.ToString(value) ?? ""; + ButtonRenderer.DrawButton(graphics, cellBounds, caption, cellStyle.Font, focused: false, PushButtonState.Disabled); + } + else + base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts); + } } #endregion }