Merge pull request #565 from Mbucari/master
About Dialog, mac menus, and hotkeys
This commit is contained in:
commit
7ada0082a9
@ -1,7 +1,8 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:LibationAvalonia"
|
||||
x:Class="LibationAvalonia.App">
|
||||
x:Class="LibationAvalonia.App"
|
||||
Name="Libation">
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
@ -69,4 +70,11 @@
|
||||
</Style>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="About Libation" />
|
||||
</NativeMenu>
|
||||
</NativeMenu.Menu>
|
||||
|
||||
</Application>
|
||||
@ -14,6 +14,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using DataLayer;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
@ -45,9 +47,6 @@ namespace LibationAvalonia
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var acceleratorKey = Configuration.IsMacOs ? KeyModifiers.Meta : KeyModifiers.Alt;
|
||||
AvaloniaLocator.CurrentMutable.Bind<IAccessKeyHandler>().ToFunc(() => new AccessKeyHandlerEx(acceleratorKey));
|
||||
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (!config.LibationSettingsAreValid)
|
||||
@ -219,9 +218,8 @@ namespace LibationAvalonia
|
||||
LoadStyles();
|
||||
var mainWindow = new MainWindow();
|
||||
desktop.MainWindow = MainWindow = mainWindow;
|
||||
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
|
||||
mainWindow.OnLoad();
|
||||
mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult());
|
||||
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
|
||||
mainWindow.Show();
|
||||
}
|
||||
|
||||
|
||||
@ -64,6 +64,33 @@
|
||||
M7.2,0.8 a 0.8,0.8 0 0 1 1.6,0 v8 l0.9929,-0.9929 a 0.8,0.8 0 0 1 1.1314,1.1314 l-2.3586,2.3586
|
||||
a 0.8,0.8 0 0 1 -1.1314,0 l-2.3586,-2.3586 a 0.8,0.8 0 0 1 1.1314,-1.1314 l0.9929,0.9929 v8
|
||||
</StreamGeometry>
|
||||
|
||||
<StreamGeometry x:Key="LibationCheersIcon">
|
||||
M139,2
|
||||
A 192,200 0 0 0 103,84
|
||||
A 222,334 41 0 0 241,320
|
||||
V478
|
||||
H160
|
||||
A 16,16 0 0 0 160,510
|
||||
H352
|
||||
A16 16 0 0 0 352,478
|
||||
H271
|
||||
V320
|
||||
A 222,334 -41 0 0 409,84
|
||||
A 192,200 0 0 0 373,2
|
||||
M355,32
|
||||
A 192,200 0 0 1 381,127
|
||||
A 187.5,334 -35 0 1 256,286
|
||||
A 187.5,334 35 0 1 131,127
|
||||
A 192,200 0 0 1 157,32
|
||||
H355
|
||||
M146,147
|
||||
A 168,300 35 0 0 256,270
|
||||
A 168,300 -35 0 0 366,128
|
||||
S 360,50 280,110
|
||||
S 192,128 147,147
|
||||
</StreamGeometry>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<DataTemplate>
|
||||
|
||||
<TextBlock
|
||||
Text="{Binding, Converter={StaticResource KnownDirectoryConverter}}" />
|
||||
Text="{Binding Converter={StaticResource KnownDirectoryConverter}}" />
|
||||
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
|
||||
@ -6,9 +6,6 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="LibationAvalonia.Controls.GroupBox">
|
||||
|
||||
<Design.DataContext>
|
||||
</Design.DataContext>
|
||||
|
||||
<ContentControl.Styles>
|
||||
<Style Selector="controls|GroupBox Border">
|
||||
<Setter Property="BorderBrush" Value="DarkGray" />
|
||||
@ -16,7 +13,7 @@
|
||||
<Style Selector="controls|GroupBox">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="7,10,*,Auto">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="8,12,*,Auto">
|
||||
|
||||
<Grid
|
||||
ZIndex="1"
|
||||
|
||||
73
Source/LibationAvalonia/Dialogs/AboutDialog.axaml
Normal file
73
Source/LibationAvalonia/Dialogs/AboutDialog.axaml
Normal file
@ -0,0 +1,73 @@
|
||||
<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="520"
|
||||
MinWidth="400" MinHeight="520"
|
||||
Width="400" Height="520"
|
||||
x:Class="LibationAvalonia.Dialogs.AboutDialog"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
Title="About Libation"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
<Grid Margin="10" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,*">
|
||||
|
||||
<controls:LinkLabel Grid.ColumnSpan="2" FontSize="16" FontWeight="Bold" Text="{Binding Version}" ToolTip.Tip="View Release Notes" Tapped="ViewReleaseNotes_Tapped" />
|
||||
|
||||
<controls:LinkLabel Grid.Column="1" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Right" Text="https://getlibation.com" Tapped="Link_getlibation"/>
|
||||
|
||||
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="0,20,0,0" IsEnabled="{Binding CanCheckForUpgrade}" Content="{Binding UpgradeButtonText}" Click="CheckForUpgrade_Click" />
|
||||
|
||||
<Canvas Grid.Row="2" Grid.ColumnSpan="2" Margin="0,30,0,20" Width="280" Height="220">
|
||||
<Path Stretch="None" Fill="{DynamicResource IconFill}" Data="{DynamicResource LibationCheersIcon}">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<RotateTransform Angle="12" />
|
||||
<ScaleTransform ScaleX="0.4" ScaleY="0.4" />
|
||||
<TranslateTransform X="-160" Y="-150" />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path Stretch="None" Fill="{DynamicResource IconFill}" Data="{DynamicResource LibationCheersIcon}">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform ScaleX="-1" ScaleY="1" />
|
||||
<RotateTransform Angle="-12" />
|
||||
<ScaleTransform ScaleX="0.4" ScaleY="0.4" />
|
||||
<TranslateTransform X="23" Y="-150" />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Canvas>
|
||||
|
||||
<controls:GroupBox Grid.Row="3" BorderWidth="2" Label="Acknowledgements" Grid.ColumnSpan="2">
|
||||
<StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
|
||||
<controls:LinkLabel FontWeight="Bold" Text="rmcrackan" Tapped="Link_GithubUser" />
|
||||
<TextBlock Grid.Column="1" Margin="10,0" Text="Creator" />
|
||||
<controls:LinkLabel Grid.Row="1" FontWeight="Bold" Text="Mbucari" Tapped="Link_GithubUser" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Margin="10,0" Text="Developer" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Margin="0,10" FontSize="12" Text="Additional Contributions by:" TextDecorations="Underline"/>
|
||||
|
||||
<WrapPanel>
|
||||
<WrapPanel.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Margin" Value="5,0" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
</WrapPanel.Styles>
|
||||
<controls:LinkLabel Text="pixil98" Tapped="Link_GithubUser" />
|
||||
<controls:LinkLabel Text="hutattedonmyarm" Tapped="Link_GithubUser" />
|
||||
<controls:LinkLabel Text="seanke" Tapped="Link_GithubUser" />
|
||||
<controls:LinkLabel Text="wtanksleyjr" Tapped="Link_GithubUser" />
|
||||
<controls:LinkLabel Text="Dr.Blank" Tapped="Link_GithubUser" />
|
||||
<controls:LinkLabel Text="CharlieRussel" Tapped="Link_GithubUser" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</controls:GroupBox>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
80
Source/LibationAvalonia/Dialogs/AboutDialog.axaml.cs
Normal file
80
Source/LibationAvalonia/Dialogs/AboutDialog.axaml.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Avalonia.Controls;
|
||||
using LibationAvalonia.Controls;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public partial class AboutDialog : DialogWindow
|
||||
{
|
||||
private readonly AboutVM _viewModel;
|
||||
public AboutDialog() : base(saveAndRestorePosition:false)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
_ = Configuration.Instance.LibationFiles;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = _viewModel = new AboutVM();
|
||||
}
|
||||
|
||||
private async void CheckForUpgrade_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var mainWindow = Owner as Views.MainWindow;
|
||||
|
||||
var upgrader = new Upgrader();
|
||||
upgrader.DownloadProgress += async (_, e) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => mainWindow.ViewModel.DownloadProgress = e.ProgressPercentage);
|
||||
upgrader.DownloadCompleted += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => mainWindow.ViewModel.DownloadProgress = null);
|
||||
|
||||
_viewModel.CanCheckForUpgrade = false;
|
||||
Version latestVersion = null;
|
||||
await upgrader.CheckForUpgradeAsync(OnUpgradeAvailable);
|
||||
|
||||
_viewModel.CanCheckForUpgrade = latestVersion is null;
|
||||
|
||||
_viewModel.UpgradeButtonText = latestVersion is null ? "Libation is up to date. Check Again." : $"Version {latestVersion:3} is available";
|
||||
|
||||
async Task OnUpgradeAvailable(UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = await new UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
|
||||
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.OK;
|
||||
latestVersion = e.UpgradeProperties.LatestRelease;
|
||||
}
|
||||
}
|
||||
|
||||
private void Link_GithubUser(object sender, Avalonia.Input.TappedEventArgs e)
|
||||
{
|
||||
if (sender is LinkLabel lbl)
|
||||
{
|
||||
Dinah.Core.Go.To.Url($"ht" + $"tps://github.com/{lbl.Text.Replace('.','-')}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Link_getlibation(object sender, Avalonia.Input.TappedEventArgs e) => Dinah.Core.Go.To.Url(AppScaffolding.LibationScaffolding.WebsiteUrl);
|
||||
|
||||
private void ViewReleaseNotes_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||
=> Dinah.Core.Go.To.Url($"{AppScaffolding.LibationScaffolding.RepositoryUrl}/releases/tag/v{AppScaffolding.LibationScaffolding.BuildVersion.ToString(3)}");
|
||||
}
|
||||
|
||||
public class AboutVM : ViewModelBase
|
||||
{
|
||||
public string Version { get; }
|
||||
public bool CanCheckForUpgrade { get => canCheckForUpgrade; set => this.RaiseAndSetIfChanged(ref canCheckForUpgrade, value); }
|
||||
public string UpgradeButtonText { get => upgradeButtonText; set => this.RaiseAndSetIfChanged(ref upgradeButtonText, value); }
|
||||
|
||||
|
||||
private bool canCheckForUpgrade = true;
|
||||
private string upgradeButtonText = "Check for Upgrade";
|
||||
|
||||
public AboutVM()
|
||||
{
|
||||
Version = $"Libation {AppScaffolding.LibationScaffolding.Variety} v{AppScaffolding.LibationScaffolding.BuildVersion}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,17 +2,17 @@
|
||||
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="120"
|
||||
mc:Ignorable="d" d:DesignWidth="550" d:DesignHeight="130"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
x:Class="LibationAvalonia.Dialogs.LiberatedStatusBatchAutoDialog"
|
||||
Title="Liberated status: Whether the book has been downloaded"
|
||||
MinHeight="100" MaxHeight="165"
|
||||
MinWidth="600" MaxWidth="800"
|
||||
Width="600"
|
||||
MinHeight="130" MaxHeight="130"
|
||||
MinWidth="550" MaxWidth="550"
|
||||
Width="550" Height="130"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||
<Grid Margin="10" RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
@ -45,7 +45,6 @@
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Padding="30,0,30,0"
|
||||
Margin="10,0,10,10"
|
||||
HorizontalAlignment="Right"
|
||||
Height="25"
|
||||
Content="Save"
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
<Button Grid.Column="1" IsVisible="{Binding HasButton2}" MinWidth="75" MinHeight="28" Name="Button2" Click="Button2_Click" Margin="5">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Button2Text}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="28" Name="Button3" Click="Button3_Click" Content="Cancel" Margin="5">
|
||||
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="28" Name="Button3" Click="Button3_Click" Margin="5">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Button3Text}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -25,12 +25,18 @@ namespace LibationAvalonia.Dialogs
|
||||
DataContext = _viewModel = new();
|
||||
|
||||
this.Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
this.KeyDown += TrashBinDialog_KeyDown;
|
||||
}
|
||||
|
||||
public async void EmptyTrash_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await _viewModel.PermanentlyDeleteCheckedAsync();
|
||||
public async void Restore_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await _viewModel.RestoreCheckedAsync();
|
||||
private void TrashBinDialog_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Avalonia.Input.Key.Escape)
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
public class TrashBinViewModel : ViewModelBase, IDisposable
|
||||
|
||||
@ -51,8 +51,8 @@
|
||||
<Compile Update="Views\LiberateStatusButton.axaml.cs">
|
||||
<DependentUpon>LiberateStatusButton.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\MainWindow.*.cs">
|
||||
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||
<Compile Update="ViewModels\MainVM.*.cs">
|
||||
<DependentUpon>MainVM.cs</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,54 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
internal class AccessKeyHandlerEx : AccessKeyHandler
|
||||
internal class MacAccessKeyHandler : AccessKeyHandler
|
||||
{
|
||||
public KeyModifiers KeyModifier { get; }
|
||||
private readonly Key[] ActivatorKeys;
|
||||
|
||||
public AccessKeyHandlerEx(KeyModifiers menuKeyModifier)
|
||||
{
|
||||
KeyModifier = menuKeyModifier;
|
||||
ActivatorKeys = menuKeyModifier switch
|
||||
{
|
||||
KeyModifiers.Alt => new[] { Key.LeftAlt, Key.RightAlt },
|
||||
KeyModifiers.Control => new[] { Key.LeftCtrl, Key.RightCtrl },
|
||||
KeyModifiers.Meta => new[] { Key.LWin, Key.RWin },
|
||||
_ => throw new System.NotSupportedException($"{nameof(KeyModifiers)}.{menuKeyModifier} is not implemented"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (ActivatorKeys.Contains(e.Key) && e.KeyModifiers.HasAllFlags(KeyModifier))
|
||||
if (e.Key is Key.LWin or Key.RWin)
|
||||
{
|
||||
var newArgs = new KeyEventArgs { Key = Key.LeftAlt, Handled = e.Handled };
|
||||
base.OnPreviewKeyDown(sender, newArgs);
|
||||
e.Handled = newArgs.Handled;
|
||||
}
|
||||
else if (e.Key is not Key.LeftAlt and not Key.RightAlt)
|
||||
base.OnPreviewKeyDown(sender, e);
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (ActivatorKeys.Contains(e.Key) && e.KeyModifiers.HasAllFlags(KeyModifier))
|
||||
if (e.Key is Key.LWin or Key.RWin)
|
||||
{
|
||||
var newArgs = new KeyEventArgs { Key = Key.LeftAlt, Handled = e.Handled };
|
||||
base.OnPreviewKeyUp(sender, newArgs);
|
||||
e.Handled = newArgs.Handled;
|
||||
}
|
||||
else if (e.Key is not Key.LeftAlt and not Key.RightAlt)
|
||||
base.OnPreviewKeyDown(sender, e);
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyModifiers.HasAllFlags(KeyModifier))
|
||||
if (e.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
|
||||
{
|
||||
var newArgs = new KeyEventArgs { Key = e.Key, Handled = e.Handled, KeyModifiers = KeyModifiers.Alt };
|
||||
base.OnKeyDown(sender, newArgs);
|
||||
e.Handled = newArgs.Handled;
|
||||
}
|
||||
else if (!e.KeyModifiers.HasFlag(KeyModifiers.Alt))
|
||||
base.OnPreviewKeyDown(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs
Normal file
58
Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private Task<LibraryCommands.LibraryStats> updateCountsTask;
|
||||
private LibraryCommands.LibraryStats _libraryStats;
|
||||
|
||||
/// <summary> The "Begin Book and PDF Backup" menu item header text </summary>
|
||||
public string BookBackupsToolStripText { get; private set; } = "Begin Book and PDF Backups: 0";
|
||||
/// <summary> The "Begin PDF Only Backup" menu item header text </summary>
|
||||
public string PdfBackupsToolStripText { get; private set; } = "Begin PDF Only Backups: 0";
|
||||
|
||||
/// <summary> The user's library statistics </summary>
|
||||
public LibraryCommands.LibraryStats LibraryStats
|
||||
{
|
||||
get => _libraryStats;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _libraryStats, value);
|
||||
|
||||
BookBackupsToolStripText
|
||||
= LibraryStats.HasPendingBooks
|
||||
? "Begin " + menufyText($"Book and PDF Backups: {LibraryStats.PendingBooks} remaining")
|
||||
: "All books have been liberated";
|
||||
|
||||
PdfBackupsToolStripText
|
||||
= LibraryStats.pdfsNotDownloaded > 0
|
||||
? "Begin " + menufyText($"PDF Only Backups: {LibraryStats.pdfsNotDownloaded} remaining")
|
||||
: "All PDFs have been downloaded";
|
||||
|
||||
this.RaisePropertyChanged(nameof(BookBackupsToolStripText));
|
||||
this.RaisePropertyChanged(nameof(PdfBackupsToolStripText));
|
||||
}
|
||||
}
|
||||
|
||||
private void Configure_BackupCounts()
|
||||
{
|
||||
MainWindow.Loaded += setBackupCounts;
|
||||
LibraryCommands.LibrarySizeChanged += setBackupCounts;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
}
|
||||
|
||||
private async void setBackupCounts(object _, object __)
|
||||
{
|
||||
if (updateCountsTask?.IsCompleted ?? true)
|
||||
{
|
||||
updateCountsTask = Task.Run(() => LibraryCommands.GetCounts());
|
||||
var stats = await updateCountsTask;
|
||||
await Dispatcher.UIThread.InvokeAsync(() => LibraryStats = stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,21 +3,22 @@ using Avalonia.Platform.Storage;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainWindow
|
||||
partial class MainVM
|
||||
{
|
||||
private void Configure_Export() { }
|
||||
|
||||
public async void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
public async Task ExportLibraryAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new FilePickerSaveOptions
|
||||
{
|
||||
Title = "Where to export Library",
|
||||
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
||||
SuggestedStartLocation = await MainWindow.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
||||
SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}",
|
||||
DefaultExtension = "xlsx",
|
||||
ShowOverwritePrompt = true,
|
||||
@ -43,7 +44,7 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
};
|
||||
|
||||
var selectedFile = (await StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
|
||||
var selectedFile = (await MainWindow.StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
|
||||
|
||||
if (selectedFile is null) return;
|
||||
|
||||
@ -66,7 +67,7 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
|
||||
await MessageBox.ShowAdminAlert(MainWindow, "Error attempting to export your library.", "Error exporting", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Source/LibationAvalonia/ViewModels/MainVM.Filters.cs
Normal file
123
Source/LibationAvalonia/ViewModels/MainVM.Filters.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private string lastGoodFilter = "";
|
||||
private string _filterString;
|
||||
private bool _firstFilterIsDefault = true;
|
||||
|
||||
/// <summary> Library filterting query </summary>
|
||||
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }
|
||||
public AvaloniaList<Control> QuickFilterMenuItems { get; } = new();
|
||||
/// <summary> Indicates if the first quick filter is the default filter </summary>
|
||||
public bool FirstFilterIsDefault { get => _firstFilterIsDefault; set => QuickFilters.UseDefault = this.RaiseAndSetIfChanged(ref _firstFilterIsDefault, value); }
|
||||
|
||||
|
||||
private void Configure_Filters()
|
||||
{
|
||||
FirstFilterIsDefault = QuickFilters.UseDefault;
|
||||
MainWindow.Initialized += updateFiltersMenu;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
|
||||
//We need to be able to dynamically add and remove menu items from the Quick Filters menu.
|
||||
//To do that, we need quick filter's menu items source to be writable, which we can only
|
||||
//achieve by creating the list ourselves (instead of allowing Avalonia to create it from the xaml)
|
||||
|
||||
QuickFilterMenuItems.Add(new MenuItem
|
||||
{
|
||||
|
||||
Header = "Start Libation with 1st filter _Default",
|
||||
Command = ReactiveCommand.Create(ToggleFirstFilterIsDefault),
|
||||
Icon = new CheckBox
|
||||
{
|
||||
BorderThickness = new Thickness(0),
|
||||
IsHitTestVisible = false,
|
||||
[!CheckBox.IsCheckedProperty] = new Binding(nameof(FirstFilterIsDefault))
|
||||
}
|
||||
});
|
||||
QuickFilterMenuItems.Add(new MenuItem { Header = "_Edit quick filters...", Command = ReactiveCommand.Create(EditQuickFiltersAsync) });
|
||||
QuickFilterMenuItems.Add(new Separator());
|
||||
}
|
||||
|
||||
public void AddQuickFilterBtn() => QuickFilters.Add(FilterString);
|
||||
public async Task FilterBtn() => await PerformFilter(FilterString);
|
||||
public async Task FilterHelpBtn() => await new LibationAvalonia.Dialogs.SearchSyntaxDialog().ShowDialog(MainWindow);
|
||||
public void ToggleFirstFilterIsDefault() => FirstFilterIsDefault = !FirstFilterIsDefault;
|
||||
public async Task EditQuickFiltersAsync() => await new LibationAvalonia.Dialogs.EditQuickFilters().ShowDialog(MainWindow);
|
||||
public async Task PerformFilter(string filterString)
|
||||
{
|
||||
FilterString = filterString;
|
||||
|
||||
try
|
||||
{
|
||||
await ProductsDisplay.Filter(filterString);
|
||||
lastGoodFilter = filterString;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFiltersMenu(object _ = null, object __ = null)
|
||||
{
|
||||
//Clear all filters
|
||||
var quickFilterNativeMenu = (NativeMenuItem)NativeMenu.GetMenu(MainWindow).Items[3];
|
||||
for (int i = quickFilterNativeMenu.Menu.Items.Count - 1; i >= 3; i--)
|
||||
{
|
||||
var command = ((NativeMenuItem)quickFilterNativeMenu.Menu.Items[i]).Command as IDisposable;
|
||||
if (command != null)
|
||||
{
|
||||
var existingBinding = MainWindow.KeyBindings.FirstOrDefault(kb => kb.Command == command);
|
||||
if (existingBinding != null)
|
||||
MainWindow.KeyBindings.Remove(existingBinding);
|
||||
|
||||
command.Dispose();
|
||||
}
|
||||
|
||||
quickFilterNativeMenu.Menu.Items.RemoveAt(i);
|
||||
QuickFilterMenuItems.RemoveAt(i);
|
||||
}
|
||||
|
||||
// re-populate
|
||||
var index = 0;
|
||||
foreach (var filter in QuickFilters.Filters)
|
||||
{
|
||||
var command = ReactiveCommand.Create(async () => await PerformFilter(filter));
|
||||
|
||||
var menuItem = new MenuItem { Header = $"{++index}: {filter}", Command = command };
|
||||
var nativeMenuItem = new NativeMenuItem { Header = $"{index}: {filter}", Command = command };
|
||||
|
||||
if (Configuration.IsMacOs && index <= 10)
|
||||
{
|
||||
//Register hotkeys Command + 1 - 0 for quick filters
|
||||
var key = index == 10 ? Key.D0 : Key.D0 + index;
|
||||
nativeMenuItem.Gesture = new KeyGesture(key, KeyModifiers.Meta);
|
||||
}
|
||||
else if (!Configuration.IsMacOs && index <= 12)
|
||||
{
|
||||
//Register hotkeys F1 - F12 for quick filters
|
||||
menuItem.InputGesture = new KeyGesture(Key.F1 + index - 1);
|
||||
MainWindow.KeyBindings.Add(new KeyBinding { Command = command, Gesture = menuItem.InputGesture });
|
||||
}
|
||||
|
||||
QuickFilterMenuItems.Add(menuItem);
|
||||
quickFilterNativeMenu.Menu.Items.Add(nativeMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
254
Source/LibationAvalonia/ViewModels/MainVM.Import.cs
Normal file
254
Source/LibationAvalonia/ViewModels/MainVM.Import.cs
Normal file
@ -0,0 +1,254 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainVM
|
||||
{
|
||||
private bool _autoScanChecked = Configuration.Instance.AutoScan;
|
||||
private string _removeBooksButtonText = "Remove # Books from Libation";
|
||||
private bool _removeBooksButtonEnabled = Design.IsDesignMode;
|
||||
private bool _removeButtonsVisible = Design.IsDesignMode;
|
||||
private int _numAccountsScanning = 2;
|
||||
private int _accountsCount = 0;
|
||||
|
||||
/// <summary> Auto scanning accounts is enables </summary>
|
||||
public bool AutoScanChecked { get => _autoScanChecked; set => Configuration.Instance.AutoScan = this.RaiseAndSetIfChanged(ref _autoScanChecked, value); }
|
||||
/// <summary> Display text for the "Remove # Books from Libation" button </summary>
|
||||
public string RemoveBooksButtonText { get => _removeBooksButtonText; set => this.RaiseAndSetIfChanged(ref _removeBooksButtonText, value); }
|
||||
/// <summary> Indicates if the "Remove # Books from Libation" button is enabled </summary>
|
||||
public bool RemoveBooksButtonEnabled { get => _removeBooksButtonEnabled; set { this.RaiseAndSetIfChanged(ref _removeBooksButtonEnabled, value); } }
|
||||
/// <summary> Indicates if the "Remove # Books from Libation" and "Done Removing" buttons should be visible </summary>
|
||||
public bool RemoveButtonsVisible
|
||||
{
|
||||
get => _removeButtonsVisible;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _removeButtonsVisible, value);
|
||||
this.RaisePropertyChanged(nameof(RemoveMenuItemsEnabled));
|
||||
}
|
||||
}
|
||||
/// <summary> Indicates if Libation is currently scanning account(s) </summary>
|
||||
public bool ActivelyScanning => _numAccountsScanning > 0;
|
||||
/// <summary> Indicates if the "Remove Books" menu items are enabled</summary>
|
||||
public bool RemoveMenuItemsEnabled => !RemoveButtonsVisible && !ActivelyScanning;
|
||||
/// <summary> The library scanning status text </summary>
|
||||
public string ScanningText => _numAccountsScanning == 1 ? "Scanning..." : $"Scanning {_numAccountsScanning} accounts...";
|
||||
/// <summary> There is at least one Audible account </summary>
|
||||
public bool AnyAccounts => AccountsCount > 0;
|
||||
/// <summary> There is exactly one Audible account </summary>
|
||||
public bool OneAccount => AccountsCount == 1;
|
||||
/// <summary> There are more than 1 Audible accounts </summary>
|
||||
public bool MultipleAccounts => AccountsCount > 1;
|
||||
/// <summary> The number of accounts added to Libation </summary>
|
||||
public int AccountsCount
|
||||
{
|
||||
get => _accountsCount;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _accountsCount, value);
|
||||
this.RaisePropertyChanged(nameof(AnyAccounts));
|
||||
this.RaisePropertyChanged(nameof(OneAccount));
|
||||
this.RaisePropertyChanged(nameof(MultipleAccounts));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Configure_Import()
|
||||
{
|
||||
MainWindow.Loaded += (_, _) =>
|
||||
{
|
||||
refreshImportMenu();
|
||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
||||
};
|
||||
|
||||
AutoScanChecked = Configuration.Instance.AutoScan;
|
||||
|
||||
setyNumScanningAccounts(0);
|
||||
LibraryCommands.ScanBegin += (_, accountsLength) => setyNumScanningAccounts(accountsLength);
|
||||
LibraryCommands.ScanEnd += (_, newCount) => setyNumScanningAccounts(0);
|
||||
|
||||
if (!Design.IsDesignMode)
|
||||
RemoveButtonsVisible = false;
|
||||
}
|
||||
|
||||
public void ToggleAutoScan() => AutoScanChecked = !AutoScanChecked;
|
||||
|
||||
public async Task AddAccountsAsync()
|
||||
{
|
||||
await MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
|
||||
await new LibationAvalonia.Dialogs.AccountsDialog().ShowDialog(MainWindow);
|
||||
}
|
||||
|
||||
public async Task ScanAccountAsync()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
await scanLibrariesAsync(persister.AccountsSettings.GetAll().FirstOrDefault());
|
||||
}
|
||||
|
||||
public async Task ScanAllAccountsAsync()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
await scanLibrariesAsync(persister.AccountsSettings.GetAll().ToArray());
|
||||
}
|
||||
|
||||
public async Task ScanSomeAccountsAsync()
|
||||
{
|
||||
var scanAccountsDialog = new LibationAvalonia.Dialogs.ScanAccountsDialog();
|
||||
|
||||
if (await scanAccountsDialog.ShowDialog<DialogResult>(MainWindow) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||
}
|
||||
|
||||
public async Task RemoveBooksAsync()
|
||||
{
|
||||
// if 0 accounts, this will not be visible
|
||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.GetAll();
|
||||
|
||||
if (accounts.Count != 1)
|
||||
return;
|
||||
|
||||
var firstAccount = accounts.Single();
|
||||
await scanLibrariesRemovedBooks(firstAccount);
|
||||
}
|
||||
|
||||
// selectively remove books from all accounts
|
||||
public async Task RemoveBooksAllAsync()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
await scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||
}
|
||||
|
||||
public async Task RemoveBooksBtn()
|
||||
{
|
||||
RemoveBooksButtonEnabled = false;
|
||||
await ProductsDisplay.RemoveCheckedBooksAsync();
|
||||
RemoveBooksButtonEnabled = true;
|
||||
}
|
||||
|
||||
public async Task DoneRemovingBtn()
|
||||
{
|
||||
RemoveButtonsVisible = false;
|
||||
|
||||
ProductsDisplay.DoneRemovingBooks();
|
||||
|
||||
//Restore the filter
|
||||
await PerformFilter(lastGoodFilter);
|
||||
}
|
||||
|
||||
// selectively remove books from some accounts
|
||||
public async Task RemoveBooksSomeAsync()
|
||||
{
|
||||
var scanAccountsDialog = new LibationAvalonia.Dialogs.ScanAccountsDialog();
|
||||
|
||||
if (await scanAccountsDialog.ShowDialog<DialogResult>(MainWindow) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
await scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||
}
|
||||
|
||||
public async Task LocateAudiobooksAsync()
|
||||
{
|
||||
var locateDialog = new LibationAvalonia.Dialogs.LocateAudiobooksDialog();
|
||||
await locateDialog.ShowDialog(MainWindow);
|
||||
}
|
||||
|
||||
private void setyNumScanningAccounts(int numScanning)
|
||||
{
|
||||
_numAccountsScanning = numScanning;
|
||||
this.RaisePropertyChanged(nameof(ActivelyScanning));
|
||||
this.RaisePropertyChanged(nameof(RemoveMenuItemsEnabled));
|
||||
this.RaisePropertyChanged(nameof(ScanningText));
|
||||
}
|
||||
|
||||
private async Task scanLibrariesRemovedBooks(params Account[] accounts)
|
||||
{
|
||||
//This action is meant to operate on the entire library.
|
||||
//For removing books within a filter set, use
|
||||
//Visible Books > Remove from library
|
||||
|
||||
await ProductsDisplay.Filter(null);
|
||||
|
||||
RemoveBooksButtonEnabled = true;
|
||||
RemoveButtonsVisible = true;
|
||||
|
||||
await ProductsDisplay.ScanAndRemoveBooksAsync(accounts);
|
||||
}
|
||||
|
||||
private async Task scanLibrariesAsync(params Account[] accounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(LibationAvalonia.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}");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Serilog.Log.Information("Audible login attempt cancelled by user");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageBox.ShowAdminAlert(
|
||||
MainWindow,
|
||||
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
|
||||
"Error importing library",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshImportMenu(object _ = null, EventArgs __ = null)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
AccountsCount = persister.AccountsSettings.Accounts.Count;
|
||||
|
||||
var importMenuItem = (NativeMenuItem)NativeMenu.GetMenu(MainWindow).Items[0];
|
||||
|
||||
for (int i = importMenuItem.Menu.Items.Count - 1; i >= 2; i--)
|
||||
importMenuItem.Menu.Items.RemoveAt(i);
|
||||
|
||||
if (AccountsCount < 1)
|
||||
{
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "No accounts yet. Add Account...", Command = ReactiveCommand.Create(AddAccountsAsync) });
|
||||
}
|
||||
else if (AccountsCount == 1)
|
||||
{
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Scan Library", Command = ReactiveCommand.Create(ScanAccountAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Alt | KeyModifiers.Meta)});
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItemSeparator());
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Remove Library Books", Command = ReactiveCommand.Create(RemoveBooksAsync), Gesture = new KeyGesture(Key.R, KeyModifiers.Alt | KeyModifiers.Meta)});
|
||||
}
|
||||
else
|
||||
{
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Scan Library of All Accounts", Command = ReactiveCommand.Create(ScanAllAccountsAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Alt | KeyModifiers.Meta)});
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Scan Library of Some Accounts", Command = ReactiveCommand.Create(ScanSomeAccountsAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Alt | KeyModifiers.Meta | KeyModifiers.Shift) });
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItemSeparator());
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Remove Books from All Accounts", Command = ReactiveCommand.Create(RemoveBooksAllAsync), Gesture = new KeyGesture(Key.R, KeyModifiers.Alt | KeyModifiers.Meta)});
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Remove Books from Some Accounts", Command = ReactiveCommand.Create(RemoveBooksSomeAsync), Gesture = new KeyGesture(Key.R, KeyModifiers.Alt | KeyModifiers.Meta | KeyModifiers.Shift) });
|
||||
}
|
||||
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItemSeparator());
|
||||
importMenuItem.Menu.Items.Add(new NativeMenuItem { Header = "Locate Audiobooks...", Command = ReactiveCommand.Create(LocateAudiobooksAsync) });
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Source/LibationAvalonia/ViewModels/MainVM.Liberate.cs
Normal file
64
Source/LibationAvalonia/ViewModels/MainVM.Liberate.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using ApplicationServices;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
public void Configure_Liberate() { }
|
||||
|
||||
public void BackupAllBooks()
|
||||
{
|
||||
try
|
||||
{
|
||||
setQueueCollapseState(false);
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backing up all library books");
|
||||
|
||||
ProcessQueue.AddDownloadDecrypt(
|
||||
DbContexts
|
||||
.GetLibrary_Flat_NoTracking()
|
||||
.UnLiberated()
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error occurred while backing up all library books");
|
||||
}
|
||||
}
|
||||
|
||||
public void BackupAllPdfs()
|
||||
{
|
||||
setQueueCollapseState(false);
|
||||
ProcessQueue.AddDownloadPdf(DbContexts.GetLibrary_Flat_NoTracking().Where(lb => lb.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated));
|
||||
}
|
||||
|
||||
public async Task ConvertAllToMp3Async()
|
||||
{
|
||||
var result = await MessageBox.Show(MainWindow,
|
||||
"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?"
|
||||
+ "\r\n\r\n(To always download titles as mp3 instead of m4b, go to Settings: Download my books as .MP3 files)",
|
||||
"Convert all M4b => Mp3?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
setQueueCollapseState(false);
|
||||
ProcessQueue.AddConvertMp3(DbContexts.GetLibrary_Flat_NoTracking().Where(lb => lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated && lb.Book.ContentType is ContentType.Product));
|
||||
}
|
||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||
}
|
||||
|
||||
private void setQueueCollapseState(bool collapsed)
|
||||
{
|
||||
QueueOpen = !collapsed;
|
||||
Configuration.Instance.SetNonString(!collapsed, nameof(QueueOpen));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,52 @@
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.GridView;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationUiBase.GridView;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainWindow
|
||||
partial class MainVM
|
||||
{
|
||||
private void Configure_ProcessQueue()
|
||||
private bool _queueOpen = false;
|
||||
|
||||
/// <summary> The Process Queue panel is open </summary>
|
||||
public bool QueueOpen
|
||||
{
|
||||
var collapseState = !Configuration.Instance.GetNonString(defaultValue: true, nameof(_viewModel.QueueOpen));
|
||||
SetQueueCollapseState(collapseState);
|
||||
get => _queueOpen;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _queueOpen, value);
|
||||
QueueButtonAngle = value ? 180 : 0;
|
||||
this.RaisePropertyChanged(nameof(QueueButtonAngle));
|
||||
}
|
||||
}
|
||||
|
||||
public async void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook)
|
||||
public double QueueButtonAngle { get; private set; }
|
||||
|
||||
private void Configure_ProcessQueue()
|
||||
{
|
||||
var collapseState = !Configuration.Instance.GetNonString(defaultValue: true, nameof(QueueOpen));
|
||||
setQueueCollapseState(collapseState);
|
||||
}
|
||||
|
||||
public async void LiberateClicked(LibraryBook libraryBook)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", libraryBook);
|
||||
SetQueueCollapseState(false);
|
||||
_viewModel.ProcessQueue.AddDownloadDecrypt(libraryBook);
|
||||
setQueueCollapseState(false);
|
||||
ProcessQueue.AddDownloadDecrypt(libraryBook);
|
||||
}
|
||||
else if (libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
|
||||
SetQueueCollapseState(false);
|
||||
_viewModel.ProcessQueue.AddDownloadPdf(libraryBook);
|
||||
setQueueCollapseState(false);
|
||||
ProcessQueue.AddDownloadPdf(libraryBook);
|
||||
}
|
||||
else if (libraryBook.Book.Audio_Exists())
|
||||
{
|
||||
@ -49,15 +66,15 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
}
|
||||
|
||||
public void ProductsDisplay_LiberateSeriesClicked(object sender, ISeriesEntry series)
|
||||
public void LiberateSeriesClicked(ISeriesEntry series)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
setQueueCollapseState(false);
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backing up all {series} episodes", series.LibraryBook);
|
||||
|
||||
_viewModel.ProcessQueue.AddDownloadDecrypt(series.Children.Select(c => c.LibraryBook).UnLiberated());
|
||||
ProcessQueue.AddDownloadDecrypt(series.Children.Select(c => c.LibraryBook).UnLiberated());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -65,15 +82,15 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
}
|
||||
|
||||
public void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook)
|
||||
public void ConvertToMp3Clicked(LibraryBook libraryBook)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
|
||||
SetQueueCollapseState(false);
|
||||
_viewModel.ProcessQueue.AddConvertMp3(libraryBook);
|
||||
Serilog.Log.Logger.Information("Begin convert to mp3 {libraryBook}", libraryBook);
|
||||
setQueueCollapseState(false);
|
||||
ProcessQueue.AddConvertMp3(libraryBook);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -81,15 +98,7 @@ namespace LibationAvalonia.Views
|
||||
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
|
||||
}
|
||||
}
|
||||
private void SetQueueCollapseState(bool collapsed)
|
||||
{
|
||||
_viewModel.QueueOpen = !collapsed;
|
||||
Configuration.Instance.SetNonString(!collapsed, nameof(_viewModel.QueueOpen));
|
||||
}
|
||||
|
||||
public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(_viewModel.QueueOpen);
|
||||
}
|
||||
public void ToggleQueueHideBtn() => setQueueCollapseState(QueueOpen);
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,19 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainWindow
|
||||
partial class MainVM
|
||||
{
|
||||
private InterruptableTimer autoScanTimer;
|
||||
private readonly InterruptableTimer autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
|
||||
|
||||
private void Configure_ScanAuto()
|
||||
{
|
||||
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
||||
autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
|
||||
|
||||
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
||||
autoScanTimer.Elapsed += async (_, __) =>
|
||||
{
|
||||
@ -30,7 +26,7 @@ namespace LibationAvalonia.Views
|
||||
// in autoScan, new books SHALL NOT show dialog
|
||||
try
|
||||
{
|
||||
await LibraryCommands.ImportAccountAsync(Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts);
|
||||
await LibraryCommands.ImportAccountAsync(LibationAvalonia.Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@ -42,10 +38,8 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
};
|
||||
|
||||
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
||||
|
||||
// if enabled: begin on load
|
||||
Opened += startAutoScan;
|
||||
MainWindow.Loaded += startAutoScan;
|
||||
|
||||
// if new 'default' account is added, run autoscan
|
||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||
@ -55,6 +49,7 @@ namespace LibationAvalonia.Views
|
||||
Configuration.Instance.PropertyChanged += startAutoScan;
|
||||
}
|
||||
|
||||
|
||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||
private List<(string AccountId, string LocaleName)> getDefaultAccounts()
|
||||
{
|
||||
@ -65,32 +60,24 @@ namespace LibationAvalonia.Views
|
||||
.Select(a => (a.AccountId, a.Locale.Name))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void accountsPreSave(object sender = null, EventArgs e = null)
|
||||
=> preSaveDefaultAccounts = getDefaultAccounts();
|
||||
|
||||
private void accountsPostSave(object sender = null, EventArgs e = null)
|
||||
{
|
||||
var postSaveDefaultAccounts = getDefaultAccounts();
|
||||
var newDefaultAccounts = postSaveDefaultAccounts.Except(preSaveDefaultAccounts).ToList();
|
||||
|
||||
if (newDefaultAccounts.Any())
|
||||
if (getDefaultAccounts().Except(preSaveDefaultAccounts).Any())
|
||||
startAutoScan();
|
||||
}
|
||||
|
||||
[PropertyChangeFilter(nameof(Configuration.AutoScan))]
|
||||
private void startAutoScan(object sender = null, EventArgs e = null)
|
||||
{
|
||||
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
||||
if (_viewModel.AutoScanChecked)
|
||||
AutoScanChecked = Configuration.Instance.AutoScan;
|
||||
if (AutoScanChecked)
|
||||
autoScanTimer.PerformNow();
|
||||
else
|
||||
autoScanTimer.Stop();
|
||||
}
|
||||
public void autoScanLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem mi && mi.Icon is CheckBox checkBox)
|
||||
{
|
||||
checkBox.IsChecked = !(checkBox.IsChecked ?? false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Source/LibationAvalonia/ViewModels/MainVM.Settings.cs
Normal file
42
Source/LibationAvalonia/ViewModels/MainVM.Settings.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private bool _menuBarVisible = !Configuration.IsMacOs;
|
||||
public bool MenuBarVisible { get => _menuBarVisible; set => this.RaiseAndSetIfChanged(ref _menuBarVisible, value); }
|
||||
private void Configure_Settings()
|
||||
{
|
||||
((NativeMenuItem)NativeMenu.GetMenu(App.Current).Items[0]).Command = ReactiveCommand.Create(ShowAboutAsync);
|
||||
}
|
||||
|
||||
public Task ShowAboutAsync() => new LibationAvalonia.Dialogs.AboutDialog().ShowDialog(MainWindow);
|
||||
public Task ShowAccountsAsync() => new LibationAvalonia.Dialogs.AccountsDialog().ShowDialog(MainWindow);
|
||||
public Task ShowSettingsAsync() => new LibationAvalonia.Dialogs.SettingsDialog().ShowDialog(MainWindow);
|
||||
public Task ShowTrashBinAsync() => new LibationAvalonia.Dialogs.TrashBinDialog().ShowDialog(MainWindow);
|
||||
|
||||
public void LaunchHangover()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : ""));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Failed to launch Hangover");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartWalkthroughAsync()
|
||||
{
|
||||
MenuBarVisible = true;
|
||||
await new Walkthrough(MainWindow).RunAsync();
|
||||
MenuBarVisible = !Configuration.IsMacOs;
|
||||
}
|
||||
}
|
||||
}
|
||||
203
Source/LibationAvalonia/ViewModels/MainVM.VisibleBooks.cs
Normal file
203
Source/LibationAvalonia/ViewModels/MainVM.VisibleBooks.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using ApplicationServices;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
using Avalonia.Threading;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private int _visibleNotLiberated = 1;
|
||||
private int _visibleCount = 1;
|
||||
|
||||
/// <summary> The Bottom-right visible book count status text </summary>
|
||||
public string VisibleCountText => $"Visible: {_visibleCount}";
|
||||
/// <summary> The Visible Books menu item header text </summary>
|
||||
public string VisibleCountMenuItemText => menufyText($"Visible Books {_visibleCount}");
|
||||
/// <summary> Indicates if any of the books visible in the Products Display haven't been liberated </summary>
|
||||
public bool AnyVisibleNotLiberated => _visibleNotLiberated > 0;
|
||||
/// <summary> The "Liberate Visible Books" menu item header text (submenu item of the "Liberate Menu" menu item) </summary>
|
||||
public string LiberateVisibleToolStripText { get; private set; } = "Liberate _Visible Books: 0";
|
||||
/// <summary> The "Liberate" menu item header text (submenu item of the "Visible Books" menu item) </summary>
|
||||
public string LiberateVisibleToolStripText_2 { get; private set; } = menufyText("Liberate: 0");
|
||||
|
||||
private void Configure_VisibleBooks()
|
||||
{
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
|
||||
ProductsDisplay.VisibleCountChanged += ProductsDisplay_VisibleCountChanged;
|
||||
}
|
||||
|
||||
private void setVisibleCount(int visibleCount)
|
||||
{
|
||||
_visibleCount = visibleCount;
|
||||
this.RaisePropertyChanged(nameof(VisibleCountText));
|
||||
this.RaisePropertyChanged(nameof(VisibleCountMenuItemText));
|
||||
}
|
||||
|
||||
private void setVisibleNotLiberatedCount(int visibleNotLiberated)
|
||||
{
|
||||
_visibleNotLiberated = visibleNotLiberated;
|
||||
|
||||
LiberateVisibleToolStripText
|
||||
= AnyVisibleNotLiberated
|
||||
? "Liberate " + menufyText($"Visible Books: {visibleNotLiberated}")
|
||||
: "All visible books are liberated";
|
||||
|
||||
LiberateVisibleToolStripText_2
|
||||
= AnyVisibleNotLiberated
|
||||
? menufyText($"Liberate: {visibleNotLiberated}")
|
||||
: "All visible books are liberated";
|
||||
|
||||
this.RaisePropertyChanged(nameof(AnyVisibleNotLiberated));
|
||||
this.RaisePropertyChanged(nameof(LiberateVisibleToolStripText));
|
||||
this.RaisePropertyChanged(nameof(LiberateVisibleToolStripText_2));
|
||||
}
|
||||
|
||||
public async void ProductsDisplay_VisibleCountChanged(object sender, int qty)
|
||||
{
|
||||
setVisibleCount(qty);
|
||||
await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem);
|
||||
}
|
||||
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, object __)
|
||||
=> await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem);
|
||||
|
||||
|
||||
public void LiberateVisible()
|
||||
{
|
||||
try
|
||||
{
|
||||
setQueueCollapseState(false);
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backing up visible library books");
|
||||
|
||||
ProcessQueue.AddDownloadDecrypt(
|
||||
ProductsDisplay
|
||||
.GetVisibleBookEntries()
|
||||
.UnLiberated()
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error occurred while backing up visible library books");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReplaceTagsAsync()
|
||||
{
|
||||
var dialog = new TagsBatchDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(MainWindow);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
MainWindow,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace tags in {0}?",
|
||||
"Replace tags?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateTags(dialog.NewTags);
|
||||
}
|
||||
|
||||
public async Task SetBookDownloadedAsync()
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchManualDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(MainWindow);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
MainWindow,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace book downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async Task SetPdfDownloadedAsync()
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchManualDialog(isPdf: true);
|
||||
var result = await dialog.ShowDialog<DialogResult>(MainWindow);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
MainWindow,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace PDF downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async Task SetDownloadedAutoAsync()
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchAutoDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(MainWindow);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(ProductsDisplay.GetVisibleBookEntries(), dialog.SetDownloaded, dialog.SetNotDownloaded);
|
||||
var count = await Task.Run(bulkSetStatus.Discover);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var confirmationResult = await MessageBox.Show(
|
||||
bulkSetStatus.AggregateMessage,
|
||||
"Replace downloaded status?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
bulkSetStatus.Execute();
|
||||
}
|
||||
|
||||
public async Task RemoveVisibleAsync()
|
||||
{
|
||||
var visibleLibraryBooks = ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
MainWindow,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?",
|
||||
MessageBoxDefaultButton.Button2);
|
||||
|
||||
if (confirmationResult is DialogResult.Yes)
|
||||
await visibleLibraryBooks.RemoveBooksAsync();
|
||||
}
|
||||
|
||||
private void setLiberatedVisibleMenuItem()
|
||||
{
|
||||
var libraryStats = LibraryCommands.GetCounts(ProductsDisplay.GetVisibleBookEntries());
|
||||
setVisibleNotLiberatedCount(libraryStats.PendingBooks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,9 @@
|
||||
using LibationUiBase;
|
||||
using System.IO;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainWindow
|
||||
partial class MainVM
|
||||
{
|
||||
private void Configure_NonUI()
|
||||
{
|
||||
39
Source/LibationAvalonia/ViewModels/MainVM.cs
Normal file
39
Source/LibationAvalonia/ViewModels/MainVM.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using ApplicationServices;
|
||||
using LibationAvalonia.Views;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainVM : ViewModelBase
|
||||
{
|
||||
public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel();
|
||||
public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel();
|
||||
|
||||
private double? _downloadProgress = null;
|
||||
public double? DownloadProgress { get => _downloadProgress; set => this.RaiseAndSetIfChanged(ref _downloadProgress, value); }
|
||||
|
||||
|
||||
private readonly MainWindow MainWindow;
|
||||
public MainVM(MainWindow mainWindow)
|
||||
{
|
||||
MainWindow = mainWindow;
|
||||
|
||||
ProductsDisplay.RemovableCountChanged += (_, removeCount) => RemoveBooksButtonText = removeCount == 1 ? "Remove 1 Book from Libation" : $"Remove {removeCount} Books from Libation";
|
||||
LibraryCommands.LibrarySizeChanged += async (_, _) => await ProductsDisplay.UpdateGridAsync(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
|
||||
Configure_NonUI();
|
||||
Configure_BackupCounts();
|
||||
Configure_Export();
|
||||
Configure_Filters();
|
||||
Configure_Import();
|
||||
Configure_Liberate();
|
||||
Configure_ProcessQueue();
|
||||
Configure_ScanAuto();
|
||||
Configure_Settings();
|
||||
Configure_VisibleBooks();
|
||||
}
|
||||
|
||||
private static string menufyText(string header) => Configuration.IsMacOs ? header : $"_{header}";
|
||||
}
|
||||
}
|
||||
@ -1,226 +0,0 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string _filterString;
|
||||
private string _removeBooksButtonText = "Remove # Books from Libation";
|
||||
private bool _removeBooksButtonEnabled = true;
|
||||
private bool _autoScanChecked = true;
|
||||
private bool _firstFilterIsDefault = true;
|
||||
private bool _removeButtonsVisible = true;
|
||||
private int _numAccountsScanning = 2;
|
||||
private int _accountsCount = 0;
|
||||
private bool _queueOpen = true;
|
||||
private int _visibleCount = 1;
|
||||
private LibraryCommands.LibraryStats _libraryStats;
|
||||
private int _visibleNotLiberated = 1;
|
||||
public bool IsMp3Supported => Configuration.IsLinux || Configuration.IsWindows;
|
||||
|
||||
/// <summary> The Process Queue's viewmodel </summary>
|
||||
public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel();
|
||||
public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel();
|
||||
|
||||
private double? _downloadProgress = null;
|
||||
public double? DownloadProgress { get => _downloadProgress; set => this.RaiseAndSetIfChanged(ref _downloadProgress, value); }
|
||||
|
||||
|
||||
/// <summary> Library filterting query </summary>
|
||||
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }
|
||||
|
||||
|
||||
/// <summary> Display text for the "Remove # Books from Libation" button </summary>
|
||||
public string RemoveBooksButtonText { get => _removeBooksButtonText; set => this.RaiseAndSetIfChanged(ref _removeBooksButtonText, value); }
|
||||
|
||||
|
||||
/// <summary> Indicates if the "Remove # Books from Libation" button is enabled </summary>
|
||||
public bool RemoveBooksButtonEnabled { get => _removeBooksButtonEnabled; set { this.RaiseAndSetIfChanged(ref _removeBooksButtonEnabled, value); } }
|
||||
|
||||
|
||||
/// <summary> Auto scanning accounts is enables </summary>
|
||||
public bool AutoScanChecked
|
||||
{
|
||||
get => _autoScanChecked;
|
||||
set
|
||||
{
|
||||
if (value != _autoScanChecked)
|
||||
Configuration.Instance.AutoScan = value;
|
||||
this.RaiseAndSetIfChanged(ref _autoScanChecked, value);
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<Control> QuickFilterMenuItems { get; } = new();
|
||||
|
||||
/// <summary> Indicates if the first quick filter is the default filter </summary>
|
||||
public bool FirstFilterIsDefault
|
||||
{
|
||||
get => _firstFilterIsDefault;
|
||||
set
|
||||
{
|
||||
if (value != _firstFilterIsDefault)
|
||||
QuickFilters.UseDefault = value;
|
||||
this.RaiseAndSetIfChanged(ref _firstFilterIsDefault, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Indicates if the "Remove # Books from Libation" and "Done Removing" buttons should be visible </summary>
|
||||
public bool RemoveButtonsVisible
|
||||
{
|
||||
get => _removeButtonsVisible;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _removeButtonsVisible, value);
|
||||
this.RaisePropertyChanged(nameof(RemoveMenuItemsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary> The number of accounts currently being scanned </summary>
|
||||
public int NumAccountsScanning
|
||||
{
|
||||
get => _numAccountsScanning;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _numAccountsScanning, value);
|
||||
this.RaisePropertyChanged(nameof(ActivelyScanning));
|
||||
this.RaisePropertyChanged(nameof(RemoveMenuItemsEnabled));
|
||||
this.RaisePropertyChanged(nameof(ScanningText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if Libation is currently scanning account(s) </summary>
|
||||
public bool ActivelyScanning => _numAccountsScanning > 0;
|
||||
/// <summary> Indicates if the "Remove Books" menu items are enabled</summary>
|
||||
public bool RemoveMenuItemsEnabled => !RemoveButtonsVisible && !ActivelyScanning;
|
||||
/// <summary> The library scanning status text </summary>
|
||||
public string ScanningText => _numAccountsScanning == 1 ? "Scanning..." : $"Scanning {_numAccountsScanning} accounts...";
|
||||
|
||||
|
||||
|
||||
/// <summary> The number of accounts added to Libation </summary>
|
||||
public int AccountsCount
|
||||
{
|
||||
get => _accountsCount;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _accountsCount, value);
|
||||
this.RaisePropertyChanged(nameof(ZeroAccounts));
|
||||
this.RaisePropertyChanged(nameof(AnyAccounts));
|
||||
this.RaisePropertyChanged(nameof(OneAccount));
|
||||
this.RaisePropertyChanged(nameof(MultipleAccounts));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> There are no Audible accounts </summary>
|
||||
public bool ZeroAccounts => _accountsCount == 0;
|
||||
/// <summary> There is at least one Audible account </summary>
|
||||
public bool AnyAccounts => _accountsCount > 0;
|
||||
/// <summary> There is exactly one Audible account </summary>
|
||||
public bool OneAccount => _accountsCount == 1;
|
||||
/// <summary> There are more than 1 Audible accounts </summary>
|
||||
public bool MultipleAccounts => _accountsCount > 1;
|
||||
|
||||
|
||||
|
||||
/// <summary> The Process Queue panel is open </summary>
|
||||
public bool QueueOpen
|
||||
{
|
||||
get => _queueOpen;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _queueOpen, value);
|
||||
QueueButtonAngle = value ? 180 : 0;
|
||||
this.RaisePropertyChanged(nameof(QueueButtonAngle));
|
||||
}
|
||||
}
|
||||
public double QueueButtonAngle { get; private set; }
|
||||
|
||||
|
||||
/// <summary> The number of books visible in the Product Display </summary>
|
||||
public int VisibleCount
|
||||
{
|
||||
get => _visibleCount;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _visibleCount, value);
|
||||
this.RaisePropertyChanged(nameof(VisibleCountText));
|
||||
this.RaisePropertyChanged(nameof(VisibleCountMenuItemText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> The Bottom-right visible book count status text </summary>
|
||||
public string VisibleCountText => $"Visible: {VisibleCount}";
|
||||
/// <summary> The Visible Books menu item header text </summary>
|
||||
public string VisibleCountMenuItemText => $"_Visible Books {VisibleCount}";
|
||||
|
||||
|
||||
|
||||
/// <summary> The user's library statistics </summary>
|
||||
public LibraryCommands.LibraryStats LibraryStats
|
||||
{
|
||||
get => _libraryStats;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _libraryStats, value);
|
||||
|
||||
BookBackupsToolStripText
|
||||
= LibraryStats.HasPendingBooks
|
||||
? $"Begin _Book and PDF Backups: {LibraryStats.PendingBooks} remaining"
|
||||
: "All books have been liberated";
|
||||
|
||||
PdfBackupsToolStripText
|
||||
= LibraryStats.pdfsNotDownloaded > 0
|
||||
? $"Begin _PDF Only Backups: {LibraryStats.pdfsNotDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
|
||||
this.RaisePropertyChanged(nameof(BookBackupsToolStripText));
|
||||
this.RaisePropertyChanged(nameof(PdfBackupsToolStripText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> The "Begin Book and PDF Backup" menu item header text </summary>
|
||||
public string BookBackupsToolStripText { get; private set; } = "Begin _Book and PDF Backups: 0";
|
||||
/// <summary> The "Begin PDF Only Backup" menu item header text </summary>
|
||||
public string PdfBackupsToolStripText { get; private set; } = "Begin _PDF Only Backups: 0";
|
||||
|
||||
|
||||
/// <summary> The number of books visible in the Products Display that have not yet been liberated </summary>
|
||||
public int VisibleNotLiberated
|
||||
{
|
||||
get => _visibleNotLiberated;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _visibleNotLiberated, value);
|
||||
|
||||
LiberateVisibleToolStripText
|
||||
= AnyVisibleNotLiberated
|
||||
? $"Liberate _Visible Books: {VisibleNotLiberated}"
|
||||
: "All visible books are liberated";
|
||||
|
||||
LiberateVisibleToolStripText_2
|
||||
= AnyVisibleNotLiberated
|
||||
? $"_Liberate: {VisibleNotLiberated}"
|
||||
: "All visible books are liberated";
|
||||
|
||||
this.RaisePropertyChanged(nameof(AnyVisibleNotLiberated));
|
||||
this.RaisePropertyChanged(nameof(LiberateVisibleToolStripText));
|
||||
this.RaisePropertyChanged(nameof(LiberateVisibleToolStripText_2));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if any of the books visible in the Products Display haven't been liberated </summary>
|
||||
public bool AnyVisibleNotLiberated => VisibleNotLiberated > 0;
|
||||
/// <summary> The "Liberate Visible Books" menu item header text (submenu item of the "Liberate Menu" menu item) </summary>
|
||||
public string LiberateVisibleToolStripText { get; private set; } = "Liberate _Visible Books: 0";
|
||||
/// <summary> The "Liberate" menu item header text (submenu item of the "Visible Books" menu item) </summary>
|
||||
public string LiberateVisibleToolStripText_2 { get; private set; } = "_Liberate: 0";
|
||||
}
|
||||
}
|
||||
@ -117,7 +117,7 @@ namespace LibationAvalonia.ViewModels
|
||||
}
|
||||
|
||||
//Create the filtered-in list before adding entries to avoid a refresh
|
||||
FilteredInGridEntries = QueryResults(geList, FilterString);
|
||||
FilteredInGridEntries = QueryResults(geList.Union(geList.OfType<ISeriesEntry>().SelectMany(s => s.Children)), FilterString);
|
||||
SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded));
|
||||
|
||||
//Add all children beneath their parent
|
||||
|
||||
@ -70,9 +70,9 @@ namespace LibationAvalonia.ViewModels
|
||||
return InternalCompare(parentA, geB);
|
||||
}
|
||||
|
||||
//both are children of the same series, always present in order of series index, ascending
|
||||
//both are children of the same series
|
||||
if (parentA == parentB)
|
||||
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (sortDirection is ListSortDirection.Ascending ? 1 : -1);
|
||||
return InternalCompare(geA, geB);
|
||||
|
||||
//a and b are children of different series.
|
||||
return InternalCompare(parentA, parentB);
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private Task updateCountsTask;
|
||||
private void Configure_BackupCounts()
|
||||
{
|
||||
Load += setBackupCounts;
|
||||
LibraryCommands.LibrarySizeChanged += setBackupCounts;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
}
|
||||
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
if (updateCountsTask?.IsCompleted is not false)
|
||||
updateCountsTask = Dispatcher.UIThread.InvokeAsync(() => _viewModel.LibraryStats = LibraryCommands.GetCounts());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
using Avalonia.Input;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
protected void Configure_Filter() { }
|
||||
|
||||
public async void filterHelpBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await (new LibationAvalonia.Dialogs.SearchSyntaxDialog()).ShowDialog(this);
|
||||
|
||||
public async void filterSearchTb_KeyPress(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
{
|
||||
await performFilter(_viewModel.FilterString);
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async void filterBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await performFilter(_viewModel.FilterString);
|
||||
|
||||
private string lastGoodFilter = "";
|
||||
private async Task performFilter(string filterString)
|
||||
{
|
||||
_viewModel.FilterString = filterString;
|
||||
|
||||
try
|
||||
{
|
||||
await _viewModel.ProductsDisplay.Filter(filterString);
|
||||
lastGoodFilter = filterString;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
using DataLayer;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Liberate() { }
|
||||
|
||||
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
|
||||
public void beginBookBackupsToolStripMenuItem_Click(object _ = null, Avalonia.Interactivity.RoutedEventArgs __ = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backing up all library books");
|
||||
|
||||
_viewModel.ProcessQueue.AddDownloadDecrypt(
|
||||
ApplicationServices.DbContexts
|
||||
.GetLibrary_Flat_NoTracking()
|
||||
.UnLiberated()
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error occurred while backing up all library books");
|
||||
}
|
||||
}
|
||||
|
||||
public async void beginPdfBackupsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => _viewModel.ProcessQueue.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
||||
}
|
||||
|
||||
public async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var result = await 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?"
|
||||
+ "\r\n\r\n(To always download titles as mp3 instead of m4b, go to Settings: Download my books as .MP3 files)",
|
||||
"Convert all M4b => Mp3?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => _viewModel.ProcessQueue.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||
.Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product)));
|
||||
}
|
||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using System.Linq;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_QuickFilters()
|
||||
{
|
||||
_viewModel.FirstFilterIsDefault = QuickFilters.UseDefault;
|
||||
Load += updateFiltersMenu;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
|
||||
//We need to be able to dynamically add and remove menu items from the Quick Filters menu.
|
||||
//To do that, we need quick filter's menu items source to be writable, which we can only
|
||||
//achieve by creating the list ourselves (instead of allowing Avalonia to create it from the xaml)
|
||||
|
||||
var startWithFilterMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Start Libation with 1st filter _Default",
|
||||
Icon = new CheckBox
|
||||
{
|
||||
BorderThickness = new Thickness(0),
|
||||
IsHitTestVisible = false,
|
||||
[!CheckBox.IsCheckedProperty] = new Binding(nameof(_viewModel.FirstFilterIsDefault))
|
||||
}
|
||||
};
|
||||
|
||||
var editFiltersMenuItem = new MenuItem { Header = "_Edit quick filters..." };
|
||||
|
||||
startWithFilterMenuItem.Click += firstFilterIsDefaultToolStripMenuItem_Click;
|
||||
editFiltersMenuItem.Click += editQuickFiltersToolStripMenuItem_Click;
|
||||
|
||||
_viewModel.QuickFilterMenuItems.Add(startWithFilterMenuItem);
|
||||
_viewModel.QuickFilterMenuItems.Add(editFiltersMenuItem);
|
||||
_viewModel.QuickFilterMenuItems.Add(new Separator());
|
||||
}
|
||||
|
||||
private async void QuickFiltersMenuItem_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
|
||||
{
|
||||
int keyNum = (int)e.Key - 34;
|
||||
|
||||
if (keyNum <=9 && keyNum >= 1)
|
||||
{
|
||||
var menuItem = _viewModel.QuickFilterMenuItems
|
||||
.OfType<MenuItem>()
|
||||
.FirstOrDefault(i => i.Header is string h && h.StartsWith($"_{keyNum}"));
|
||||
|
||||
if (menuItem is not null)
|
||||
{
|
||||
await performFilter(menuItem.Tag as string);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFiltersMenu(object _ = null, object __ = null)
|
||||
{
|
||||
//Clear all filters
|
||||
_viewModel.QuickFilterMenuItems.RemoveAll(_viewModel.QuickFilterMenuItems.Where(i => i.Tag is string).ToList());
|
||||
|
||||
// re-populate
|
||||
var index = 0;
|
||||
foreach (var filter in QuickFilters.Filters)
|
||||
{
|
||||
var quickFilterMenuItem = new MenuItem
|
||||
{
|
||||
Tag = filter,
|
||||
Header = $"_{++index}: {filter}"
|
||||
};
|
||||
quickFilterMenuItem.Click += async (_, __) => await performFilter(filter);
|
||||
_viewModel.QuickFilterMenuItems.Add(quickFilterMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.FirstFilterIsDefault = !_viewModel.FirstFilterIsDefault;
|
||||
}
|
||||
|
||||
private void addQuickFilterBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> QuickFilters.Add(_viewModel.FilterString);
|
||||
|
||||
private async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await new Dialogs.EditQuickFilters().ShowDialog(this);
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
using AudibleUtilities;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_RemoveBooks()
|
||||
{
|
||||
if (Avalonia.Controls.Design.IsDesignMode)
|
||||
return;
|
||||
|
||||
_viewModel.RemoveButtonsVisible = false;
|
||||
}
|
||||
|
||||
public async void openTrashBinToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var trash = new TrashBinDialog();
|
||||
await trash.ShowDialog(this);
|
||||
}
|
||||
|
||||
public void removeLibraryBooksToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
// if 0 accounts, this will not be visible
|
||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.GetAll();
|
||||
|
||||
if (accounts.Count != 1)
|
||||
return;
|
||||
|
||||
var firstAccount = accounts.Single();
|
||||
scanLibrariesRemovedBooks(firstAccount);
|
||||
}
|
||||
|
||||
// selectively remove books from all accounts
|
||||
public void removeAllAccountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||
}
|
||||
|
||||
// selectively remove books from some accounts
|
||||
public async void removeSomeAccountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var scanAccountsDialog = new Dialogs.ScanAccountsDialog();
|
||||
|
||||
if (await scanAccountsDialog.ShowDialog<DialogResult>(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||
}
|
||||
|
||||
private async void scanLibrariesRemovedBooks(params Account[] accounts)
|
||||
{
|
||||
//This action is meant to operate on the entire library.
|
||||
//For removing books within a filter set, use
|
||||
//Visible Books > Remove from library
|
||||
|
||||
await _viewModel.ProductsDisplay.Filter(null);
|
||||
|
||||
_viewModel.RemoveBooksButtonEnabled = true;
|
||||
_viewModel.RemoveButtonsVisible = true;
|
||||
|
||||
await _viewModel.ProductsDisplay.ScanAndRemoveBooksAsync(accounts);
|
||||
}
|
||||
|
||||
public async void removeBooksBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.RemoveBooksButtonEnabled = false;
|
||||
await _viewModel.ProductsDisplay.RemoveCheckedBooksAsync();
|
||||
_viewModel.RemoveBooksButtonEnabled = true;
|
||||
}
|
||||
|
||||
public async void doneRemovingBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.RemoveButtonsVisible = false;
|
||||
|
||||
_viewModel.ProductsDisplay.DoneRemovingBooks();
|
||||
|
||||
//Restore the filter
|
||||
await performFilter(lastGoodFilter);
|
||||
}
|
||||
|
||||
public void ProductsDisplay_RemovableCountChanged(object sender, int removeCount)
|
||||
{
|
||||
_viewModel.RemoveBooksButtonText = removeCount switch
|
||||
{
|
||||
1 => "Remove 1 Book from Libation",
|
||||
_ => $"Remove {removeCount} Books from Libation"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_ScanManual()
|
||||
{
|
||||
Load += refreshImportMenu;
|
||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
||||
}
|
||||
|
||||
private void refreshImportMenu(object _, EventArgs __)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
_viewModel.AccountsCount = persister.AccountsSettings.Accounts.Count;
|
||||
}
|
||||
|
||||
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");
|
||||
await new Dialogs.AccountsDialog().ShowDialog(this);
|
||||
}
|
||||
|
||||
public async void scanLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
|
||||
await scanLibrariesAsync(firstAccount);
|
||||
}
|
||||
|
||||
public async void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
await scanLibrariesAsync(allAccounts);
|
||||
}
|
||||
|
||||
public async void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var scanAccountsDialog = new Dialogs.ScanAccountsDialog();
|
||||
|
||||
if (await scanAccountsDialog.ShowDialog<DialogResult>(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts);
|
||||
}
|
||||
|
||||
private async Task scanLibrariesAsync(IEnumerable<Account> accounts) => await scanLibrariesAsync(accounts.ToArray());
|
||||
private async Task scanLibrariesAsync(params Account[] accounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
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}");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Serilog.Log.Information("Audible login attempt cancelled by user");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await 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",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void locateAudiobooksToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var locateDialog = new LocateAudiobooksDialog();
|
||||
await locateDialog.ShowDialog(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
using ApplicationServices;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_ScanNotification()
|
||||
{
|
||||
_viewModel.NumAccountsScanning = 0;
|
||||
LibraryCommands.ScanBegin += LibraryCommands_ScanBegin;
|
||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||
}
|
||||
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
|
||||
{
|
||||
_viewModel.NumAccountsScanning = accountsLength;
|
||||
}
|
||||
|
||||
private void LibraryCommands_ScanEnd(object sender, int newCount)
|
||||
{
|
||||
_viewModel.NumAccountsScanning = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Settings() { }
|
||||
|
||||
public async void accountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await new Dialogs.AccountsDialog().ShowDialog(this);
|
||||
|
||||
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($"Libation {AppScaffolding.LibationScaffolding.Variety}{Environment.NewLine}Version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
|
||||
|
||||
public async void tourToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await new Walkthrough(this).RunAsync();
|
||||
|
||||
public void launchHangoverToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : ""));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Failed to launch Hangover");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
using Avalonia.Threading;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationUiBase;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Upgrade()
|
||||
{
|
||||
setProgressVisible(false);
|
||||
#if !DEBUG
|
||||
async Task upgradeAvailable(UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = await new UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
|
||||
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.OK;
|
||||
}
|
||||
|
||||
var upgrader = new Upgrader();
|
||||
upgrader.DownloadProgress += async (_, e) => await Dispatcher.UIThread.InvokeAsync(() => _viewModel.DownloadProgress = e.ProgressPercentage);
|
||||
upgrader.DownloadBegin += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(true));
|
||||
upgrader.DownloadCompleted += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(false));
|
||||
|
||||
Opened += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void setProgressVisible(bool visible) => _viewModel.DownloadProgress = visible ? 0 : null;
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_VisibleBooks()
|
||||
{
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
|
||||
}
|
||||
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, object __)
|
||||
=> await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem);
|
||||
|
||||
public void liberateVisible(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backing up visible library books");
|
||||
|
||||
_viewModel.ProcessQueue.AddDownloadDecrypt(
|
||||
_viewModel
|
||||
.ProductsDisplay
|
||||
.GetVisibleBookEntries()
|
||||
.UnLiberated()
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error occurred while backing up visible library books");
|
||||
}
|
||||
}
|
||||
public async void replaceTagsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.TagsBatchDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace tags in {0}?",
|
||||
"Replace tags?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateTags(dialog.NewTags);
|
||||
}
|
||||
|
||||
public async void setBookDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchManualDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace book downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async void setPdfDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchManualDialog(isPdf: true);
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace PDF downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async void setDownloadedAutoToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchAutoDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(_viewModel.ProductsDisplay.GetVisibleBookEntries(), dialog.SetDownloaded, dialog.SetNotDownloaded);
|
||||
var count = await Task.Run(bulkSetStatus.Discover);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var confirmationResult = await MessageBox.Show(
|
||||
bulkSetStatus.AggregateMessage,
|
||||
"Replace downloaded status?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
bulkSetStatus.Execute();
|
||||
}
|
||||
|
||||
public async void removeToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?",
|
||||
MessageBoxDefaultButton.Button2);
|
||||
|
||||
if (confirmationResult is DialogResult.Yes)
|
||||
await visibleLibraryBooks.RemoveBooksAsync();
|
||||
}
|
||||
public async void ProductsDisplay_VisibleCountChanged(object sender, int qty)
|
||||
{
|
||||
_viewModel.VisibleCount = qty;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem);
|
||||
}
|
||||
void setLiberatedVisibleMenuItem()
|
||||
{
|
||||
var libraryStats = LibraryCommands.GetCounts(_viewModel.ProductsDisplay.GetVisibleBookEntries());
|
||||
_viewModel.VisibleNotLiberated = libraryStats.PendingBooks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,15 +9,77 @@
|
||||
mc:Ignorable="d" d:DesignWidth="1850" d:DesignHeight="700"
|
||||
x:Class="LibationAvalonia.Views.MainWindow"
|
||||
Title="Libation: Liberate your Library"
|
||||
x:DataType="vm:MainVM"
|
||||
Name="Form1"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="Import">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem
|
||||
Header="Auto Scan Library"
|
||||
Command="{CompiledBinding ToggleAutoScan}"
|
||||
IsChecked="{CompiledBinding AutoScanChecked}"
|
||||
ToggleType="CheckBox" />
|
||||
<NativeMenuItemSeparator />
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItem Header="Liberate">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Command="{CompiledBinding BackupAllBooks}" Header="{CompiledBinding BookBackupsToolStripText}" Gesture="{OnPlatform macOS='alt+⌘+B'}" />
|
||||
<NativeMenuItem Command="{CompiledBinding BackupAllPdfs}" Header="{CompiledBinding PdfBackupsToolStripText}" Gesture="{OnPlatform macOS='alt+⌘+P'}"/>
|
||||
<NativeMenuItem Command="{CompiledBinding ConvertAllToMp3Async}" Header="Convert all M4b to Mp3 [Long-running]..." />
|
||||
<NativeMenuItem Command="{CompiledBinding LiberateVisible}" Header="{CompiledBinding LiberateVisibleToolStripText}" IsEnabled="{CompiledBinding AnyVisibleNotLiberated}" />
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItem Header="Export">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Command="{CompiledBinding ExportLibraryAsync}" Header="Export Library" Gesture="{OnPlatform macOS='alt+⌘+X'}"/>
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItem Header="Quick Filters">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem
|
||||
Header="Start Libation with 1st filter Default"
|
||||
Command="{CompiledBinding ToggleFirstFilterIsDefault}"
|
||||
IsChecked="{CompiledBinding FirstFilterIsDefault}"
|
||||
ToggleType="CheckBox" />
|
||||
<NativeMenuItem Command="{CompiledBinding EditQuickFiltersAsync}" Header="Edit quick filters..." Gesture="{OnPlatform macOS='alt+⌘+Q'}" />
|
||||
<NativeMenuItemSeparator />
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItem Header="Visible Books">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Command="{CompiledBinding LiberateVisible}" Header="{CompiledBinding LiberateVisibleToolStripText_2}" IsEnabled="{CompiledBinding AnyVisibleNotLiberated}" Gesture="{OnPlatform macOS='alt+⌘+V'}" />
|
||||
<NativeMenuItem Command="{CompiledBinding ReplaceTagsAsync}" Header="Replace Tags..." />
|
||||
<NativeMenuItem Command="{CompiledBinding SetBookDownloadedAsync}" Header="Set book 'Downloaded' status manually..." />
|
||||
<NativeMenuItem Command="{CompiledBinding SetPdfDownloadedAsync}" Header="Set PDF 'Downloaded' status manually..." />
|
||||
<NativeMenuItem Command="{CompiledBinding SetDownloadedAutoAsync}" Header="Set 'Downloaded' status automatically..." />
|
||||
<NativeMenuItem Command="{CompiledBinding RemoveVisibleAsync}" Header="Remove from library..." />
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItem Header="Settings">
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Command="{CompiledBinding ShowAccountsAsync}" Header="Accounts..." Gesture="{OnPlatform macOS='⌘+.'}" />
|
||||
<NativeMenuItem Command="{CompiledBinding ShowSettingsAsync}" Header="Settings..." Gesture="{OnPlatform macOS='⌘+,'}" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Command="{CompiledBinding ShowTrashBinAsync}" Header="Trash Bin" Gesture="{OnPlatform macOS='alt+⌘+T'}" />
|
||||
<NativeMenuItem Command="{CompiledBinding LaunchHangover}" Header="Launch Hangover" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Command="{CompiledBinding StartWalkthroughAsync}" Header="Take a Guided Tour of Libation" />
|
||||
<NativeMenuItem Command="{CompiledBinding ShowAboutAsync}" Header="About..." />
|
||||
</NativeMenu>
|
||||
</NativeMenuItem>
|
||||
</NativeMenu>
|
||||
</NativeMenu.Menu>
|
||||
|
||||
<Border BorderBrush="{DynamicResource DataGridGridLinesBrush}" BorderThickness="2" Padding="10,0,10,10">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="1*,Auto">
|
||||
|
||||
<!-- Menu Strip -->
|
||||
<Menu Grid.Column="0" VerticalAlignment="Top">
|
||||
<Menu Grid.Column="0" VerticalAlignment="Top" IsVisible="{CompiledBinding MenuBarVisible}">
|
||||
<!-- Decrease height of menu strop -->
|
||||
|
||||
<Menu.Styles>
|
||||
@ -35,26 +97,27 @@
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem IsVisible="{Binding AnyAccounts}" Click="autoScanLibraryToolStripMenuItem_Click" Header="A_uto Scan Library">
|
||||
<MenuItem IsVisible="{CompiledBinding AnyAccounts}" Command="{CompiledBinding ToggleAutoScan}" Header="A_uto Scan Library">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox BorderThickness="0" IsChecked="{Binding AutoScanChecked, Mode=TwoWay}" IsHitTestVisible="False" />
|
||||
<CheckBox BorderThickness="0" IsChecked="{CompiledBinding AutoScanChecked, Mode=TwoWay}" IsHitTestVisible="False" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem IsVisible="{Binding !AnyAccounts}" Click="noAccountsYetAddAccountToolStripMenuItem_Click" Header="No accounts yet. A_dd Account..." />
|
||||
<MenuItem IsVisible="{CompiledBinding !AnyAccounts}" Command="{CompiledBinding AddAccountsAsync}" Header="No accounts yet. A_dd Account..." />
|
||||
|
||||
<!-- Scan Library -->
|
||||
<MenuItem IsVisible="{Binding OneAccount}" IsEnabled="{Binding !ActivelyScanning}" Name="scanLibraryToolStripMenuItem" Click="scanLibraryToolStripMenuItem_Click" Header="Scan _Library" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding !ActivelyScanning}" Name="scanLibraryOfAllAccountsToolStripMenuItem" Click="scanLibraryOfAllAccountsToolStripMenuItem_Click" Header="Scan Library of _All Accounts" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding !ActivelyScanning}" Click="scanLibraryOfSomeAccountsToolStripMenuItem_Click" Header="Scan Library of _Some Accounts" />
|
||||
<Separator />
|
||||
<MenuItem IsVisible="{CompiledBinding OneAccount}" IsEnabled="{CompiledBinding !ActivelyScanning}" Name="scanLibraryToolStripMenuItem" Command="{CompiledBinding ScanAccountAsync}" Header="Scan _Library" />
|
||||
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding !ActivelyScanning}" Name="scanLibraryOfAllAccountsToolStripMenuItem" Command="{CompiledBinding ScanAllAccountsAsync}" Header="Scan Library of _All Accounts" />
|
||||
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding !ActivelyScanning}" Command="{CompiledBinding ScanSomeAccountsAsync}" Header="Scan Library of _Some Accounts" />
|
||||
|
||||
<Separator IsVisible="{CompiledBinding AnyAccounts}" />
|
||||
|
||||
<!-- Remove Books -->
|
||||
<MenuItem IsVisible="{Binding OneAccount}" IsEnabled="{Binding RemoveMenuItemsEnabled}" Click="removeLibraryBooksToolStripMenuItem_Click" Header="_Remove Library Books" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding RemoveMenuItemsEnabled}" Click="removeAllAccountsToolStripMenuItem_Click" Header="_Remove Books from All Accounts" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding RemoveMenuItemsEnabled}" Click="removeSomeAccountsToolStripMenuItem_Click" Header="_Remove Books from Some Accounts" />
|
||||
<MenuItem IsVisible="{CompiledBinding OneAccount}" IsEnabled="{CompiledBinding RemoveMenuItemsEnabled}" Command="{CompiledBinding RemoveBooksAsync}" Header="_Remove Library Books" />
|
||||
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding RemoveMenuItemsEnabled}" Command="{CompiledBinding RemoveBooksAllAsync}" Header="_Remove Books from All Accounts" />
|
||||
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding RemoveMenuItemsEnabled}" Command="{CompiledBinding RemoveBooksSomeAsync}" Header="_Remove Books from Some Accounts" />
|
||||
|
||||
<Separator />
|
||||
<MenuItem Click="locateAudiobooksToolStripMenuItem_Click" Header="L_ocate Audiobooks" />
|
||||
<MenuItem Command="{CompiledBinding LocateAudiobooksAsync}" Header="L_ocate Audiobooks..." />
|
||||
|
||||
</MenuItem>
|
||||
|
||||
@ -67,10 +130,10 @@
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem Click="beginBookBackupsToolStripMenuItem_Click" Header="{Binding BookBackupsToolStripText}" />
|
||||
<MenuItem Click="beginPdfBackupsToolStripMenuItem_Click" Header="{Binding PdfBackupsToolStripText}" />
|
||||
<MenuItem Click="convertAllM4bToMp3ToolStripMenuItem_Click" Header="Convert all _M4b to Mp3 [Long-running]..." IsVisible="{Binding IsMp3Supported}" />
|
||||
<MenuItem Click="liberateVisible" Header="{Binding LiberateVisibleToolStripText}" IsEnabled="{Binding AnyVisibleNotLiberated}" />
|
||||
<MenuItem Command="{CompiledBinding BackupAllBooks}" Header="{CompiledBinding BookBackupsToolStripText}" />
|
||||
<MenuItem Command="{CompiledBinding BackupAllPdfs}" Header="{CompiledBinding PdfBackupsToolStripText}" />
|
||||
<MenuItem Command="{CompiledBinding ConvertAllToMp3Async}" Header="Convert all _M4b to Mp3 [Long-running]..." />
|
||||
<MenuItem Command="{CompiledBinding LiberateVisible}" Header="{CompiledBinding LiberateVisibleToolStripText}" IsEnabled="{CompiledBinding AnyVisibleNotLiberated}" />
|
||||
</MenuItem>
|
||||
|
||||
<!-- Export Menu -->
|
||||
@ -82,12 +145,12 @@
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem IsEnabled="{Binding LibraryStats.HasBookResults}" Click="exportLibraryToolStripMenuItem_Click" Header="E_xport Library" />
|
||||
<MenuItem IsEnabled="{CompiledBinding LibraryStats.HasBookResults}" Command="{CompiledBinding ExportLibraryAsync}" Header="E_xport Library" InputGesture="ctrl+S" />
|
||||
</MenuItem>
|
||||
|
||||
<!-- Quick Filters Menu -->
|
||||
|
||||
<MenuItem Name="quickFiltersToolStripMenuItem" Header="Quick _Filters" ItemsSource="{Binding QuickFilterMenuItems}" KeyDown="QuickFiltersMenuItem_KeyDown">
|
||||
<MenuItem Name="quickFiltersToolStripMenuItem" Header="Quick _Filters" ItemsSource="{CompiledBinding QuickFilterMenuItems}">
|
||||
<!-- Remove height style property for menu item -->
|
||||
<MenuItem.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
@ -98,19 +161,19 @@
|
||||
|
||||
<!-- Visible Books Menu -->
|
||||
|
||||
<MenuItem Header="{Binding VisibleCountMenuItemText}" >
|
||||
<MenuItem Header="{CompiledBinding VisibleCountMenuItemText}" >
|
||||
<!-- Remove height style property for menu item -->
|
||||
<MenuItem.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem Click="liberateVisible" Header="{Binding LiberateVisibleToolStripText_2}" IsEnabled="{Binding AnyVisibleNotLiberated}" />
|
||||
<MenuItem Click="replaceTagsToolStripMenuItem_Click" Header="Replace _Tags..." />
|
||||
<MenuItem Click="setBookDownloadedManualToolStripMenuItem_Click" Header="Set book '_Downloaded' status manually..." />
|
||||
<MenuItem Click="setPdfDownloadedManualToolStripMenuItem_Click" Header="Set _PDF 'Downloaded' status manually..." />
|
||||
<MenuItem Click="setDownloadedAutoToolStripMenuItem_Click" Header="Set '_Downloaded' status automatically..." />
|
||||
<MenuItem Click="removeToolStripMenuItem_Click" Header="_Remove from library..." />
|
||||
<MenuItem Command="{CompiledBinding LiberateVisible}" Header="{CompiledBinding LiberateVisibleToolStripText_2}" IsEnabled="{CompiledBinding AnyVisibleNotLiberated}" />
|
||||
<MenuItem Command="{CompiledBinding ReplaceTagsAsync}" Header="Replace _Tags..." />
|
||||
<MenuItem Command="{CompiledBinding SetBookDownloadedAsync}" Header="Set book '_Downloaded' status manually..." />
|
||||
<MenuItem Command="{CompiledBinding SetPdfDownloadedAsync}" Header="Set _PDF 'Downloaded' status manually..." />
|
||||
<MenuItem Command="{CompiledBinding SetDownloadedAutoAsync}" Header="Set '_Downloaded' status automatically..." />
|
||||
<MenuItem Command="{CompiledBinding RemoveVisibleAsync}" Header="_Remove from library..." />
|
||||
</MenuItem>
|
||||
|
||||
<!-- Settings Menu -->
|
||||
@ -122,19 +185,19 @@
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem Name="accountsToolStripMenuItem" Click="accountsToolStripMenuItem_Click" Header="_Accounts..." />
|
||||
<MenuItem Name="basicSettingsToolStripMenuItem" Click="basicSettingsToolStripMenuItem_Click" Header="_Settings..." />
|
||||
<MenuItem Name="accountsToolStripMenuItem" Command="{CompiledBinding ShowAccountsAsync}" Header="_Accounts..." InputGesture="ctrl+shift+A"/>
|
||||
<MenuItem Name="basicSettingsToolStripMenuItem" Command="{CompiledBinding ShowSettingsAsync}" Header="_Settings..." InputGesture="ctrl+P" />
|
||||
<Separator />
|
||||
<MenuItem Click="openTrashBinToolStripMenuItem_Click" Header="Trash Bin" />
|
||||
<MenuItem Click="launchHangoverToolStripMenuItem_Click" Header="Launch _Hangover" />
|
||||
<MenuItem Command="{CompiledBinding ShowTrashBinAsync}" Header="Trash Bin" />
|
||||
<MenuItem Command="{CompiledBinding LaunchHangover}" Header="Launch _Hangover" />
|
||||
<Separator />
|
||||
<MenuItem Click="tourToolStripMenuItem_Click" Header="Take a Guided _Tour of Libation" />
|
||||
<MenuItem Click="aboutToolStripMenuItem_Click" Header="A_bout..." />
|
||||
<MenuItem Command="{CompiledBinding StartWalkthroughAsync}" Header="Take a Guided _Tour of Libation" />
|
||||
<MenuItem Command="{CompiledBinding ShowAboutAsync}" Header="A_bout..." />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<StackPanel IsVisible="{Binding ActivelyScanning}" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<StackPanel IsVisible="{CompiledBinding ActivelyScanning}" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Path VerticalAlignment="Center" Fill="{StaticResource IconFill}" Data="{StaticResource ImportIcon}" />
|
||||
<TextBlock Margin="5,0,5,0" VerticalAlignment="Center" Text="{Binding ScanningText}"/>
|
||||
<TextBlock Margin="5,0,5,0" VerticalAlignment="Center" Text="{CompiledBinding ScanningText}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -155,23 +218,23 @@
|
||||
</Grid.Styles>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Button Name="filterHelpBtn" Margin="0" Click="filterHelpBtn_Click" Content="?"/>
|
||||
<Button Name="addQuickFilterBtn" Click="addQuickFilterBtn_Click" Content="Add To Quick Filters"/>
|
||||
<Button Name="filterHelpBtn" Margin="0" Command="{CompiledBinding FilterHelpBtn}" Content="?"/>
|
||||
<Button Name="addQuickFilterBtn" Command="{CompiledBinding AddQuickFilterBtn}" Content="Add To Quick Filters"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button IsVisible="{Binding RemoveButtonsVisible}" IsEnabled="{Binding RemoveBooksButtonEnabled}" Click="removeBooksBtn_Click" Content="{Binding RemoveBooksButtonText}"/>
|
||||
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Content="Done Removing Books"/>
|
||||
<Button IsVisible="{CompiledBinding RemoveButtonsVisible}" IsEnabled="{CompiledBinding RemoveBooksButtonEnabled}" Command="{CompiledBinding RemoveBooksBtn}" Content="{CompiledBinding RemoveBooksButtonText}"/>
|
||||
<Button IsVisible="{CompiledBinding RemoveButtonsVisible}" Command="{CompiledBinding DoneRemovingBtn}" Content="Done Removing Books"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{Binding !RemoveButtonsVisible}" Text="{Binding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{CompiledBinding !RemoveButtonsVisible}" Text="{CompiledBinding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
|
||||
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
||||
<Button Name="filterBtn" Click="filterBtn_Click" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
<Button Padding="2,6,2,6" VerticalAlignment="Stretch" Click="ToggleQueueHideBtn_Click">
|
||||
<Button Name="filterBtn" Command="{CompiledBinding FilterBtn}" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
<Button Padding="2,6,2,6" VerticalAlignment="Stretch" Command="{CompiledBinding ToggleQueueHideBtn}">
|
||||
<Path Stretch="Uniform" Fill="{DynamicResource IconFill}" Data="{StaticResource LeftArrows}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="{Binding QueueButtonAngle}"/>
|
||||
<RotateTransform Angle="{CompiledBinding QueueButtonAngle}"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Button>
|
||||
@ -179,17 +242,17 @@
|
||||
|
||||
</Grid>
|
||||
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{DynamicResource DataGridGridLinesBrush}">
|
||||
<SplitView IsPaneOpen="{Binding QueueOpen}" DisplayMode="Inline" OpenPaneLength="400" MinWidth="400" PanePlacement="Right">
|
||||
<SplitView IsPaneOpen="{CompiledBinding QueueOpen}" DisplayMode="Inline" OpenPaneLength="400" MinWidth="400" PanePlacement="Right">
|
||||
|
||||
<!-- Process Queue -->
|
||||
<SplitView.Pane>
|
||||
<views:ProcessQueueControl DataContext="{Binding ProcessQueue}"/>
|
||||
<views:ProcessQueueControl DataContext="{CompiledBinding ProcessQueue}"/>
|
||||
</SplitView.Pane>
|
||||
|
||||
<!-- Product Display Grid -->
|
||||
<views:ProductsDisplay
|
||||
Name="productsDisplay"
|
||||
DataContext="{Binding ProductsDisplay}"
|
||||
DataContext="{CompiledBinding ProductsDisplay}"
|
||||
LiberateClicked="ProductsDisplay_LiberateClicked"
|
||||
LiberateSeriesClicked="ProductsDisplay_LiberateSeriesClicked"
|
||||
ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" />
|
||||
@ -203,10 +266,10 @@
|
||||
<Setter Property="MinWidth" Value="100" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<TextBlock FontSize="14" Grid.Column="0" Text="Upgrading:" VerticalAlignment="Center" IsVisible="{Binding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<ProgressBar Grid.Column="1" Margin="5,0,10,0" VerticalAlignment="Stretch" Width="100" Value="{Binding DownloadProgress}" IsVisible="{Binding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<TextBlock FontSize="14" Grid.Column="2" Text="{Binding VisibleCountText}" VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14" Grid.Column="3" Text="{Binding LibraryStats.StatusString}" VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14" Grid.Column="0" Text="Upgrading:" VerticalAlignment="Center" IsVisible="{CompiledBinding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<ProgressBar Grid.Column="1" Margin="5,0,10,0" VerticalAlignment="Stretch" Width="100" Value="{CompiledBinding DownloadProgress}" IsVisible="{CompiledBinding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<TextBlock FontSize="14" Grid.Column="2" Text="{CompiledBinding VisibleCountText}" VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14" Grid.Column="3" Text="{CompiledBinding LibraryStats.StatusString}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@ -1,58 +1,41 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.ReactiveUI;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.GridView;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
||||
public partial class MainWindow : ReactiveWindow<MainVM>
|
||||
{
|
||||
public event EventHandler Load;
|
||||
public event EventHandler<List<LibraryBook>> LibraryLoaded;
|
||||
private readonly MainWindowViewModel _viewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.DataContext = _viewModel = new MainWindowViewModel();
|
||||
DataContext = new MainVM(this);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
// eg: if one of these init'd productsGrid, then another can't reliably subscribe to it
|
||||
Configure_BackupCounts();
|
||||
Configure_ScanAuto();
|
||||
Configure_ScanNotification();
|
||||
Configure_VisibleBooks();
|
||||
Configure_QuickFilters();
|
||||
Configure_ScanManual();
|
||||
Configure_RemoveBooks();
|
||||
Configure_Liberate();
|
||||
Configure_Export();
|
||||
Configure_Settings();
|
||||
Configure_ProcessQueue();
|
||||
Configure_Upgrade();
|
||||
Configure_Filter();
|
||||
// misc which belongs in winforms app but doesn't have a UI element
|
||||
Configure_NonUI();
|
||||
|
||||
_viewModel.ProductsDisplay.RemovableCountChanged += ProductsDisplay_RemovableCountChanged;
|
||||
_viewModel.ProductsDisplay.VisibleCountChanged += ProductsDisplay_VisibleCountChanged;
|
||||
|
||||
{
|
||||
this.LibraryLoaded += MainWindow_LibraryLoaded;
|
||||
|
||||
LibraryCommands.LibrarySizeChanged += async (_, _) => await _viewModel.ProductsDisplay.UpdateGridAsync(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
Loaded += MainWindow_Loaded;
|
||||
Closing += MainWindow_Closing;
|
||||
LibraryLoaded += MainWindow_LibraryLoaded;
|
||||
|
||||
Opened += MainWindow_Opened;
|
||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(selectAndFocusSearchBox), Gesture = new KeyGesture(Key.F, Configuration.IsMacOs ? KeyModifiers.Meta : KeyModifiers.Control) });
|
||||
|
||||
if (!Configuration.IsMacOs)
|
||||
{
|
||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ShowSettingsAsync), Gesture = new KeyGesture(Key.P, KeyModifiers.Control) });
|
||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ShowAccountsAsync), Gesture = new KeyGesture(Key.A, KeyModifiers.Control | KeyModifiers.Shift) });
|
||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ExportLibraryAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Control) });
|
||||
}
|
||||
}
|
||||
|
||||
private async void MainWindow_Opened(object sender, EventArgs e)
|
||||
private async void MainWindow_Loaded(object sender, EventArgs e)
|
||||
{
|
||||
if (Configuration.Instance.FirstLaunch)
|
||||
{
|
||||
@ -70,17 +53,60 @@ namespace LibationAvalonia.Views
|
||||
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
productsDisplay?.CloseImageDisplay();
|
||||
this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
private async void MainWindow_LibraryLoaded(object sender, List<LibraryBook> dbBooks)
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
await performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
|
||||
_viewModel.ProductsDisplay.BindToGrid(dbBooks);
|
||||
ViewModel.ProductsDisplay.BindToGrid(dbBooks);
|
||||
}
|
||||
|
||||
private void selectAndFocusSearchBox()
|
||||
{
|
||||
filterSearchTb.SelectAll();
|
||||
filterSearchTb.Focus();
|
||||
}
|
||||
|
||||
public void OnLoad() => Load?.Invoke(this, EventArgs.Empty);
|
||||
public void OnLibraryLoaded(List<LibraryBook> initialLibrary) => LibraryLoaded?.Invoke(this, initialLibrary);
|
||||
public void ProductsDisplay_LiberateClicked(object _, LibraryBook libraryBook) => ViewModel.LiberateClicked(libraryBook);
|
||||
public void ProductsDisplay_LiberateSeriesClicked(object _, ISeriesEntry series) => ViewModel.LiberateSeriesClicked(series);
|
||||
public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook);
|
||||
|
||||
public async void filterSearchTb_KeyPress(object _, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
{
|
||||
await ViewModel.PerformFilter(ViewModel.FilterString);
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Configure_Upgrade()
|
||||
{
|
||||
setProgressVisible(false);
|
||||
#if !DEBUG
|
||||
async System.Threading.Tasks.Task upgradeAvailable(LibationUiBase.UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = await new Dialogs.UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
|
||||
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.OK;
|
||||
}
|
||||
|
||||
var upgrader = new LibationUiBase.Upgrader();
|
||||
upgrader.DownloadProgress += async (_, e) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => ViewModel.DownloadProgress = e.ProgressPercentage);
|
||||
upgrader.DownloadBegin += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(true));
|
||||
upgrader.DownloadCompleted += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(false));
|
||||
|
||||
Opened += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void setProgressVisible(bool visible) => ViewModel.DownloadProgress = visible ? 0 : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,9 +11,6 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<views:DecimalConverter x:Key="myConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.Resources>
|
||||
<RecyclePool x:Key="RecyclePool" />
|
||||
<DataTemplate x:Key="queuedBook">
|
||||
<views:ProcessBookControl />
|
||||
|
||||
@ -149,7 +149,7 @@ namespace LibationAvalonia
|
||||
await displayControlAsync(MainForm.importToolStripMenuItem);
|
||||
await displayControlAsync(scanItem);
|
||||
|
||||
scanItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent));
|
||||
scanItem.Command.Execute(null);
|
||||
MainForm.importToolStripMenuItem.Close();
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
@ -193,7 +193,7 @@ namespace LibationAvalonia
|
||||
|
||||
await displayControlAsync(MainForm.filterBtn);
|
||||
|
||||
MainForm.filterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||
MainForm.filterBtn.Command.Execute(null);
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
@ -222,7 +222,7 @@ namespace LibationAvalonia
|
||||
|
||||
await Task.Delay(750);
|
||||
await displayControlAsync(MainForm.addQuickFilterBtn);
|
||||
MainForm.addQuickFilterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||
MainForm.addQuickFilterBtn.Command.Execute(null);
|
||||
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
||||
await displayControlAsync(editQuickFiltersToolStripMenuItem);
|
||||
|
||||
|
||||
329
Source/LibationWinForms/Dialogs/AboutDialog.Designer.cs
generated
Normal file
329
Source/LibationWinForms/Dialogs/AboutDialog.Designer.cs
generated
Normal file
@ -0,0 +1,329 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class AboutDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
pictureBox1 = new System.Windows.Forms.PictureBox();
|
||||
releaseNotesLbl = new System.Windows.Forms.LinkLabel();
|
||||
checkForUpgradeBtn = new System.Windows.Forms.Button();
|
||||
getLibationLbl = new System.Windows.Forms.LinkLabel();
|
||||
rmcrackanLbl = new System.Windows.Forms.LinkLabel();
|
||||
MBucariLbl = new System.Windows.Forms.LinkLabel();
|
||||
groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
label3 = new System.Windows.Forms.Label();
|
||||
label4 = new System.Windows.Forms.Label();
|
||||
label2 = new System.Windows.Forms.Label();
|
||||
label1 = new System.Windows.Forms.Label();
|
||||
flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
|
||||
linkLabel4 = new System.Windows.Forms.LinkLabel();
|
||||
linkLabel2 = new System.Windows.Forms.LinkLabel();
|
||||
linkLabel3 = new System.Windows.Forms.LinkLabel();
|
||||
linkLabel1 = new System.Windows.Forms.LinkLabel();
|
||||
linkLabel5 = new System.Windows.Forms.LinkLabel();
|
||||
linkLabel6 = new System.Windows.Forms.LinkLabel();
|
||||
((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
|
||||
groupBox1.SuspendLayout();
|
||||
flowLayoutPanel1.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// pictureBox1
|
||||
//
|
||||
pictureBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
pictureBox1.Image = Properties.Resources.cheers;
|
||||
pictureBox1.Location = new System.Drawing.Point(12, 91);
|
||||
pictureBox1.Name = "pictureBox1";
|
||||
pictureBox1.Size = new System.Drawing.Size(410, 210);
|
||||
pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
|
||||
pictureBox1.TabIndex = 0;
|
||||
pictureBox1.TabStop = false;
|
||||
//
|
||||
// releaseNotesLbl
|
||||
//
|
||||
releaseNotesLbl.AutoSize = true;
|
||||
releaseNotesLbl.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
releaseNotesLbl.Location = new System.Drawing.Point(12, 12);
|
||||
releaseNotesLbl.Name = "releaseNotesLbl";
|
||||
releaseNotesLbl.Size = new System.Drawing.Size(171, 20);
|
||||
releaseNotesLbl.TabIndex = 2;
|
||||
releaseNotesLbl.TabStop = true;
|
||||
releaseNotesLbl.Text = "Libation Classic v11.0.0.0";
|
||||
//
|
||||
// checkForUpgradeBtn
|
||||
//
|
||||
checkForUpgradeBtn.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
checkForUpgradeBtn.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
checkForUpgradeBtn.Location = new System.Drawing.Point(12, 54);
|
||||
checkForUpgradeBtn.Name = "checkForUpgradeBtn";
|
||||
checkForUpgradeBtn.Size = new System.Drawing.Size(410, 31);
|
||||
checkForUpgradeBtn.TabIndex = 3;
|
||||
checkForUpgradeBtn.Text = "Check for Upgrade";
|
||||
checkForUpgradeBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// getLibationLbl
|
||||
//
|
||||
getLibationLbl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||
getLibationLbl.AutoSize = true;
|
||||
getLibationLbl.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
getLibationLbl.Location = new System.Drawing.Point(245, 12);
|
||||
getLibationLbl.Name = "getLibationLbl";
|
||||
getLibationLbl.Size = new System.Drawing.Size(162, 20);
|
||||
getLibationLbl.TabIndex = 7;
|
||||
getLibationLbl.TabStop = true;
|
||||
getLibationLbl.Text = "https://getlibation.com";
|
||||
getLibationLbl.LinkClicked += getLibationLbl_LinkClicked;
|
||||
//
|
||||
// rmcrackanLbl
|
||||
//
|
||||
rmcrackanLbl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
rmcrackanLbl.AutoSize = true;
|
||||
rmcrackanLbl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
||||
rmcrackanLbl.Location = new System.Drawing.Point(6, 19);
|
||||
rmcrackanLbl.Name = "rmcrackanLbl";
|
||||
rmcrackanLbl.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
rmcrackanLbl.Size = new System.Drawing.Size(80, 25);
|
||||
rmcrackanLbl.TabIndex = 8;
|
||||
rmcrackanLbl.TabStop = true;
|
||||
rmcrackanLbl.Text = "rmcrackan";
|
||||
rmcrackanLbl.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// MBucariLbl
|
||||
//
|
||||
MBucariLbl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
MBucariLbl.AutoSize = true;
|
||||
MBucariLbl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
||||
MBucariLbl.Location = new System.Drawing.Point(6, 40);
|
||||
MBucariLbl.Name = "MBucariLbl";
|
||||
MBucariLbl.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
MBucariLbl.Size = new System.Drawing.Size(64, 25);
|
||||
MBucariLbl.TabIndex = 9;
|
||||
MBucariLbl.TabStop = true;
|
||||
MBucariLbl.Text = "Mbucari";
|
||||
MBucariLbl.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// groupBox1
|
||||
//
|
||||
groupBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
groupBox1.Controls.Add(label3);
|
||||
groupBox1.Controls.Add(label4);
|
||||
groupBox1.Controls.Add(label2);
|
||||
groupBox1.Controls.Add(label1);
|
||||
groupBox1.Controls.Add(flowLayoutPanel1);
|
||||
groupBox1.Controls.Add(MBucariLbl);
|
||||
groupBox1.Controls.Add(rmcrackanLbl);
|
||||
groupBox1.Location = new System.Drawing.Point(12, 307);
|
||||
groupBox1.Name = "groupBox1";
|
||||
groupBox1.Size = new System.Drawing.Size(410, 172);
|
||||
groupBox1.TabIndex = 10;
|
||||
groupBox1.TabStop = false;
|
||||
groupBox1.Text = "Acknowledgements";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.AutoSize = true;
|
||||
label3.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
label3.Location = new System.Drawing.Point(92, 43);
|
||||
label3.Name = "label3";
|
||||
label3.Padding = new System.Windows.Forms.Padding(0, 0, 0, 3);
|
||||
label3.Size = new System.Drawing.Size(71, 22);
|
||||
label3.TabIndex = 12;
|
||||
label3.Text = "Developer";
|
||||
//
|
||||
// label4
|
||||
//
|
||||
label4.AutoSize = true;
|
||||
label4.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
label4.Location = new System.Drawing.Point(92, 22);
|
||||
label4.Name = "label4";
|
||||
label4.Padding = new System.Windows.Forms.Padding(0, 0, 0, 3);
|
||||
label4.Size = new System.Drawing.Size(55, 22);
|
||||
label4.TabIndex = 12;
|
||||
label4.Text = "Creator";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.AutoSize = true;
|
||||
label2.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
label2.Location = new System.Drawing.Point(92, 22);
|
||||
label2.Name = "label2";
|
||||
label2.Padding = new System.Windows.Forms.Padding(0, 0, 0, 3);
|
||||
label2.Size = new System.Drawing.Size(45, 22);
|
||||
label2.TabIndex = 12;
|
||||
label2.Text = "label2";
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new System.Drawing.Point(6, 82);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new System.Drawing.Size(157, 15);
|
||||
label1.TabIndex = 11;
|
||||
label1.Text = "Additional Contributions by:";
|
||||
//
|
||||
// flowLayoutPanel1
|
||||
//
|
||||
flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
flowLayoutPanel1.Controls.Add(linkLabel4);
|
||||
flowLayoutPanel1.Controls.Add(linkLabel2);
|
||||
flowLayoutPanel1.Controls.Add(linkLabel3);
|
||||
flowLayoutPanel1.Controls.Add(linkLabel1);
|
||||
flowLayoutPanel1.Controls.Add(linkLabel5);
|
||||
flowLayoutPanel1.Controls.Add(linkLabel6);
|
||||
flowLayoutPanel1.Location = new System.Drawing.Point(6, 100);
|
||||
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||
flowLayoutPanel1.Size = new System.Drawing.Size(398, 66);
|
||||
flowLayoutPanel1.TabIndex = 10;
|
||||
//
|
||||
// linkLabel4
|
||||
//
|
||||
linkLabel4.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel4.AutoSize = true;
|
||||
linkLabel4.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel4.Location = new System.Drawing.Point(3, 0);
|
||||
linkLabel4.Name = "linkLabel4";
|
||||
linkLabel4.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel4.Size = new System.Drawing.Size(41, 21);
|
||||
linkLabel4.TabIndex = 9;
|
||||
linkLabel4.TabStop = true;
|
||||
linkLabel4.Text = "pixil98";
|
||||
linkLabel4.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// linkLabel2
|
||||
//
|
||||
linkLabel2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel2.AutoSize = true;
|
||||
linkLabel2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel2.Location = new System.Drawing.Point(50, 0);
|
||||
linkLabel2.Name = "linkLabel2";
|
||||
linkLabel2.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel2.Size = new System.Drawing.Size(104, 21);
|
||||
linkLabel2.TabIndex = 9;
|
||||
linkLabel2.TabStop = true;
|
||||
linkLabel2.Text = "hutattedonmyarm";
|
||||
linkLabel2.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// linkLabel3
|
||||
//
|
||||
linkLabel3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel3.AutoSize = true;
|
||||
linkLabel3.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel3.Location = new System.Drawing.Point(160, 0);
|
||||
linkLabel3.Name = "linkLabel3";
|
||||
linkLabel3.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel3.Size = new System.Drawing.Size(43, 21);
|
||||
linkLabel3.TabIndex = 9;
|
||||
linkLabel3.TabStop = true;
|
||||
linkLabel3.Text = "seanke";
|
||||
linkLabel3.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// linkLabel1
|
||||
//
|
||||
linkLabel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel1.AutoSize = true;
|
||||
linkLabel1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel1.Location = new System.Drawing.Point(209, 0);
|
||||
linkLabel1.Name = "linkLabel1";
|
||||
linkLabel1.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel1.Size = new System.Drawing.Size(66, 21);
|
||||
linkLabel1.TabIndex = 9;
|
||||
linkLabel1.TabStop = true;
|
||||
linkLabel1.Text = "wtanksleyjr";
|
||||
linkLabel1.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// linkLabel5
|
||||
//
|
||||
linkLabel5.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel5.AutoSize = true;
|
||||
linkLabel5.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel5.Location = new System.Drawing.Point(281, 0);
|
||||
linkLabel5.Name = "linkLabel5";
|
||||
linkLabel5.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel5.Size = new System.Drawing.Size(51, 21);
|
||||
linkLabel5.TabIndex = 9;
|
||||
linkLabel5.TabStop = true;
|
||||
linkLabel5.Text = "Dr.Blank";
|
||||
linkLabel5.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// linkLabel6
|
||||
//
|
||||
linkLabel6.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
linkLabel6.AutoSize = true;
|
||||
linkLabel6.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
linkLabel6.Location = new System.Drawing.Point(3, 21);
|
||||
linkLabel6.Name = "linkLabel6";
|
||||
linkLabel6.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
linkLabel6.Size = new System.Drawing.Size(77, 21);
|
||||
linkLabel6.TabIndex = 9;
|
||||
linkLabel6.TabStop = true;
|
||||
linkLabel6.Text = "CharlieRussel";
|
||||
linkLabel6.LinkClicked += Link_GithubUser;
|
||||
//
|
||||
// AboutDialog
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
ClientSize = new System.Drawing.Size(434, 491);
|
||||
Controls.Add(groupBox1);
|
||||
Controls.Add(getLibationLbl);
|
||||
Controls.Add(checkForUpgradeBtn);
|
||||
Controls.Add(releaseNotesLbl);
|
||||
Controls.Add(pictureBox1);
|
||||
MinimumSize = new System.Drawing.Size(445, 530);
|
||||
Name = "AboutDialog";
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
Text = "About Libation";
|
||||
((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
|
||||
groupBox1.ResumeLayout(false);
|
||||
groupBox1.PerformLayout();
|
||||
flowLayoutPanel1.ResumeLayout(false);
|
||||
flowLayoutPanel1.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PictureBox pictureBox1;
|
||||
private System.Windows.Forms.LinkLabel releaseNotesLbl;
|
||||
private System.Windows.Forms.Button checkForUpgradeBtn;
|
||||
private System.Windows.Forms.LinkLabel getLibationLbl;
|
||||
private System.Windows.Forms.LinkLabel rmcrackanLbl;
|
||||
private System.Windows.Forms.LinkLabel MBucariLbl;
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
|
||||
private System.Windows.Forms.LinkLabel linkLabel1;
|
||||
private System.Windows.Forms.LinkLabel linkLabel4;
|
||||
private System.Windows.Forms.LinkLabel linkLabel2;
|
||||
private System.Windows.Forms.LinkLabel linkLabel3;
|
||||
private System.Windows.Forms.LinkLabel linkLabel5;
|
||||
private System.Windows.Forms.LinkLabel linkLabel6;
|
||||
private System.Windows.Forms.Label label3;
|
||||
private System.Windows.Forms.Label label4;
|
||||
private System.Windows.Forms.Label label2;
|
||||
}
|
||||
}
|
||||
62
Source/LibationWinForms/Dialogs/AboutDialog.cs
Normal file
62
Source/LibationWinForms/Dialogs/AboutDialog.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using LibationUiBase;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class AboutDialog : Form
|
||||
{
|
||||
public AboutDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
releaseNotesLbl.Text = $"Libation {AppScaffolding.LibationScaffolding.Variety} v{AppScaffolding.LibationScaffolding.BuildVersion}";
|
||||
|
||||
var toolTip = new ToolTip();
|
||||
toolTip.SetToolTip(releaseNotesLbl, "View Release Notes");
|
||||
}
|
||||
|
||||
private void releaseNotesLbl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
=> Dinah.Core.Go.To.Url($"{AppScaffolding.LibationScaffolding.RepositoryUrl}/releases/tag/v{AppScaffolding.LibationScaffolding.BuildVersion.ToString(3)}");
|
||||
|
||||
private async void checkForUpgradeBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var form1 = Owner as Form1;
|
||||
var upgrader = new Upgrader();
|
||||
upgrader.DownloadBegin += (_, _) => form1.Invoke(() => form1.upgradeLbl.Visible = form1.upgradePb.Visible = true);
|
||||
upgrader.DownloadProgress += (_, e) => form1.Invoke(() => form1.upgradePb.Value = int.Max(0, int.Min(100, (int)(e.ProgressPercentage ?? 0))));
|
||||
upgrader.DownloadCompleted += (_, _) => form1.Invoke(() => form1.upgradeLbl.Visible = form1.upgradePb.Visible = false);
|
||||
|
||||
checkForUpgradeBtn.Enabled = false;
|
||||
Version latestVersion = null;
|
||||
await upgrader.CheckForUpgradeAsync(OnUpgradeAvailable);
|
||||
|
||||
checkForUpgradeBtn.Enabled = latestVersion is null;
|
||||
|
||||
checkForUpgradeBtn.Text = latestVersion is null ? "Libation is up to date. Check Again." : $"Version {latestVersion:3} is available";
|
||||
|
||||
Task OnUpgradeAvailable(UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = new UpgradeNotificationDialog(e.UpgradeProperties).ShowDialog(this);
|
||||
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.Yes;
|
||||
latestVersion = e.UpgradeProperties.LatestRelease;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private void getLibationLbl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
=> Dinah.Core.Go.To.Url(AppScaffolding.LibationScaffolding.WebsiteUrl);
|
||||
|
||||
private void Link_GithubUser(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
if (sender is LinkLabel lbl)
|
||||
{
|
||||
Dinah.Core.Go.To.Url($"ht" + $"tps://github.com/{lbl.Text.Replace('.', '-')}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Source/LibationWinForms/Dialogs/AboutDialog.resx
Normal file
60
Source/LibationWinForms/Dialogs/AboutDialog.resx
Normal file
@ -0,0 +1,60 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
4
Source/LibationWinForms/Form1.Designer.cs
generated
4
Source/LibationWinForms/Form1.Designer.cs
generated
@ -697,7 +697,7 @@
|
||||
private System.Windows.Forms.Button removeBooksBtn;
|
||||
private System.Windows.Forms.Button doneRemovingBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem setPdfDownloadedManualToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripProgressBar upgradePb;
|
||||
private System.Windows.Forms.ToolStripStatusLabel upgradeLbl;
|
||||
public System.Windows.Forms.ToolStripProgressBar upgradePb;
|
||||
public System.Windows.Forms.ToolStripStatusLabel upgradeLbl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,7 @@ namespace LibationWinForms
|
||||
|
||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||
|
||||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> MessageBox.Show($"Libation {AppScaffolding.LibationScaffolding.Variety}{Environment.NewLine}Version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
|
||||
|
||||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e) => new AboutDialog().ShowDialog(this);
|
||||
private async void tourToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> await new Walkthrough(this).RunAsync();
|
||||
|
||||
|
||||
@ -201,8 +201,8 @@ namespace LibationWinForms.GridView
|
||||
{
|
||||
var pIndex = itemsList.IndexOf(parent);
|
||||
|
||||
//children should always be sorted by series index.
|
||||
foreach (var c in children.Where(c => c.Parent == parent).OrderBy(c => c.SeriesIndex))
|
||||
//children are sorted beneath their series parent
|
||||
foreach (var c in children.Where(c => c.Parent == parent).OrderBy(c => c, Comparer))
|
||||
itemsList.Insert(++pIndex, c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,16 @@ namespace LibationWinForms.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap cheers {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("cheers", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
|
||||
@ -118,6 +118,9 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="cheers" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\cheers.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="default_cover_300x300" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\img-coverart-prod-unavailable_300x300.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
|
||||
BIN
Source/LibationWinForms/Resources/cheers.png
Normal file
BIN
Source/LibationWinForms/Resources/cheers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Loading…
x
Reference in New Issue
Block a user