From 702219ee693aa0170c6a49bab7947093b370ed77 Mon Sep 17 00:00:00 2001 From: Mbucari Date: Mon, 27 Mar 2023 16:11:30 -0600 Subject: [PATCH] Add guided walkthrough --- .../Dialogs/SettingsDialog.axaml | 2 +- .../LibationAvalonia/Views/MainWindow.axaml | 13 +- .../Views/MainWindow.axaml.cs | 17 +++ Source/LibationAvalonia/Walkthrough.cs | 127 ++++++++++++++++++ .../Configuration.PersistentSettings.cs | 3 + .../Dialogs/SettingsDialog.Designer.cs | 2 +- Source/LibationWinForms/Form1.Designer.cs | 12 +- Source/LibationWinForms/Form1.cs | 16 +++ Source/LibationWinForms/Walkthrough.cs | 124 +++++++++++++++++ 9 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 Source/LibationAvalonia/Walkthrough.cs create mode 100644 Source/LibationWinForms/Walkthrough.cs diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml index 0f1207dc..e7771a78 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml @@ -21,7 +21,7 @@ Content="Save" Click="SaveButton_Clicked" /> - + - - + + diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow.axaml.cs index b33cb440..20dfe906 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow.axaml.cs @@ -48,6 +48,23 @@ namespace LibationAvalonia.Views Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance); } Closing += MainWindow_Closing; + + Opened += MainWindow_Opened; + } + + private async void MainWindow_Opened(object sender, EventArgs e) + { + if (Configuration.Instance.FirstLaunch) + { + var result = await MessageBox.Show(this, "Would you like a guided tour to get started?", "Libation Walkthrough", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); + + if (result is DialogResult.Yes) + { + await new Walkthrough(this).RunAsync(); + } + + Configuration.Instance.FirstLaunch = false; + } } private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) diff --git a/Source/LibationAvalonia/Walkthrough.cs b/Source/LibationAvalonia/Walkthrough.cs new file mode 100644 index 00000000..99823e63 --- /dev/null +++ b/Source/LibationAvalonia/Walkthrough.cs @@ -0,0 +1,127 @@ +using AudibleUtilities; +using Avalonia.Controls; +using Avalonia.Threading; +using Dinah.Core.StepRunner; +using LibationAvalonia.Dialogs; +using LibationAvalonia.Views; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace LibationAvalonia +{ + internal class Walkthrough + { + private static Dictionary settingTabMessages = new() + { + { "Important Settings", "Change where liberated books are stored."}, + { "Import Library", "Change how your library is scanned, imported, and liberated."}, + { "Download/Decrypt", "Control how liberated files and folders are named and stored."}, + { "Audio File Settings", "Control how audio files are decrypted, including audio format and metadata handling."}, + }; + + private readonly MainWindow MainForm; + private readonly AsyncStepSequence sequence = new(); + public Walkthrough(MainWindow mainForm) + { + MainForm = mainForm; + sequence[nameof(ShowAccountDialog)] = ShowAccountDialog; + sequence[nameof(ShowSettingsDialog)] = ShowSettingsDialog; + sequence[nameof(ScanAccounts)] = ScanAccounts; + } + + public async Task RunAsync() => await sequence.RunAsync(); + + private async Task ShowAccountDialog() + { + await Dispatcher.UIThread.InvokeAsync(() => MessageBox.Show(MainForm, "First, add you Audible account(s).", "Add Accounts")); + + await Task.Delay(750); + await Dispatcher.UIThread.InvokeAsync(MainForm.settingsToolStripMenuItem.Open); + await Task.Delay(500); + await Dispatcher.UIThread.InvokeAsync(() => MainForm.accountsToolStripMenuItem.IsSelected = true); + await Task.Delay(1000); + var accountSettings = await Dispatcher.UIThread.InvokeAsync(() => new AccountsDialog()); + accountSettings.Opened += (_, _) => MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account"); + await Dispatcher.UIThread.InvokeAsync(() => accountSettings.ShowDialog(MainForm)); + return true; + } + + private async Task ShowSettingsDialog() + { + await Dispatcher.UIThread.InvokeAsync(() => MessageBox.Show(MainForm, "Next, adjust Libation's settings", "Change Settings")); + + await Task.Delay(750); + await Dispatcher.UIThread.InvokeAsync(MainForm.settingsToolStripMenuItem.Open); + await Task.Delay(500); + await Dispatcher.UIThread.InvokeAsync(() => MainForm.basicSettingsToolStripMenuItem.IsSelected = true); + await Task.Delay(1000); + + var settingsDialog = await Dispatcher.UIThread.InvokeAsync(() => new SettingsDialog()); + + var tabsToVisit = settingsDialog.tabControl.Items.OfType().ToList(); + + foreach (var tab in tabsToVisit) + tab.PropertyChanged += TabControl_PropertyChanged; + + settingsDialog.Closing += SettingsDialog_FormClosing; + + await Dispatcher.UIThread.InvokeAsync(() => settingsDialog.ShowDialog(MainForm)); + + return true; + + async void TabControl_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == TabItem.IsSelectedProperty) + { + var selectedTab = sender as TabItem; + + tabsToVisit.Remove(selectedTab); + + if (!selectedTab.IsVisible || !(selectedTab.Header is TextBlock header && settingTabMessages.ContainsKey(header.Text))) return; + + await MessageBox.Show(settingsDialog, settingTabMessages[header.Text], header.Text + " Tab", MessageBoxButtons.OK, MessageBoxIcon.Information); + + settingTabMessages.Remove(header.Text); + } + } + + void SettingsDialog_FormClosing(object sender, WindowClosingEventArgs e) + { + if (tabsToVisit.Count > 0) + { + var nextTab = tabsToVisit[0]; + tabsToVisit.RemoveAt(0); + + settingsDialog.tabControl.SelectedItem = nextTab; + e.Cancel = true; + } + } + } + + private async Task ScanAccounts() + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var count = persister.AccountsSettings.Accounts.Count; + + if (count < 1) + { + await Dispatcher.UIThread.InvokeAsync(() => MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the \"Import\" menu", "Add an Audible Account")); + return false; + } + + var accounts = count > 1 ? "accounts" : "account"; + var library = count > 1 ? "libraries" : "library"; + await Dispatcher.UIThread.InvokeAsync(() => MessageBox.Show(MainForm, $"Finally, scan your Audible {accounts} to sync your {library} with Libation", $"Scan {accounts}")); + + await Task.Delay(750); + await Dispatcher.UIThread.InvokeAsync(MainForm.importToolStripMenuItem.Open); + await Task.Delay(500); + await Dispatcher.UIThread.InvokeAsync(() => (count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem).IsSelected = true); + await Task.Delay(1000); + await Dispatcher.UIThread.InvokeAsync(() => (count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem).RaiseEvent(new Avalonia.Interactivity.RoutedEventArgs(MenuItem.ClickEvent))); + + return true; + } + } +} diff --git a/Source/LibationFileManager/Configuration.PersistentSettings.cs b/Source/LibationFileManager/Configuration.PersistentSettings.cs index cb9e8d2f..70443f7b 100644 --- a/Source/LibationFileManager/Configuration.PersistentSettings.cs +++ b/Source/LibationFileManager/Configuration.PersistentSettings.cs @@ -191,6 +191,9 @@ namespace LibationFileManager Ignore = 3 } + [Description("Indicates that this is the first time Libation has been run")] + public bool FirstLaunch { get => GetNonString(defaultValue: true); set => SetNonString(value); } + [Description("When liberating books and there is an error, Libation should:")] public BadBookAction BadBook { get => GetNonString(defaultValue: BadBookAction.Ask); set => SetNonString(value); } diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index dfea7df1..74ec91b6 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -1229,7 +1229,7 @@ private System.Windows.Forms.CheckBox downloadEpisodesCb; private System.Windows.Forms.CheckBox importEpisodesCb; private System.Windows.Forms.CheckBox splitFilesByChapterCbox; - private System.Windows.Forms.TabControl tabControl; + public System.Windows.Forms.TabControl tabControl; private System.Windows.Forms.TabPage tab1ImportantSettings; private System.Windows.Forms.GroupBox booksGb; private System.Windows.Forms.TabPage tab2ImportLibrary; diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index 995bf2ed..89a10d44 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -634,7 +634,7 @@ #endregion private System.Windows.Forms.MenuStrip menuStrip1; - private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem; private System.Windows.Forms.StatusStrip statusStrip1; private System.Windows.Forms.ToolStripStatusLabel springLbl; private LibationWinForms.FormattableToolStripStatusLabel visibleCountLbl; @@ -645,16 +645,16 @@ private System.Windows.Forms.TextBox filterSearchTb; private System.Windows.Forms.Button filterBtn; private System.Windows.Forms.Button filterHelpBtn; - private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem; private System.Windows.Forms.Button addQuickFilterBtn; private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; - private System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem accountsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem accountsToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem; diff --git a/Source/LibationWinForms/Form1.cs b/Source/LibationWinForms/Form1.cs index cb5743fc..956e53c0 100644 --- a/Source/LibationWinForms/Form1.cs +++ b/Source/LibationWinForms/Form1.cs @@ -60,6 +60,22 @@ namespace LibationWinForms this.Load += (_, __) => productsDisplay.Display(); LibraryCommands.LibrarySizeChanged += (_, __) => this.UIThreadAsync(() => productsDisplay.Display()); } + Shown += Form1_Shown; + } + + private async void Form1_Shown(object sender, EventArgs e) + { + if (Configuration.Instance.FirstLaunch) + { + var result = MessageBox.Show(this, "Would you like a guided tour to get started?", "Libation Walkthrough", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); + + if (result is DialogResult.Yes) + { + await new Walkthrough(this).RunAsync(); + } + + Configuration.Instance.FirstLaunch = false; + } } private void Form1_Load(object sender, EventArgs e) diff --git a/Source/LibationWinForms/Walkthrough.cs b/Source/LibationWinForms/Walkthrough.cs new file mode 100644 index 00000000..4cf98ee8 --- /dev/null +++ b/Source/LibationWinForms/Walkthrough.cs @@ -0,0 +1,124 @@ +using AudibleUtilities; +using Dinah.Core.StepRunner; +using LibationWinForms.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LibationWinForms +{ + internal class Walkthrough + { + private static Dictionary settingTabMessages = new() + { + { "Important settings", "Change where liberated books are stored."}, + { "Import library", "Change how your library is scanned, imported, and liberated."}, + { "Download/Decrypt", "Control how liberated files and folders are named and stored."}, + { "Audio File Options", "Control how audio files are decrypted, including audio format and metadata handling."}, + }; + + private readonly Form1 MainForm; + private readonly AsyncStepSequence sequence = new(); + public Walkthrough(Form1 form1) + { + MainForm = form1; + sequence[nameof(ShowAccountDialog)] = ShowAccountDialog; + sequence[nameof(ShowSettingsDialog)] = ShowSettingsDialog; + sequence[nameof(ScanAccounts)] = ScanAccounts; + } + + public async Task RunAsync() => await sequence.RunAsync(); + + private async Task ShowAccountDialog() + { + MainForm.Invoke(() => MessageBox.Show(MainForm, "First, add you Audible account(s).", "Add Accounts")); + + await Task.Delay(750); + MainForm.Invoke(MainForm.settingsToolStripMenuItem.ShowDropDown); + await Task.Delay(500); + MainForm.Invoke(MainForm.accountsToolStripMenuItem.Select); + await Task.Delay(1000); + + using var accountSettings = MainForm.Invoke(() => new AccountsDialog()); + accountSettings.StartPosition = FormStartPosition.CenterParent; + accountSettings.Shown += (_, _) => MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account"); + MainForm.Invoke(() => accountSettings.ShowDialog(MainForm)); + return true; + } + + private async Task ShowSettingsDialog() + { + MainForm.Invoke(() => MessageBox.Show(MainForm, "Next, adjust Libation's settings", "Change Settings")); + + await Task.Delay(750); + MainForm.Invoke(MainForm.settingsToolStripMenuItem.ShowDropDown); + await Task.Delay(500); + MainForm.Invoke(MainForm.basicSettingsToolStripMenuItem.Select); + await Task.Delay(1000); + + using var settingsDialog = MainForm.Invoke(() => new SettingsDialog()); + + var tabsToVisit = settingsDialog.tabControl.TabPages.Cast().ToList(); + + settingsDialog.StartPosition = FormStartPosition.CenterParent; + settingsDialog.FormClosing += SettingsDialog_FormClosing; + settingsDialog.Shown += TabControl_TabIndexChanged; + settingsDialog.tabControl.SelectedIndexChanged += TabControl_TabIndexChanged; + + MainForm.Invoke(() => settingsDialog.ShowDialog(MainForm)); + + return true; + + void TabControl_TabIndexChanged(object sender, EventArgs e) + { + var selectedTab = settingsDialog.tabControl.SelectedTab; + + tabsToVisit.Remove(selectedTab); + + if (!selectedTab.Visible || !settingTabMessages.ContainsKey(selectedTab.Text)) return; + + MessageBox.Show(selectedTab, settingTabMessages[selectedTab.Text], selectedTab.Text + " Tab", MessageBoxButtons.OK, MessageBoxIcon.Information); + + settingTabMessages.Remove(selectedTab.Text); + } + + void SettingsDialog_FormClosing(object sender, FormClosingEventArgs e) + { + if (tabsToVisit.Count > 0) + { + var nextTab = tabsToVisit[0]; + tabsToVisit.RemoveAt(0); + + settingsDialog.tabControl.SelectedTab = nextTab; + e.Cancel = true; + } + } + } + + private async Task ScanAccounts() + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var count = persister.AccountsSettings.Accounts.Count; + + if (count < 1) + { + MainForm.Invoke(() => MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the \"Import\" menu", "Add an Audible Account")); + return false; + } + + var accounts = count > 1 ? "accounts" :"account"; + var library = count > 1 ? "libraries" : "library"; + MainForm.Invoke(() => MessageBox.Show(MainForm, $"Finally, scan your Audible {accounts} to sync your {library} with Libation", $"Scan {accounts}")); + + await Task.Delay(750); + MainForm.Invoke(MainForm.importToolStripMenuItem.ShowDropDown); + await Task.Delay(500); + MainForm.Invoke(() => (count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem).Select()); + await Task.Delay(1000); + MainForm.Invoke(() => (count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem).PerformClick()); + return true; + } + } +}