@ -43,9 +43,11 @@ namespace DataLayer
|
||||
public ContentType ContentType { get; private set; }
|
||||
public string Locale { get; private set; }
|
||||
|
||||
//This field is now unused, however, there is little sense in adding a
|
||||
//database migration to remove an unused field. Leave it for compatibility.
|
||||
internal long _audioFormat;
|
||||
//This field is now unused, however, there is little sense in adding a
|
||||
//database migration to remove an unused field. Leave it for compatibility.
|
||||
#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
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
return options;
|
||||
@ -356,7 +365,7 @@ public partial class DownloadOptions
|
||||
else if (titleConcat is null)
|
||||
{
|
||||
chaps.Add(c);
|
||||
chaps.AddRange(flattenChapters(c.Chapters));
|
||||
chaps.AddRange(flattenChapters(c.Chapters, titleConcat));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -369,7 +378,7 @@ public partial class DownloadOptions
|
||||
else
|
||||
chaps.Add(c);
|
||||
|
||||
var children = flattenChapters(c.Chapters);
|
||||
var children = flattenChapters(c.Chapters, titleConcat);
|
||||
|
||||
foreach (var child in children)
|
||||
child.Title = $"{c.Title}{titleConcat}{child.Title}";
|
||||
|
||||
@ -6,7 +6,30 @@
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme/>
|
||||
</Application.Styles>
|
||||
<Application.Styles>
|
||||
<FluentTheme>
|
||||
<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>
|
||||
|
||||
@ -5,26 +5,15 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
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
|
||||
Name="scroller"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsRepeater IsVisible="True"
|
||||
VerticalCacheLength="1.2"
|
||||
HorizontalCacheLength="1"
|
||||
ItemsSource="{Binding CheckboxItems}"
|
||||
ItemTemplate="{StaticResource elementFactory}" />
|
||||
<ItemsControl ItemsSource="{Binding $parent[1].Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@ -2,103 +2,18 @@ using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
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;
|
||||
|
||||
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; }
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,13 +71,12 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||
<!--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 Include="Avalonia.ReactiveUI" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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;
|
||||
using DataLayer;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
namespace HangoverAvalonia.ViewModels;
|
||||
|
||||
namespace HangoverAvalonia.ViewModels
|
||||
public partial class MainVM
|
||||
{
|
||||
public partial class MainVM
|
||||
{
|
||||
private List<LibraryBook> _deletedBooks;
|
||||
public List<LibraryBook> DeletedBooks { get => _deletedBooks; set => this.RaiseAndSetIfChanged(ref _deletedBooks, value); }
|
||||
public string CheckedCountText => $"Checked : {_checkedBooksCount} of {_totalBooksCount}";
|
||||
public TrashBinViewModel TrashBinViewModel { get; } = new();
|
||||
|
||||
private int _totalBooksCount = 0;
|
||||
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));
|
||||
}
|
||||
}
|
||||
private void Load_deletedVM() { }
|
||||
}
|
||||
|
||||
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;
|
||||
using DataLayer;
|
||||
using HangoverAvalonia.Controls;
|
||||
using System.Linq;
|
||||
namespace HangoverAvalonia.Views;
|
||||
|
||||
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.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();
|
||||
}
|
||||
_viewModel.TrashBinViewModel.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,13 +15,11 @@
|
||||
|
||||
<TabControl Name="tabControl1" Grid.Row="0">
|
||||
<TabControl.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="23"/>
|
||||
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="33"/>
|
||||
</Style>
|
||||
<Style Selector="TabItem">
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
<Setter Property="Height" Value="40"/>
|
||||
<Setter Property="Padding" Value="8,2,8,5"/>
|
||||
<Style Selector="TabItem /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Height" Value="33"/>
|
||||
</Style>
|
||||
<Style Selector="TabItem#Header TextBlock">
|
||||
<Setter Property="MinHeight" Value="5"/>
|
||||
@ -51,6 +49,7 @@
|
||||
|
||||
<TextBox
|
||||
Margin="0,5,0,5"
|
||||
AcceptsReturn="True"
|
||||
Grid.Row="2" Text="{Binding SqlQuery, Mode=OneWayToSource}" />
|
||||
|
||||
<Button
|
||||
@ -73,33 +72,58 @@
|
||||
<TabItem.Header>
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center">Deleted Books</TextBlock>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid
|
||||
DataContext="{Binding TrashBinViewModel}"
|
||||
RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
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
|
||||
Grid.Row="1"
|
||||
Margin="5,0,5,0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="Gray"
|
||||
Name="deletedCbl"
|
||||
IsEnabled="{Binding ControlsEnabled}"
|
||||
Items="{Binding DeletedBooks}" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
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" />
|
||||
<Button Grid.Column="1" Margin="0,0,20,0" Content="Uncheck All" Click="Deleted_UncheckAll_Click" />
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding CheckedCountText}" />
|
||||
<Button Grid.Column="3" HorizontalAlignment="Right" Content="Save" Click="Deleted_Save_Click" />
|
||||
<CheckBox
|
||||
IsEnabled="{Binding ControlsEnabled}"
|
||||
IsThreeState="True"
|
||||
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>
|
||||
</TabItem>
|
||||
|
||||
@ -18,7 +18,6 @@ namespace HangoverAvalonia.Views
|
||||
|
||||
public void OnLoad()
|
||||
{
|
||||
deletedCbl.ItemCheck += Deleted_CheckedListBox_ItemCheck;
|
||||
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); };
|
||||
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"
|
||||
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
|
||||
Name="scroller"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsRepeater IsVisible="True"
|
||||
VerticalCacheLength="1.2"
|
||||
HorizontalCacheLength="1"
|
||||
ItemsSource="{Binding CheckboxItems}"
|
||||
ItemTemplate="{StaticResource elementFactory}" />
|
||||
<ItemsControl ItemsSource="{Binding $parent[1].Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@ -12,24 +12,10 @@ namespace LibationAvalonia.Controls
|
||||
AvaloniaProperty.Register<CheckedListBox, AvaloniaList<CheckBoxViewModel>>(nameof(Items));
|
||||
|
||||
public AvaloniaList<CheckBoxViewModel> Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
|
||||
private CheckedListBoxViewModel _viewModel = new();
|
||||
|
||||
public CheckedListBox()
|
||||
{
|
||||
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"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
<Panel Grid.Column="0" Margin="5,0,5,0" VerticalAlignment="Top">
|
||||
<Image IsVisible="{Binding IsAsterisk}" Stretch="None" Source="/Assets/MBIcons/Asterisk.png"/>
|
||||
<Image IsVisible="{Binding IsError}" Stretch="None" Source="/Assets/MBIcons/error.png"/>
|
||||
<Image IsVisible="{Binding IsQuestion}" Stretch="None" Source="/Assets/MBIcons/Question.png"/>
|
||||
<Image IsVisible="{Binding IsExclamation}" Stretch="None" Source="/Assets/MBIcons/Exclamation.png"/>
|
||||
<Panel Height="32" Width="32" Grid.Column="0" Margin="5,0,5,0" VerticalAlignment="Top">
|
||||
<Image IsVisible="{Binding IsAsterisk}" Stretch="Uniform" Source="/Assets/MBIcons/Asterisk_64.png"/>
|
||||
<Image IsVisible="{Binding IsError}" Stretch="Uniform" Source="/Assets/MBIcons/Error_64.png"/>
|
||||
<Image IsVisible="{Binding IsQuestion}" Stretch="Uniform" Source="/Assets/MBIcons/Question_64.png"/>
|
||||
<Image IsVisible="{Binding IsExclamation}" Stretch="Uniform" Source="/Assets/MBIcons/Exclamation_64.png"/>
|
||||
</Panel>
|
||||
|
||||
<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_80x80.jpg" />
|
||||
<None Remove="Assets\1x1.png" />
|
||||
<None Remove="Assets\MBIcons\Asterisk.png" />
|
||||
<None Remove="Assets\MBIcons\error.png" />
|
||||
<None Remove="Assets\MBIcons\Exclamation.png" />
|
||||
<None Remove="Assets\MBIcons\Question.png" />
|
||||
<None Remove="Assets\MBIcons\Asterisk_64.png" />
|
||||
<None Remove="Assets\MBIcons\Error_64.png" />
|
||||
<None Remove="Assets\MBIcons\Exclamation_64.png" />
|
||||
<None Remove="Assets\MBIcons\Question_64.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -73,14 +73,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.8" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.0" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -40,6 +40,12 @@ namespace LibationAvalonia.ViewModels
|
||||
if (libraryBooks.Length == 1)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||
|
||||
@ -130,6 +130,11 @@ namespace LibationAvalonia.ViewModels
|
||||
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)
|
||||
=> AddDownloadPdf(new List<LibraryBook>() { libraryBook });
|
||||
|
||||
|
||||
@ -9,24 +9,16 @@
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="650"
|
||||
Background="{DynamicResource SystemRegionColor}"
|
||||
x:Class="LibationAvalonia.Views.ProcessQueueControl">
|
||||
|
||||
<UserControl.Resources>
|
||||
<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>
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<TabControl Grid.Row="0">
|
||||
<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"/>
|
||||
</Style>
|
||||
</TabControl.Styles>
|
||||
@ -42,14 +34,13 @@
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AllowAutoHide="False">
|
||||
<ItemsRepeater IsVisible="True"
|
||||
Grid.Column="0"
|
||||
Name="repeater"
|
||||
VerticalCacheLength="1.2"
|
||||
HorizontalCacheLength="1"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Items}"
|
||||
ItemTemplate="{StaticResource elementFactory}" />
|
||||
<ItemsControl ItemsSource="{Binding Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<views:ProcessBookControl DataContext="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Grid Grid.Column="0" Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
||||
|
||||
@ -30,7 +30,8 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
|
||||
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)
|
||||
|
||||
@ -30,6 +30,12 @@ namespace LibationWinForms
|
||||
if (libraryBooks.Length == 1)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||
|
||||
@ -92,6 +92,11 @@ namespace LibationWinForms.ProcessQueue
|
||||
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)
|
||||
=> AddDownloadPdf(new List<DataLayer.LibraryBook>() { libraryBook });
|
||||
|
||||
|
||||