@ -43,9 +43,11 @@ namespace DataLayer
|
|||||||
public ContentType ContentType { get; private set; }
|
public ContentType ContentType { get; private set; }
|
||||||
public string Locale { get; private set; }
|
public string Locale { get; private set; }
|
||||||
|
|
||||||
//This field is now unused, however, there is little sense in adding a
|
//This field is now unused, however, there is little sense in adding a
|
||||||
//database migration to remove an unused field. Leave it for compatibility.
|
//database migration to remove an unused field. Leave it for compatibility.
|
||||||
internal long _audioFormat;
|
#pragma warning disable CS0649 // Field 'Book._audioFormat' is never assigned to, and will always have its default value 0
|
||||||
|
internal long _audioFormat;
|
||||||
|
#pragma warning restore CS0649
|
||||||
|
|
||||||
// mutable
|
// mutable
|
||||||
public string PictureId { get; set; }
|
public string PictureId { get; set; }
|
||||||
|
|||||||
@ -27,6 +27,15 @@ public partial class DownloadOptions
|
|||||||
public static async Task<DownloadOptions> InitiateDownloadAsync(Api api, Configuration config, LibraryBook libraryBook)
|
public static async Task<DownloadOptions> InitiateDownloadAsync(Api api, Configuration config, LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
var license = await ChooseContent(api, libraryBook, config);
|
var license = await ChooseContent(api, libraryBook, config);
|
||||||
|
|
||||||
|
//Come audiobooks will have incorrect chapters in the metadata returned from the license request,
|
||||||
|
//but the metadata returned by the content metadata endpoint will be correct. Call the content
|
||||||
|
//metadata endpoint and use its chapters. Only replace the license request chapters if the total
|
||||||
|
//lengths match (defensive against different audio formats having slightly different lengths).
|
||||||
|
var metadata = await api.GetContentMetadataAsync(libraryBook.Book.AudibleProductId);
|
||||||
|
if (metadata.ChapterInfo.RuntimeLengthMs == license.ContentMetadata.ChapterInfo.RuntimeLengthMs)
|
||||||
|
license.ContentMetadata.ChapterInfo = metadata.ChapterInfo;
|
||||||
|
|
||||||
var options = BuildDownloadOptions(libraryBook, config, license);
|
var options = BuildDownloadOptions(libraryBook, config, license);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
@ -356,7 +365,7 @@ public partial class DownloadOptions
|
|||||||
else if (titleConcat is null)
|
else if (titleConcat is null)
|
||||||
{
|
{
|
||||||
chaps.Add(c);
|
chaps.Add(c);
|
||||||
chaps.AddRange(flattenChapters(c.Chapters));
|
chaps.AddRange(flattenChapters(c.Chapters, titleConcat));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -369,7 +378,7 @@ public partial class DownloadOptions
|
|||||||
else
|
else
|
||||||
chaps.Add(c);
|
chaps.Add(c);
|
||||||
|
|
||||||
var children = flattenChapters(c.Chapters);
|
var children = flattenChapters(c.Chapters, titleConcat);
|
||||||
|
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
child.Title = $"{c.Title}{titleConcat}{child.Title}";
|
child.Title = $"{c.Title}{titleConcat}{child.Title}";
|
||||||
|
|||||||
@ -6,7 +6,30 @@
|
|||||||
<local:ViewLocator/>
|
<local:ViewLocator/>
|
||||||
</Application.DataTemplates>
|
</Application.DataTemplates>
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme/>
|
<FluentTheme>
|
||||||
</Application.Styles>
|
<FluentTheme.Palettes>
|
||||||
|
<ColorPaletteResources x:Key="Light" />
|
||||||
|
<ColorPaletteResources x:Key="Dark" />
|
||||||
|
</FluentTheme.Palettes>
|
||||||
|
</FluentTheme>
|
||||||
|
|
||||||
|
<Style Selector="TextBox[IsReadOnly=true]">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemChromeDisabledHighColor}" />
|
||||||
|
<Setter Property="CaretBrush" Value="{DynamicResource SystemControlTransparentBrush}" />
|
||||||
|
<Style Selector="^ /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemChromeDisabledHighColor}" />
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Style Selector="^">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemChromeAltLowColor}" />
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ScrollBar">
|
||||||
|
<!-- It's called AutoHide, but this is really the mouseover shrink/expand. -->
|
||||||
|
<Setter Property="AllowAutoHide" Value="false"/>
|
||||||
|
</Style>
|
||||||
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@ -4,27 +4,16 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="HangoverAvalonia.Controls.CheckedListBox">
|
x:Class="HangoverAvalonia.Controls.CheckedListBox">
|
||||||
|
|
||||||
<UserControl.Resources>
|
|
||||||
<RecyclePool x:Key="RecyclePool" />
|
|
||||||
<DataTemplate x:Key="queuedBook">
|
|
||||||
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
|
||||||
</DataTemplate>
|
|
||||||
<RecyclingElementFactory x:Key="elementFactory" RecyclePool="{StaticResource RecyclePool}">
|
|
||||||
<RecyclingElementFactory.Templates>
|
|
||||||
<StaticResource x:Key="queuedBook" ResourceKey="queuedBook" />
|
|
||||||
</RecyclingElementFactory.Templates>
|
|
||||||
</RecyclingElementFactory>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Name="scroller"
|
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<ItemsRepeater IsVisible="True"
|
<ItemsControl ItemsSource="{Binding $parent[1].Items}">
|
||||||
VerticalCacheLength="1.2"
|
<ItemsControl.ItemTemplate>
|
||||||
HorizontalCacheLength="1"
|
<DataTemplate>
|
||||||
ItemsSource="{Binding CheckboxItems}"
|
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
||||||
ItemTemplate="{StaticResource elementFactory}" />
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -2,103 +2,18 @@ using Avalonia;
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using HangoverAvalonia.ViewModels;
|
using HangoverAvalonia.ViewModels;
|
||||||
using ReactiveUI;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace HangoverAvalonia.Controls
|
namespace HangoverAvalonia.Controls;
|
||||||
|
|
||||||
|
public partial class CheckedListBox : UserControl
|
||||||
{
|
{
|
||||||
public partial class CheckedListBox : UserControl
|
public static readonly StyledProperty<AvaloniaList<CheckBoxViewModel>> ItemsProperty =
|
||||||
|
AvaloniaProperty.Register<CheckedListBox, AvaloniaList<CheckBoxViewModel>>(nameof(Items));
|
||||||
|
|
||||||
|
public AvaloniaList<CheckBoxViewModel> Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
|
||||||
|
|
||||||
|
public CheckedListBox()
|
||||||
{
|
{
|
||||||
public event EventHandler<ItemCheckEventArgs> ItemCheck;
|
InitializeComponent();
|
||||||
|
|
||||||
public static readonly StyledProperty<IEnumerable> ItemsProperty =
|
|
||||||
AvaloniaProperty.Register<CheckedListBox, IEnumerable>(nameof(Items));
|
|
||||||
|
|
||||||
public IEnumerable Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
|
|
||||||
private CheckedListBoxViewModel _viewModel = new();
|
|
||||||
|
|
||||||
public IEnumerable<object> CheckedItems =>
|
|
||||||
_viewModel
|
|
||||||
.CheckboxItems
|
|
||||||
.Where(i => i.IsChecked)
|
|
||||||
.Select(i => i.Item);
|
|
||||||
|
|
||||||
public void SetItemChecked(int i, bool isChecked) => _viewModel.CheckboxItems[i].IsChecked = isChecked;
|
|
||||||
public void SetItemChecked(object item, bool isChecked)
|
|
||||||
{
|
|
||||||
var obj = _viewModel.CheckboxItems.SingleOrDefault(i => i.Item == item);
|
|
||||||
if (obj is not null)
|
|
||||||
obj.IsChecked = isChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CheckedListBox()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
scroller.DataContext = _viewModel;
|
|
||||||
_viewModel.CheckedChanged += _viewModel_CheckedChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _viewModel_CheckedChanged(object sender, CheckBoxViewModel e)
|
|
||||||
{
|
|
||||||
var args = new ItemCheckEventArgs { Item = e.Item, ItemIndex = _viewModel.CheckboxItems.IndexOf(e), IsChecked = e.IsChecked };
|
|
||||||
ItemCheck?.Invoke(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
|
||||||
{
|
|
||||||
if (change.Property.Name == nameof(Items) && Items != null)
|
|
||||||
_viewModel.SetItems(Items);
|
|
||||||
base.OnPropertyChanged(change);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CheckedListBoxViewModel : ViewModelBase
|
|
||||||
{
|
|
||||||
public event EventHandler<CheckBoxViewModel> CheckedChanged;
|
|
||||||
public AvaloniaList<CheckBoxViewModel> CheckboxItems { get; private set; }
|
|
||||||
|
|
||||||
public void SetItems(IEnumerable items)
|
|
||||||
{
|
|
||||||
UnsubscribeFromItems(CheckboxItems);
|
|
||||||
CheckboxItems = new(items.OfType<object>().Select(o => new CheckBoxViewModel { Item = o }));
|
|
||||||
SubscribeToItems(CheckboxItems);
|
|
||||||
this.RaisePropertyChanged(nameof(CheckboxItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SubscribeToItems(IEnumerable objects)
|
|
||||||
{
|
|
||||||
foreach (var i in objects.OfType<INotifyPropertyChanged>())
|
|
||||||
i.PropertyChanged += I_PropertyChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnsubscribeFromItems(AvaloniaList<CheckBoxViewModel> objects)
|
|
||||||
{
|
|
||||||
if (objects is null) return;
|
|
||||||
|
|
||||||
foreach (var i in objects)
|
|
||||||
i.PropertyChanged -= I_PropertyChanged;
|
|
||||||
}
|
|
||||||
private void I_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
CheckedChanged?.Invoke(this, (CheckBoxViewModel)sender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class CheckBoxViewModel : ViewModelBase
|
|
||||||
{
|
|
||||||
private bool _isChecked;
|
|
||||||
public bool IsChecked { get => _isChecked; set => this.RaiseAndSetIfChanged(ref _isChecked, value); }
|
|
||||||
private object _bookText;
|
|
||||||
public object Item { get => _bookText; set => this.RaiseAndSetIfChanged(ref _bookText, value); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ItemCheckEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public int ItemIndex { get; init; }
|
|
||||||
public bool IsChecked { get; init; }
|
|
||||||
public object Item { get; init; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,13 +71,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
||||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.8" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.8" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
||||||
|
|||||||
11
Source/HangoverAvalonia/ViewModels/CheckBoxViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace HangoverAvalonia.ViewModels;
|
||||||
|
|
||||||
|
public class CheckBoxViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private bool _isChecked;
|
||||||
|
public bool IsChecked { get => _isChecked; set => this.RaiseAndSetIfChanged(ref _isChecked, value); }
|
||||||
|
private object _bookText;
|
||||||
|
public object Item { get => _bookText; set => this.RaiseAndSetIfChanged(ref _bookText, value); }
|
||||||
|
}
|
||||||
@ -1,41 +1,8 @@
|
|||||||
using ApplicationServices;
|
namespace HangoverAvalonia.ViewModels;
|
||||||
using DataLayer;
|
|
||||||
using ReactiveUI;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace HangoverAvalonia.ViewModels
|
public partial class MainVM
|
||||||
{
|
{
|
||||||
public partial class MainVM
|
public TrashBinViewModel TrashBinViewModel { get; } = new();
|
||||||
{
|
|
||||||
private List<LibraryBook> _deletedBooks;
|
|
||||||
public List<LibraryBook> DeletedBooks { get => _deletedBooks; set => this.RaiseAndSetIfChanged(ref _deletedBooks, value); }
|
|
||||||
public string CheckedCountText => $"Checked : {_checkedBooksCount} of {_totalBooksCount}";
|
|
||||||
|
|
||||||
private int _totalBooksCount = 0;
|
private void Load_deletedVM() { }
|
||||||
private int _checkedBooksCount = 0;
|
|
||||||
public int CheckedBooksCount
|
|
||||||
{
|
|
||||||
get => _checkedBooksCount;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_checkedBooksCount != value)
|
|
||||||
{
|
|
||||||
_checkedBooksCount = value;
|
|
||||||
this.RaisePropertyChanged(nameof(CheckedCountText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void Load_deletedVM()
|
|
||||||
{
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reload()
|
|
||||||
{
|
|
||||||
DeletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
|
||||||
_checkedBooksCount = 0;
|
|
||||||
_totalBooksCount = DeletedBooks.Count;
|
|
||||||
this.RaisePropertyChanged(nameof(CheckedCountText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
117
Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
using ApplicationServices;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using DataLayer;
|
||||||
|
using ReactiveUI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HangoverAvalonia.ViewModels;
|
||||||
|
|
||||||
|
public class TrashBinViewModel : ViewModelBase, IDisposable
|
||||||
|
{
|
||||||
|
public AvaloniaList<CheckBoxViewModel> DeletedBooks { get; }
|
||||||
|
public string CheckedCountText => $"Checked : {_checkedBooksCount} of {_totalBooksCount}";
|
||||||
|
|
||||||
|
private bool _controlsEnabled = true;
|
||||||
|
public bool ControlsEnabled { get => _controlsEnabled; set => this.RaiseAndSetIfChanged(ref _controlsEnabled, value); }
|
||||||
|
|
||||||
|
private bool? everythingChecked = false;
|
||||||
|
public bool? EverythingChecked
|
||||||
|
{
|
||||||
|
get => everythingChecked;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
everythingChecked = value ?? false;
|
||||||
|
|
||||||
|
if (everythingChecked is true)
|
||||||
|
CheckAll();
|
||||||
|
else if (everythingChecked is false)
|
||||||
|
UncheckAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _totalBooksCount = 0;
|
||||||
|
private int _checkedBooksCount = -1;
|
||||||
|
public int CheckedBooksCount
|
||||||
|
{
|
||||||
|
get => _checkedBooksCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_checkedBooksCount = value;
|
||||||
|
this.RaisePropertyChanged(nameof(CheckedCountText));
|
||||||
|
|
||||||
|
everythingChecked
|
||||||
|
= _checkedBooksCount == 0 || _totalBooksCount == 0 ? false
|
||||||
|
: _checkedBooksCount == _totalBooksCount ? true
|
||||||
|
: null;
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(EverythingChecked));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<LibraryBook> CheckedBooks => DeletedBooks.Where(i => i.IsChecked).Select(i => i.Item).Cast<LibraryBook>();
|
||||||
|
|
||||||
|
public TrashBinViewModel()
|
||||||
|
{
|
||||||
|
DeletedBooks = new()
|
||||||
|
{
|
||||||
|
ResetBehavior = ResetBehavior.Remove
|
||||||
|
};
|
||||||
|
|
||||||
|
tracker = DeletedBooks.TrackItemPropertyChanged(CheckboxPropertyChanged);
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CheckAll()
|
||||||
|
{
|
||||||
|
foreach (var item in DeletedBooks)
|
||||||
|
item.IsChecked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UncheckAll()
|
||||||
|
{
|
||||||
|
foreach (var item in DeletedBooks)
|
||||||
|
item.IsChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RestoreCheckedAsync()
|
||||||
|
{
|
||||||
|
ControlsEnabled = false;
|
||||||
|
var qtyChanges = await Task.Run(CheckedBooks.RestoreBooks);
|
||||||
|
if (qtyChanges > 0)
|
||||||
|
Reload();
|
||||||
|
ControlsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PermanentlyDeleteCheckedAsync()
|
||||||
|
{
|
||||||
|
ControlsEnabled = false;
|
||||||
|
var qtyChanges = await Task.Run(CheckedBooks.PermanentlyDeleteBooks);
|
||||||
|
if (qtyChanges > 0)
|
||||||
|
Reload();
|
||||||
|
ControlsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
||||||
|
|
||||||
|
DeletedBooks.Clear();
|
||||||
|
DeletedBooks.AddRange(deletedBooks.Select(lb => new CheckBoxViewModel { Item = lb }));
|
||||||
|
|
||||||
|
_totalBooksCount = DeletedBooks.Count;
|
||||||
|
CheckedBooksCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDisposable tracker;
|
||||||
|
private void CheckboxPropertyChanged(Tuple<object, PropertyChangedEventArgs> e)
|
||||||
|
{
|
||||||
|
if (e.Item2.PropertyName == nameof(CheckBoxViewModel.IsChecked))
|
||||||
|
CheckedBooksCount = DeletedBooks.Count(b => b.IsChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => tracker?.Dispose();
|
||||||
|
}
|
||||||
@ -1,40 +1,12 @@
|
|||||||
using ApplicationServices;
|
namespace HangoverAvalonia.Views;
|
||||||
using DataLayer;
|
|
||||||
using HangoverAvalonia.Controls;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace HangoverAvalonia.Views
|
public partial class MainWindow
|
||||||
{
|
{
|
||||||
public partial class MainWindow
|
private void deletedTab_VisibleChanged(bool isVisible)
|
||||||
{
|
{
|
||||||
private void deletedTab_VisibleChanged(bool isVisible)
|
if (!isVisible)
|
||||||
{
|
return;
|
||||||
if (!isVisible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_viewModel.DeletedBooks.Count == 0)
|
_viewModel.TrashBinViewModel.Reload();
|
||||||
_viewModel.reload();
|
|
||||||
}
|
|
||||||
public void Deleted_CheckedListBox_ItemCheck(object sender, ItemCheckEventArgs args)
|
|
||||||
{
|
|
||||||
_viewModel.CheckedBooksCount = deletedCbl.CheckedItems.Count();
|
|
||||||
}
|
|
||||||
public void Deleted_CheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
foreach (var item in deletedCbl.Items)
|
|
||||||
deletedCbl.SetItemChecked(item, true);
|
|
||||||
}
|
|
||||||
public void Deleted_UncheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
foreach (var item in deletedCbl.Items)
|
|
||||||
deletedCbl.SetItemChecked(item, false);
|
|
||||||
}
|
|
||||||
public void Deleted_Save_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var libraryBooksToRestore = deletedCbl.CheckedItems.Cast<LibraryBook>().ToList();
|
|
||||||
var qtyChanges = libraryBooksToRestore.RestoreBooks();
|
|
||||||
if (qtyChanges > 0)
|
|
||||||
_viewModel.reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,13 +15,11 @@
|
|||||||
|
|
||||||
<TabControl Name="tabControl1" Grid.Row="0">
|
<TabControl Name="tabControl1" Grid.Row="0">
|
||||||
<TabControl.Styles>
|
<TabControl.Styles>
|
||||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter">
|
||||||
<Setter Property="Height" Value="23"/>
|
<Setter Property="Height" Value="33"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TabItem">
|
<Style Selector="TabItem /template/ Border#PART_LayoutRoot">
|
||||||
<Setter Property="MinHeight" Value="40"/>
|
<Setter Property="Height" Value="33"/>
|
||||||
<Setter Property="Height" Value="40"/>
|
|
||||||
<Setter Property="Padding" Value="8,2,8,5"/>
|
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TabItem#Header TextBlock">
|
<Style Selector="TabItem#Header TextBlock">
|
||||||
<Setter Property="MinHeight" Value="5"/>
|
<Setter Property="MinHeight" Value="5"/>
|
||||||
@ -51,6 +49,7 @@
|
|||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="0,5,0,5"
|
Margin="0,5,0,5"
|
||||||
|
AcceptsReturn="True"
|
||||||
Grid.Row="2" Text="{Binding SqlQuery, Mode=OneWayToSource}" />
|
Grid.Row="2" Text="{Binding SqlQuery, Mode=OneWayToSource}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -73,33 +72,58 @@
|
|||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
<TextBlock FontSize="14" VerticalAlignment="Center">Deleted Books</TextBlock>
|
<TextBlock FontSize="14" VerticalAlignment="Center">Deleted Books</TextBlock>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
|
DataContext="{Binding TrashBinViewModel}"
|
||||||
RowDefinitions="Auto,*,Auto">
|
RowDefinitions="Auto,*,Auto">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Text="To restore deleted book, check box and save" />
|
Text="Check books you want to permanently delete from or restore to Libation" />
|
||||||
|
|
||||||
<controls:CheckedListBox
|
<controls:CheckedListBox
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
BorderBrush="Gray"
|
BorderBrush="Gray"
|
||||||
Name="deletedCbl"
|
IsEnabled="{Binding ControlsEnabled}"
|
||||||
Items="{Binding DeletedBooks}" />
|
Items="{Binding DeletedBooks}" />
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
ColumnDefinitions="Auto,Auto,Auto,*">
|
ColumnDefinitions="Auto,Auto,*,Auto">
|
||||||
|
|
||||||
<Button Grid.Column="0" Margin="0,0,20,0" Content="Check All" Click="Deleted_CheckAll_Click" />
|
<CheckBox
|
||||||
<Button Grid.Column="1" Margin="0,0,20,0" Content="Uncheck All" Click="Deleted_UncheckAll_Click" />
|
IsEnabled="{Binding ControlsEnabled}"
|
||||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding CheckedCountText}" />
|
IsThreeState="True"
|
||||||
<Button Grid.Column="3" HorizontalAlignment="Right" Content="Save" Click="Deleted_Save_Click" />
|
Margin="0,0,20,0"
|
||||||
|
IsChecked="{Binding EverythingChecked}"
|
||||||
|
Content="Everything" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding CheckedCountText}" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
IsEnabled="{Binding ControlsEnabled}"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="0,0,20,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Content="Restore"
|
||||||
|
Command="{Binding RestoreCheckedAsync}"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
IsEnabled="{Binding ControlsEnabled}"
|
||||||
|
Grid.Column="3"
|
||||||
|
Command="{Binding PermanentlyDeleteCheckedAsync}" >
|
||||||
|
<TextBlock
|
||||||
|
TextAlignment="Center"
|
||||||
|
Text="Permanently Delete
from Libation" />
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@ -18,7 +18,6 @@ namespace HangoverAvalonia.Views
|
|||||||
|
|
||||||
public void OnLoad()
|
public void OnLoad()
|
||||||
{
|
{
|
||||||
deletedCbl.ItemCheck += Deleted_CheckedListBox_ItemCheck;
|
|
||||||
databaseTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) databaseTab_VisibleChanged(databaseTab.IsSelected); };
|
databaseTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) databaseTab_VisibleChanged(databaseTab.IsSelected); };
|
||||||
deletedTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) deletedTab_VisibleChanged(deletedTab.IsSelected); };
|
deletedTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) deletedTab_VisibleChanged(deletedTab.IsSelected); };
|
||||||
cliTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) cliTab_VisibleChanged(cliTab.IsSelected); };
|
cliTab.PropertyChanged += (_, e) => { if (e.Property.Name == nameof(TabItem.IsSelected)) cliTab_VisibleChanged(cliTab.IsSelected); };
|
||||||
|
|||||||
BIN
Source/LibationAvalonia/Assets/MBIcons/Asterisk.ico
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Asterisk_64.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Error.ico
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Error_64.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Exclamation.ico
Normal file
|
After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Exclamation_64.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Question.ico
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
BIN
Source/LibationAvalonia/Assets/MBIcons/Question_64.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
@ -5,26 +5,15 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="LibationAvalonia.Controls.CheckedListBox">
|
x:Class="LibationAvalonia.Controls.CheckedListBox">
|
||||||
|
|
||||||
<UserControl.Resources>
|
|
||||||
<RecyclePool x:Key="RecyclePool" />
|
|
||||||
<DataTemplate x:Key="queuedBook">
|
|
||||||
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
|
||||||
</DataTemplate>
|
|
||||||
<RecyclingElementFactory x:Key="elementFactory" RecyclePool="{StaticResource RecyclePool}">
|
|
||||||
<RecyclingElementFactory.Templates>
|
|
||||||
<StaticResource x:Key="queuedBook" ResourceKey="queuedBook" />
|
|
||||||
</RecyclingElementFactory.Templates>
|
|
||||||
</RecyclingElementFactory>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Name="scroller"
|
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<ItemsRepeater IsVisible="True"
|
<ItemsControl ItemsSource="{Binding $parent[1].Items}">
|
||||||
VerticalCacheLength="1.2"
|
<ItemsControl.ItemTemplate>
|
||||||
HorizontalCacheLength="1"
|
<DataTemplate>
|
||||||
ItemsSource="{Binding CheckboxItems}"
|
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
||||||
ItemTemplate="{StaticResource elementFactory}" />
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -12,24 +12,10 @@ namespace LibationAvalonia.Controls
|
|||||||
AvaloniaProperty.Register<CheckedListBox, AvaloniaList<CheckBoxViewModel>>(nameof(Items));
|
AvaloniaProperty.Register<CheckedListBox, AvaloniaList<CheckBoxViewModel>>(nameof(Items));
|
||||||
|
|
||||||
public AvaloniaList<CheckBoxViewModel> Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
|
public AvaloniaList<CheckBoxViewModel> Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
|
||||||
private CheckedListBoxViewModel _viewModel = new();
|
|
||||||
|
|
||||||
public CheckedListBox()
|
public CheckedListBox()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
scroller.DataContext = _viewModel;
|
|
||||||
}
|
|
||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
|
||||||
{
|
|
||||||
if (change.Property.Name == nameof(Items) && Items != null)
|
|
||||||
_viewModel.CheckboxItems = Items;
|
|
||||||
base.OnPropertyChanged(change);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CheckedListBoxViewModel : ViewModelBase
|
|
||||||
{
|
|
||||||
private AvaloniaList<CheckBoxViewModel> _checkboxItems;
|
|
||||||
public AvaloniaList<CheckBoxViewModel> CheckboxItems { get => _checkboxItems; set => this.RaiseAndSetIfChanged(ref _checkboxItems, value); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,11 +14,11 @@
|
|||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
|
||||||
VerticalAlignment="Top">
|
VerticalAlignment="Top">
|
||||||
|
|
||||||
<Panel Grid.Column="0" Margin="5,0,5,0" VerticalAlignment="Top">
|
<Panel Height="32" Width="32" Grid.Column="0" Margin="5,0,5,0" VerticalAlignment="Top">
|
||||||
<Image IsVisible="{Binding IsAsterisk}" Stretch="None" Source="/Assets/MBIcons/Asterisk.png"/>
|
<Image IsVisible="{Binding IsAsterisk}" Stretch="Uniform" Source="/Assets/MBIcons/Asterisk_64.png"/>
|
||||||
<Image IsVisible="{Binding IsError}" Stretch="None" Source="/Assets/MBIcons/error.png"/>
|
<Image IsVisible="{Binding IsError}" Stretch="Uniform" Source="/Assets/MBIcons/Error_64.png"/>
|
||||||
<Image IsVisible="{Binding IsQuestion}" Stretch="None" Source="/Assets/MBIcons/Question.png"/>
|
<Image IsVisible="{Binding IsQuestion}" Stretch="Uniform" Source="/Assets/MBIcons/Question_64.png"/>
|
||||||
<Image IsVisible="{Binding IsExclamation}" Stretch="None" Source="/Assets/MBIcons/Exclamation.png"/>
|
<Image IsVisible="{Binding IsExclamation}" Stretch="Uniform" Source="/Assets/MBIcons/Exclamation_64.png"/>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<TextBlock Margin="5,0,0,0" Name="messageTextBlock" MinHeight="45" MinWidth="193" TextWrapping="WrapWithOverflow" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="12" Text="{Binding Message}" />
|
<TextBlock Margin="5,0,0,0" Name="messageTextBlock" MinHeight="45" MinWidth="193" TextWrapping="WrapWithOverflow" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="12" Text="{Binding Message}" />
|
||||||
|
|||||||
@ -41,10 +41,10 @@
|
|||||||
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
|
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
|
||||||
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
|
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
|
||||||
<None Remove="Assets\1x1.png" />
|
<None Remove="Assets\1x1.png" />
|
||||||
<None Remove="Assets\MBIcons\Asterisk.png" />
|
<None Remove="Assets\MBIcons\Asterisk_64.png" />
|
||||||
<None Remove="Assets\MBIcons\error.png" />
|
<None Remove="Assets\MBIcons\Error_64.png" />
|
||||||
<None Remove="Assets\MBIcons\Exclamation.png" />
|
<None Remove="Assets\MBIcons\Exclamation_64.png" />
|
||||||
<None Remove="Assets\MBIcons\Question.png" />
|
<None Remove="Assets\MBIcons\Question_64.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -73,14 +73,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.2.8" />
|
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.8" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.0" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.8" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -40,6 +40,12 @@ namespace LibationAvalonia.ViewModels
|
|||||||
if (libraryBooks.Length == 1)
|
if (libraryBooks.Length == 1)
|
||||||
{
|
{
|
||||||
var item = libraryBooks[0];
|
var item = libraryBooks[0];
|
||||||
|
|
||||||
|
//Remove this item from the queue if it's already present and completed.
|
||||||
|
//Only do this when adding a single book at a time to prevent accidental
|
||||||
|
//extra downloads when queueing in batches.
|
||||||
|
ProcessQueue.RemoveCompleted(item);
|
||||||
|
|
||||||
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||||
|
|||||||
@ -130,6 +130,11 @@ namespace LibationAvalonia.ViewModels
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool RemoveCompleted(LibraryBook libraryBook)
|
||||||
|
=> Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId) is ProcessBookViewModel entry
|
||||||
|
&& entry.Status is ProcessBookStatus.Completed
|
||||||
|
&& Queue.RemoveCompleted(entry);
|
||||||
|
|
||||||
public void AddDownloadPdf(LibraryBook libraryBook)
|
public void AddDownloadPdf(LibraryBook libraryBook)
|
||||||
=> AddDownloadPdf(new List<LibraryBook>() { libraryBook });
|
=> AddDownloadPdf(new List<LibraryBook>() { libraryBook });
|
||||||
|
|
||||||
|
|||||||
@ -9,24 +9,16 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="650"
|
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="650"
|
||||||
Background="{DynamicResource SystemRegionColor}"
|
Background="{DynamicResource SystemRegionColor}"
|
||||||
x:Class="LibationAvalonia.Views.ProcessQueueControl">
|
x:Class="LibationAvalonia.Views.ProcessQueueControl">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<views:DecimalConverter x:Key="myConverter" />
|
<views:DecimalConverter x:Key="myConverter" />
|
||||||
<RecyclePool x:Key="RecyclePool" />
|
|
||||||
<DataTemplate x:Key="queuedBook">
|
|
||||||
<views:ProcessBookControl />
|
|
||||||
</DataTemplate>
|
|
||||||
<RecyclingElementFactory x:Key="elementFactory" RecyclePool="{StaticResource RecyclePool}">
|
|
||||||
<RecyclingElementFactory.Templates>
|
|
||||||
<StaticResource x:Key="queuedBook" ResourceKey="queuedBook" />
|
|
||||||
</RecyclingElementFactory.Templates>
|
|
||||||
</RecyclingElementFactory>
|
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid RowDefinitions="*,Auto">
|
<Grid RowDefinitions="*,Auto">
|
||||||
<TabControl Grid.Row="0">
|
<TabControl Grid.Row="0">
|
||||||
<TabControl.Styles>
|
<TabControl.Styles>
|
||||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter">
|
||||||
|
<Setter Property="Height" Value="33"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem /template/ Border#PART_LayoutRoot">
|
||||||
<Setter Property="Height" Value="33"/>
|
<Setter Property="Height" Value="33"/>
|
||||||
</Style>
|
</Style>
|
||||||
</TabControl.Styles>
|
</TabControl.Styles>
|
||||||
@ -42,14 +34,13 @@
|
|||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
AllowAutoHide="False">
|
AllowAutoHide="False">
|
||||||
<ItemsRepeater IsVisible="True"
|
<ItemsControl ItemsSource="{Binding Items}">
|
||||||
Grid.Column="0"
|
<ItemsControl.ItemTemplate>
|
||||||
Name="repeater"
|
<DataTemplate>
|
||||||
VerticalCacheLength="1.2"
|
<views:ProcessBookControl DataContext="{Binding}" />
|
||||||
HorizontalCacheLength="1"
|
</DataTemplate>
|
||||||
Background="Transparent"
|
</ItemsControl.ItemTemplate>
|
||||||
ItemsSource="{Binding Items}"
|
</ItemsControl>
|
||||||
ItemTemplate="{StaticResource elementFactory}" />
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
<Grid Grid.Column="0" Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
<Grid Grid.Column="0" Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
||||||
|
|||||||
@ -30,7 +30,8 @@ namespace LibationWinForms.Dialogs
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
System.Media.SystemSounds.Hand.Play();
|
System.Media.SystemSounds.Hand.Play();
|
||||||
pictureBox1.Image = SystemIcons.Error.ToBitmap();
|
//This is a different (and newer) icon from SystemIcons.Error
|
||||||
|
pictureBox1.Image = SystemIcons.GetStockIcon(StockIconId.Error).ToBitmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void githubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
private void githubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
|
|||||||
@ -30,6 +30,12 @@ namespace LibationWinForms
|
|||||||
if (libraryBooks.Length == 1)
|
if (libraryBooks.Length == 1)
|
||||||
{
|
{
|
||||||
var item = libraryBooks[0];
|
var item = libraryBooks[0];
|
||||||
|
|
||||||
|
//Remove this item from the queue if it's already present and completed.
|
||||||
|
//Only do this when adding a single book at a time to prevent accidental
|
||||||
|
//extra downloads when queueing in batches.
|
||||||
|
processBookQueue1.RemoveCompleted(item);
|
||||||
|
|
||||||
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||||
|
|||||||
@ -92,6 +92,11 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool RemoveCompleted(DataLayer.LibraryBook libraryBook)
|
||||||
|
=> Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId) is ProcessBook entry
|
||||||
|
&& entry.Status is ProcessBookStatus.Completed
|
||||||
|
&& Queue.RemoveCompleted(entry);
|
||||||
|
|
||||||
public void AddDownloadPdf(DataLayer.LibraryBook libraryBook)
|
public void AddDownloadPdf(DataLayer.LibraryBook libraryBook)
|
||||||
=> AddDownloadPdf(new List<DataLayer.LibraryBook>() { libraryBook });
|
=> AddDownloadPdf(new List<DataLayer.LibraryBook>() { libraryBook });
|
||||||
|
|
||||||
|
|||||||