Added login dialogs

This commit is contained in:
Michael Bucari-Tovo 2022-07-22 18:25:47 -06:00
parent 503e1e143e
commit 9ecb32c3d2
40 changed files with 1100 additions and 169 deletions

View File

@ -1,5 +1,7 @@
using Avalonia.Media;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI
{
@ -13,5 +15,14 @@ namespace LibationWinForms.AvaloniaUI
return brush;
return defaultBrush;
}
public static T ShowDialogSynchronously<T>(this Avalonia.Controls.Window window, Avalonia.Controls.Window owner)
{
using var source = new CancellationTokenSource();
var dialogTask = window.ShowDialog<T>(owner);
dialogTask.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext());
Avalonia.Threading.Dispatcher.UIThread.MainLoop(source.Token);
return dialogTask.Result;
}
}
}

View File

@ -116,10 +116,12 @@ namespace LibationWinForms.AvaloniaUI.Controls
private void setDirectory()
{
Directory
var path1
= customStates.CustomChecked ? customStates.CustomDir
: directorySelectControl.SelectedDirectory is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute
: Configuration.GetKnownDirectoryPath(directorySelectControl.SelectedDirectory);
Directory
= System.IO.Path.Combine(path1 ?? string.Empty, SubDirectory);
}

View File

@ -26,7 +26,6 @@ namespace LibationWinForms.AvaloniaUI
Continue = 11
}
public enum MessageBoxIcon
{
None = 0,
@ -39,6 +38,7 @@ namespace LibationWinForms.AvaloniaUI
Asterisk = 64,
Information = 64
}
public enum MessageBoxButtons
{
OK,
@ -74,10 +74,8 @@ namespace LibationWinForms.AvaloniaUI
/// -or-
/// <paramref name="defaultButton" /> is not a member of <see cref="T:System.Windows.Forms.MessageBoxDefaultButton" />.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt was made to display the <see cref="T:System.Windows.Forms.MessageBox" /> in a process that is not running in User Interactive mode. This is specified by the <see cref="P:System.Windows.Forms.SystemInformation.UserInteractive" /> property.</exception>
public static async Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
return await ShowCore(null, text, caption, buttons, icon, defaultButton);
}
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
=> ShowCoreAsync(null, text, caption, buttons, icon, defaultButton);
/// <summary>Displays a message box with specified text, caption, buttons, and icon.</summary>
@ -90,10 +88,8 @@ namespace LibationWinForms.AvaloniaUI
/// -or-
/// The <paramref name="icon" /> parameter specified is not a member of <see cref="T:System.Windows.Forms.MessageBoxIcon" />.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt was made to display the <see cref="T:System.Windows.Forms.MessageBox" /> in a process that is not running in User Interactive mode. This is specified by the <see cref="P:System.Windows.Forms.SystemInformation.UserInteractive" /> property.</exception>
public static async Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
return await ShowCore(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box with specified text, caption, and buttons.</summary>
@ -103,28 +99,22 @@ namespace LibationWinForms.AvaloniaUI
/// <returns>One of the <see cref="T:System.Windows.Forms.DialogResult" /> values.</returns>
/// <exception cref="T:System.ComponentModel.InvalidEnumArgumentException">The <paramref name="buttons" /> parameter specified is not a member of <see cref="T:System.Windows.Forms.MessageBoxButtons" />.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt was made to display the <see cref="T:System.Windows.Forms.MessageBox" /> in a process that is not running in User Interactive mode. This is specified by the <see cref="P:System.Windows.Forms.SystemInformation.UserInteractive" /> property.</exception>
public static async Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons)
{
return await ShowCore(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons)
=> ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box with specified text and caption.</summary>
/// <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>
/// <returns>One of the <see cref="T:System.Windows.Forms.DialogResult" /> values.</returns>
public static async Task<DialogResult> Show(string text, string caption)
{
return await ShowCore(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(string text, string caption)
=> ShowCoreAsync(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box with specified text.</summary>
/// <param name="text">The text to display in the message box.</param>
/// <returns>One of the <see cref="T:System.Windows.Forms.DialogResult" /> values.</returns>
public static async Task<DialogResult> Show(string text)
{
return await ShowCore(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(string text)
=> ShowCoreAsync(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box in front of the specified object and with the specified text, caption, buttons, icon, default button, and options.</summary>
@ -146,10 +136,9 @@ namespace LibationWinForms.AvaloniaUI
/// <exception cref="T:System.ArgumentException">
/// -or-
/// <paramref name="buttons" /> specified an invalid combination of <see cref="T:System.Windows.Forms.MessageBoxButtons" />.</exception>
public static async Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
return await ShowCore(owner, text, caption, buttons, icon, defaultButton);
}
public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
=> ShowCoreAsync(owner, text, caption, buttons, icon, defaultButton);
/// <summary>Displays a message box in front of the specified object and with the specified text, caption, buttons, and icon.</summary>
@ -164,10 +153,8 @@ namespace LibationWinForms.AvaloniaUI
/// -or-
/// <paramref name="icon" /> is not a member of <see cref="T:System.Windows.Forms.MessageBoxIcon" />.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt was made to display the <see cref="T:System.Windows.Forms.MessageBox" /> in a process that is not running in User Interactive mode. This is specified by the <see cref="P:System.Windows.Forms.SystemInformation.UserInteractive" /> property.</exception>
public static async Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
return await ShowCore(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
=> ShowCoreAsync(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box in front of the specified object and with the specified text, caption, and buttons.</summary>
/// <param name="owner">An implementation of <see cref="T:System.Windows.Forms.IWin32Window" /> that will own the modal dialog box.</param>
@ -178,35 +165,29 @@ namespace LibationWinForms.AvaloniaUI
/// <exception cref="T:System.ComponentModel.InvalidEnumArgumentException">
/// <paramref name="buttons" /> is not a member of <see cref="T:System.Windows.Forms.MessageBoxButtons" />.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt was made to display the <see cref="T:System.Windows.Forms.MessageBox" /> in a process that is not running in User Interactive mode. This is specified by the <see cref="P:System.Windows.Forms.SystemInformation.UserInteractive" /> property.</exception>
public static async Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons)
{
return await ShowCore(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons)
=> ShowCoreAsync(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box in front of the specified object and with the specified text and caption.</summary>
/// <param name="owner">An implementation of <see cref="T:System.Windows.Forms.IWin32Window" /> that will own the modal dialog box.</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>
/// <returns>One of the <see cref="T:System.Windows.Forms.DialogResult" /> values.</returns>
public static async Task<DialogResult> Show(Window owner, string text, string caption)
{
return await ShowCore(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static DialogResult Show(Window owner, string text, string caption)
=> ShowCoreAsync(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
/// <summary>Displays a message box in front of the specified object and with the specified text.</summary>
/// <param name="owner">An implementation of <see cref="T:System.Windows.Forms.IWin32Window" /> that will own the modal dialog box.</param>
/// <param name="text">The text to display in the message box.</param>
/// <returns>One of the <see cref="T:System.Windows.Forms.DialogResult" /> values.</returns>
public static async Task<DialogResult> Show(Window owner, string text)
{
return await ShowCore(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
}
public static 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(@"
Show(@"
Warning: verbose logging is enabled.
This should be used for debugging only. It creates many
@ -219,7 +200,7 @@ Libation.
".Trim(), "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
public static async Task<DialogResult> ShowConfirmationDialog(Window owner, IEnumerable<LibraryBook> libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
public static DialogResult ShowConfirmationDialog(Window owner, IEnumerable<LibraryBook> libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
{
if (libraryBooks is null || !libraryBooks.Any())
return DialogResult.Cancel;
@ -234,7 +215,7 @@ Libation.
= string.Format(format, $"{thisThese} {count} {bookBooks}")
+ $"\r\n\r\n{titlesAgg}";
return await ShowCore(owner,
return ShowCoreAsync(owner,
message,
title,
MessageBoxButtons.YesNo,
@ -263,18 +244,11 @@ Libation.
var form = new MessageBoxAlertAdminDialog(text, caption, exception);
await DisplayWindow(form, owner);
DisplayWindow(form, owner);
}
private static async Task<DialogResult> ShowCore(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess())
return await ShowCore2(owner, message, caption, buttons, icon, defaultButton);
else
return await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => ShowCore2(owner, message, caption, buttons, icon, defaultButton));
}
private static async Task<DialogResult> ShowCore2(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
private static DialogResult ShowCoreAsync(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
var dialog = new MessageBoxWindow();
@ -307,15 +281,15 @@ Libation.
dialog.Height = dialog.MinHeight;
dialog.Width = dialog.MinWidth;
return await DisplayWindow(dialog, owner);
return DisplayWindow(dialog, owner);
}
private static async Task<DialogResult> DisplayWindow(Window toDisplay, Window owner)
private static DialogResult DisplayWindow(Window toDisplay, Window owner)
{
if (owner is null)
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
return await toDisplay.ShowDialog<DialogResult>(desktop.MainWindow);
return toDisplay.ShowDialogSynchronously<DialogResult>(desktop.MainWindow);
}
else
{
@ -329,7 +303,7 @@ Libation.
};
window.Show();
var result = await toDisplay.ShowDialog<DialogResult>(window);
var result = toDisplay.ShowDialogSynchronously<DialogResult>(window);
window.Close();
return result;
}
@ -337,7 +311,7 @@ Libation.
}
else
{
return await toDisplay.ShowDialog<DialogResult>(owner);
return toDisplay.ShowDialogSynchronously<DialogResult>(owner);
}
}

View File

@ -346,7 +346,7 @@ $@" Title: {libraryBook.Book.Title}
}
// if null then ask user
dialogResult ??= await MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
if (dialogResult == DialogResult.Abort)
return ProcessBookResult.FailedAbort;

View File

@ -243,7 +243,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
return;
var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList();
var result = await MessageBox.ShowConfirmationDialog(
var result = MessageBox.ShowConfirmationDialog(
null,
libraryBooks,
$"Are you sure you want to remove {selectedBooks.Count} books from Libation's library?",
@ -307,7 +307,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
.Select(lbe => lbe.LibraryBook)
.Where(lb => !lb.Book.HasLiberated());
var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.ApiExtendedFunc, lib, accounts);
var removedBooks = await LibraryCommands.FindInactiveBooks(Views.Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, lib, accounts);
var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList();

View File

@ -56,13 +56,17 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
// only persist in 'save' step
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.Accounts;
if (!accounts.Any())
return;
DataContext = this;
if (accounts.Any())
{
foreach (var account in accounts)
AddAccountToGrid(account);
}
DataContext = this;
addBlankAccount();
}
private void addBlankAccount()
{
var newBlank = new AccountDto();
newBlank.PropertyChanged += AccountDto_PropertyChanged;
@ -89,9 +93,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (Accounts.Any(a => a.IsDefault))
return;
var newBlank = new AccountDto();
newBlank.PropertyChanged += AccountDto_PropertyChanged;
Accounts.Insert(Accounts.Count, newBlank);
addBlankAccount();
}
public void DeleteButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
@ -134,7 +136,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (persister.AccountsSettings.Accounts.Any(a => a.AccountId == account.AccountId && a.IdentityTokens.Locale.Name == account.Locale.Name))
{
await MessageBox.Show(this, $"An account with that account id and country already exists.\r\n\r\nAccount ID: {account.AccountId}\r\nCountry: {account.Locale.Name}", "Cannot Add Duplicate Account");
MessageBox.Show(this, $"An account with that account id and country already exists.\r\n\r\nAccount ID: {account.AccountId}\r\nCountry: {account.Locale.Name}", "Cannot Add Duplicate Account");
return;
}
@ -144,7 +146,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
MessageBox.ShowAdminAlert(
this,
$"An error occurred while importing an account from:\r\n{filePath[0]}\r\n\r\nIs the file encrypted?",
"Error Importing Account",
@ -163,7 +165,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
try
{
if (!await inputIsValid())
if (!inputIsValid())
return;
// without transaction, accounts persister will write ANY EDIT immediately to file
@ -190,8 +192,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
AvaloniaXamlLoader.Load(this);
}
private void persist(AccountsSettings accountsSettings)
{
var existingAccounts = accountsSettings.Accounts;
@ -222,7 +222,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
: dto.AccountName.Trim();
}
}
private async Task<bool> inputIsValid()
private bool inputIsValid()
{
foreach (var dto in Accounts.ToList())
{
@ -234,13 +234,13 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (string.IsNullOrWhiteSpace(dto.AccountId))
{
await MessageBox.Show(this, "Account id cannot be blank. Please enter an account id for all accounts.", "Blank account", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show(this, "Account id cannot be blank. Please enter an account id for all accounts.", "Blank account", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if (string.IsNullOrWhiteSpace(dto.SelectedLocale?.Name))
{
await MessageBox.Show(this, "Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show(this, "Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
@ -260,7 +260,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (account.IdentityTokens?.IsValid != true)
{
await MessageBox.Show(this, "This account hasn't been authenticated yet. First scan your library to log into your account, then try exporting again.", "Account Not Authenticated");
MessageBox.Show(this, "This account hasn't been authenticated yet. First scan your library to log into your account, then try exporting again.", "Account Not Authenticated");
return;
}
@ -283,11 +283,11 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
File.WriteAllText(fileName, jsonText);
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{fileName}", "Success!");
MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{fileName}", "Success!");
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
MessageBox.ShowAdminAlert(
this,
$"An error occurred while exporting account:\r\n{account.AccountName}",
"Error Exporting Account",

View File

@ -17,13 +17,30 @@
<Setter Property="BorderThickness" Value="2" />
</Style>
</Grid.Styles>
<Grid ColumnDefinitions="Auto,*" Margin="10,10,10,0">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="*,Auto" Margin="10,10,10,0">
<Panel VerticalAlignment="Top" Margin="5" Background="LightGray" Width="80" Height="80" >
<Image Grid.Column="0" Width="80" Height="80" Source="{Binding Cover}" />
</Panel>
<Panel Grid.Column="0" Grid.Row="1">
<Panel.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</Panel.Styles>
<TextBlock
Margin="10" TextWrapping="Wrap" TextAlignment="Center"
Tapped="GoToAudible_Tapped"
Text="Open in&#xa;Audible&#xa;(Browser)" />
</Panel>
<TextBox
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
TextWrapping="Wrap"
Margin="5"
FontSize="12"
@ -43,7 +60,7 @@
<TextBox Margin="0,5,0,5"
MinHeight="25"
FontSize="12"
FontSize="12" Name="tagsTbox"
Text="{Binding Tags, Mode=TwoWay}"/>
</StackPanel>
</controls:GroupBox>

View File

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using DataLayer;
using Dinah.Core;
using LibationFileManager;
using LibationWinForms.AvaloniaUI.ViewModels;
using System.Collections.Generic;
@ -34,6 +35,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
public BookDetailsDialog()
{
InitializeComponent();
ControlToFocusOnShow = this.Find<TextBox>(nameof(tagsTbox));
if (Design.IsDesignMode)
{
@ -46,13 +48,18 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
LibraryBook = libraryBook;
}
protected override void SaveAndClose()
{
SaveButton_Clicked(null, null);
base.SaveAndClose();
}
public void GoToAudible_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var locale = AudibleApi.Localization.Get(_libraryBook.Book.Locale);
var link = $"https://www.audible.{locale.TopDomain}/pd/{_libraryBook.Book.AudibleProductId}";
Go.To.Url(link);
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{

View File

@ -38,7 +38,7 @@
Content="Reset to Default"
Click="ResetButton_Click" />
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="*,*">
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<Border
Grid.Row="0"
@ -62,7 +62,7 @@
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="Description">
<DataGridTemplateColumn Width="Auto" Header="Description">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextPresenter
@ -80,10 +80,9 @@
<Grid
Grid.Row="1"
Grid.Column="1"
Margin="5"
RowDefinitions="Auto,*,80">
RowDefinitions="Auto,*,80" HorizontalAlignment="Stretch">
<TextBlock
Margin="5,5,5,10"

View File

@ -19,7 +19,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
{
class BracketEscapeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string str && str[0] != '<' && str[^1] != '>')
@ -68,7 +67,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
}
protected override async Task SaveAndCloseAsync()
{
if (!await _viewModel.Validate())
if (!_viewModel.Validate())
return;
TemplateText = _viewModel.workingTemplateText;
@ -119,7 +118,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
public void resetTextBox(string value) => workingTemplateText = value;
public async Task<bool> Validate()
public bool Validate()
{
if (template.IsValid(workingTemplateText))
return true;
@ -127,7 +126,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
.GetErrors(workingTemplateText)
.Select(err => $"- {err}")
.Aggregate((a, b) => $"{a}\r\n{b}");
await MessageBox.Show($"This template text is not valid. Errors:\r\n{errors}", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"This template text is not valid. Errors:\r\n{errors}", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
@ -232,11 +231,13 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
for(int i = 0; i < wordsSplit.Length; i++)
{
var tb = new TextBlock();
tb.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom;
tb.Text = wordsSplit[i] + (i == wordsSplit.Length - 1 ? "" : " ");
tb.FontWeight = item.Item2;
var tb = new TextBlock
{
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom,
TextWrapping = TextWrapping.Wrap,
Text = wordsSplit[i] + (i == wordsSplit.Length - 1 ? "" : " "),
FontWeight = item.Item2
};
WrapPanel.Children.Add(tb);
}

View File

@ -64,7 +64,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"Failed to save picture to {fileName}");
await MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
}

View File

@ -35,7 +35,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (!System.IO.Directory.Exists(libationDir))
{
await MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

View File

@ -0,0 +1,33 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="120"
MinWidth="240" MinHeight="120"
MaxWidth="240" MaxHeight="120"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.ApprovalNeededDialog"
Title="Approval Alert Detected"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,*">
<TextBlock
Grid.Row="0"
Margin="10"
TextWrapping="Wrap"
Text="Amazon is sending you an email."/>
<TextBlock
Grid.Row="1" Margin="10,0,10,0"
TextWrapping="Wrap"
Text="Please press this button after you've approved the notification."/>
<Button
Grid.Row="2"
Margin="10"
VerticalAlignment="Bottom"
Padding="30,3,30,3"
Content="Approve"
Click="Approve_Click" />
</Grid>
</Window>

View File

@ -0,0 +1,30 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class ApprovalNeededDialog : DialogWindow
{
public ApprovalNeededDialog()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Approve button clicked");
return base.SaveAndCloseAsync();
}
public async void Approve_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
}

View File

@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using System;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public abstract class AvaloniaLoginBase
{
/// <returns>True if ShowDialog's DialogResult == OK</returns>
protected static bool ShowDialog(DialogWindow dialog)
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return false;
var result = dialog.ShowDialogSynchronously<DialogResult>(desktop.MainWindow);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
return result == DialogResult.OK;
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using AudibleApi;
using AudibleUtilities;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public class AvaloniaLoginCallback : AvaloniaLoginBase, ILoginCallback
{
private Account _account { get; }
public AvaloniaLoginCallback(Account account)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
}
public string Get2faCode()
{
var dialog = new _2faCodeDialog();
if (ShowDialog(dialog))
return dialog.Code;
return null;
}
public string GetCaptchaAnswer(byte[] captchaImage)
{
var dialog = new CaptchaDialog(captchaImage);
if (ShowDialog(dialog))
return dialog.Answer;
return null;
}
public (string name, string value) GetMfaChoice(MfaConfig mfaConfig)
{
var dialog = new MfaDialog(mfaConfig);
if (ShowDialog(dialog))
return (dialog.SelectedName, dialog.SelectedValue);
return (null, null);
}
public (string email, string password) GetLogin()
{
var dialog = new LoginCallbackDialog(_account);
if (ShowDialog(dialog))
return (_account.AccountId, dialog.Password);
return (null, null);
}
public void ShowApprovalNeeded()
{
var dialog = new ApprovalNeededDialog();
ShowDialog(dialog);
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
using AudibleApi;
using AudibleUtilities;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public class AvaloniaLoginChoiceEager : AvaloniaLoginBase, ILoginChoiceEager
{
/// <summary>Convenience method. Recommended when wiring up Winforms to <see cref="ApplicationServices.LibraryCommands.ImportAccountAsync"/></summary>
public static async Task<ApiExtended> ApiExtendedFunc(Account account) => await ApiExtended.CreateAsync(account, new AvaloniaLoginChoiceEager(account));
public ILoginCallback LoginCallback { get; private set; }
private Account _account { get; }
public AvaloniaLoginChoiceEager(Account account)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
LoginCallback = new AvaloniaLoginCallback(_account);
}
public ChoiceOut Start(ChoiceIn choiceIn)
{
var dialog = new LoginChoiceEagerDialog(_account);
if (!ShowDialog(dialog))
return null;
switch (dialog.LoginMethod)
{
case LoginMethod.Api:
return ChoiceOut.WithApi(dialog.Account.AccountId, dialog.Password);
case LoginMethod.External:
{
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return ShowDialog(externalDialog)
? ChoiceOut.External(externalDialog.ResponseUrl)
: null;
}
default:
throw new Exception($"Unknown {nameof(LoginMethod)} value");
}
}
}
}

View File

@ -0,0 +1,54 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="180"
MinWidth="220" MinHeight="180"
MaxWidth="220" MaxHeight="180"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.CaptchaDialog"
Title="CAPTCHA"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,*">
<Panel
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="10"
MinWidth="200"
MinHeight="70"
Background="LightGray">
<Image
Stretch="None"
Source="{Binding CaptchaImage}" />
</Panel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10,0,10,0"
VerticalAlignment="Center"
Text="CAPTCHA&#xa;answer:" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="10,0,10,0" Text="{Binding Answer}" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="10"
Padding="0,5,0,5"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Content="Submit"
Click="Submit_Click" />
</Grid>
</Window>

View File

@ -0,0 +1,40 @@
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using System.IO;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class CaptchaDialog : DialogWindow
{
public string Answer { get; set; }
public Bitmap CaptchaImage { get; }
public CaptchaDialog()
{
InitializeComponent();
}
public CaptchaDialog(byte[] captchaImage) :this()
{
using var ms = new MemoryStream(captchaImage);
CaptchaImage = new Bitmap(ms);
DataContext = this;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
return base.SaveAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
}

View File

@ -0,0 +1,38 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="120"
MinWidth="300" MinHeight="120"
Width="300" Height="120"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.LoginCallbackDialog"
Title="Audible Login"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,Auto,*" ColumnDefinitions="*" Margin="5">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="Locale: " />
<TextBlock Text="{Binding Account.Locale.Name}" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBlock Text="Username: " />
<TextBlock Text="{Binding Account.AccountId}" />
</StackPanel>
<Grid Margin="0,5,0,5" Grid.Row="2" Grid.Column="0" ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="Password: " />
<TextBox Grid.Column="1" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" />
</Grid>
<Button
Grid.Row="3"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Padding="30,5,30,5"
Content="Submit"
Click="Submit_Click"/>
</Grid>
</Window>

View File

@ -0,0 +1,50 @@
using AudibleUtilities;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Dinah.Core;
using System.Linq;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class LoginCallbackDialog : DialogWindow
{
public Account Account { get; }
public string Password { get; set; }
public LoginCallbackDialog()
{
InitializeComponent();
if (Design.IsDesignMode)
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.Accounts;
Account = accounts.FirstOrDefault();
DataContext = this;
}
}
public LoginCallbackDialog(Account account) : this()
{
Account = account;
DataContext = this;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { email = Account?.AccountId?.ToMask(), passwordLength = Password?.Length });
return base.SaveAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
}

View File

@ -0,0 +1,66 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="350" d:DesignHeight="200"
MinWidth="350" MinHeight="200"
Width="350" Height="200"
WindowStartupLocation="CenterOwner"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.LoginChoiceEagerDialog"
Title="Audible Login"
Icon="/AvaloniaUI/Assets/libation.ico" >
<Grid RowDefinitions="Auto,Auto,Auto,*" ColumnDefinitions="*" Margin="5">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Text="Locale: " />
<TextBlock Text="{Binding Account.Locale.Name}" />
</StackPanel>
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<TextBlock Text="Username: " />
<TextBlock Text="{Binding Account.AccountId}" />
</StackPanel>
<Grid
Grid.Row="2"
Grid.Column="0"
Margin="0,5,0,5"
ColumnDefinitions="Auto,*">
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="Password: " />
<TextBox
Grid.Column="1"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}" />
</Grid>
<StackPanel
Grid.Row="3"
VerticalAlignment="Bottom">
<TextBlock
Foreground="Blue"
TextDecorations="Underline"
Tapped="ExternalLoginLink_Tapped"
Text="Or click here to log in with your browser." />
<TextBlock
TextWrapping="Wrap"
Text="This more advanced login is recommended if you're experiencing errors logging in the conventional way above or if you're not comfortable typing your password here." />
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,45 @@
using AudibleApi;
using AudibleUtilities;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Linq;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class LoginChoiceEagerDialog : DialogWindow
{
public Account Account { get; }
public string Password { get; set; }
public LoginMethod LoginMethod { get; private set; }
public LoginChoiceEagerDialog()
{
InitializeComponent();
if (Design.IsDesignMode)
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.Accounts;
Account = accounts.FirstOrDefault();
DataContext = this;
}
}
public LoginChoiceEagerDialog(Account account):this()
{
Account = account;
DataContext = this;
}
public async void ExternalLoginLink_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
LoginMethod = LoginMethod.External;
await SaveAndCloseAsync();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,111 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="650" d:DesignHeight="500"
Width="650" Height="500"
WindowStartupLocation="CenterOwner"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.LoginExternalDialog"
Title="Audible Login External"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,*,Auto,*" ColumnDefinitions="*" Margin="5">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Text="Locale: " />
<TextBlock Text="{Binding Account.Locale.Name}" />
</StackPanel>
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<TextBlock Text="Username: " />
<TextBlock Text="{Binding Account.AccountId}" />
</StackPanel>
<Grid
Margin="0,5,0,5"
Grid.Row="2"
Grid.Column="0"
RowDefinitions="Auto,*,Auto"
ColumnDefinitions="*,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Text="Paste this URL into your browser:" />
<TextBox
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
IsReadOnly="True"
TextWrapping="Wrap"
Text="{Binding ExternalLoginUrl}" />
<Button
Grid.Row="2"
Grid.Column="0"
Margin="0,5,0,0"
Content="Copy URL to Clipboard"
Click="CopyUrlToClipboard_Click" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="0,5,0,0"
Content="Launch in Browser"
Click="LaunchInBrowser_Click" />
</Grid>
<StackPanel
Grid.Row="3"
Orientation="Vertical"
VerticalAlignment="Bottom">
<TextBlock
TextWrapping="Wrap"
FontWeight="Bold"
Text="tl;dr : an ERROR on Amazon is GOOD. Sorry, I can't control their weird login" />
<TextBlock
TextWrapping="Wrap"
Text="Login with your Amazon/Audible credentials.
&#xa;After login is complete, your browser will show you an error page similar to:
&#xa; Looking for Something?
&#xa; We're sorry. The Web address you entered is not a functioning page on our site
&#xa;Don't worry -- this is ACTUALLY A SUCCESSFUL LOGIN.
&#xa;Copy the current url from your browser's address bar and paste it here:
" />
</StackPanel>
<Grid
Grid.Row="4"
Grid.Column="0"
Margin="0,5,0,5"
RowDefinitions="*,Auto">
<TextBox
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
TextWrapping="Wrap"
Text="{Binding ResponseUrl, Mode=TwoWay}" />
<Button
Grid.Row="1"
Margin="0,5,0,0"
Padding="30,3,30,3" HorizontalAlignment="Right"
Content="Submit"
Click="Submit_Click" />
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,71 @@
using AudibleApi;
using AudibleUtilities;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Dinah.Core;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class LoginExternalDialog : DialogWindow
{
public Account Account { get; }
public string ExternalLoginUrl { get; }
public string ResponseUrl { get; set; }
public LoginExternalDialog()
{
InitializeComponent();
if (Design.IsDesignMode)
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.Accounts;
Account = accounts.FirstOrDefault();
ExternalLoginUrl = "ht" + "tps://us.audible.com/Test_url";
DataContext = this;
}
}
public LoginExternalDialog(Account account, string loginUrl):this()
{
Account = account;
ExternalLoginUrl = loginUrl;
DataContext = this;
}
public LoginExternalDialog(Account account)
{
Account = account;
DataContext = this;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out var result))
{
MessageBox.Show("Invalid response URL");
return;
}
await base.SaveAndCloseAsync();
}
public async void CopyUrlToClipboard_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await Application.Current.Clipboard.SetTextAsync(ExternalLoginUrl);
public void LaunchInBrowser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> Go.To.Url(ExternalLoginUrl);
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
}

View File

@ -0,0 +1,18 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="160"
MinWidth="400" MinHeight="160"
MaxWidth="400" MaxHeight="160"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login.MfaDialog"
Title="Two-Step Verification"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="0" Margin="10,0,10,10" Name="rbStackPanel" Orientation="Vertical"/>
<Button Grid.Row="1" Content="Submit" Margin="10" Padding="30,5,30,5" Click="Submit_Click" />
</Grid>
</Window>

View File

@ -0,0 +1,142 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
using ReactiveUI;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class MfaDialog : DialogWindow
{
public string SelectedName { get; private set; }
public string SelectedValue { get; private set; }
private RbValues Values { get; } = new();
public MfaDialog()
{
InitializeComponent();
if (Design.IsDesignMode)
{
var mfaConfig = new AudibleApi.MfaConfig { Title = "My title" };
mfaConfig.Buttons.Add(new() { Text = "Enter the OTP from the authenticator app", Name = "otpDeviceContext", Value = "aAbBcC=, TOTP" });
mfaConfig.Buttons.Add(new() { Text = "Send an SMS to my number ending with 123", Name = "otpDeviceContext", Value = "dDeEfE=, SMS" });
mfaConfig.Buttons.Add(new() { Text = "Call me on my number ending with 123", Name = "otpDeviceContext", Value = "dDeEfE=, VOICE" });
loadRadioButtons(mfaConfig);
}
}
public MfaDialog(AudibleApi.MfaConfig mfaConfig) : this()
{
loadRadioButtons(mfaConfig);
}
private void loadRadioButtons(AudibleApi.MfaConfig mfaConfig)
{
if (!string.IsNullOrWhiteSpace(mfaConfig.Title))
Title = mfaConfig.Title;
rbStackPanel = this.Find<StackPanel>(nameof(rbStackPanel));
foreach (var conf in mfaConfig.Buttons)
{
var rb = new RbValue(conf);
Values.AddButton(rb);
RadioButton radioButton = new()
{
Content = new TextBlock { Text = conf.Text },
Margin = new Thickness(0, 10, 0, 0),
};
radioButton.Bind(
RadioButton.IsCheckedProperty,
new Binding
{
Source = rb,
Path = nameof(rb.IsChecked)
});
rbStackPanel.Children.Add(radioButton);
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync()
{
var selected = Values.CheckedButton;
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new
{
text = selected?.Text,
name = selected?.Name,
value = selected?.Value
});
if (selected is null)
{
MessageBox.Show("No MFA option selected", "None selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
SelectedName = selected.Name;
SelectedValue = selected.Value;
await base.SaveAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
private class RbValue : ViewModels.ViewModelBase
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set => this.RaiseAndSetIfChanged(ref _isChecked, value);
}
public AudibleApi.MfaConfigButton MfaConfigButton { get; }
public RbValue(AudibleApi.MfaConfigButton mfaConfig)
{
MfaConfigButton = mfaConfig;
}
}
private class RbValues
{
private List<RbValue> ButtonValues { get; } = new();
public AudibleApi.MfaConfigButton CheckedButton => ButtonValues.SingleOrDefault(rb => rb.IsChecked)?.MfaConfigButton;
public void AddButton(RbValue rbValue)
{
if (ButtonValues.Contains(rbValue))
return;
rbValue.PropertyChanged += RbValue_PropertyChanged;
rbValue.IsChecked = ButtonValues.Count == 0;
ButtonValues.Add(rbValue);
}
private void RbValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var button = sender as RbValue;
if (button.IsChecked)
{
foreach (var rb in ButtonValues.Where(rb => rb != button))
rb.IsChecked = false;
}
}
}
}
}

View File

@ -0,0 +1,32 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="140" d:DesignHeight="100"
MinWidth="140" MinHeight="100"
MaxWidth="140" MaxHeight="100"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.Login._2faCodeDialog"
Title="2FA Code"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,*">
<TextBlock
Margin="5"
TextAlignment="Center"
Text="Enter 2FA Code" />
<TextBox
Margin="5,0,5,0"
Grid.Row="1"
Text="{Binding Code, Mode=TwoWay}" />
<Button
Margin="5"
Grid.Row="2"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Content="Submit"
Click="Submit_Click" />
</Grid>
</Window>

View File

@ -0,0 +1,33 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs.Login
{
public partial class _2faCodeDialog : DialogWindow
{
public string Code { get; set; }
public _2faCodeDialog()
{
InitializeComponent();
DataContext = this;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Code });
return base.SaveAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
}

View File

@ -28,7 +28,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
DataContext = this;
}
private async void GoToGithub_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void GoToGithub_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var url = "https://github.com/rmcrackan/Libation/issues";
try
@ -37,7 +37,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
}
catch
{
await MessageBox.Show($"Error opening url\r\n{url}", "Error opening url", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"Error opening url\r\n{url}", "Error opening url", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
@ -56,7 +56,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
}
catch
{
await MessageBox.Show($"Error opening folder\r\n{dir}", "Error opening folder", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"Error opening folder\r\n{dir}", "Error opening folder", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

View File

@ -30,12 +30,16 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync()
{
settingsDisp.SaveSettings(config);
if (!settingsDisp.SaveSettings(config))
return;
await MessageBox.VerboseLoggingWarning_ShowIfTrue();
await base.SaveAndCloseAsync();
}
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
@ -44,7 +48,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
Go.To.Folder(((LongPath)Configuration.Instance.LibationFiles).ShortPathName);
}
public async void EditFolderTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(Templates.Folder, settingsDisp.DownloadDecryptSettings.FolderTemplate);
@ -52,7 +55,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
settingsDisp.DownloadDecryptSettings.FolderTemplate = newTemplate;
}
public async void EditFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(Templates.File, settingsDisp.DownloadDecryptSettings.FileTemplate);
@ -60,7 +62,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
settingsDisp.DownloadDecryptSettings.FileTemplate = newTemplate;
}
public async void EditChapterFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(Templates.ChapterFile, settingsDisp.DownloadDecryptSettings.ChapterFileTemplate);
@ -68,7 +69,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
}
public async void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var form = new LibationWinForms.Dialogs.EditReplacementChars(config);
@ -83,7 +83,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
private async Task<string> editTemplate(Templates template, string existingTemplate)
{
var form = new EditTemplateDialog(template, existingTemplate);
@ -96,7 +95,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
internal interface ISettingsDisplay
{
void LoadSettings(Configuration config);
void SaveSettings(Configuration config);
bool SaveSettings(Configuration config);
}
public class SettingsPages : ISettingsDisplay
@ -119,19 +118,19 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
AudioSettings = new(config);
}
public void SaveSettings(Configuration config)
public bool SaveSettings(Configuration config)
{
ImportantSettings.SaveSettings(config);
ImportSettings.SaveSettings(config);
DownloadDecryptSettings.SaveSettings(config);
AudioSettings.SaveSettings(config);
var result = ImportantSettings.SaveSettings(config);
result &= ImportSettings.SaveSettings(config);
result &= DownloadDecryptSettings.SaveSettings(config);
result &= AudioSettings.SaveSettings(config);
return result;
}
}
public class ImportantSettings : ISettingsDisplay
{
private static Func<string, string> desc { get; } = Configuration.GetDescription;
public ImportantSettings(Configuration config)
{
LoadSettings(config);
@ -145,12 +144,27 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
BetaOptIn = config.BetaOptIn;
}
public void SaveSettings(Configuration config)
public bool SaveSettings(Configuration config)
{
#region validation
if (string.IsNullOrWhiteSpace(BooksDirectory))
{
MessageBox.Show("Cannot set Books Location to blank", "Location is blank", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
#endregion
LongPath lonNewBooks = BooksDirectory;
if (!System.IO.Directory.Exists(lonNewBooks))
System.IO.Directory.CreateDirectory(lonNewBooks);
config.Books = BooksDirectory;
config.SavePodcastsToParentFolder = SavePodcastsToParentFolder;
config.LogLevel = LoggingLevel;
config.BetaOptIn = BetaOptIn;
return true;
}
@ -161,10 +175,10 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
Configuration.KnownDirectories.MyDocs
};
public string BooksText => desc(nameof(Configuration.Books));
public string SavePodcastsToParentFolderText => desc(nameof(Configuration.SavePodcastsToParentFolder));
public string BooksText { get; } = Configuration.GetDescription(nameof(Configuration.Books));
public string SavePodcastsToParentFolderText { get; } = Configuration.GetDescription(nameof(Configuration.SavePodcastsToParentFolder));
public Serilog.Events.LogEventLevel[] LoggingLevels { get; } = Enum.GetValues<Serilog.Events.LogEventLevel>();
public string BetaOptInText => desc(nameof(Configuration.BetaOptIn));
public string BetaOptInText { get; } = Configuration.GetDescription(nameof(Configuration.BetaOptIn));
public string BooksDirectory { get; set; }
public bool SavePodcastsToParentFolder { get; set; }
@ -172,11 +186,8 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
public bool BetaOptIn { get; set; }
}
public class ImportSettings : ISettingsDisplay
{
private static Func<string, string> desc { get; } = Configuration.GetDescription;
public ImportSettings(Configuration config)
{
LoadSettings(config);
@ -191,20 +202,21 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
AutoDownloadEpisodes = config.AutoDownloadEpisodes;
}
public void SaveSettings(Configuration config)
public bool SaveSettings(Configuration config)
{
config.AutoScan = AutoScan;
config.ShowImportedStats = ShowImportedStats;
config.ImportEpisodes = ImportEpisodes;
config.DownloadEpisodes = DownloadEpisodes;
config.AutoDownloadEpisodes = AutoDownloadEpisodes;
return true;
}
public string AutoScanText => desc(nameof(Configuration.AutoScan));
public string ShowImportedStatsText => desc(nameof(Configuration.ShowImportedStats));
public string ImportEpisodesText => desc(nameof(Configuration.ImportEpisodes));
public string DownloadEpisodesText => desc(nameof(Configuration.DownloadEpisodes));
public string AutoDownloadEpisodesText => desc(nameof(Configuration.AutoDownloadEpisodes));
public string AutoScanText { get; } = Configuration.GetDescription(nameof(Configuration.AutoScan));
public string ShowImportedStatsText { get; } = Configuration.GetDescription(nameof(Configuration.ShowImportedStats));
public string ImportEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.ImportEpisodes));
public string DownloadEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadEpisodes));
public string AutoDownloadEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.AutoDownloadEpisodes));
public bool AutoScan { get; set; }
public bool ShowImportedStats { get; set; }
@ -215,7 +227,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsDisplay
{
private static Func<string, string> desc { get; } = Configuration.GetDescription;
private bool _badBookAsk;
private bool _badBookAbort;
@ -246,8 +257,28 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
: Configuration.GetKnownDirectory(config.InProgress);
}
public void SaveSettings(Configuration config)
public bool SaveSettings(Configuration config)
{
static void validationError(string text, string caption)
=> MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
// these 3 should do nothing. Configuration will only init these with a valid value. EditTemplateDialog ensures valid before returning
if (!Templates.Folder.IsValid(FolderTemplate))
{
validationError($"Not saving change to folder naming template. Invalid format.", "Invalid folder template");
return false;
}
if (!Templates.File.IsValid(FileTemplate))
{
validationError($"Not saving change to file naming template. Invalid format.", "Invalid file template");
return false;
}
if (!Templates.ChapterFile.IsValid(ChapterFileTemplate))
{
validationError($"Not saving change to chapter file naming template. Invalid format.", "Invalid chapter file template");
return false;
}
config.BadBook
= BadBookAbort ? Configuration.BadBookAction.Abort
: BadBookRetry ? Configuration.BadBookAction.Retry
@ -260,24 +291,25 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
config.InProgress
= InProgressDirectory is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute
: Configuration.GetKnownDirectoryPath(InProgressDirectory);
return true;
}
public string BadBookGroupboxText => desc(nameof(Configuration.BadBook));
public string BadBookGroupboxText { get; } = Configuration.GetDescription(nameof(Configuration.BadBook));
public string BadBookAskText { get; } = Configuration.BadBookAction.Ask.GetDescription();
public string BadBookAbortText { get; } = Configuration.BadBookAction.Abort.GetDescription();
public string BadBookRetryText { get; } = Configuration.BadBookAction.Retry.GetDescription();
public string BadBookIgnoreText { get; } = Configuration.BadBookAction.Ignore.GetDescription();
public string FolderTemplateText => desc(nameof(Configuration.FolderTemplate));
public string FileTemplateText => desc(nameof(Configuration.FileTemplate));
public string ChapterFileTemplateText => desc(nameof(Configuration.ChapterFileTemplate));
public string EditCharReplacementText => desc(nameof(Configuration.ReplacementCharacters));
public string InProgressDescriptionText => desc(nameof(Configuration.InProgress));
public string FolderTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate));
public string FileTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.FileTemplate));
public string ChapterFileTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
public string EditCharReplacementText { get; } = Configuration.GetDescription(nameof(Configuration.ReplacementCharacters));
public string InProgressDescriptionText { get; } = Configuration.GetDescription(nameof(Configuration.InProgress));
public string FolderTemplate { get => _folderTemplate; set { this.RaiseAndSetIfChanged(ref _folderTemplate, value); } }
public string FileTemplate { get => _fileTemplate; set { this.RaiseAndSetIfChanged(ref _fileTemplate, value); } }
public string ChapterFileTemplate { get => _chapterFileTemplate; set { this.RaiseAndSetIfChanged(ref _chapterFileTemplate, value); } }
public bool BadBookAsk
{
get => _badBookAsk;
@ -346,7 +378,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
private int _lameBitrate;
private int _lameVBRQuality;
private string _chapterTitleTemplate;
private static Func<string, string> desc { get; } = Configuration.GetDescription;
public AudioSettings(Configuration config)
{
LoadSettings(config);
@ -371,7 +403,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
LameVBRQuality = config.LameVBRQuality;
}
public void SaveSettings(Configuration config)
public bool SaveSettings(Configuration config)
{
config.CreateCueSheet = CreateCueSheet;
config.AllowLibationFixup = AllowLibationFixup;
@ -389,17 +421,19 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
config.LameMatchSourceBR = LameMatchSource;
config.LameBitrate = LameBitrate;
config.LameVBRQuality = LameVBRQuality;
return true;
}
public string CreateCueSheetText => desc(nameof(Configuration.CreateCueSheet));
public string AllowLibationFixupText => desc(nameof(Configuration.AllowLibationFixup));
public string DownloadCoverArtText => desc(nameof(Configuration.DownloadCoverArt));
public string RetainAaxFileText => desc(nameof(Configuration.RetainAaxFile));
public string SplitFilesByChapterText => desc(nameof(Configuration.SplitFilesByChapter));
public string MergeOpeningEndCreditsText => desc(nameof(Configuration.MergeOpeningAndEndCredits));
public string StripAudibleBrandingText => desc(nameof(Configuration.StripAudibleBrandAudio));
public string StripUnabridgedText => desc(nameof(Configuration.StripUnabridged));
public string ChapterTitleTemplateText => desc(nameof(Configuration.ChapterTitleTemplate));
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
public string RetainAaxFileText { get; } = Configuration.GetDescription(nameof(Configuration.RetainAaxFile));
public string SplitFilesByChapterText { get; } = Configuration.GetDescription(nameof(Configuration.SplitFilesByChapter));
public string MergeOpeningEndCreditsText { get; } = Configuration.GetDescription(nameof(Configuration.MergeOpeningAndEndCredits));
public string StripAudibleBrandingText { get; } = Configuration.GetDescription(nameof(Configuration.StripAudibleBrandAudio));
public string StripUnabridgedText { get; } = Configuration.GetDescription(nameof(Configuration.StripUnabridged));
public string ChapterTitleTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate));
public bool CreateCueSheet { get; set; }
public bool DownloadCoverArt { get; set; }

View File

@ -9,7 +9,7 @@ namespace LibationWinForms.AvaloniaUI.Views
{
private void Configure_Export() { }
public async void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
try
{
@ -37,11 +37,11 @@ namespace LibationWinForms.AvaloniaUI.Views
break;
}
await MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
}
}
}

View File

@ -40,7 +40,7 @@ namespace LibationWinForms.AvaloniaUI.Views
}
catch (Exception ex)
{
await MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error);
// re-apply last good filter
await performFilter(lastGoodFilter);

View File

@ -40,7 +40,7 @@ namespace LibationWinForms.AvaloniaUI.Views
public async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
{
var result = await MessageBox.Show(
var result = MessageBox.Show(
"This converts all m4b titles in your library to mp3 files. Original files are not deleted."
+ "\r\nFor large libraries this will take a long time and will take up more disk space."
+ "\r\n\r\nContinue?"

View File

@ -15,7 +15,7 @@ namespace LibationWinForms.AvaloniaUI.Views
SetQueueCollapseState(collapseState);
}
public async void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook)
public void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook)
{
try
{
@ -38,7 +38,7 @@ namespace LibationWinForms.AvaloniaUI.Views
if (!Go.To.File(filePath?.ShortPathName))
{
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
await MessageBox.Show($"File not found" + suffix);
MessageBox.Show($"File not found" + suffix);
}
}
}

View File

@ -35,7 +35,7 @@ namespace LibationWinForms.AvaloniaUI.Views
// in autoScan, new books SHALL NOT show dialog
try
{
await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
await LibraryCommands.ImportAccountAsync(Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts);
}
catch (Exception ex)
{

View File

@ -27,7 +27,7 @@ namespace LibationWinForms.AvaloniaUI.Views
public async void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
await MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
await new Dialogs.AccountsDialog().ShowDialog(this);
}
@ -63,15 +63,15 @@ namespace LibationWinForms.AvaloniaUI.Views
{
try
{
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts);
// this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop
if (Configuration.Instance.ShowImportedStats && newAdded > 0)
await MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
MessageBox.ShowAdminAlert(
this,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library",

View File

@ -14,6 +14,6 @@ namespace LibationWinForms.AvaloniaUI.Views
public async void basicSettingsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) => await new Dialogs.SettingsDialog().ShowDialog(this);
public async void aboutToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await MessageBox.Show($"Running Libation version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
=> MessageBox.Show($"Running Libation version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
}
}

View File

@ -48,7 +48,7 @@ namespace LibationWinForms.AvaloniaUI.Views
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
var confirmationResult = await MessageBox.ShowConfirmationDialog(
var confirmationResult = MessageBox.ShowConfirmationDialog(
this,
visibleLibraryBooks,
"Are you sure you want to replace tags in {0}?",
@ -71,7 +71,7 @@ namespace LibationWinForms.AvaloniaUI.Views
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
var confirmationResult = await MessageBox.ShowConfirmationDialog(
var confirmationResult = MessageBox.ShowConfirmationDialog(
this,
visibleLibraryBooks,
"Are you sure you want to replace downloaded status in {0}?",
@ -89,7 +89,7 @@ namespace LibationWinForms.AvaloniaUI.Views
{
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
var confirmationResult = await MessageBox.ShowConfirmationDialog(
var confirmationResult = MessageBox.ShowConfirmationDialog(
this,
visibleLibraryBooks,
"Are you sure you want to remove {0} from Libation's library?",

View File

@ -67,8 +67,7 @@ namespace LibationWinForms.AvaloniaUI.Views
private async void MainWindow_Opened(object sender, EventArgs e)
{
//var dialog = new EditReplacementChars();
//await dialog.ShowDialog(this);
}
public void ProductsDisplay_Initialized1(object sender, EventArgs e)