diff --git a/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.Designer.cs b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.Designer.cs
new file mode 100644
index 00000000..dc75e648
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.Designer.cs
@@ -0,0 +1,46 @@
+namespace LibationWinForms.Login
+{
+ partial class WebLoginDialog
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ SuspendLayout();
+ //
+ // WebLoginDialog
+ //
+ AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ ClientSize = new System.Drawing.Size(484, 761);
+ Name = "WebLoginDialog";
+ StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ Text = "Audible Login";
+ ResumeLayout(false);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs
new file mode 100644
index 00000000..75abedd4
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs
@@ -0,0 +1,67 @@
+using Dinah.Core;
+using Microsoft.Web.WebView2.WinForms;
+using System;
+using System.Windows.Forms;
+
+namespace LibationWinForms.Login
+{
+ public partial class WebLoginDialog : Form
+ {
+ public string ResponseUrl { get; private set; }
+ private readonly string loginUrl;
+ private readonly string accountID;
+ private readonly WebView2 webView = new();
+ public WebLoginDialog()
+ {
+ InitializeComponent();
+ webView.Dock = DockStyle.Fill;
+ Controls.Add(webView);
+ Shown += WebLoginDialog_Shown;
+ this.SetLibationIcon();
+ }
+
+ public WebLoginDialog(string accountID, string loginUrl) : this()
+ {
+ this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
+ this.loginUrl = ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl));
+ }
+
+ private async void WebLoginDialog_Shown(object sender, EventArgs e)
+ {
+ await webView.EnsureCoreWebView2Async();
+ webView.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
+ webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
+ webView.CoreWebView2.Navigate(loginUrl);
+ }
+
+ private async void CoreWebView2_DOMContentLoaded(object sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
+ {
+ await webView.CoreWebView2.ExecuteScriptAsync(getScript(accountID));
+ }
+
+ private static string getScript(string accountID) => $$"""
+ (function() {
+ var inputs = document.getElementsByTagName('input');
+ for (index = 0; index < inputs.length; ++index) {
+ if (inputs[index].name.includes('email')) {
+ inputs[index].value = '{{accountID}}';
+ }
+ if (inputs[index].name.includes('password')) {
+ inputs[index].focus();
+ }
+ }
+ })()
+ """;
+
+ private void CoreWebView2_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
+ {
+ if (new Uri(e.Uri).AbsolutePath.Contains("/ap/maplanding"))
+ {
+ ResponseUrl = e.Uri;
+ e.Cancel = true;
+ DialogResult = DialogResult.OK;
+ Close();
+ }
+ }
+ }
+}
diff --git a/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.resx b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.resx
new file mode 100644
index 00000000..f298a7be
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginBase.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginBase.cs
index 1de76464..790df589 100644
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginBase.cs
+++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginBase.cs
@@ -1,15 +1,22 @@
using System;
+using System.Windows.Forms;
namespace LibationWinForms.Dialogs.Login
{
public abstract class WinformLoginBase
{
- /// True if ShowDialog's DialogResult == OK
- protected static bool ShowDialog(System.Windows.Forms.Form dialog)
+ private readonly IWin32Window _owner;
+ protected WinformLoginBase(IWin32Window owner)
{
- var result = dialog.ShowDialog();
+ _owner = owner;
+ }
+
+ /// True if ShowDialog's DialogResult == OK
+ protected bool ShowDialog(Form dialog)
+ {
+ var result = dialog.ShowDialog(_owner);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
- return result == System.Windows.Forms.DialogResult.OK;
+ return result == DialogResult.OK;
}
}
}
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
index fd3e0e83..92c36c03 100644
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
+++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using System.Windows.Forms;
using AudibleApi;
using AudibleUtilities;
using LibationWinForms.Dialogs.Login;
@@ -12,7 +13,7 @@ namespace LibationWinForms.Login
public string DeviceName { get; } = "Libation";
- public WinformLoginCallback(Account account)
+ public WinformLoginCallback(Account account, IWin32Window owner) : base(owner)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
}
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
index 062893dd..96c8425e 100644
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
+++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using System.Windows.Forms;
using AudibleApi;
using AudibleUtilities;
using LibationWinForms.Dialogs.Login;
@@ -9,20 +10,31 @@ namespace LibationWinForms.Login
public class WinformLoginChoiceEager : WinformLoginBase, ILoginChoiceEager
{
/// Convenience method. Recommended when wiring up Winforms to
- public static async Task ApiExtendedFunc(Account account) => await ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account));
+ public static Func> CreateApiExtendedFunc(IWin32Window owner) => a => ApiExtendedFunc(a, owner);
+
+ private static async Task ApiExtendedFunc(Account account, IWin32Window owner)
+ => await ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account, owner));
public ILoginCallback LoginCallback { get; private set; }
private Account _account { get; }
- public WinformLoginChoiceEager(Account account)
+ private WinformLoginChoiceEager(Account account, IWin32Window owner) : base(owner)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
- LoginCallback = new WinformLoginCallback(_account);
+ LoginCallback = new WinformLoginCallback(_account, owner);
}
public Task StartAsync(ChoiceIn choiceIn)
{
+ if (Environment.OSVersion.Version.Major >= 10)
+ {
+ using var browserLogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
+
+ if (ShowDialog(browserLogin))
+ return Task.FromResult(ChoiceOut.External(browserLogin.ResponseUrl));
+ }
+
using var dialog = new LoginChoiceEagerDialog(_account);
if (!ShowDialog(dialog) || (dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password)))
@@ -33,13 +45,13 @@ namespace LibationWinForms.Login
case LoginMethod.Api:
return Task.FromResult(ChoiceOut.WithApi(dialog.Email, dialog.Password));
case LoginMethod.External:
- {
- using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
+ {
+ using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return Task.FromResult(
ShowDialog(externalDialog)
? ChoiceOut.External(externalDialog.ResponseUrl)
: null);
- }
+ }
default:
throw new Exception($"Unknown {nameof(LoginMethod)} value");
}
diff --git a/Source/LibationWinForms/Form1.ScanAuto.cs b/Source/LibationWinForms/Form1.ScanAuto.cs
index ca91c770..6b671c6a 100644
--- a/Source/LibationWinForms/Form1.ScanAuto.cs
+++ b/Source/LibationWinForms/Form1.ScanAuto.cs
@@ -32,7 +32,7 @@ namespace LibationWinForms
// in autoScan, new books SHALL NOT show dialog
try
{
- Task importAsync() => LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
+ Task importAsync() => LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), accounts);
if (InvokeRequired)
await Invoke(importAsync);
else
diff --git a/Source/LibationWinForms/Form1.ScanManual.cs b/Source/LibationWinForms/Form1.ScanManual.cs
index d745c6d2..83f5149f 100644
--- a/Source/LibationWinForms/Form1.ScanManual.cs
+++ b/Source/LibationWinForms/Form1.ScanManual.cs
@@ -74,7 +74,7 @@ namespace LibationWinForms
{
try
{
- var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
+ var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), 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)
diff --git a/Source/LibationWinForms/GridView/ProductsDisplay.cs b/Source/LibationWinForms/GridView/ProductsDisplay.cs
index 6196b6a6..95c1ea7f 100644
--- a/Source/LibationWinForms/GridView/ProductsDisplay.cs
+++ b/Source/LibationWinForms/GridView/ProductsDisplay.cs
@@ -279,7 +279,7 @@ namespace LibationWinForms.GridView
.Select(lbe => lbe.LibraryBook)
.Where(lb => !lb.Book.HasLiberated());
- var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.ApiExtendedFunc, lib, accounts);
+ var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), lib, accounts);
var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList();
diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj
index c482fe34..b051ac6e 100644
--- a/Source/LibationWinForms/LibationWinForms.csproj
+++ b/Source/LibationWinForms/LibationWinForms.csproj
@@ -38,6 +38,7 @@
+