mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-01 18:38:01 -05:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
012d94a146 | ||
|
|
22bd1ed121 | ||
|
|
c832f26b08 | ||
|
|
efd73d334e | ||
|
|
0db3ee6fd7 | ||
|
|
6aaf4f63d1 | ||
|
|
ab392a9285 | ||
|
|
efc9ff4bd8 | ||
|
|
a52b466c85 | ||
|
|
5611431abf | ||
|
|
a75932d1f4 | ||
|
|
6c8464b650 | ||
|
|
ba4a1c5a51 | ||
|
|
3681c0f18f | ||
|
|
e365ba7296 | ||
|
|
2afb5365dd | ||
|
|
00cf7693d5 | ||
|
|
dac6877a06 | ||
|
|
36005508a1 | ||
|
|
d9e27fd32e | ||
|
|
d86bcbb414 | ||
|
|
00cbab5b58 | ||
|
|
807725f6ff | ||
|
|
ec9356b36e | ||
|
|
add31024da | ||
|
|
27d2ada5a4 | ||
|
|
702219ee69 | ||
|
|
cdf1a01457 | ||
|
|
a71ccbac6e | ||
|
|
f8c6b836c3 | ||
|
|
090871f50d | ||
|
|
68af6a5ebb | ||
|
|
8bba8538d5 |
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>9.4.7.1</Version>
|
||||
<Version>10.0.2.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="5.0.2" />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using NPOI.XWPF.UserModel;
|
||||
using System;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AppScaffolding
|
||||
|
||||
@@ -12,7 +12,6 @@ using DtoImporterService;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NPOI.OpenXmlFormats.Spreadsheet;
|
||||
using Serilog;
|
||||
using static DtoImporterService.PerfLogger;
|
||||
|
||||
@@ -21,7 +20,7 @@ namespace ApplicationServices
|
||||
public static class LibraryCommands
|
||||
{
|
||||
public static event EventHandler<int> ScanBegin;
|
||||
public static event EventHandler ScanEnd;
|
||||
public static event EventHandler<int> ScanEnd;
|
||||
|
||||
public static bool Scanning { get; private set; }
|
||||
private static object _lock { get; } = new();
|
||||
@@ -95,7 +94,7 @@ namespace ApplicationServices
|
||||
{
|
||||
stop();
|
||||
var putBreakPointHere = logOutput;
|
||||
ScanEnd?.Invoke(null, null);
|
||||
ScanEnd?.Invoke(null, 0);
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +107,8 @@ namespace ApplicationServices
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
return (0, 0);
|
||||
|
||||
try
|
||||
int newCount = 0;
|
||||
try
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -134,7 +134,7 @@ namespace ApplicationServices
|
||||
|
||||
Log.Logger.Information("Begin long-running import");
|
||||
logTime($"pre {nameof(importIntoDbAsync)}");
|
||||
var newCount = await importIntoDbAsync(importItems);
|
||||
newCount = await importIntoDbAsync(importItems);
|
||||
logTime($"post {nameof(importIntoDbAsync)}");
|
||||
Log.Logger.Information($"Import complete. New count {newCount}");
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace ApplicationServices
|
||||
{
|
||||
stop();
|
||||
var putBreakPointHere = logOutput;
|
||||
ScanEnd?.Invoke(null, null);
|
||||
ScanEnd?.Invoke(null, newCount);
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,8 @@ namespace ApplicationServices
|
||||
catch(ImportValidationException ex)
|
||||
{
|
||||
await logDtoItemsAsync(ex.Items, ex.InnerExceptions.ToArray());
|
||||
throw;
|
||||
//If ImportValidationException is thrown, all Dto items get logged as part of the exception
|
||||
throw new AggregateException(ex.InnerExceptions);
|
||||
}
|
||||
|
||||
async Task logDtoItemsAsync(IEnumerable<AudibleApi.Common.Item> dtoItems, IEnumerable<Exception> exceptions = null)
|
||||
|
||||
@@ -152,13 +152,17 @@ namespace AudibleUtilities
|
||||
SetSeries(parent, children);
|
||||
}
|
||||
|
||||
int orphansRemoved = items.RemoveAll(i => (i.IsEpisodes || i.IsSeriesParent) && i.Series is null);
|
||||
if (orphansRemoved > 0)
|
||||
Serilog.Log.Debug("{orphansRemoved} podcast orphans not imported", orphansRemoved);
|
||||
|
||||
sw.Stop();
|
||||
totalTime += sw.Elapsed;
|
||||
Serilog.Log.Logger.Information("Completed indexing series episodes after {elappsed_ms} ms.", sw.ElapsedMilliseconds);
|
||||
Serilog.Log.Logger.Information($"Completed library scan in {totalTime.TotalMilliseconds:F0} ms.");
|
||||
|
||||
var allExceptions = IValidator.GetAllValidators().SelectMany(v => v.Validate(items));
|
||||
if (allExceptions?.Any() is true)
|
||||
var allExceptions = IValidator.GetAllValidators().SelectMany(v => v.Validate(items)).ToList();
|
||||
if (allExceptions?.Count > 0)
|
||||
throw new ImportValidationException(items, allExceptions);
|
||||
|
||||
return items;
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace DtoImporterService
|
||||
var productIds = importItems
|
||||
.Select(i => i.DtoItem.ProductId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
.ToHashSet();
|
||||
|
||||
Cache = DbContext.Books
|
||||
.GetBooks(b => productIds.Contains(b.AudibleProductId))
|
||||
|
||||
@@ -41,11 +41,18 @@ namespace DtoImporterService
|
||||
//
|
||||
// CURRENT SOLUTION: don't re-insert
|
||||
|
||||
var existingEntries = DbContext.LibraryBooks.AsEnumerable().Where(l => l.Book is not null).ToDictionary(l => l.Book.AudibleProductId);
|
||||
var hash = ToDictionarySafe(importItems, dto => dto.DtoItem.ProductId, tieBreak);
|
||||
|
||||
//When Books are upserted during the BookImporter run, they are linked to their LibraryBook in the DbContext
|
||||
//instance. If a LibraryBook has a null book here, that means it's Book was not imported during by BookImporter.
|
||||
//There should never be duplicates, but this is defensive.
|
||||
var existingEntries = DbContext.LibraryBooks.AsEnumerable().Where(l => l.Book is not null).ToDictionarySafe(l => l.Book.AudibleProductId);
|
||||
|
||||
//If importItems are contains duplicates by asin, keep the Item that's "available"
|
||||
var uniqueImportItems = ToDictionarySafe(importItems, dto => dto.DtoItem.ProductId, tieBreak);
|
||||
|
||||
int qtyNew = 0;
|
||||
|
||||
foreach (var item in hash.Values)
|
||||
foreach (var item in uniqueImportItems.Values)
|
||||
{
|
||||
if (existingEntries.TryGetValue(item.DtoItem.ProductId, out LibraryBook existing))
|
||||
{
|
||||
@@ -109,7 +116,7 @@ namespace DtoImporterService
|
||||
=> isPlusTitleUnavailable(item1) && !isPlusTitleUnavailable(item2) ? item2 : item1;
|
||||
|
||||
private static bool isPlusTitleUnavailable(ImportItem item)
|
||||
=> item.DtoItem.IsAyce is true
|
||||
&& item.DtoItem.Plans?.Any(p => p.IsAyce) is not true;
|
||||
=> item.DtoItem.ContentType is null
|
||||
|| (item.DtoItem.IsAyce is true && item.DtoItem.Plans?.Any(p => p.IsAyce) is not true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
using Avalonia;
|
||||
using ApplicationServices;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using LibationFileManager;
|
||||
using LibationAvalonia.Views;
|
||||
using System;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using LibationAvalonia.Views;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using ApplicationServices;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
public static Window MainWindow { get;private set; }
|
||||
public static Window MainWindow { get; private set; }
|
||||
public static IBrush ProcessQueueBookFailedBrush { get; private set; }
|
||||
public static IBrush ProcessQueueBookCompletedBrush { get; private set; }
|
||||
public static IBrush ProcessQueueBookCancelledBrush { get; private set; }
|
||||
@@ -39,18 +39,15 @@ namespace LibationAvalonia
|
||||
}
|
||||
|
||||
public static Task<List<DataLayer.LibraryBook>> LibraryTask;
|
||||
public static bool SetupRequired;
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
LoadStyles();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (SetupRequired)
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (!config.LibationSettingsAreValid)
|
||||
{
|
||||
var defaultLibationFilesDir = Configuration.UserProfile;
|
||||
|
||||
// check for existing settings in default location
|
||||
@@ -60,7 +57,7 @@ namespace LibationAvalonia
|
||||
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
ShowMainWindow(desktop);
|
||||
}
|
||||
else
|
||||
@@ -86,11 +83,29 @@ namespace LibationAvalonia
|
||||
{
|
||||
// all returns should be preceded by either:
|
||||
// - if config.LibationSettingsAreValid
|
||||
// - error message, Exit()
|
||||
// - error message, Exit()
|
||||
if (setupDialog.IsNewUser)
|
||||
{
|
||||
Configuration.SetLibationFiles(Configuration.UserProfile);
|
||||
ShowSettingsWindow(desktop, setupDialog.Config, OnSettingsCompleted);
|
||||
setupDialog.Config.Books = Path.Combine(Configuration.UserProfile, nameof(Configuration.Books));
|
||||
|
||||
if (setupDialog.Config.LibationSettingsAreValid)
|
||||
{
|
||||
var theme
|
||||
= setupDialog.SelectedTheme.Content is nameof(ThemeVariant.Dark)
|
||||
? nameof(ThemeVariant.Dark)
|
||||
: nameof(ThemeVariant.Light);
|
||||
|
||||
setupDialog.Config.SetString(theme, nameof(ThemeVariant));
|
||||
|
||||
|
||||
await RunMigrationsAsync(setupDialog.Config);
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
ShowMainWindow(desktop);
|
||||
}
|
||||
else
|
||||
await CancelInstallation();
|
||||
}
|
||||
else if (setupDialog.IsReturningUser)
|
||||
{
|
||||
@@ -130,40 +145,6 @@ namespace LibationAvalonia
|
||||
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);
|
||||
}
|
||||
|
||||
private void ShowSettingsWindow(IClassicDesktopStyleApplicationLifetime desktop, Configuration config, Action<IClassicDesktopStyleApplicationLifetime, SettingsDialog, Configuration> OnClose)
|
||||
{
|
||||
config.Books ??= Path.Combine(Configuration.UserProfile, "Books");
|
||||
|
||||
var settingsDialog = new SettingsDialog();
|
||||
desktop.MainWindow = settingsDialog;
|
||||
settingsDialog.RestoreSizeAndLocation(Configuration.Instance);
|
||||
settingsDialog.Show();
|
||||
|
||||
void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
settingsDialog.Closing -= WindowClosing;
|
||||
e.Cancel = true;
|
||||
OnClose?.Invoke(desktop, settingsDialog, config);
|
||||
}
|
||||
settingsDialog.Closing += WindowClosing;
|
||||
}
|
||||
|
||||
private async void OnSettingsCompleted(IClassicDesktopStyleApplicationLifetime desktop, SettingsDialog settingsDialog, Configuration config)
|
||||
{
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
await RunMigrationsAsync(config);
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
ShowMainWindow(desktop);
|
||||
}
|
||||
else
|
||||
await CancelInstallation();
|
||||
|
||||
settingsDialog.Close();
|
||||
}
|
||||
|
||||
|
||||
private void ShowLibationFilesDialog(IClassicDesktopStyleApplicationLifetime desktop, Configuration config, Action<IClassicDesktopStyleApplicationLifetime, LibationFilesDialog, Configuration> OnClose)
|
||||
{
|
||||
var libationFilesDialog = new LibationFilesDialog();
|
||||
@@ -200,11 +181,23 @@ namespace LibationAvalonia
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (continueResult == DialogResult.Yes)
|
||||
ShowSettingsWindow(desktop, config, OnSettingsCompleted);
|
||||
{
|
||||
config.Books = Path.Combine(libationFilesDialog.SelectedDirectory, nameof(Configuration.Books));
|
||||
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
await RunMigrationsAsync(config);
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
ShowMainWindow(desktop);
|
||||
}
|
||||
else
|
||||
await CancelInstallation();
|
||||
}
|
||||
else
|
||||
await CancelInstallation();
|
||||
|
||||
}
|
||||
|
||||
libationFilesDialog.Close();
|
||||
}
|
||||
|
||||
@@ -230,11 +223,11 @@ namespace LibationAvalonia
|
||||
|
||||
private static void LoadStyles()
|
||||
{
|
||||
ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush");
|
||||
ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush");
|
||||
ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush");
|
||||
SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush");
|
||||
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush");
|
||||
ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookFailedBrush));
|
||||
ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookCompletedBrush));
|
||||
ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookCancelledBrush));
|
||||
SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources(nameof(SeriesEntryGridBackgroundBrush));
|
||||
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookDefaultBrush));
|
||||
HyperlinkVisited = AvaloniaUtils.GetBrushFromResources(nameof(HyperlinkVisited));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace LibationAvalonia
|
||||
public static IBrush GetBrushFromResources(string name)
|
||||
=> GetBrushFromResources(name, Brushes.Transparent);
|
||||
public static IBrush GetBrushFromResources(string name, IBrush defaultBrush)
|
||||
{
|
||||
{
|
||||
if (App.Current.TryGetResource(name, App.Current.ActualThemeVariant, out var value) && value is IBrush brush)
|
||||
return brush;
|
||||
return defaultBrush;
|
||||
|
||||
@@ -3,9 +3,6 @@ using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
@@ -15,7 +12,7 @@ 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();
|
||||
private CheckedListBoxViewModel _viewModel = new();
|
||||
|
||||
public CheckedListBox()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using LibationUiBase.GridView;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
|
||||
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
|
||||
{
|
||||
protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace LibationAvalonia.Controls
|
||||
|
||||
static DataGridContextMenus()
|
||||
{
|
||||
ContextMenu.Items = MenuItems;
|
||||
ContextMenu.ItemsSource = MenuItems;
|
||||
OwningColumnProperty = typeof(DataGridCell).GetProperty("OwningColumn", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,6 @@ namespace LibationAvalonia.Controls
|
||||
public IGridEntry GridEntry { get; init; }
|
||||
public ContextMenu ContextMenu { get; init; }
|
||||
public AvaloniaList<Control> ContextMenuItems
|
||||
=> ContextMenu.Items as AvaloniaList<Control>;
|
||||
=> ContextMenu.ItemsSource as AvaloniaList<Control>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using DataLayer;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
@@ -56,12 +55,12 @@ namespace LibationAvalonia.Controls
|
||||
directorySelectControl.PropertyChanged += DirectorySelectControl_PropertyChanged;
|
||||
}
|
||||
|
||||
private class CustomState: ViewModels.ViewModelBase
|
||||
private class CustomState : ViewModels.ViewModelBase
|
||||
{
|
||||
private string _customDir;
|
||||
private bool _knownChecked;
|
||||
private bool _customChecked;
|
||||
public string CustomDir { get=> _customDir; set => this.RaiseAndSetIfChanged(ref _customDir, value); }
|
||||
public string CustomDir { get => _customDir; set => this.RaiseAndSetIfChanged(ref _customDir, value); }
|
||||
public bool KnownChecked
|
||||
{
|
||||
get => _knownChecked;
|
||||
@@ -141,7 +140,7 @@ namespace LibationAvalonia.Controls
|
||||
var known = Configuration.GetKnownDirectory(noSubDir);
|
||||
|
||||
if (known == Configuration.KnownDirectories.None && noSubDir == Configuration.AppDir_Absolute)
|
||||
known = Configuration.KnownDirectories.AppDir;
|
||||
known = Configuration.KnownDirectories.AppDir;
|
||||
|
||||
if (known is Configuration.KnownDirectories.None)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
using System.IO;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using System;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace LibationAvalonia.Controls
|
||||
|
||||
public bool IsEditingMode { get; set; }
|
||||
public Rating Rating { get => GetValue(RatingProperty); set => SetValue(RatingProperty, value); }
|
||||
|
||||
|
||||
public MyRatingCellEditor()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Controls
|
||||
{
|
||||
|
||||
@@ -64,11 +64,14 @@
|
||||
<DataGridCheckBoxColumn
|
||||
Binding="{Binding LibraryScan, Mode=TwoWay}"
|
||||
Header="Include in
library scan?"/>
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="2*"
|
||||
Binding="{Binding AccountId, Mode=TwoWay}"
|
||||
Header="Audible
email/login"/>
|
||||
|
||||
<DataGridTemplateColumn Width="2*" Header="Audible
email/login">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding AccountId, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="Auto" Header="Locale">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
@@ -93,10 +96,13 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="3*"
|
||||
Binding="{Binding AccountName, Mode=TwoWay}"
|
||||
Header="Account Nickname
(optional)"/>
|
||||
<DataGridTemplateColumn Width="3*" Header="Account Nickname
(optional)">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding AccountName, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using AudibleApi;
|
||||
using Avalonia.Platform.Storage;
|
||||
using LibationFileManager;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public partial class AccountsDialog : DialogWindow
|
||||
{
|
||||
public ObservableCollection<AccountDto> Accounts { get; } = new();
|
||||
public AvaloniaList<AccountDto> Accounts { get; } = new();
|
||||
public class AccountDto : ViewModels.ViewModelBase
|
||||
{
|
||||
private string _accountId;
|
||||
private Locale _selectedLocale;
|
||||
public IReadOnlyList<Locale> Locales => AccountsDialog.Locales;
|
||||
public bool LibraryScan { get; set; } = true;
|
||||
public string AccountId
|
||||
@@ -31,19 +28,21 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
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 Locale SelectedLocale { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public bool IsDefault => string.IsNullOrEmpty(AccountId) && SelectedLocale is null;
|
||||
public bool IsDefault => string.IsNullOrEmpty(AccountId);
|
||||
|
||||
public AccountDto() { }
|
||||
public AccountDto(Account account)
|
||||
{
|
||||
LibraryScan = account.LibraryScan;
|
||||
AccountId = account.AccountId;
|
||||
SelectedLocale = Locales.Single(l => l.Name == account.Locale.Name);
|
||||
AccountName = account.AccountName;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetAudibleCliAppDataPath()
|
||||
@@ -58,57 +57,40 @@ namespace LibationAvalonia.Dialogs
|
||||
// here: copy strings and dispose of persister
|
||||
// only persist in 'save' step
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.Accounts;
|
||||
if (accounts.Any())
|
||||
{
|
||||
foreach (var account in accounts)
|
||||
AddAccountToGrid(account);
|
||||
}
|
||||
|
||||
Accounts.CollectionChanged += Accounts_CollectionChanged;
|
||||
Accounts.AddRange(persister.AccountsSettings.Accounts.Select(a => new AccountDto(a)));
|
||||
|
||||
DataContext = this;
|
||||
addBlankAccount();
|
||||
}
|
||||
private void addBlankAccount()
|
||||
{
|
||||
|
||||
var newBlank = new AccountDto();
|
||||
newBlank.PropertyChanged += AccountDto_PropertyChanged;
|
||||
Accounts.Insert(Accounts.Count, newBlank);
|
||||
}
|
||||
|
||||
private void AddAccountToGrid(Account account)
|
||||
private void Accounts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
AccountDto accountDto = new()
|
||||
if (e.Action is NotifyCollectionChangedAction.Add && e.NewItems?.Count > 0)
|
||||
{
|
||||
LibraryScan = account.LibraryScan,
|
||||
AccountId = account.AccountId,
|
||||
SelectedLocale = Locales.Single(l => l.Name == account.Locale.Name),
|
||||
AccountName = account.AccountName,
|
||||
};
|
||||
accountDto.PropertyChanged += AccountDto_PropertyChanged;
|
||||
|
||||
//ObservableCollection doesn't fire CollectionChanged on Add, so use Insert instead
|
||||
Accounts.Insert(Accounts.Count, accountDto);
|
||||
foreach (var newItem in e.NewItems.OfType<AccountDto>())
|
||||
newItem.PropertyChanged += AccountDto_PropertyChanged;
|
||||
}
|
||||
else if (e.Action is NotifyCollectionChangedAction.Remove && e.OldItems?.Count > 0)
|
||||
{
|
||||
foreach (var oldItem in e.OldItems.OfType<AccountDto>())
|
||||
oldItem.PropertyChanged -= AccountDto_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void addBlankAccount() => Accounts.Insert(Accounts.Count, new AccountDto());
|
||||
|
||||
private void AccountDto_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (Accounts.Any(a => a.IsDefault))
|
||||
return;
|
||||
|
||||
addBlankAccount();
|
||||
if (!Accounts.Any(a => a.IsDefault))
|
||||
addBlankAccount();
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -119,11 +101,11 @@ namespace LibationAvalonia.Dialogs
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new FilePickerFileType[]
|
||||
{
|
||||
new("JSON files (*.json)")
|
||||
{
|
||||
Patterns = new[] { "*.json" },
|
||||
AppleUniformTypeIdentifiers = new[] { "public.json" }
|
||||
}
|
||||
new("JSON files (*.json)")
|
||||
{
|
||||
Patterns = new[] { "*.json" },
|
||||
AppleUniformTypeIdentifiers = new[] { "public.json" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,7 +135,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
persister.AccountsSettings.Add(account);
|
||||
|
||||
AddAccountToGrid(account);
|
||||
Accounts.Add(new AccountDto(account));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
@@ -42,14 +41,14 @@ namespace LibationAvalonia.Dialogs
|
||||
LibraryBook = context.GetLibraryBook_Flat_NoTracking("B017V4IM1G");
|
||||
}
|
||||
}
|
||||
public BookDetailsDialog(LibraryBook libraryBook) :this()
|
||||
public BookDetailsDialog(LibraryBook libraryBook) : this()
|
||||
{
|
||||
LibraryBook = libraryBook;
|
||||
}
|
||||
|
||||
protected override void SaveAndClose()
|
||||
{
|
||||
LibraryBook.Book.UpdateUserDefinedItem(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
||||
{
|
||||
LibraryBook.Book.UpdateUserDefinedItem(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
||||
base.SaveAndClose();
|
||||
}
|
||||
|
||||
@@ -61,7 +60,7 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
|
||||
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> SaveAndClose();
|
||||
=> SaveAndClose();
|
||||
|
||||
private class BookDetailsDialogViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
public void CheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
foreach (var record in bookRecordEntries)
|
||||
foreach (var record in bookRecordEntries)
|
||||
record.IsChecked = true;
|
||||
}
|
||||
public void UncheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
@@ -12,9 +11,7 @@ namespace LibationAvalonia.Dialogs
|
||||
public DescriptionDisplayDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
||||
DescriptionTextBox = this.FindControl<TextBox>(nameof(DescriptionTextBox));
|
||||
this.Activated += DescriptionDisplay_Activated;
|
||||
Opened += DescriptionDisplay_Opened;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
@@ -17,10 +16,6 @@ namespace LibationAvalonia.Dialogs
|
||||
this.Initialized += DialogWindow_Initialized;
|
||||
this.Opened += DialogWindow_Opened;
|
||||
this.Closing += DialogWindow_Closing;
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
public DialogWindow(bool saveAndRestorePosition) : this()
|
||||
{
|
||||
|
||||
@@ -42,13 +42,16 @@
|
||||
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="Filter">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding FilterString, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding FilterString, Mode=TwoWay}"
|
||||
Header="Filter"/>
|
||||
|
||||
<DataGridTemplateColumn Header="Move
Up">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
MinWidth="500" MinHeight="450"
|
||||
Width="500" Height="450"
|
||||
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
|
||||
Title="Illegal Character Replacement"
|
||||
Icon="/Assets/libation.ico">
|
||||
@@ -26,21 +27,31 @@
|
||||
Items="{Binding replacements}">
|
||||
|
||||
<DataGrid.Columns>
|
||||
|
||||
|
||||
<DataGridTemplateColumn Header="Char to
Replace">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding CharacterToReplace, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding CharacterToReplace, Mode=TwoWay}"
|
||||
Header="Char to
Replace"/>
|
||||
<DataGridTemplateColumn Header="Replacement
Text">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding ReplacementText, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding ReplacementText, Mode=TwoWay}"
|
||||
Header="Replacement
Text"/>
|
||||
|
||||
<DataGridTextColumn Width="*"
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding Description, Mode=TwoWay}"
|
||||
Header="Description"/>
|
||||
<DataGridTemplateColumn Width="*" Header="Description">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding Description, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Data;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
@@ -37,7 +36,7 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
|
||||
public void Defaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
||||
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
||||
public void LoFiDefaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
|
||||
public void Barebones_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
@@ -50,7 +49,7 @@ namespace LibationAvalonia.Dialogs
|
||||
protected override void SaveAndClose()
|
||||
{
|
||||
var replacements = SOURCE
|
||||
.Where(r=> !r.IsDefault)
|
||||
.Where(r => !r.IsDefault)
|
||||
.Select(r => new Replacement(r.Character, r.ReplacementText, r.Description) { Mandatory = r.Mandatory })
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Width="800" Height="450"
|
||||
x:Class="LibationAvalonia.Dialogs.EditTemplateDialog"
|
||||
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||
Icon="/Assets/libation.ico"
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml.Templates;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="500"
|
||||
x:Class="LibationAvalonia.Dialogs.ImageDisplayDialog"
|
||||
MinWidth="500" MinHeight="500"
|
||||
Width="500" Height="520"
|
||||
Title="Cover"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using ReactiveUI;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibationFileManager;
|
||||
using LibationAvalonia.Controls;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
@@ -18,7 +14,7 @@ namespace LibationAvalonia.Dialogs
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyDocs
|
||||
};
|
||||
|
||||
|
||||
public string Directory { get; set; } = Configuration.GetKnownDirectoryPath(Configuration.KnownDirectories.UserProfile);
|
||||
}
|
||||
private DirSelectOptions dirSelectOptions;
|
||||
@@ -28,9 +24,6 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
DataContext = dirSelectOptions = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
Title="Liberated status: Whether the book has been downloaded"
|
||||
MinHeight="100" MaxHeight="165"
|
||||
MinWidth="600" MaxWidth="800"
|
||||
Width="600"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public partial class LiberatedStatusBatchAutoDialog : DialogWindow
|
||||
{
|
||||
public bool SetDownloaded { get; set; }
|
||||
public bool SetNotDownloaded { get; set; }
|
||||
{
|
||||
public bool SetDownloaded { get; set; }
|
||||
public bool SetNotDownloaded { get; set; }
|
||||
|
||||
public LiberatedStatusBatchAutoDialog()
|
||||
public LiberatedStatusBatchAutoDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
Title="Liberated status: Whether the book has been downloaded"
|
||||
MinWidth="400" MinHeight="120"
|
||||
MaxWidth="400" MaxHeight="120"
|
||||
Width="400" Height="120"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DataLayer;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -34,13 +33,13 @@ namespace LibationAvalonia.Dialogs
|
||||
new liberatedComboBoxItem { Status = LiberatedStatus.NotLiberated, Text = "Not Downloaded" },
|
||||
};
|
||||
|
||||
public LiberatedStatusBatchManualDialog(bool isPdf) : this()
|
||||
{
|
||||
if (isPdf)
|
||||
this.Title = this.Title.Replace("book", "PDF");
|
||||
}
|
||||
public LiberatedStatusBatchManualDialog(bool isPdf) : this()
|
||||
{
|
||||
if (isPdf)
|
||||
this.Title = this.Title.Replace("book", "PDF");
|
||||
}
|
||||
|
||||
public LiberatedStatusBatchManualDialog()
|
||||
public LiberatedStatusBatchManualDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
SelectedItem = BookStatuses[0] as liberatedComboBoxItem;
|
||||
|
||||
@@ -2,7 +2,6 @@ using ApplicationServices;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
@@ -53,7 +52,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
private void LocateAudiobooks_FileFound(object sender, FilePathCache.CacheEntry e)
|
||||
{
|
||||
var newItem = new Tuple<string,string>($"[{e.Id}]", Path.GetFileName(e.Path));
|
||||
var newItem = new Tuple<string, string>($"[{e.Id}]", Path.GetFileName(e.Path));
|
||||
_viewModel.FoundFiles.Add(newItem);
|
||||
foundAudiobooksLB.SelectedItem = newItem;
|
||||
|
||||
@@ -70,7 +69,7 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
Title = "Select the folder to search for audiobooks",
|
||||
AllowMultiple = false,
|
||||
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix)
|
||||
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix)
|
||||
};
|
||||
|
||||
var selectedFolder = (await StorageProvider.OpenFolderPickerAsync(folderPicker))?.SingleOrDefault()?.TryGetLocalPath();
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using ReactiveUI;
|
||||
@@ -21,7 +20,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
captchaBox = this.FindControl<TextBox>(nameof(captchaBox));
|
||||
}
|
||||
|
||||
public CaptchaDialog(string password, byte[] captchaImage) :this()
|
||||
public CaptchaDialog(string password, byte[] captchaImage) : this()
|
||||
{
|
||||
//Avalonia doesn't support animated gifs.
|
||||
//Deconstruct gifs into frames and manually switch them.
|
||||
@@ -34,7 +33,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
var frameMetadata = gif.Frames[i].Metadata.GetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance);
|
||||
|
||||
using var clonedFrame = gif.Frames.CloneFrame(i);
|
||||
using var clonedFrame = gif.Frames.CloneFrame(i);
|
||||
using var framems = new MemoryStream();
|
||||
|
||||
clonedFrame.Save(framems, gifEncoder);
|
||||
@@ -66,7 +65,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
protected override async Task CancelAndCloseAsync()
|
||||
{
|
||||
await _viewModel.StopAsync();
|
||||
await base.CancelAndCloseAsync();
|
||||
await base.CancelAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
@@ -106,7 +105,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
private async Task SwitchFramesAsync(Bitmap[] gifFrames, int[] frameDelayMs)
|
||||
{
|
||||
int index = 0;
|
||||
while(keepSwitching)
|
||||
while (keepSwitching)
|
||||
{
|
||||
CaptchaImage = gifFrames[index];
|
||||
await Task.Delay(frameDelayMs[index++]);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using AudibleUtilities;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Dinah.Core;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -26,7 +24,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
DataContext = this;
|
||||
}
|
||||
}
|
||||
public LoginChoiceEagerDialog(Account account):this()
|
||||
public LoginChoiceEagerDialog(Account account) : this()
|
||||
{
|
||||
Account = account;
|
||||
DataContext = this;
|
||||
@@ -34,7 +32,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
|
||||
protected override async Task SaveAndCloseAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Password))
|
||||
if (LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
await MessageBox.Show(this, "Please enter your password");
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using AudibleUtilities;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Dinah.Core;
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -28,7 +27,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
DataContext = this;
|
||||
}
|
||||
}
|
||||
public LoginExternalDialog(Account account, string loginUrl):this()
|
||||
public LoginExternalDialog(Account account, string loginUrl) : this()
|
||||
{
|
||||
Account = account;
|
||||
ExternalLoginUrl = loginUrl;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Data;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Data;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
@@ -12,7 +11,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
|
||||
public _2faCodeDialog()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
InitializeComponent();
|
||||
_2FABox = this.FindControl<TextBox>(nameof(_2FABox));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Dinah.Core;
|
||||
using FileManager;
|
||||
using System;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibationAvalonia.ViewModels.Dialogs;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
@@ -12,7 +9,7 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
public MessageBoxWindow(bool saveAndRestorePosition):base(saveAndRestorePosition)
|
||||
public MessageBoxWindow(bool saveAndRestorePosition) : base(saveAndRestorePosition)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="185"
|
||||
x:Class="LibationAvalonia.Dialogs.ScanAccountsDialog"
|
||||
MinWidth="500" MinHeight="160"
|
||||
MaxWidth="500" MaxHeight="185"
|
||||
Width="500" Height="200"
|
||||
Title="Which Accounts?"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using AudibleUtilities;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="950" d:DesignHeight="650"
|
||||
MinWidth="950" MinHeight="650"
|
||||
MaxWidth="950" MaxHeight="650"
|
||||
Width="950" Height="650"
|
||||
x:Class="LibationAvalonia.Dialogs.SearchSyntaxDialog"
|
||||
Title="Filter Options"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public partial class SearchSyntaxDialog : DialogWindow
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="750"
|
||||
MinWidth="900" MinHeight="700"
|
||||
Width="900" Height="700"
|
||||
x:Class="LibationAvalonia.Dialogs.SettingsDialog"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
Title="Edit Settings"
|
||||
@@ -18,9 +19,10 @@
|
||||
Height="30"
|
||||
Padding="30,3,30,3"
|
||||
Content="Save"
|
||||
Name="saveBtn"
|
||||
Click="SaveButton_Clicked" />
|
||||
|
||||
<TabControl Grid.Column="0">
|
||||
<TabControl Name="tabControl" Grid.Column="0">
|
||||
<TabControl.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="28"/>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using Dinah.Core;
|
||||
using System.Linq;
|
||||
using FileManager;
|
||||
using Avalonia.Collections;
|
||||
using LibationUiBase;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
@@ -51,7 +51,7 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
|
||||
public async void EditFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
{
|
||||
var newTemplate = await editTemplate(TemplateEditor<Templates.FileTemplate>.CreateFilenameEditor(config.Books, settingsDisp.DownloadDecryptSettings.FileTemplate));
|
||||
if (newTemplate is not null)
|
||||
settingsDisp.DownloadDecryptSettings.FileTemplate = newTemplate;
|
||||
@@ -59,7 +59,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
public async void EditChapterFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
var newTemplate = await editTemplate(TemplateEditor<Templates.ChapterFileTemplate>.CreateFilenameEditor(config.Books, settingsDisp.DownloadDecryptSettings.ChapterFileTemplate));
|
||||
if (newTemplate is not null)
|
||||
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
|
||||
@@ -197,7 +197,7 @@ namespace LibationAvalonia.Dialogs
|
||||
this.RaiseAndSetIfChanged(ref themeVariant, value);
|
||||
|
||||
SelectionChanged = ThemeVariant != InitialThemeVariant;
|
||||
this.RaisePropertyChanged(nameof(SelectionChanged));
|
||||
this.RaisePropertyChanged(nameof(SelectionChanged));
|
||||
}
|
||||
}
|
||||
public string InitialThemeVariant { get; private set; }
|
||||
@@ -242,7 +242,7 @@ namespace LibationAvalonia.Dialogs
|
||||
public bool DownloadEpisodes { get; set; }
|
||||
public bool AutoDownloadEpisodes { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsDisplay
|
||||
{
|
||||
private bool _badBookAsk;
|
||||
@@ -345,7 +345,7 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool BadBookRetry
|
||||
public bool BadBookRetry
|
||||
{
|
||||
get => _badBookRetry;
|
||||
set
|
||||
@@ -392,7 +392,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
public AvaloniaList<SampleRateSelection> SampleRates { get; }
|
||||
= new(
|
||||
new []
|
||||
new[]
|
||||
{
|
||||
AAXClean.SampleRate.Hz_44100,
|
||||
AAXClean.SampleRate.Hz_32000,
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="350"
|
||||
MinWidth="500" MinHeight="350"
|
||||
MaxWidth="500" MaxHeight="350"
|
||||
x:Class="LibationAvalonia.Dialogs.SetupDialog"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Width="500" Height="350"
|
||||
Icon="/Assets/libation.ico"
|
||||
Title="Welcome to Libation">
|
||||
|
||||
<Grid Margin="10" ColumnDefinitions="*" RowDefinitions="*,Auto,Auto">
|
||||
<Grid
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
RowDefinitions="*,Auto,Auto">
|
||||
|
||||
<TextBlock Grid.Row="0" TextWrapping="Wrap" Text="This appears to be your first time using Libation or a previous setup was incomplete.
|
||||


|
||||
@@ -21,18 +23,48 @@
|
||||


|
||||

Download your entire library from the "Liberate" tab or
|
||||

liberate your books one at a time by clicking the stoplight." />
|
||||
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Width="480"
|
||||
Margin="0,0,0,10"
|
||||
Click="NewUser_Click">
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Margin="0,0,0,10">
|
||||
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Padding="0,20"
|
||||
Margin="0,0,10,0"
|
||||
Click="NewUser_Click">
|
||||
|
||||
<TextBlock
|
||||
FontSize="18"
|
||||
TextAlignment="Center"
|
||||
Text="NEW USER"/>
|
||||
|
||||
</Button>
|
||||
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
RowDefinitions="*,*">
|
||||
|
||||
<TextBlock
|
||||
TextAlignment="Center"
|
||||
Text="NEW USER

Choose Settings"/>
|
||||
VerticalAlignment="Top"
|
||||
Text="Theme: " />
|
||||
|
||||
</Button>
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Bottom"
|
||||
SelectedIndex="0"
|
||||
SelectedItem="{Binding SelectedTheme, Mode=OneWayToSource}">
|
||||
<ComboBox.Items>
|
||||
<ComboBoxItem Content="Light" />
|
||||
<ComboBoxItem Content="Dark" />
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public partial class SetupDialog : Window
|
||||
{
|
||||
public bool IsNewUser { get;private set; }
|
||||
public bool IsReturningUser { get;private set; }
|
||||
public bool IsNewUser { get; private set; }
|
||||
public bool IsReturningUser { get; private set; }
|
||||
public ComboBoxItem SelectedTheme { get; set; }
|
||||
public Configuration Config { get; init; }
|
||||
public SetupDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public void NewUser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
x:Class="LibationAvalonia.Dialogs.TagsBatchDialog"
|
||||
MinWidth="630" MinHeight="110"
|
||||
MaxWidth="630" MaxHeight="110"
|
||||
Width="630" Height="110"
|
||||
Title="Replace Tags"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
x:Class="LibationAvalonia.Dialogs.TrashBinDialog"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
MinWidth="630" MinHeight="480"
|
||||
Width="630" Height="480"
|
||||
Title="Trash Bin"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.Controls;
|
||||
using LibationAvalonia.ViewModels;
|
||||
@@ -25,7 +24,7 @@ namespace LibationAvalonia.Dialogs
|
||||
this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
DataContext = _viewModel = new();
|
||||
|
||||
this.Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
this.Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
public async void EmptyTrash_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
@@ -40,7 +39,7 @@ namespace LibationAvalonia.Dialogs
|
||||
public string CheckedCountText => $"Checked : {_checkedBooksCount} of {_totalBooksCount}";
|
||||
|
||||
private bool _controlsEnabled = true;
|
||||
public bool ControlsEnabled { get => _controlsEnabled; set=> this.RaiseAndSetIfChanged(ref _controlsEnabled, value); }
|
||||
public bool ControlsEnabled { get => _controlsEnabled; set => this.RaiseAndSetIfChanged(ref _controlsEnabled, value); }
|
||||
|
||||
private bool? everythingChecked = false;
|
||||
public bool? EverythingChecked
|
||||
@@ -96,7 +95,7 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
foreach (var item in DeletedBooks)
|
||||
item.IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UncheckAll()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using AppScaffolding;
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using Dinah.Core.Logging;
|
||||
using LibationAvalonia.ViewModels.Dialogs;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationAvalonia.ViewModels.Dialogs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
@@ -64,23 +63,23 @@ namespace LibationAvalonia
|
||||
|
||||
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
||||
=> ShowCoreAsync(null, text, caption, buttons, icon, defaultButton);
|
||||
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, bool saveAndRestorePosition = true)
|
||||
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, saveAndRestorePosition);
|
||||
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons)
|
||||
=> ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(string text, string caption)
|
||||
=> ShowCoreAsync(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(string text)
|
||||
=> ShowCoreAsync(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, icon, defaultButton);
|
||||
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, bool saveAndRestorePosition = true)
|
||||
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, saveAndRestorePosition);
|
||||
public static Task<DialogResult> Show(string text, string caption, MessageBoxButtons buttons)
|
||||
=> ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(string text, string caption)
|
||||
=> ShowCoreAsync(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(string text)
|
||||
=> ShowCoreAsync(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, icon, defaultButton);
|
||||
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption)
|
||||
=> ShowCoreAsync(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption, MessageBoxButtons buttons)
|
||||
=> ShowCoreAsync(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text, string caption)
|
||||
=> ShowCoreAsync(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
public static Task<DialogResult> Show(Window owner, string text)
|
||||
=> ShowCoreAsync(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
||||
|
||||
@@ -102,10 +101,10 @@ Libation.
|
||||
".Trim(), "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: the format field should use {0} and NOT use the `$` string interpolation. Formatting is done inside this method.
|
||||
/// </summary>
|
||||
public static async Task<DialogResult> ShowConfirmationDialog(Window owner, IEnumerable<LibraryBook> libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
|
||||
/// <summary>
|
||||
/// Note: the format field should use {0} and NOT use the `$` string interpolation. Formatting is done inside this method.
|
||||
/// </summary>
|
||||
public static async Task<DialogResult> ShowConfirmationDialog(Window owner, IEnumerable<LibraryBook> libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return DialogResult.Cancel;
|
||||
@@ -120,7 +119,7 @@ Libation.
|
||||
= string.Format(format, $"{thisThese} {count} {bookBooks}")
|
||||
+ $"\r\n\r\n{titlesAgg}";
|
||||
|
||||
return await ShowCoreAsync(owner,
|
||||
return await ShowCoreAsync(owner,
|
||||
message,
|
||||
title,
|
||||
MessageBoxButtons.YesNo,
|
||||
|
||||
@@ -43,8 +43,6 @@ namespace LibationAvalonia
|
||||
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
|
||||
var config = LibationScaffolding.RunPreConfigMigrations();
|
||||
|
||||
App.SetupRequired = !config.LibationSettingsAreValid;
|
||||
|
||||
//Start as much work in parallel as possible.
|
||||
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
|
||||
var appBuilderTask = Task.Run(BuildAvaloniaApp);
|
||||
@@ -55,7 +53,7 @@ namespace LibationAvalonia
|
||||
return;
|
||||
|
||||
|
||||
if (!App.SetupRequired)
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
if (!RunDbMigrations(config))
|
||||
return;
|
||||
@@ -63,7 +61,7 @@ namespace LibationAvalonia
|
||||
App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
}
|
||||
|
||||
(appBuilderTask.GetAwaiter().GetResult()).SetupWithLifetime(classicLifetimeTask.GetAwaiter().GetResult());
|
||||
appBuilderTask.GetAwaiter().GetResult().SetupWithLifetime(classicLifetimeTask.GetAwaiter().GetResult());
|
||||
|
||||
classicLifetimeTask.Result.Start(null);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public class AvaloniaEntryStatus : EntryStatus, IEntryStatus, IComparable
|
||||
public class AvaloniaEntryStatus : EntryStatus, IEntryStatus, IComparable
|
||||
{
|
||||
public override IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : Brushes.Transparent;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace LibationAvalonia.ViewModels.Dialogs
|
||||
public bool HasButton3 => !string.IsNullOrEmpty(Button3Text);
|
||||
public bool HasButton2 => !string.IsNullOrEmpty(Button2Text);
|
||||
|
||||
public int WindowHeight { get;private set; }
|
||||
public int WindowWidth { get;private set; }
|
||||
public int WindowHeight { get; private set; }
|
||||
public int WindowWidth { get; private set; }
|
||||
|
||||
public string Button1Text => _button switch
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace LibationAvalonia.ViewModels.Dialogs
|
||||
MessageBoxButtons.CancelTryContinue => "Try",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
|
||||
public string Button3Text => _button switch
|
||||
{
|
||||
MessageBoxButtons.AbortRetryIgnore => "Ignore",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
|
||||
@@ -41,24 +43,25 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
|
||||
/// <summary> Auto scanning accounts is enables </summary>
|
||||
public bool AutoScanChecked
|
||||
{
|
||||
get => _autoScanChecked;
|
||||
set
|
||||
{
|
||||
public bool AutoScanChecked
|
||||
{
|
||||
get => _autoScanChecked;
|
||||
set
|
||||
{
|
||||
if (value != _autoScanChecked)
|
||||
Configuration.Instance.AutoScan = value;
|
||||
this.RaiseAndSetIfChanged(ref _autoScanChecked, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<Control> QuickFilterMenuItems { get; } = new();
|
||||
|
||||
/// <summary> Indicates if the first quick filter is the default filter </summary>
|
||||
public bool FirstFilterIsDefault
|
||||
{
|
||||
get => _firstFilterIsDefault;
|
||||
set
|
||||
{
|
||||
{
|
||||
get => _firstFilterIsDefault;
|
||||
set
|
||||
{
|
||||
if (value != _firstFilterIsDefault)
|
||||
QuickFilters.UseDefault = value;
|
||||
this.RaiseAndSetIfChanged(ref _firstFilterIsDefault, value);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using ApplicationServices;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Common;
|
||||
using Avalonia.Media;
|
||||
@@ -15,6 +11,10 @@ using FileLiberator;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
@@ -65,7 +65,7 @@ namespace LibationAvalonia.ViewModels
|
||||
public string Author { get => _author; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _author, value)); }
|
||||
public string Title { get => _title; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _title, value)); }
|
||||
public int Progress { get => _progress; private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _progress, value)); }
|
||||
public string ETA { get => _eta; private set => Dispatcher.UIThread.Post(() =>this.RaiseAndSetIfChanged(ref _eta, value)); }
|
||||
public string ETA { get => _eta; private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _eta, value)); }
|
||||
public Bitmap Cover { get => _cover; private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _cover, value)); }
|
||||
public bool IsFinished => Status is not ProcessBookStatus.Queued and not ProcessBookStatus.Working;
|
||||
public bool IsDownloading => Status is ProcessBookStatus.Working;
|
||||
@@ -315,36 +315,36 @@ namespace LibationAvalonia.ViewModels
|
||||
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
|
||||
UnlinkProcessable((Processable)sender);
|
||||
|
||||
if (Processes.Count == 0)
|
||||
{
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
if (Processes.Count == 0)
|
||||
{
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NextProcessable();
|
||||
LinkProcessable(CurrentProcessable);
|
||||
NextProcessable();
|
||||
LinkProcessable(CurrentProcessable);
|
||||
|
||||
StatusHandler result;
|
||||
try
|
||||
try
|
||||
{
|
||||
result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Processable_Completed)} error");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Processable_Completed)} error");
|
||||
|
||||
result = new StatusHandler();
|
||||
result.AddError($"{nameof(Processable_Completed)} error. See log for details. Error summary: {ex.Message}");
|
||||
}
|
||||
result = new StatusHandler();
|
||||
result.AddError($"{nameof(Processable_Completed)} error. See log for details. Error summary: {ex.Message}");
|
||||
}
|
||||
|
||||
if (result.HasErrors)
|
||||
{
|
||||
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
|
||||
Logger.Error(errorMessage);
|
||||
if (result.HasErrors)
|
||||
{
|
||||
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
|
||||
Logger.Error(errorMessage);
|
||||
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace LibationAvalonia.ViewModels
|
||||
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
_ = Configuration.Instance.LibationFiles;
|
||||
_ = Configuration.Instance.LibationFiles;
|
||||
|
||||
SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ namespace LibationAvalonia.ViewModels
|
||||
}
|
||||
|
||||
private void GridEntries_CollectionChanged(object sender = null, EventArgs e = null)
|
||||
{
|
||||
{
|
||||
var count
|
||||
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
|
||||
?? SOURCE.OfType<ILibraryBookEntry>().Count();
|
||||
|
||||
VisibleCountChanged?.Invoke(this, count);
|
||||
VisibleCountChanged?.Invoke(this, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
Name="button"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsEnabled="{Binding IsButtonEnabled}" Padding="0" Click="Button_Click" >
|
||||
IsEnabled="{CompiledBinding IsButtonEnabled}" Padding="0" Click="Button_Click" >
|
||||
<Panel>
|
||||
|
||||
<Panel
|
||||
|
||||
@@ -3,6 +3,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationUiBase.GridView;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
@@ -45,6 +46,16 @@ namespace LibationAvalonia.Views
|
||||
PdfStatus = null;
|
||||
IsSeries = true;
|
||||
}
|
||||
|
||||
DataContextChanged += LiberateStatusButton_DataContextChanged;
|
||||
}
|
||||
|
||||
private void LiberateStatusButton_DataContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
//Force book status recheck when an entry is scrolled into view.
|
||||
//This will force a recheck for a paprtially downloaded file.
|
||||
var status = DataContext as ILibraryBookEntry;
|
||||
status?.Liberate.Invalidate(nameof(status.Liberate.BookStatus));
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e) => Click?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using ApplicationServices;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -19,7 +17,7 @@ namespace LibationAvalonia.Views
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
if (updateCountsTask?.IsCompleted is not false)
|
||||
updateCountsTask = Dispatcher.UIThread.InvokeAsync(() => _viewModel.LibraryStats = LibraryCommands.GetCounts());
|
||||
updateCountsTask = Dispatcher.UIThread.InvokeAsync(() => _viewModel.LibraryStats = LibraryCommands.GetCounts());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Export() { }
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
using Avalonia.Input;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
protected void Configure_Filter() { }
|
||||
|
||||
public async void filterHelpBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await (new LibationAvalonia.Dialogs.SearchSyntaxDialog()).ShowDialog(this);
|
||||
|
||||
|
||||
public async void filterSearchTb_KeyPress(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Liberate() { }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
@@ -13,11 +11,11 @@ namespace LibationAvalonia.Views
|
||||
using var ms1 = new MemoryStream();
|
||||
App.OpenAsset("img-coverart-prod-unavailable_80x80.jpg").CopyTo(ms1);
|
||||
PictureStorage.SetDefaultImage(PictureSize._80x80, ms1.ToArray());
|
||||
|
||||
|
||||
using var ms2 = new MemoryStream();
|
||||
App.OpenAsset("img-coverart-prod-unavailable_300x300.jpg").CopyTo(ms2);
|
||||
PictureStorage.SetDefaultImage(PictureSize._300x300, ms2.ToArray());
|
||||
|
||||
|
||||
using var ms3 = new MemoryStream();
|
||||
App.OpenAsset("img-coverart-prod-unavailable_500x500.jpg").CopyTo(ms3);
|
||||
PictureStorage.SetDefaultImage(PictureSize._500x500, ms3.ToArray());
|
||||
|
||||
@@ -3,7 +3,6 @@ using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.GridView;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_QuickFilters()
|
||||
@@ -13,24 +13,54 @@ namespace LibationAvalonia.Views
|
||||
_viewModel.FirstFilterIsDefault = QuickFilters.UseDefault;
|
||||
Load += updateFiltersMenu;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
|
||||
//We need to be able to dynamically add and remove menu items from the Quick Filters menu.
|
||||
//To do that, we need quick filter's menu items source to be writable, which we can only
|
||||
//achieve by creating the list ourselves (instead of allowing Avalonia to create it from the xaml)
|
||||
|
||||
var startWithFilterMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Start Libation with 1st filter _Default",
|
||||
Icon = new CheckBox
|
||||
{
|
||||
BorderThickness = new Thickness(0),
|
||||
IsHitTestVisible = false,
|
||||
[!CheckBox.IsCheckedProperty] = new Binding(nameof(_viewModel.FirstFilterIsDefault))
|
||||
}
|
||||
};
|
||||
|
||||
var editFiltersMenuItem = new MenuItem { Header = "_Edit quick filters..." };
|
||||
|
||||
startWithFilterMenuItem.Click += firstFilterIsDefaultToolStripMenuItem_Click;
|
||||
editFiltersMenuItem.Click += editQuickFiltersToolStripMenuItem_Click;
|
||||
|
||||
_viewModel.QuickFilterMenuItems.Add(startWithFilterMenuItem);
|
||||
_viewModel.QuickFilterMenuItems.Add(editFiltersMenuItem);
|
||||
_viewModel.QuickFilterMenuItems.Add(new Separator());
|
||||
}
|
||||
|
||||
private async void QuickFiltersMenuItem_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
|
||||
{
|
||||
int keyNum = (int)e.Key - 34;
|
||||
|
||||
if (keyNum <=9 && keyNum >= 1)
|
||||
{
|
||||
var menuItem = _viewModel.QuickFilterMenuItems
|
||||
.OfType<MenuItem>()
|
||||
.FirstOrDefault(i => i.Header is string h && h.StartsWith($"_{keyNum}"));
|
||||
|
||||
if (menuItem is not null)
|
||||
{
|
||||
await performFilter(menuItem.Tag as string);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object quickFilterTag { get; } = new();
|
||||
private void updateFiltersMenu(object _ = null, object __ = null)
|
||||
{
|
||||
var allItems = quickFiltersToolStripMenuItem
|
||||
.Items
|
||||
.Cast<Control>()
|
||||
.ToList();
|
||||
|
||||
var toRemove = allItems
|
||||
.OfType<MenuItem>()
|
||||
.Where(mi => mi.Tag == quickFilterTag)
|
||||
.ToList();
|
||||
|
||||
allItems = allItems
|
||||
.Except(toRemove)
|
||||
.ToList();
|
||||
//Clear all filters
|
||||
_viewModel.QuickFilterMenuItems.RemoveAll(_viewModel.QuickFilterMenuItems.Where(i => i.Tag is string).ToList());
|
||||
|
||||
// re-populate
|
||||
var index = 0;
|
||||
@@ -38,27 +68,23 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
var quickFilterMenuItem = new MenuItem
|
||||
{
|
||||
Tag = quickFilterTag,
|
||||
Tag = filter,
|
||||
Header = $"_{++index}: {filter}"
|
||||
};
|
||||
quickFilterMenuItem.Click += async (_, __) => await performFilter(filter);
|
||||
allItems.Add(quickFilterMenuItem);
|
||||
_viewModel.QuickFilterMenuItems.Add(quickFilterMenuItem);
|
||||
}
|
||||
quickFiltersToolStripMenuItem.Items = allItems;
|
||||
}
|
||||
|
||||
public void firstFilterIsDefaultToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem mi && mi.Icon is CheckBox checkBox)
|
||||
{
|
||||
checkBox.IsChecked = !(checkBox.IsChecked ?? false);
|
||||
}
|
||||
_viewModel.FirstFilterIsDefault = !_viewModel.FirstFilterIsDefault;
|
||||
}
|
||||
|
||||
public void addQuickFilterBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
private void addQuickFilterBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> QuickFilters.Add(_viewModel.FilterString);
|
||||
|
||||
public async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
private async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await new Dialogs.EditQuickFilters().ShowDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
using AudibleUtilities;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_RemoveBooks()
|
||||
private void Configure_RemoveBooks()
|
||||
{
|
||||
if (Avalonia.Controls.Design.IsDesignMode)
|
||||
return;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace LibationAvalonia.Views
|
||||
};
|
||||
|
||||
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
||||
|
||||
|
||||
// if enabled: begin on load
|
||||
Opened += startAutoScan;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
@@ -10,7 +9,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_ScanManual()
|
||||
@@ -69,7 +67,7 @@ namespace LibationAvalonia.Views
|
||||
if (Configuration.Instance.ShowImportedStats && newAdded > 0)
|
||||
await MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
|
||||
}
|
||||
catch(OperationCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Serilog.Log.Information("Audible login attempt cancelled by user");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using ApplicationServices;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_ScanNotification()
|
||||
@@ -18,7 +16,7 @@ namespace LibationAvalonia.Views
|
||||
_viewModel.NumAccountsScanning = accountsLength;
|
||||
}
|
||||
|
||||
private void LibraryCommands_ScanEnd(object sender, EventArgs e)
|
||||
private void LibraryCommands_ScanEnd(object sender, int newCount)
|
||||
{
|
||||
_viewModel.NumAccountsScanning = 0;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Settings() { }
|
||||
@@ -17,13 +16,16 @@ namespace LibationAvalonia.Views
|
||||
public async void aboutToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await MessageBox.Show($"Libation {AppScaffolding.LibationScaffolding.Variety}{Environment.NewLine}Version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
|
||||
|
||||
public async void tourToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await new Walkthrough(this).RunAsync();
|
||||
|
||||
public void launchHangoverToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : ""));
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Failed to launch Hangover");
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_Upgrade()
|
||||
{
|
||||
setProgressVisible(false);
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
//DONE
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void Configure_VisibleBooks()
|
||||
@@ -52,15 +48,15 @@ namespace LibationAvalonia.Views
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace tags in {0}?",
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace tags in {0}?",
|
||||
"Replace tags?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateTags(dialog.NewTags);
|
||||
}
|
||||
}
|
||||
|
||||
public async void setBookDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
@@ -74,73 +70,73 @@ namespace LibationAvalonia.Views
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace book downloaded status in {0}?",
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace book downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async void setPdfDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchManualDialog(isPdf: true);
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
public async void setPdfDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchManualDialog(isPdf: true);
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace PDF downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to replace PDF downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async void setDownloadedAutoToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchAutoDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
public async void setDownloadedAutoToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var dialog = new Dialogs.LiberatedStatusBatchAutoDialog();
|
||||
var result = await dialog.ShowDialog<DialogResult>(this);
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(_viewModel.ProductsDisplay.GetVisibleBookEntries(), dialog.SetDownloaded, dialog.SetNotDownloaded);
|
||||
var count = await Task.Run(bulkSetStatus.Discover);
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(_viewModel.ProductsDisplay.GetVisibleBookEntries(), dialog.SetDownloaded, dialog.SetNotDownloaded);
|
||||
var count = await Task.Run(bulkSetStatus.Discover);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var confirmationResult = await MessageBox.Show(
|
||||
bulkSetStatus.AggregateMessage,
|
||||
"Replace downloaded status?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
var confirmationResult = await MessageBox.Show(
|
||||
bulkSetStatus.AggregateMessage,
|
||||
"Replace downloaded status?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
bulkSetStatus.Execute();
|
||||
}
|
||||
bulkSetStatus.Execute();
|
||||
}
|
||||
|
||||
public async void removeToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
public async void removeToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
{
|
||||
var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries();
|
||||
|
||||
var confirmationResult = await MessageBox.ShowConfirmationDialog(
|
||||
this,
|
||||
visibleLibraryBooks,
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to remove {0} from Libation's library?",
|
||||
// do not use `$` string interpolation. See impl.
|
||||
"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?",
|
||||
MessageBoxDefaultButton.Button2);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="1850" d:DesignHeight="700"
|
||||
x:Class="LibationAvalonia.Views.MainWindow"
|
||||
Title="Libation"
|
||||
Title="Libation: Liberate your Library"
|
||||
Name="Form1"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<!-- Menu Strip -->
|
||||
<Menu Grid.Column="0" VerticalAlignment="Top">
|
||||
<!-- Decrease height of menu strop -->
|
||||
|
||||
<Menu.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="25"/>
|
||||
@@ -27,7 +28,7 @@
|
||||
|
||||
<!-- Import Menu -->
|
||||
|
||||
<MenuItem Header="_Import">
|
||||
<MenuItem Name="importToolStripMenuItem" Header="_Import">
|
||||
<!-- Remove height style property for menu item -->
|
||||
<MenuItem.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
@@ -42,8 +43,8 @@
|
||||
<MenuItem IsVisible="{Binding !AnyAccounts}" Click="noAccountsYetAddAccountToolStripMenuItem_Click" Header="No accounts yet. A_dd Account..." />
|
||||
|
||||
<!-- Scan Library -->
|
||||
<MenuItem IsVisible="{Binding OneAccount}" IsEnabled="{Binding !ActivelyScanning}" Click="scanLibraryToolStripMenuItem_Click" Header="Scan _Library" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding !ActivelyScanning}" Click="scanLibraryOfAllAccountsToolStripMenuItem_Click" Header="Scan Library of _All Accounts" />
|
||||
<MenuItem IsVisible="{Binding OneAccount}" IsEnabled="{Binding !ActivelyScanning}" Name="scanLibraryToolStripMenuItem" Click="scanLibraryToolStripMenuItem_Click" Header="Scan _Library" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding !ActivelyScanning}" Name="scanLibraryOfAllAccountsToolStripMenuItem" Click="scanLibraryOfAllAccountsToolStripMenuItem_Click" Header="Scan Library of _All Accounts" />
|
||||
<MenuItem IsVisible="{Binding MultipleAccounts}" IsEnabled="{Binding !ActivelyScanning}" Click="scanLibraryOfSomeAccountsToolStripMenuItem_Click" Header="Scan Library of _Some Accounts" />
|
||||
<Separator />
|
||||
|
||||
@@ -86,20 +87,13 @@
|
||||
|
||||
<!-- Quick Filters Menu -->
|
||||
|
||||
<MenuItem Name="quickFiltersToolStripMenuItem" Header="Quick _Filters">
|
||||
<MenuItem Name="quickFiltersToolStripMenuItem" Header="Quick _Filters" ItemsSource="{Binding QuickFilterMenuItems}" KeyDown="QuickFiltersMenuItem_KeyDown">
|
||||
<!-- Remove height style property for menu item -->
|
||||
<MenuItem.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem Click="firstFilterIsDefaultToolStripMenuItem_Click" Header="Start Libation with 1st filter _Default">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox BorderThickness="0" IsChecked="{Binding FirstFilterIsDefault, Mode=TwoWay}" IsHitTestVisible="False" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Click="editQuickFiltersToolStripMenuItem_Click" Header="_Edit quick filters..." />
|
||||
<Separator />
|
||||
</MenuItem>
|
||||
|
||||
<!-- Visible Books Menu -->
|
||||
@@ -121,19 +115,20 @@
|
||||
|
||||
<!-- Settings Menu -->
|
||||
|
||||
<MenuItem Header="_Settings">
|
||||
<MenuItem Header="_Settings" Name="settingsToolStripMenuItem">
|
||||
<!-- Remove height style property for menu item -->
|
||||
<MenuItem.Styles>
|
||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="Height" Value="NaN"/>
|
||||
</Style>
|
||||
</MenuItem.Styles>
|
||||
<MenuItem Click="accountsToolStripMenuItem_Click" Header="_Accounts..." />
|
||||
<MenuItem Click="basicSettingsToolStripMenuItem_Click" Header="_Settings..." />
|
||||
<MenuItem Name="accountsToolStripMenuItem" Click="accountsToolStripMenuItem_Click" Header="_Accounts..." />
|
||||
<MenuItem Name="basicSettingsToolStripMenuItem" Click="basicSettingsToolStripMenuItem_Click" Header="_Settings..." />
|
||||
<Separator />
|
||||
<MenuItem Click="openTrashBinToolStripMenuItem_Click" Header="Trash Bin" />
|
||||
<MenuItem Click="launchHangoverToolStripMenuItem_Click" Header="Launch _Hangover" />
|
||||
<Separator />
|
||||
<MenuItem Click="tourToolStripMenuItem_Click" Header="Take a Guided _Tour of Libation" />
|
||||
<MenuItem Click="aboutToolStripMenuItem_Click" Header="A_bout..." />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
@@ -160,8 +155,8 @@
|
||||
</Grid.Styles>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Button Margin="0" Click="filterHelpBtn_Click" Content="?"/>
|
||||
<Button Click="addQuickFilterBtn_Click" Content="Add To Quick Filters"/>
|
||||
<Button Name="filterHelpBtn" Margin="0" Click="filterHelpBtn_Click" Content="?"/>
|
||||
<Button Name="addQuickFilterBtn" Click="addQuickFilterBtn_Click" Content="Add To Quick Filters"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
@@ -169,10 +164,10 @@
|
||||
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Content="Done Removing Books"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" IsVisible="{Binding !RemoveButtonsVisible}" Text="{Binding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{Binding !RemoveButtonsVisible}" Text="{Binding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
|
||||
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
||||
<Button Click="filterBtn_Click" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
<Button Name="filterBtn" Click="filterBtn_Click" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
<Button Padding="2,6,2,6" VerticalAlignment="Stretch" Click="ToggleQueueHideBtn_Click">
|
||||
<Path Stretch="Uniform" Fill="{DynamicResource IconFill}" Data="{StaticResource LeftArrows}">
|
||||
<Path.RenderTransform>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ApplicationServices;
|
||||
using Avalonia.ReactiveUI;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
@@ -48,6 +48,23 @@ namespace LibationAvalonia.Views
|
||||
Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
Closing += MainWindow_Closing;
|
||||
|
||||
Opened += MainWindow_Opened;
|
||||
}
|
||||
|
||||
private async void MainWindow_Opened(object sender, EventArgs e)
|
||||
{
|
||||
if (Configuration.Instance.FirstLaunch)
|
||||
{
|
||||
var result = await MessageBox.Show(this, "Would you like a guided tour to get started?", "Libation Walkthrough", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (result is DialogResult.Yes)
|
||||
{
|
||||
await new Walkthrough(this).RunAsync();
|
||||
}
|
||||
|
||||
Configuration.Instance.FirstLaunch = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
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:vm="clr-namespace:LibationAvalonia.ViewModels"
|
||||
x:DataType="vm:ProcessBookViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="87" MaxHeight="87" MinHeight="87" MinWidth="300"
|
||||
x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{Binding BackgroundColor}">
|
||||
x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{CompiledBinding BackgroundColor}">
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumBrush}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<Panel Grid.Column="0" Margin="3" Width="80" Height="80" HorizontalAlignment="Left">
|
||||
<Image Width="80" Height="80" Source="{Binding Cover}" Stretch="Uniform" />
|
||||
<Image Width="80" Height="80" Source="{CompiledBinding Cover}" Stretch="Uniform" />
|
||||
</Panel>
|
||||
<Grid Margin="0,3,0,3" Grid.Column="1" ColumnDefinitions="*" RowDefinitions="*,16">
|
||||
<StackPanel Grid.Column="0" Grid.Row="0" Orientation="Vertical">
|
||||
<TextBlock ClipToBounds="True" TextWrapping="Wrap" FontSize="11" Text="{Binding Title}" />
|
||||
<TextBlock FontSize="10" TextWrapping="NoWrap" Text="{Binding Author}" />
|
||||
<TextBlock FontSize="10" TextWrapping="NoWrap" Text="{Binding Narrator}" />
|
||||
<TextBlock ClipToBounds="True" TextWrapping="Wrap" FontSize="11" Text="{CompiledBinding Title}" />
|
||||
<TextBlock FontSize="10" TextWrapping="NoWrap" Text="{CompiledBinding Author}" />
|
||||
<TextBlock FontSize="10" TextWrapping="NoWrap" Text="{CompiledBinding Narrator}" />
|
||||
</StackPanel>
|
||||
<Panel Grid.Column="0" Grid.Row="1">
|
||||
<Panel.Styles>
|
||||
@@ -23,8 +25,8 @@
|
||||
<Setter Property="MinWidth" Value="20" />
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
<ProgressBar IsVisible="{Binding IsDownloading}" Value="{Binding Progress}" ShowProgressText="True" FontSize="12" />
|
||||
<TextBlock IsVisible="{Binding !IsDownloading}" Text="{Binding StatusText}"/>
|
||||
<ProgressBar IsVisible="{CompiledBinding IsDownloading}" Value="{CompiledBinding Progress}" ShowProgressText="True" FontSize="12" />
|
||||
<TextBlock IsVisible="{CompiledBinding !IsDownloading}" Text="{CompiledBinding StatusText}"/>
|
||||
</Panel>
|
||||
</Grid>
|
||||
<Grid Margin="3" Grid.Column="2" HorizontalAlignment="Right" ColumnDefinitions="Auto,Auto">
|
||||
@@ -39,7 +41,7 @@
|
||||
</Style>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<StackPanel IsVisible="{Binding Queued}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Vertical">
|
||||
<StackPanel IsVisible="{CompiledBinding Queued}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Vertical">
|
||||
|
||||
<Button Click="MoveFirst_Click">
|
||||
<Path VerticalAlignment="Top" Data="{StaticResource FirstButtonIcon}" />
|
||||
@@ -55,13 +57,13 @@
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Panel Margin="3,0,0,0" Grid.Column="1" VerticalAlignment="Top">
|
||||
<Button Height="32" Background="{DynamicResource CancelRed}" Width="22" IsVisible="{Binding !IsFinished}" CornerRadius="11" Click="Cancel_Click">
|
||||
<Button Height="32" Background="{DynamicResource CancelRed}" Width="22" IsVisible="{CompiledBinding !IsFinished}" CornerRadius="11" Click="Cancel_Click">
|
||||
<Path Fill="{DynamicResource ProcessQueueBookDefaultBrush}" VerticalAlignment="Center" Data="{StaticResource CancelButtonIcon}" RenderTransform="{StaticResource Rotate45Transform}" />
|
||||
</Button>
|
||||
</Panel>
|
||||
</Grid>
|
||||
<Panel Margin="3" Width="50" Grid.Column="2">
|
||||
<TextPresenter FontSize="9" VerticalAlignment="Bottom" HorizontalAlignment="Right" IsVisible="{Binding IsDownloading}" Text="{Binding ETA}" />
|
||||
<TextPresenter FontSize="9" VerticalAlignment="Bottom" HorizontalAlignment="Right" IsVisible="{CompiledBinding IsDownloading}" Text="{CompiledBinding ETA}" />
|
||||
</Panel>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using ApplicationServices;
|
||||
using Avalonia.Controls;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationUiBase;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationUiBase;
|
||||
@@ -14,7 +13,7 @@ using System.Linq;
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class ProcessQueueControl : UserControl
|
||||
{
|
||||
{
|
||||
private TrackedQueue<ProcessBookViewModel> Queue => _viewModel.Queue;
|
||||
private ProcessQueueViewModel _viewModel => DataContext as ProcessQueueViewModel;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="clr-namespace:LibationAvalonia.Views"
|
||||
xmlns:uibase="clr-namespace:LibationUiBase.GridView;assembly=LibationUiBase"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="1560" d:DesignHeight="400"
|
||||
x:Class="LibationAvalonia.Views.ProductsDisplay">
|
||||
@@ -37,6 +38,14 @@
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Fill" Value="{DynamicResource IconFill}" />
|
||||
</Style>
|
||||
<Style Selector="DataGridColumnHeader ContextMenu MenuItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Style Selector="^:icon /template/ Viewbox#PART_IconPresenter">
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="Margin" Value="6,0" />
|
||||
</Style>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
@@ -51,25 +60,25 @@
|
||||
Width="75">
|
||||
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<CheckBox
|
||||
HorizontalAlignment="Center"
|
||||
IsThreeState="True"
|
||||
IsChecked="{Binding Remove, Mode=TwoWay}" />
|
||||
IsChecked="{CompiledBinding Remove, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<controls:DataGridTemplateColumnExt CanUserSort="True" Width="75" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<views:LiberateStatusButton
|
||||
ToolTip.Tip="{Binding Liberate.ToolTip}"
|
||||
BookStatus="{Binding Liberate.BookStatus}"
|
||||
PdfStatus="{Binding Liberate.PdfStatus}"
|
||||
IsUnavailable="{Binding Liberate.IsUnavailable}"
|
||||
IsSeries="{Binding Liberate.IsSeries}"
|
||||
Expanded="{Binding Liberate.Expanded}"
|
||||
ToolTip.Tip="{CompiledBinding Liberate.ToolTip}"
|
||||
BookStatus="{CompiledBinding Liberate.BookStatus}"
|
||||
PdfStatus="{CompiledBinding Liberate.PdfStatus}"
|
||||
IsUnavailable="{CompiledBinding Liberate.IsUnavailable}"
|
||||
IsSeries="{CompiledBinding Liberate.IsSeries}"
|
||||
Expanded="{CompiledBinding Liberate.Expanded}"
|
||||
Click="LiberateButton_Click" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -77,17 +86,17 @@
|
||||
|
||||
<DataGridTemplateColumn CanUserSort="False" Width="80" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Image Opacity="{Binding Liberate.Opacity}" Tapped="Cover_Click" Height="80" Source="{Binding Cover}" ToolTip.Tip="Click to see full size" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Height="80" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<controls:DataGridTemplateColumnExt MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock FontSize="14" Text="{Binding Title}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock FontSize="14" Text="{CompiledBinding Title}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -95,9 +104,9 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Authors}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Authors}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -105,9 +114,9 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Narrators}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Narrators}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -115,9 +124,9 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Length}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Length}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -125,9 +134,19 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Series}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Series}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="Auto" Header="Series
Order" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding SeriesOrder}" HorizontalAlignment="Center" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -135,9 +154,9 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding LongDescription}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" VerticalAlignment="Top" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
|
||||
<TextBlock Text="{CompiledBinding Description}" FontSize="11" VerticalAlignment="Top" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -145,49 +164,51 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Category}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Category}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridMyRatingColumn
|
||||
x:DataType="uibase:IGridEntry"
|
||||
Header="Product
Rating"
|
||||
IsReadOnly="true"
|
||||
Width="115"
|
||||
SortMemberPath="ProductRating" CanUserSort="True"
|
||||
OpacityBinding="{Binding Liberate.Opacity}"
|
||||
BackgroundBinding="{Binding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{Binding ProductRating}"
|
||||
Binding="{Binding ProductRating}" />
|
||||
OpacityBinding="{CompiledBinding Liberate.Opacity}"
|
||||
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{CompiledBinding ProductRating}"
|
||||
Binding="{CompiledBinding ProductRating}" />
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="90" Header="Purchase
Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding PurchaseDate}" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding PurchaseDate}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridMyRatingColumn
|
||||
x:DataType="uibase:IGridEntry"
|
||||
Header="My Rating"
|
||||
IsReadOnly="false"
|
||||
Width="115"
|
||||
SortMemberPath="MyRating" CanUserSort="True"
|
||||
OpacityBinding="{Binding Liberate.Opacity}"
|
||||
BackgroundBinding="{Binding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{Binding MyRating}"
|
||||
Binding="{Binding MyRating, Mode=TwoWay}" />
|
||||
OpacityBinding="{CompiledBinding Liberate.Opacity}"
|
||||
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{CompiledBinding MyRating}"
|
||||
Binding="{CompiledBinding MyRating, Mode=TwoWay}" />
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{Binding Misc}" TextWrapping="WrapWithOverflow" FontSize="10" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
<TextBlock Text="{CompiledBinding Misc}" TextWrapping="WrapWithOverflow" FontSize="10" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -195,9 +216,9 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="102" Header="Last
Download" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Opacity="{Binding Liberate.Opacity}" Background="{Binding Liberate.BackgroundBrush}" ToolTip.Tip="{Binding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
|
||||
<TextBlock Text="{Binding LastDownload}" TextWrapping="WrapWithOverflow" FontSize="10" />
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
|
||||
<TextBlock Text="{CompiledBinding LastDownload}" TextWrapping="WrapWithOverflow" FontSize="10" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -205,13 +226,13 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt CanUserSort="True" Width="100" Header="Tags" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button IsVisible="{Binding !Liberate.IsSeries}" Width="100" Height="80" Click="OnTagsButtonClick" ToolTip.Tip="Click to edit tags" >
|
||||
<Panel Opacity="{Binding Liberate.Opacity}">
|
||||
<Panel Width="24" Height="24" IsVisible="{Binding BookTags, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Button IsVisible="{CompiledBinding !Liberate.IsSeries}" Width="100" Height="80" Click="OnTagsButtonClick" ToolTip.Tip="Click to edit tags" >
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<Panel Width="24" Height="24" IsVisible="{CompiledBinding BookTags, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||
<Path Stretch="Uniform" Fill="{DynamicResource IconFill}" Data="{StaticResource EditTagsIcon}" />
|
||||
</Panel>
|
||||
<TextBlock IsVisible="{Binding BookTags, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" FontSize="12" TextWrapping="WrapWithOverflow" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding BookTags}"/>
|
||||
<TextBlock IsVisible="{CompiledBinding BookTags, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" FontSize="12" TextWrapping="WrapWithOverflow" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{CompiledBinding BookTags}"/>
|
||||
</Panel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using ApplicationServices;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform.Storage;
|
||||
using DataLayer;
|
||||
using FileLiberator;
|
||||
@@ -256,7 +255,7 @@ namespace LibationAvalonia.Views
|
||||
contextMenu.MenuClosed += ContextMenu_MenuClosed;
|
||||
contextMenu.ContextMenuOpening += ContextMenu_ContextMenuOpening;
|
||||
List<Control> menuItems = new();
|
||||
contextMenu.Items = menuItems;
|
||||
contextMenu.ItemsSource = menuItems;
|
||||
|
||||
menuItems.Add(new MenuItem { Header = "Show / Hide Columns" });
|
||||
menuItems.Add(new MenuItem { Header = "-" });
|
||||
@@ -274,13 +273,9 @@ namespace LibationAvalonia.Views
|
||||
(
|
||||
new MenuItem
|
||||
{
|
||||
Header = ((string)column.Header).Replace((char)0xa, ' '),
|
||||
Header = ((string)column.Header).Replace('\n', ' '),
|
||||
Tag = column,
|
||||
Margin = new Thickness(6, 0),
|
||||
Icon = new CheckBox
|
||||
{
|
||||
Width = 50,
|
||||
}
|
||||
Icon = new CheckBox(),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -419,7 +414,9 @@ namespace LibationAvalonia.Views
|
||||
if (!isDefault)
|
||||
PictureStorage.PictureCached -= PictureCached;
|
||||
|
||||
if (!imageDisplayDialog.IsVisible)
|
||||
if (imageDisplayDialog.IsVisible)
|
||||
imageDisplayDialog.Activate();
|
||||
else
|
||||
imageDisplayDialog.Show();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
using AudibleApi.Common;
|
||||
using AudibleApi;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Collections;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationUiBase.SeriesView;
|
||||
using System;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
xmlns:uibase="clr-namespace:LibationUiBase.SeriesView;assembly=LibationUiBase"
|
||||
x:Class="LibationAvalonia.Views.SeriesViewGrid">
|
||||
|
||||
<DataGrid
|
||||
@@ -34,11 +35,11 @@
|
||||
|
||||
<DataGridTemplateColumn Width="80" Header="Cover" CanUserSort="False">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:SeriesItem">
|
||||
<Image
|
||||
Tapped="Cover_Click"
|
||||
Height="80"
|
||||
Source="{Binding Cover}"
|
||||
Source="{CompiledBinding Cover}"
|
||||
ToolTip.Tip="Click to see full size" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -46,10 +47,10 @@
|
||||
|
||||
<DataGridTemplateColumn Width="Auto" Header="Series
Order" CanUserSort="True" SortMemberPath="Order">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:SeriesItem">
|
||||
<Panel>
|
||||
<TextBlock
|
||||
Text="{Binding Order}"
|
||||
Text="{CompiledBinding Order}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Panel>
|
||||
@@ -59,7 +60,7 @@
|
||||
|
||||
<DataGridTemplateColumn Width="Auto" Header="Availability" CanUserSort="True" SortMemberPath="Button">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:SeriesItem">
|
||||
<Panel>
|
||||
<Panel.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
@@ -71,17 +72,17 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="Availability_Click"
|
||||
IsVisible="{Binding Button.HasButtonAction}"
|
||||
IsEnabled="{Binding Button.Enabled}">
|
||||
IsVisible="{CompiledBinding Button.HasButtonAction}"
|
||||
IsEnabled="{CompiledBinding Button.Enabled}">
|
||||
<TextBlock
|
||||
Text="{Binding Button.DisplayText}"
|
||||
Text="{CompiledBinding Button.DisplayText}"
|
||||
TextAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding !Button.HasButtonAction}"
|
||||
Text="{Binding Button.DisplayText}" />
|
||||
IsVisible="{CompiledBinding !Button.HasButtonAction}"
|
||||
Text="{CompiledBinding Button.DisplayText}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -89,11 +90,11 @@
|
||||
|
||||
<DataGridTemplateColumn MinWidth="150" Width="*" Header="Title" CanUserSort="True" SortMemberPath="Title">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="uibase:SeriesItem">
|
||||
<Panel ToolTip.Tip="Open Audible product page">
|
||||
<controls:LinkLabel
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Title}"
|
||||
Text="{CompiledBinding Title}"
|
||||
Tapped="Title_Click" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using ApplicationServices;
|
||||
using AudibleApi.Common;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using DataLayer;
|
||||
@@ -83,7 +81,9 @@ namespace LibationAvalonia.Views
|
||||
if (!isDefault)
|
||||
PictureStorage.PictureCached -= PictureCached;
|
||||
|
||||
if (!imageDisplayDialog.IsVisible)
|
||||
if (imageDisplayDialog.IsVisible)
|
||||
imageDisplayDialog.Activate();
|
||||
else
|
||||
imageDisplayDialog.Show();
|
||||
}
|
||||
}
|
||||
|
||||
301
Source/LibationAvalonia/Walkthrough.cs
Normal file
301
Source/LibationAvalonia/Walkthrough.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Dinah.Core.StepRunner;
|
||||
using LibationAvalonia.Dialogs;
|
||||
using LibationAvalonia.Views;
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Avalonia.Threading.Dispatcher;
|
||||
|
||||
namespace LibationAvalonia
|
||||
{
|
||||
internal class Walkthrough
|
||||
{
|
||||
private Dictionary<string, string> settingTabMessages = new()
|
||||
{
|
||||
{ "Important Settings", "From here you can change where liberated books are stored and how detailed Libation's logs are.\r\n\r\nIf you experience a problem and need help, you'll be asked to provide your log file. In certain circumstances we may need you to reproduce the error with a higher level of logging detail."},
|
||||
{ "Import Library", "In this tab you can change how your library is scanned and imported into Libation, as well as automatic liberation."},
|
||||
{ "Download/Decrypt", "These settings allow you to control how liberated files and folders are named and stored.\r\nYou can customize the 'Naming Templates' to use any number of the audiobook's properties to build a customized file and folder naming format. Learn more about the syntax from the wiki at\r\n\r\nhttps://github.com/rmcrackan/Libation/blob/master/Documentation/NamingTemplates.md"},
|
||||
{ "Audio File Settings", "Control how audio files are decrypted, including audio format and metadata handling.\r\n\r\nIf you choose to split your audiobook into multiple files by chapter marker, you may edit the chapter file 'Naming Template' to control how each chapter file is named."},
|
||||
};
|
||||
|
||||
private static readonly IBrush FlashColor = Brushes.DodgerBlue;
|
||||
private readonly MainWindow MainForm;
|
||||
private readonly AsyncStepSequence sequence = new();
|
||||
private readonly bool AutoScan;
|
||||
public Walkthrough(MainWindow mainForm)
|
||||
{
|
||||
AutoScan = Configuration.Instance.AutoScan;
|
||||
Configuration.Instance.AutoScan = false;
|
||||
MainForm = mainForm;
|
||||
sequence[nameof(ShowAccountDialog)] = () => UIThread.InvokeAsync(ShowAccountDialog);
|
||||
sequence[nameof(ShowSettingsDialog)] = () => UIThread.InvokeAsync(ShowSettingsDialog);
|
||||
sequence[nameof(ShowAccountScanning)] = () => UIThread.InvokeAsync(ShowAccountScanning);
|
||||
sequence[nameof(ShowSearching)] = () => UIThread.InvokeAsync(ShowSearching);
|
||||
sequence[nameof(ShowQuickFilters)] = () => UIThread.InvokeAsync(ShowQuickFilters);
|
||||
sequence[nameof(ShowTourComplete)] = () => UIThread.InvokeAsync(ShowTourComplete);
|
||||
}
|
||||
|
||||
public async Task RunAsync()
|
||||
{
|
||||
await sequence.RunAsync();
|
||||
Configuration.Instance.AutoScan = AutoScan;
|
||||
}
|
||||
|
||||
private async Task<bool> ShowAccountDialog()
|
||||
{
|
||||
if (!await ProceedMessageBox("First, add your Audible account(s).", "Add Accounts"))
|
||||
return false;
|
||||
|
||||
await Task.Delay(750);
|
||||
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||
await displayControlAsync(MainForm.accountsToolStripMenuItem);
|
||||
|
||||
var accountSettings = new AccountsDialog();
|
||||
accountSettings.Loaded += async (_, _) => await MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account");
|
||||
await accountSettings.ShowDialog(MainForm);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ShowSettingsDialog()
|
||||
{
|
||||
if (!await ProceedMessageBox("Next, adjust Libation's settings", "Change Settings"))
|
||||
return false;
|
||||
|
||||
await Task.Delay(750);
|
||||
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||
await displayControlAsync(MainForm.basicSettingsToolStripMenuItem);
|
||||
|
||||
var settingsDialog = await UIThread.InvokeAsync(() => new SettingsDialog());
|
||||
|
||||
var tabsToVisit = settingsDialog.tabControl.Items.OfType<TabItem>().ToList();
|
||||
|
||||
foreach (var tab in tabsToVisit)
|
||||
tab.PropertyChanged += TabControl_PropertyChanged;
|
||||
|
||||
settingsDialog.Loaded += SettingsDialog_Loaded;
|
||||
settingsDialog.Closing += SettingsDialog_FormClosing;
|
||||
settingsDialog.saveBtn.Content = "Next Tab";
|
||||
|
||||
await settingsDialog.ShowDialog(MainForm);
|
||||
|
||||
return true;
|
||||
|
||||
async Task ShowTabPageMessageBoxAsync(TabItem selectedTab)
|
||||
{
|
||||
tabsToVisit.Remove(selectedTab);
|
||||
|
||||
if (!selectedTab.IsVisible || !(selectedTab.Header is TextBlock header && settingTabMessages.ContainsKey(header.Text))) return;
|
||||
|
||||
if (tabsToVisit.Count == 0)
|
||||
settingsDialog.saveBtn.Content = "Save";
|
||||
|
||||
await MessageBox.Show(settingsDialog, settingTabMessages[header.Text], header.Text + " Tab", MessageBoxButtons.OK);
|
||||
|
||||
settingTabMessages.Remove(header.Text);
|
||||
}
|
||||
|
||||
async void SettingsDialog_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ShowTabPageMessageBoxAsync(tabsToVisit[0]);
|
||||
}
|
||||
|
||||
async void TabControl_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == TabItem.IsSelectedProperty && settingsDialog.IsLoaded)
|
||||
{
|
||||
await ShowTabPageMessageBoxAsync(sender as TabItem);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog_FormClosing(object sender, WindowClosingEventArgs e)
|
||||
{
|
||||
if (tabsToVisit.Count > 0)
|
||||
{
|
||||
settingsDialog.tabControl.SelectedItem = tabsToVisit[0];
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ShowAccountScanning()
|
||||
{
|
||||
var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var count = persister.AccountsSettings.Accounts.Count;
|
||||
persister.Dispose();
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
await MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the 'Import' menu", "Add an Audible Account", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return false;
|
||||
}
|
||||
|
||||
var accounts = count > 1 ? "accounts" : "account";
|
||||
var library = count > 1 ? "libraries" : "library";
|
||||
if(! await ProceedMessageBox($"Finally, scan your Audible {accounts} to sync your {library} with Libation.\r\n\r\nIf this is your first time scanning an account, you'll be prompted to enter your account's password to log into your Audible account.", $"Scan {accounts}"))
|
||||
return false;
|
||||
|
||||
var scanItem = count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem;
|
||||
|
||||
await Task.Delay(750);
|
||||
await displayControlAsync(MainForm.importToolStripMenuItem);
|
||||
await displayControlAsync(scanItem);
|
||||
|
||||
scanItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent));
|
||||
MainForm.importToolStripMenuItem.Close();
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||
await tcs.Task;
|
||||
LibraryCommands.ScanEnd -= LibraryCommands_ScanEnd;
|
||||
MainForm.ViewModel.ProductsDisplay.VisibleCountChanged -= productsDisplay_VisibleCountChanged;
|
||||
|
||||
return true;
|
||||
|
||||
void LibraryCommands_ScanEnd(object sender, int newCount)
|
||||
{
|
||||
//if we imported new books, wait for the grid to update before proceeding.
|
||||
if (newCount > 0)
|
||||
MainForm.ViewModel.ProductsDisplay.VisibleCountChanged += productsDisplay_VisibleCountChanged;
|
||||
else
|
||||
tcs.SetResult();
|
||||
}
|
||||
void productsDisplay_VisibleCountChanged(object sender, int e) => tcs.SetResult();
|
||||
}
|
||||
|
||||
private async Task<bool> ShowSearching()
|
||||
{
|
||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
if (books.Count == 0) return true;
|
||||
|
||||
var firstAuthor = getFirstAuthor();
|
||||
if (firstAuthor == null) return true;
|
||||
|
||||
if (!await ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
||||
return false;
|
||||
|
||||
await displayControlAsync(MainForm.filterSearchTb);
|
||||
|
||||
MainForm.filterSearchTb.Text = string.Empty;
|
||||
foreach (var c in firstAuthor)
|
||||
{
|
||||
MainForm.filterSearchTb.Text += c;
|
||||
await Task.Delay(150);
|
||||
}
|
||||
|
||||
await displayControlAsync(MainForm.filterBtn);
|
||||
|
||||
MainForm.filterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
await MessageBox.Show(MainForm, "Libation provides a built-in cheat sheet for its query language", "Search Cheat Sheet");
|
||||
|
||||
await displayControlAsync(MainForm.filterHelpBtn);
|
||||
|
||||
var filterHelp = new SearchSyntaxDialog();
|
||||
await filterHelp.ShowDialog(MainForm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ShowQuickFilters()
|
||||
{
|
||||
var firstAuthor = getFirstAuthor();
|
||||
|
||||
if (firstAuthor == null) return true;
|
||||
|
||||
if (!await ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
||||
return false;
|
||||
|
||||
MainForm.filterSearchTb.Text = firstAuthor;
|
||||
|
||||
var editQuickFiltersToolStripMenuItem = MainForm.quickFiltersToolStripMenuItem.ItemsSource.OfType<MenuItem>().ElementAt(1);
|
||||
|
||||
await Task.Delay(750);
|
||||
await displayControlAsync(MainForm.addQuickFilterBtn);
|
||||
MainForm.addQuickFilterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
||||
await displayControlAsync(editQuickFiltersToolStripMenuItem);
|
||||
|
||||
var editQuickFilters = new EditQuickFilters();
|
||||
editQuickFilters.Loaded += async (_, _) => await MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
|
||||
await editQuickFilters.ShowDialog(MainForm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ShowTourComplete()
|
||||
{
|
||||
await MessageBox.Show(MainForm, "You're now ready to begin using Libation.\r\n\r\nEnjoy!", "Tour Finished");
|
||||
return true;
|
||||
}
|
||||
|
||||
private string getFirstAuthor()
|
||||
{
|
||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
return books.SelectMany(lb => lb.Book.Authors).FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name))?.Name;
|
||||
}
|
||||
|
||||
private async Task displayControlAsync(TemplatedControl control)
|
||||
{
|
||||
await UIThread.InvokeAsync(() => control.IsEnabled = false);
|
||||
await UIThread.InvokeAsync(MainForm.productsDisplay.Focus);
|
||||
await UIThread.InvokeAsync(() => flashControlAsync(control));
|
||||
if (control is MenuItem menuItem) await UIThread.InvokeAsync(menuItem.Open);
|
||||
await Task.Delay(500);
|
||||
await UIThread.InvokeAsync(() => control.IsEnabled = true);
|
||||
}
|
||||
|
||||
private static async Task flashControlAsync(TemplatedControl control, int flashCount = 3)
|
||||
{
|
||||
for (int i = 0; i < flashCount; i++)
|
||||
{
|
||||
control.Styles.Add(disabledStyle);
|
||||
control.Styles.Add(disabledStyle2);
|
||||
await Task.Delay(200);
|
||||
control.Styles.Remove(disabledStyle);
|
||||
control.Styles.Remove(disabledStyle2);
|
||||
control.Styles.Add(enabedStyle);
|
||||
control.Styles.Add(enabedStyle2);
|
||||
control.InvalidateVisual();
|
||||
await Task.Delay(200);
|
||||
control.Styles.Remove(enabedStyle);
|
||||
control.Styles.Remove(enabedStyle2);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ProceedMessageBox(string message, string caption)
|
||||
=> await MessageBox.Show(MainForm, message, caption, MessageBoxButtons.OKCancel) is DialogResult.OK;
|
||||
|
||||
private static readonly Setter HighlightSetter = new Setter(Border.BackgroundProperty, FlashColor);
|
||||
private static readonly Setter HighlightSetter2 = new Setter(ContentPresenter.BackgroundProperty, FlashColor);
|
||||
private static readonly Setter TransparentSetter = new Setter(Border.BackgroundProperty, Brushes.Transparent);
|
||||
private static readonly Setter TransparentSetter2 = new Setter(ContentPresenter.BackgroundProperty, Brushes.Transparent);
|
||||
|
||||
private static Selector TemplateSelector = Selectors.Is<TemplatedControl>(null).PropertyEquals(Avalonia.Input.InputElement.IsEnabledProperty, false).Template();
|
||||
private static Selector ContentPresenterSelector = TemplateSelector.Is<ContentPresenter>();
|
||||
private static Selector BorderSelector = TemplateSelector.Is<Border>();
|
||||
|
||||
private static readonly Style disabledStyle = new Style(_ => BorderSelector);
|
||||
private static readonly Style disabledStyle2 = new Style(_ => ContentPresenterSelector);
|
||||
private static readonly Style enabedStyle = new Style(_ => BorderSelector);
|
||||
private static readonly Style enabedStyle2 = new Style(_ => ContentPresenterSelector);
|
||||
|
||||
static Walkthrough()
|
||||
{
|
||||
disabledStyle.Setters.Add(HighlightSetter);
|
||||
disabledStyle2.Setters.Add(HighlightSetter2);
|
||||
enabedStyle.Setters.Add(TransparentSetter);
|
||||
enabedStyle2.Setters.Add(TransparentSetter2);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user