Fixed visible books count not updating

This commit is contained in:
Michael Bucari-Tovo 2022-07-18 10:07:13 -06:00
parent e9a331292a
commit 720fd64c97
8 changed files with 1009 additions and 65 deletions

View File

@ -0,0 +1,53 @@
<TemplatedControl 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"
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="LibationWinForms.AvaloniaUI.Controls.DirectorySelectControl">
<Design.DataContext>
</Design.DataContext>
<TemplatedControl.Styles>
<Style Selector="controls|DirectorySelectControl Border">
<Setter Property="BorderBrush" Value="DarkGray" />
</Style>
<Style Selector="controls|DirectorySelectControl">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Orientation="Vertical">
<StackPanel.Styles>
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Height" Value="NaN"/>
</Style>
</StackPanel.Styles>
<ComboBox
HorizontalContentAlignment = "Stretch"
HorizontalAlignment = "Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
SelectedItem="{Binding SelectedComboBoxItem, Mode=TwoWay}"
Items="{Binding ComboBoxItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
FontSize="12"
Text="{Binding Description}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Margin="0,10,0,10" Text="{Binding SelectedComboBoxItem.UiDisplayPath}" />
</StackPanel>
</ControlTemplate>
</Setter>
</Style>
</TemplatedControl.Styles>
</TemplatedControl>

View File

@ -0,0 +1,146 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Dinah.Core;
using LibationFileManager;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
using Avalonia.Controls.Primitives;
using System.Collections;
using Avalonia.Data.Converters;
using System;
using System.Globalization;
using Avalonia.Data;
namespace LibationWinForms.AvaloniaUI.Controls
{
public class TextCaseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Configuration.KnownDirectories dir)
{
}
return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public partial class DirectorySelectControl : TemplatedControl
{
private static readonly List<Configuration.KnownDirectories> defaultList = new List<Configuration.KnownDirectories>()
{
Configuration.KnownDirectories.WinTemp,
Configuration.KnownDirectories.UserProfile,
Configuration.KnownDirectories.AppDir,
Configuration.KnownDirectories.MyDocs,
Configuration.KnownDirectories.LibationFiles
};
public static readonly StyledProperty<Configuration.KnownDirectories?> SelectedirectoryProperty =
AvaloniaProperty.Register<DirectorySelectControl, Configuration.KnownDirectories?>(nameof(Selectedirectory), defaultList[0]);
public static readonly StyledProperty<List<Configuration.KnownDirectories>> KnownDirectoriesProperty =
AvaloniaProperty.Register<DirectorySelectControl, List<Configuration.KnownDirectories>>(nameof(KnownDirectories), defaultList);
public static readonly StyledProperty<string?> SubdirectoryProperty =
AvaloniaProperty.Register<DirectorySelectControl, string?>(nameof(Subdirectory), "subdir");
DirectorySelectViewModel DirectorySelect { get; } = new();
public DirectorySelectControl()
{
InitializeComponent();
}
protected override void OnInitialized()
{
DirectorySelect.Directories.Clear();
int insertIndex = 0;
foreach (var kd in KnownDirectories.Distinct())
DirectorySelect.Directories.Insert(insertIndex++, new(this, kd));
DataContext = DirectorySelect;
base.OnInitialized();
}
public List<Configuration.KnownDirectories> KnownDirectories
{
get { return GetValue(KnownDirectoriesProperty); }
set
{
SetValue(KnownDirectoriesProperty, value);
//SetDirectoryItems(KnownDirectories);
}
}
public Configuration.KnownDirectories? Selectedirectory
{
get { return GetValue(SelectedirectoryProperty); }
set
{
SetValue(SelectedirectoryProperty, value);
if (value is null or Configuration.KnownDirectories.None)
return;
// set default
var item = DirectorySelect.Directories.SingleOrDefault(item => item.Value == value.Value);
if (item is null)
return;
DirectorySelect.SelectedDirectory = item;
}
}
public string? Subdirectory
{
get { return GetValue(SubdirectoryProperty); }
set
{
SetValue(SubdirectoryProperty, value);
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
public class DirectorySelectViewModel : ViewModels.ViewModelBase
{
public class DirectoryComboBoxItem
{
private readonly DirectorySelectControl _parentControl;
public string Description { get; }
public Configuration.KnownDirectories Value { get; }
public string FullPath => AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value));
/// <summary>Displaying relative paths is confusing. UI should display absolute equivalent</summary>
public string UiDisplayPath => Value == Configuration.KnownDirectories.AppDir ? AddSubDirectoryToPath(Configuration.AppDir_Absolute) : FullPath;
public DirectoryComboBoxItem(DirectorySelectControl parentControl, Configuration.KnownDirectories knownDirectory)
{
_parentControl = parentControl;
Value = knownDirectory;
Description = Value.GetDescription();
}
internal string AddSubDirectoryToPath(string path) => string.IsNullOrWhiteSpace(_parentControl.Subdirectory) ? path : System.IO.Path.Combine(path, _parentControl.Subdirectory);
public override string ToString() => Description;
}
public ObservableCollection<DirectoryComboBoxItem> Directories { get; } = new(new());
private DirectoryComboBoxItem _selectedDirectory;
public DirectoryComboBoxItem SelectedDirectory { get => _selectedDirectory; set => this.RaiseAndSetIfChanged(ref _selectedDirectory, value); }
}
}

View File

@ -86,10 +86,8 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
GridEntries = new GridEntryCollection(CreateGridEntries(dbBooks)); GridEntries = new GridEntryCollection(CreateGridEntries(dbBooks));
GridEntries.CollapseAll(); GridEntries.CollapseAll();
int bookEntryCount = GridEntries.BookEntries().Count();
InitialLoaded?.Invoke(this, EventArgs.Empty); InitialLoaded?.Invoke(this, EventArgs.Empty);
VisibleCountChanged?.Invoke(this, bookEntryCount); VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count());
RegisterCollectionChanged(); RegisterCollectionChanged();
} }
@ -112,7 +110,12 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
var existingSeriesEntries = GridEntries.AllItems().SeriesEntries().ToList(); var existingSeriesEntries = GridEntries.AllItems().SeriesEntries().ToList();
await Dispatcher.UIThread.InvokeAsync(() => GridEntries.ReplaceList(newEntries)); await Dispatcher.UIThread.InvokeAsync(() =>
{
GridEntries.ReplaceList(newEntries);
GridEntries.Filter = existingFilter;
ReSort();
});
//We're replacing the list, so preserve usere's existing collapse/expand //We're replacing the list, so preserve usere's existing collapse/expand
//state. When resetting a list, default state is open. //state. When resetting a list, default state is open.
@ -122,11 +125,8 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
if (sEntry is SeriesEntry se && !series.Liberate.Expanded) if (sEntry is SeriesEntry se && !series.Liberate.Expanded)
await Dispatcher.UIThread.InvokeAsync(() => GridEntries.CollapseItem(se)); await Dispatcher.UIThread.InvokeAsync(() => GridEntries.CollapseItem(se));
} }
await Dispatcher.UIThread.InvokeAsync(() =>
{ await Dispatcher.UIThread.InvokeAsync(() => VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count()));
GridEntries.Filter = existingFilter;
ReSort();
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -36,6 +36,7 @@
Width="60" Width="60"
Height="30" Height="30"
Content="X" Content="X"
IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" /> Click="DeleteButton_Clicked" />
</DataTemplate> </DataTemplate>
@ -50,6 +51,7 @@
Width="60" Width="60"
Height="30" Height="30"
Content="Export" Content="Export"
IsEnabled="{Binding !IsDefault}"
ToolTip.Tip="Export account authorization to audible-cli" ToolTip.Tip="Export account authorization to audible-cli"
Click="ExportButton_Clicked" /> Click="ExportButton_Clicked" />
@ -71,13 +73,13 @@
<DataTemplate> <DataTemplate>
<ComboBox <ComboBox
MinHeight="30" MinHeight="30"
HorizontalAlignment="Center" HorizontalContentAlignment = "Stretch"
HorizontalAlignment = "Stretch"
SelectedItem="{Binding SelectedLocale, Mode=TwoWay}" SelectedItem="{Binding SelectedLocale, Mode=TwoWay}"
Items="{Binding Locales}"> Items="{Binding Locales}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock ZIndex="2" <TextBlock ZIndex="2"
FontSize="12" FontSize="12"
Text="{Binding Name}" /> Text="{Binding Name}" />
@ -101,25 +103,11 @@
Margin="10" Margin="10"
ColumnDefinitions="*,Auto" > ColumnDefinitions="*,Auto" >
<StackPanel <Button
Grid.Column="0" Grid.Column="0"
Orientation="Horizontal"> Height="30"
Content="Import from audible-cli"
<Button Click="ImportButton_Clicked" />
Grid.Column="0"
Height="30"
Content="Add an Account"
Name="AddAccountButton"
Click="AddAccountButton_Clicked" />
<Button
Grid.Column="0"
Height="30"
Margin="20,0,0,0"
Content="Import from audible-cli"
Click="ImportButton_Clicked" />
</StackPanel>
<Button <Button
Grid.Column="1" Grid.Column="1"

View File

@ -1,34 +1,52 @@
using AudibleUtilities; using AudibleUtilities;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ReactiveUI;
using AudibleApi;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs namespace LibationWinForms.AvaloniaUI.Views.Dialogs
{ {
public partial class AccountsDialog : DialogWindow public partial class AccountsDialog : DialogWindow
{ {
public ObservableCollection<AccountDto> Accounts { get; } = new(); public ObservableCollection<AccountDto> Accounts { get; } = new();
public class AccountDto public class AccountDto : ViewModels.ViewModelBase
{ {
public IList<AudibleApi.Locale> Locales { get; init; } private string _accountId;
private Locale _selectedLocale;
public IReadOnlyList<Locale> Locales => AccountsDialog.Locales;
public bool LibraryScan { get; set; } = true; public bool LibraryScan { get; set; } = true;
public string AccountId { get; set; } public string AccountId
public AudibleApi.Locale SelectedLocale { get; set; } {
get => _accountId;
set
{
this.RaiseAndSetIfChanged(ref _accountId, value);
this.RaisePropertyChanged(nameof(IsDefault));
}
}
public Locale SelectedLocale
{
get => _selectedLocale;
set
{
this.RaiseAndSetIfChanged(ref _selectedLocale, value);
this.RaisePropertyChanged(nameof(IsDefault));
}
}
public string AccountName { get; set; } public string AccountName { get; set; }
public bool IsDefault => AccountId is null && SelectedLocale is null && AccountName is null; public bool IsDefault => string.IsNullOrEmpty(AccountId) && SelectedLocale is null;
} }
private static string GetAudibleCliAppDataPath() private static string GetAudibleCliAppDataPath()
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Audible"); => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Audible");
private List<AudibleApi.Locale> Locales => AudibleApi.Localization.Locales.OrderBy(l => l.Name).ToList(); private static IReadOnlyList<Locale> Locales => Localization.Locales.OrderBy(l => l.Name).ToList();
public AccountsDialog() public AccountsDialog()
{ {
InitializeComponent(); InitializeComponent();
@ -41,26 +59,51 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
if (!accounts.Any()) if (!accounts.Any())
return; return;
ControlToFocusOnShow = this.FindControl<Button>(nameof(AddAccountButton));
DataContext = this; DataContext = this;
foreach (var account in accounts) foreach (var account in accounts)
AddAccountToGrid(account); AddAccountToGrid(account);
var newBlank = new AccountDto();
newBlank.PropertyChanged += AccountDto_PropertyChanged;
Accounts.Insert(Accounts.Count, newBlank);
} }
private void AddAccountToGrid(Account account) private void AddAccountToGrid(Account account)
{ {
//ObservableCollection doesn't fire CollectionChanged on Add, so use Insert instead AccountDto accountDto = new()
Accounts.Insert(Accounts.Count, new()
{ {
LibraryScan = account.LibraryScan, LibraryScan = account.LibraryScan,
AccountId = account.AccountId, AccountId = account.AccountId,
SelectedLocale = Locales.Single(l => l.Name == account.Locale.Name), SelectedLocale = Locales.Single(l => l.Name == account.Locale.Name),
AccountName = account.AccountName, AccountName = account.AccountName,
Locales = Locales };
}); accountDto.PropertyChanged += AccountDto_PropertyChanged;
//ObservableCollection doesn't fire CollectionChanged on Add, so use Insert instead
Accounts.Insert(Accounts.Count, accountDto);
}
private void AccountDto_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (Accounts.Any(a => a.IsDefault))
return;
var newBlank = new AccountDto();
newBlank.PropertyChanged += AccountDto_PropertyChanged;
Accounts.Insert(Accounts.Count, newBlank);
}
public void DeleteButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (e.Source is Button expBtn && expBtn.DataContext is AccountDto acc)
{
var index = Accounts.IndexOf(acc);
if (index < 0) return;
acc.PropertyChanged -= AccountDto_PropertyChanged;
Accounts.Remove(acc);
}
} }
public async void ImportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public async void ImportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
@ -109,21 +152,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
} }
} }
public void AddAccountButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (Accounts.Any(a => a.IsDefault))
return;
//ObservableCollection doesn't fire CollectionChanged on Add, so use Insert instead
Accounts.Insert(Accounts.Count, new() { Locales = Locales });
}
public void DeleteButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (e.Source is Button expBtn && expBtn.DataContext is AccountDto acc)
Accounts.Remove(acc);
}
public void ExportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void ExportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
if (e.Source is Button expBtn && expBtn.DataContext is AccountDto acc) if (e.Source is Button expBtn && expBtn.DataContext is AccountDto acc)
@ -177,7 +205,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
var existing = existingAccounts[i]; var existing = existingAccounts[i];
if (!Accounts.Any(dto => if (!Accounts.Any(dto =>
dto.AccountId?.ToLower().Trim() == existing.AccountId.ToLower() dto.AccountId?.ToLower().Trim() == existing.AccountId.ToLower()
&& dto.SelectedLocale.Name == existing.Locale?.Name)) && dto.SelectedLocale?.Name == existing.Locale?.Name))
{ {
accountsSettings.Delete(existing); accountsSettings.Delete(existing);
} }
@ -186,11 +214,11 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
// upsert each. validation occurs through Account and AccountsSettings // upsert each. validation occurs through Account and AccountsSettings
foreach (var dto in Accounts) foreach (var dto in Accounts)
{ {
var acct = accountsSettings.Upsert(dto.AccountId, dto.SelectedLocale.Name); var acct = accountsSettings.Upsert(dto.AccountId, dto.SelectedLocale?.Name);
acct.LibraryScan = dto.LibraryScan; acct.LibraryScan = dto.LibraryScan;
acct.AccountName acct.AccountName
= string.IsNullOrWhiteSpace(dto.AccountName) = string.IsNullOrWhiteSpace(dto.AccountName)
? $"{dto.AccountId} - {dto.SelectedLocale.Name}" ? $"{dto.AccountId} - {dto.SelectedLocale?.Name}"
: dto.AccountName.Trim(); : dto.AccountName.Trim();
} }
} }
@ -210,7 +238,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
return false; return false;
} }
if (string.IsNullOrWhiteSpace(dto.SelectedLocale.Name)) if (string.IsNullOrWhiteSpace(dto.SelectedLocale?.Name))
{ {
await MessageBox.Show(this, "Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error); await MessageBox.Show(this, "Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false; return false;
@ -225,7 +253,7 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs
// without transaction, accounts persister will write ANY EDIT immediately to file // without transaction, accounts persister will write ANY EDIT immediately to file
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var account = persister.AccountsSettings.Accounts.FirstOrDefault(a => a.AccountId == acc.AccountId && a.Locale.Name == acc.SelectedLocale.Name); var account = persister.AccountsSettings.Accounts.FirstOrDefault(a => a.AccountId == acc.AccountId && a.Locale.Name == acc.SelectedLocale?.Name);
if (account is null) if (account is null)
return; return;

View File

@ -0,0 +1,406 @@
<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="900" d:DesignHeight="600"
x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.SettingsDialog"
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
Title="Edit Settings"
Icon="/AvaloniaUI/Assets/libation.ico">
<Grid RowDefinitions="*,Auto">
<Button
Grid.Row="1"
Margin="10"
HorizontalAlignment="Right"
Height="30"
Padding="30,3,30,3"
Content="Save"
Click="SaveButton_Clicked" />
<TabControl Grid.Column="0">
<TabControl.Styles>
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Height" Value="33"/>
</Style>
<Style Selector="TextBlock">
<Setter Property="FontSize" Value="12"/>
</Style>
</TabControl.Styles>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Download/Decrypt"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
Background="WhiteSmoke"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<Grid RowDefinitions="Auto,Auto,*">
<controls:GroupBox Grid.Row="0" BorderWidth="1" Label="{Binding DownloadDecryptSettings.BadBookGroupboxText}">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto">
<RadioButton
Grid.Column="0"
Grid.Row="0"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookAsk, Mode=TwoWay}">
<TextBlock Text="{Binding DownloadDecryptSettings.BadBookAskText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="0"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookAbort, Mode=TwoWay}">
<TextBlock Text="{Binding DownloadDecryptSettings.BadBookAbortText}" />
</RadioButton>
<RadioButton
Grid.Column="0"
Grid.Row="1"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookRetry, Mode=TwoWay}">
<TextBlock Text="{Binding DownloadDecryptSettings.BadBookRetryText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="1"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookIgnore, Mode=TwoWay}">
<TextBlock Text="{Binding DownloadDecryptSettings.BadBookIgnoreText}" />
</RadioButton>
</Grid>
</controls:GroupBox>
<controls:GroupBox Grid.Row="1" BorderWidth="1" Label="Custom File Naming">
<Grid RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="*,Auto">
<TextBox
Grid.Row="0"
Grid.Column="0"
Margin="0,10,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.FolderTemplate}" />
<Button
Grid.Row="0"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditFolderTemplateButton_Click" />
<TextBox
Grid.Row="1"
Grid.Column="0"
Margin="0,10,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.FileTemplate}" />
<Button
Grid.Row="1"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditFileTemplateButton_Click" />
<TextBox
Grid.Row="2"
Grid.Column="0"
Margin="0,10,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.ChapterFileTemplate}" />
<Button
Grid.Row="2"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditChapterFileTemplateButton_Click" />
<Button
Grid.Row="3"
Grid.Column="0"
Content="{Binding DownloadDecryptSettings.EditCharReplacementText}"
Height="30"
Padding="30,3,30,3"
Click="EditChapterTitleTemplateButton_Click" />
</Grid>
</controls:GroupBox>
<controls:DirectorySelectControl
Grid.Row="2"
Subdirectory="{Binding DownloadDecryptSettings.EditCharReplacementText}"
KnownDirectories="{Binding DownloadDecryptSettings.KnownDirectories}"
Selectedirectory="{Binding DownloadDecryptSettings.SelectedDirectory, Mode=TwoWay}" />
</Grid>
</Border>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Audio File Settings"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
Background="WhiteSmoke"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<Grid
RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
<StackPanel Margin="5"
Grid.Row="0"
Grid.Column="0">
<CheckBox IsChecked="{Binding AudioSettings.CreateCueSheet, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.CreateCueSheetText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.DownloadCoverArt, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.DownloadCoverArtText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.RetainAaxFile, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.RetainAaxFileText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.MergeOpeningAndEndCredits, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.MergeOpeningEndCreditsText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.AllowLibationFixup, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.AllowLibationFixupText}" />
</CheckBox>
<controls:GroupBox BorderWidth="1" Label="Audiobook Fix-ups" IsEnabled="{Binding AudioSettings.AllowLibationFixup}">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding AudioSettings.SplitFilesByChapter, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.SplitFilesByChapterText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.StripAudibleBrandAudio, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.StripAudibleBrandingText}" />
</CheckBox>
<CheckBox IsChecked="{Binding AudioSettings.StripUnabridged, Mode=TwoWay}">
<TextBlock Text="{Binding AudioSettings.StripUnabridgedText}" />
</CheckBox>
<RadioButton Margin="0,5,0,5" IsChecked="{Binding !AudioSettings.DecryptToLossy, Mode=TwoWay}">
<TextBlock Text="Download my books in the original audio format (Lossless)" />
</RadioButton>
<RadioButton Margin="0,5,0,5" IsChecked="{Binding AudioSettings.DecryptToLossy, Mode=TwoWay}">
<TextBlock Text="Download my books as .MP3 files (transcode if necessary)" />
</RadioButton>
</StackPanel>
</controls:GroupBox>
</StackPanel>
<StackPanel
Grid.Row="0"
Grid.Column="1">
<controls:GroupBox BorderWidth="1" Label="Mp3 Encoding Options">
<StackPanel Orientation="Vertical">
<Grid Margin="5,5,5,0" ColumnDefinitions="Auto,*">
<controls:GroupBox BorderWidth="1" Grid.Column="0" Label="Target">
<StackPanel Orientation="Horizontal">
<RadioButton Margin="10" IsChecked="{Binding AudioSettings.LameTargetBitrate, Mode=TwoWay}">
<TextBlock Text="Bitrate" />
</RadioButton>
<RadioButton Margin="10" IsChecked="{Binding !AudioSettings.LameTargetBitrate, Mode=TwoWay}">
<TextBlock Text="Quality" />
</RadioButton>
</StackPanel>
</controls:GroupBox>
<CheckBox HorizontalAlignment="Right" Grid.Column="1" IsChecked="{Binding AudioSettings.LameDownsampleMono, Mode=TwoWay}">
<TextBlock Text="Downsample to mono?&#xa;(Recommended)" />
</CheckBox>
</Grid>
<controls:GroupBox Margin="5,5,5,0" BorderWidth="1" Label="Bitrate" IsEnabled="{Binding AudioSettings.LameTargetBitrate}" >
<StackPanel>
<Grid ColumnDefinitions="*,25,Auto">
<Slider
Grid.Column="0"
IsEnabled="{Binding !AudioSettings.LameMatchSource}"
Value="{Binding AudioSettings.LameBitrate, Mode=TwoWay}"
Minimum="16"
Maximum="320"
IsSnapToTickEnabled="True" TickFrequency="16"
Ticks="16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,272,288,304,320"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='\{0:f0\} Kbps'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<TextBlock Grid.Column="1" HorizontalAlignment="Right" Text="{Binding AudioSettings.LameBitrate}" />
<TextBlock Grid.Column="2" Text=" Kbps" />
</Grid>
<Grid ColumnDefinitions="Auto,*">
<CheckBox Grid.Column="0" IsChecked="{Binding AudioSettings.LameConstantBitrate, Mode=TwoWay}">
<TextBlock Text="Restrict Encoder to Constant Bitrate?" />
</CheckBox>
<CheckBox Grid.Column="1" IsChecked="{Binding AudioSettings.LameMatchSource, Mode=TwoWay}" HorizontalAlignment="Right">
<TextBlock Text="Match Source Bitrate?" />
</CheckBox>
</Grid>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Margin="5,5,5,0" BorderWidth="1" Label="Quality" IsEnabled="{Binding !AudioSettings.LameTargetBitrate}" >
<Grid ColumnDefinitions="*,*,25" RowDefinitions="*,Auto">
<Slider
Grid.Column="0"
Grid.ColumnSpan="2"
Value="{Binding AudioSettings.LameVBRQuality, Mode=TwoWay}"
Minimum="0"
Maximum="9"
IsSnapToTickEnabled="True" TickFrequency="1"
Ticks="0,1,2,3,4,5,6,7,8,9"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='V\{0:f0\}'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Text="V" />
<TextBlock Text="{Binding AudioSettings.LameVBRQuality}" />
</StackPanel>
<TextBlock Margin="10,0,0,0" Grid.Column="0" Grid.Row="1" Text="Higher" />
<TextBlock Margin="0,0,10,0" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Right" Text="Lower" />
</Grid>
</controls:GroupBox>
<TextBlock Margin="5,5,5,5" Text="Using L.A.M.E encoding engine" FontStyle="Italic" />
</StackPanel>
</controls:GroupBox>
</StackPanel>
<controls:GroupBox
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="5"
BorderWidth="1" IsEnabled="{Binding AudioSettings.SplitFilesByChapter}"
Label="{Binding AudioSettings.ChapterTitleTemplateText}">
<Grid ColumnDefinitions="*,Auto">
<TextBox
Grid.Column="0"
Margin="0,10,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding AudioSettings.ChapterTitleTemplate}" />
<Button
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditChapterTitleTemplateButton_Click" />
</Grid>
</controls:GroupBox>
</Grid>
</Border>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Important Settings"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
Background="WhiteSmoke"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
</Border>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Import Library"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
Background="WhiteSmoke"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
</Border>
</TabItem>
</TabControl>
</Grid>
</Window>

View File

@ -0,0 +1,322 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using LibationFileManager;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ReactiveUI;
using Dinah.Core;
namespace LibationWinForms.AvaloniaUI.Views.Dialogs
{
public partial class SettingsDialog : DialogWindow
{
private SettingsPages settingsDisp;
public SettingsDialog()
{
if (Design.IsDesignMode)
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
InitializeComponent();
DataContext = settingsDisp = new(Configuration.Instance);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync()
{
await base.SaveAndCloseAsync();
}
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
public void EditFolderTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = editTemplate(Templates.ChapterTitle, settingsDisp.AudioSettings.ChapterTitleTemplate);
if (newTemplate is not null)
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
public void EditFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = editTemplate(Templates.ChapterTitle, settingsDisp.AudioSettings.ChapterTitleTemplate);
if (newTemplate is not null)
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
public void EditChapterFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = editTemplate(Templates.ChapterTitle, settingsDisp.AudioSettings.ChapterTitleTemplate);
if (newTemplate is not null)
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
public void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = editTemplate(Templates.ChapterTitle, settingsDisp.AudioSettings.ChapterTitleTemplate);
if (newTemplate is not null)
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
private static string editTemplate(Templates template, string existingTemplate)
{
var form = new LibationWinForms.Dialogs.EditTemplateDialog(template, existingTemplate);
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
return form.TemplateText;
else return null;
}
}
internal interface ISettingsTab
{
void LoadSettings(Configuration config);
void SaveSettings(Configuration config);
}
public class SettingsPages
{
public Configuration config { get; }
public SettingsPages(Configuration config)
{
this.config = config;
AudioSettings = new(config);
DownloadDecryptSettings = new(config);
}
public AudioSettings AudioSettings { get;}
public DownloadDecryptSettings DownloadDecryptSettings { get;}
}
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsTab
{
private static Func<string, string> desc { get; } = Configuration.GetDescription;
private bool _badBookAsk;
private bool _badBookAbort;
private bool _badBookRetry;
private bool _badBookIgnore;
private string _folderTemplate;
private string _fileTemplate;
private string _chapterFileTemplate;
public DownloadDecryptSettings(Configuration config)
{
LoadSettings(config);
}
public void LoadSettings(Configuration config)
{
BadBookAsk = config.BadBook is Configuration.BadBookAction.Ask;
BadBookAbort = config.BadBook is Configuration.BadBookAction.Abort;
BadBookRetry = config.BadBook is Configuration.BadBookAction.Retry;
BadBookIgnore = config.BadBook is Configuration.BadBookAction.Ignore;
FolderTemplate = config.FolderTemplate;
FileTemplate = config.FileTemplate;
ChapterFileTemplate = config.ChapterFileTemplate;
}
public void SaveSettings(Configuration config)
{
config.BadBook
= BadBookAbort ? Configuration.BadBookAction.Abort
: BadBookRetry ? Configuration.BadBookAction.Retry
: BadBookIgnore ? Configuration.BadBookAction.Ignore
: Configuration.BadBookAction.Ask;
config.FolderTemplate = FolderTemplate;
config.FileTemplate = FileTemplate;
config.ChapterFileTemplate = ChapterFileTemplate;
}
public string BadBookGroupboxText => desc(nameof(Configuration.BadBook));
public string BadBookAskText { get; } = Configuration.BadBookAction.Ask.GetDescription();
public string BadBookAbortText { get; } = Configuration.BadBookAction.Abort.GetDescription();
public string BadBookRetryText { get; } = Configuration.BadBookAction.Retry.GetDescription();
public string BadBookIgnoreText { get; } = Configuration.BadBookAction.Ignore.GetDescription();
public string FolderTemplateText => desc(nameof(Configuration.FolderTemplate));
public string FileTemplateText => desc(nameof(Configuration.FileTemplate));
public string ChapterFileTemplateText => desc(nameof(Configuration.ChapterFileTemplate));
public string? EditCharReplacementText => desc(nameof(Configuration.ReplacementCharacters));
public string FolderTemplate { get => _folderTemplate; set { this.RaiseAndSetIfChanged(ref _folderTemplate, value); } }
public string FileTemplate { get => _fileTemplate; set { this.RaiseAndSetIfChanged(ref _fileTemplate, value); } }
public string ChapterFileTemplate { get => _chapterFileTemplate; set { this.RaiseAndSetIfChanged(ref _chapterFileTemplate, value); } }
public bool BadBookAsk
{
get => _badBookAsk;
set
{
this.RaiseAndSetIfChanged(ref _badBookAsk, value);
if (value)
{
BadBookAbort = false;
BadBookRetry = false;
BadBookIgnore = false;
}
}
}
public bool BadBookAbort
{
get => _badBookAbort;
set
{
this.RaiseAndSetIfChanged(ref _badBookAbort, value);
if (value)
{
BadBookAsk = false;
BadBookRetry = false;
BadBookIgnore = false;
}
}
}
public bool BadBookRetry
{
get => _badBookRetry;
set
{
this.RaiseAndSetIfChanged(ref _badBookRetry, value);
if (value)
{
BadBookAsk = false;
BadBookAbort = false;
BadBookIgnore = false;
}
}
}
public bool BadBookIgnore
{
get => _badBookIgnore;
set
{
this.RaiseAndSetIfChanged(ref _badBookIgnore, value);
if (value)
{
BadBookAsk = false;
BadBookAbort = false;
BadBookRetry = false;
}
}
}
}
public class AudioSettings : ViewModels.ViewModelBase, ISettingsTab
{
private bool _splitFilesByChapter;
private bool _allowLibationFixup;
private bool _lameTargetBitrate;
private bool _lameMatchSource;
private int _lameBitrate;
private int _lameVBRQuality;
private string _chapterTitleTemplate;
private static Func<string, string> desc { get; } = Configuration.GetDescription;
public AudioSettings(Configuration config)
{
LoadSettings(config);
}
public void LoadSettings(Configuration config)
{
CreateCueSheet = config.CreateCueSheet;
AllowLibationFixup = config.AllowLibationFixup;
DownloadCoverArt = config.DownloadCoverArt;
RetainAaxFile = config.RetainAaxFile;
SplitFilesByChapter = config.SplitFilesByChapter;
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
StripAudibleBrandAudio = config.StripAudibleBrandAudio;
StripUnabridged = config.StripUnabridged;
ChapterTitleTemplate = config.ChapterTitleTemplate;
DecryptToLossy = config.DecryptToLossy;
LameTargetBitrate = config.LameTargetBitrate;
LameDownsampleMono = config.LameDownsampleMono;
LameConstantBitrate = config.LameConstantBitrate;
LameMatchSource = config.LameMatchSourceBR;
LameBitrate = config.LameBitrate;
LameVBRQuality = config.LameVBRQuality;
}
public void SaveSettings(Configuration config)
{
config.CreateCueSheet = CreateCueSheet;
config.AllowLibationFixup = AllowLibationFixup;
config.DownloadCoverArt = DownloadCoverArt;
config.RetainAaxFile = RetainAaxFile;
config.SplitFilesByChapter = SplitFilesByChapter;
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
config.StripAudibleBrandAudio = StripAudibleBrandAudio;
config.StripUnabridged = StripUnabridged;
config.ChapterTitleTemplate = ChapterTitleTemplate;
config.DecryptToLossy = DecryptToLossy;
config.LameTargetBitrate = LameTargetBitrate;
config.LameDownsampleMono = LameDownsampleMono;
config.LameConstantBitrate = LameConstantBitrate;
config.LameMatchSourceBR = LameMatchSource;
config.LameBitrate = LameBitrate;
config.LameVBRQuality = LameVBRQuality;
}
public string CreateCueSheetText => desc(nameof(Configuration.CreateCueSheet));
public string AllowLibationFixupText => desc(nameof(Configuration.AllowLibationFixup));
public string DownloadCoverArtText => desc(nameof(Configuration.DownloadCoverArt));
public string RetainAaxFileText => desc(nameof(Configuration.RetainAaxFile));
public string SplitFilesByChapterText => desc(nameof(Configuration.SplitFilesByChapter));
public string MergeOpeningEndCreditsText => desc(nameof(Configuration.MergeOpeningAndEndCredits));
public string StripAudibleBrandingText => desc(nameof(Configuration.StripAudibleBrandAudio));
public string StripUnabridgedText => desc(nameof(Configuration.StripUnabridged));
public string ChapterTitleTemplateText => desc(nameof(Configuration.ChapterTitleTemplate));
public bool CreateCueSheet { get; set; }
public bool DownloadCoverArt { get; set; }
public bool RetainAaxFile { get; set; }
public bool MergeOpeningAndEndCredits { get; set; }
public bool StripAudibleBrandAudio { get; set; }
public bool StripUnabridged { get; set; }
public bool DecryptToLossy { get; set; }
public bool LameDownsampleMono { get; set; } = Design.IsDesignMode;
public bool LameConstantBitrate { get; set; } = Design.IsDesignMode;
public bool SplitFilesByChapter { get => _splitFilesByChapter; set { this.RaiseAndSetIfChanged(ref _splitFilesByChapter, value); } }
public bool LameTargetBitrate { get => _lameTargetBitrate; set { this.RaiseAndSetIfChanged(ref _lameTargetBitrate, value); } }
public bool LameMatchSource { get => _lameMatchSource; set { this.RaiseAndSetIfChanged(ref _lameMatchSource, value); } }
public int LameBitrate { get => _lameBitrate; set { this.RaiseAndSetIfChanged(ref _lameBitrate, value); } }
public int LameVBRQuality { get => _lameVBRQuality; set { this.RaiseAndSetIfChanged(ref _lameVBRQuality, value); } }
public string ChapterTitleTemplate { get => _chapterTitleTemplate; set { this.RaiseAndSetIfChanged(ref _chapterTitleTemplate, value); } }
public bool AllowLibationFixup
{
get => _allowLibationFixup;
set
{
this.RaiseAndSetIfChanged(ref _allowLibationFixup, value);
if (!_allowLibationFixup)
{
SplitFilesByChapter = false;
StripAudibleBrandAudio = false;
StripUnabridged = false;
DecryptToLossy = false;
this.RaisePropertyChanged(nameof(SplitFilesByChapter));
this.RaisePropertyChanged(nameof(StripAudibleBrandAudio));
this.RaisePropertyChanged(nameof(StripUnabridged));
this.RaisePropertyChanged(nameof(DecryptToLossy));
}
}
}
}
}

View File

@ -67,7 +67,8 @@ namespace LibationWinForms.AvaloniaUI.Views
private async void MainWindow_Opened(object sender, EventArgs e) private async void MainWindow_Opened(object sender, EventArgs e)
{ {
//var settings = new SettingsDialog();
//settings.Show();
} }
public void ProductsDisplay_Initialized1(object sender, EventArgs e) public void ProductsDisplay_Initialized1(object sender, EventArgs e)