Compare commits

...

110 Commits

Author SHA1 Message Date
Robert McRackan
48c69a1339 Can log to zip files with new ZipFile sink 2023-04-15 15:55:53 -04:00
rmcrackan
1ab882f327 Merge pull request #587 from Mbucari/master
Serilog log to zip file
2023-04-15 15:53:41 -04:00
MBucari
019b110a8a Fix #585 2023-04-15 13:43:50 -06:00
MBucari
9e14169e15 Update dependencies 2023-04-15 13:39:43 -06:00
MBucari
e08a68219d Add Serilog.Sinks.ZipFile to write logs into a zip file 2023-04-15 12:45:20 -06:00
Mbucari
af24c6e07b Merge branch 'rmcrackan:master' into master 2023-04-15 10:58:06 -06:00
Robert McRackan
e31847e669 Incr. ver. 2023-04-14 14:38:45 -04:00
Mbucari
c4f55d2ad1 Change "Click here" link verbiage 2023-04-14 11:37:22 -06:00
rmcrackan
1439e38cb0 Merge pull request #584 from Mbucari/master
Web Browser Login for Windows
2023-04-14 13:33:23 -04:00
Mbucari
4456432116 Add WebLoginDialog for Windows Chardonnay 2023-04-13 19:16:32 -06:00
Mbucari
df2936e0b6 Use WebLoginDialog as primary login method on Win10+ 2023-04-13 09:10:13 -06:00
Mbucari
53b5c1b902 Fix rare bug where episode may not sort beneath its parent 2023-04-11 14:43:01 -06:00
Mbucari
82fba7e752 Grid refresh performance and behavior improvements 2023-04-11 14:33:45 -06:00
rmcrackan
1a95f2923b Merge pull request #579 from Mbucari/master
Bug fixes and more shared code moved to UI base
2023-04-10 22:47:24 -04:00
Mbucari
1939aae81c Simplify and comment 2023-04-10 19:50:30 -06:00
Mbucari
9a663fda15 Filtering bugfix 2023-04-10 17:08:09 -06:00
Mbucari
84b2996102 Merge branch 'rmcrackan:master' into master 2023-04-10 16:17:24 -06:00
Mbucari
af8e1cd5ef Change episode default sorting to SeriesOrder descending 2023-04-10 16:17:10 -06:00
Mbucari
8a1b375f0d Fix #574 (for realsies this time) 2023-04-10 15:00:32 -06:00
Mbucari
6800986f25 Update GridEntryBindingList to behave move like Chardonnay 2023-04-10 14:10:50 -06:00
Mbucari
6110b08d16 Fix typo 2023-04-10 13:05:50 -06:00
Mbucari
666b5d83df Move filter query and RowComparer into UI base 2023-04-10 13:05:38 -06:00
rmcrackan
7db5a34f1b Merge pull request #577 from Mbucari/master
Fixed your issues
2023-04-10 13:14:05 -04:00
Mbucari
e52772826a Merge branch 'rmcrackan:master' into master 2023-04-09 17:45:38 -06:00
Mbucari
8ea9b2abc6 Fix #574 2023-04-09 17:41:24 -06:00
Mbucari
c10bb276f5 Fix #575 2023-04-09 17:41:10 -06:00
Mbucari
9dcb3b3a25 Slight chardonnay refactor and UI tweak 2023-04-09 17:39:31 -06:00
Robert McRackan
d857882220 Bug fix: logins 2023-04-07 19:58:13 -04:00
rmcrackan
d731db4036 Update Docker.md 2023-04-05 08:02:42 -04:00
rmcrackan
ca5b40b176 Merge pull request #571 from Mbucari/master
Chardonnay UI Refinements and Refactor
2023-04-05 08:00:44 -04:00
MBucari
b29ec26f63 Remove useless interface 2023-04-04 22:38:02 -06:00
MBucari
7569b01bd0 MacOS Compatibility 2023-04-04 22:26:13 -06:00
MBucari
6465b0a885 Fix possible NRE 2023-04-04 19:17:43 -06:00
Mbucari
5e99cb6f02 Refine dialog layouts and presentation 2023-04-04 19:08:52 -06:00
Mbucari
d737cd2199 Improve LinkLabel control 2023-04-04 12:10:23 -06:00
Mbucari
2d2907e076 Refactor settings dialog 2023-04-04 11:18:28 -06:00
Mbucari
05c454dce4 Fix directory select controls 2023-04-04 10:49:27 -06:00
rmcrackan
e64a9d2adf Merge pull request #566 from Mbucari/master
Reattach event handlers
2023-04-03 16:23:10 -04:00
Mbucari
6252f015b3 Reattach event handlers 2023-04-03 14:09:22 -06:00
rmcrackan
7ada0082a9 Merge pull request #565 from Mbucari/master
About Dialog, mac menus, and hotkeys
2023-04-03 15:54:40 -04:00
Mbucari
826e53c9cb Remove assemblies add acknowledgements to About 2023-04-03 13:34:20 -06:00
Mbucari
2248d7b24e Sort episodes by column beneath their parents 2023-04-02 21:28:55 -06:00
Mbucari
69918c2587 Cleanup 2023-04-02 21:28:37 -06:00
Michael Bucari-Tovo
1991bf5b4d Add more info to About dialog 2023-04-02 18:16:01 -06:00
MBucari
756d387238 UI Tweaks and new application hotkeys 2023-04-02 15:08:03 -06:00
MBucari
8d73f5cc7e Add About dialog 2023-04-02 13:27:51 -06:00
MBucari
4a65d6bbd3 Add native menu for mac and refactor MainWindow 2023-04-01 23:58:22 -06:00
Robert McRackan
10a1b56b3c incr ver 2023-03-31 16:39:13 -04:00
Robert McRackan
66fb392b7f Merge branch 'master' of https://github.com/rmcrackan/Libation 2023-03-31 16:16:23 -04:00
Robert McRackan
49ef96055c update dependencies 2023-03-31 16:16:21 -04:00
rmcrackan
cb4a209f69 Merge pull request #564 from Mbucari/master
Fix #563 and probably fix #534
2023-03-31 14:27:43 -04:00
Mbucari
255e18eb5e Fix external login failure error (#563) 2023-03-31 12:00:20 -06:00
Mbucari
7e1ec47b46 Tweak AccessKeyHandler 2023-03-31 11:59:48 -06:00
MBucari
40c725b8c2 Merge branch 'master' of https://github.com/Mbucari/Libation 2023-03-30 19:58:19 -06:00
MBucari
5d0937dc48 Add support for custom access keys 2023-03-30 19:57:39 -06:00
Robert McRackan
bff81bfc4b update paypal links 2023-03-30 09:44:20 -04:00
MBucari
aa7c159985 Define window dimensions 2023-03-29 19:44:33 -06:00
Robert McRackan
012d94a146 incr ver 2023-03-29 18:02:33 -04:00
Mbucari
22bd1ed121 Fix autoscan bug 2023-03-29 15:54:46 -06:00
Mbucari
c832f26b08 Merge pull request #561 from Mbucari/master
Try fix #560
2023-03-29 15:40:52 -06:00
Mbucari
efd73d334e inv ver 2023-03-29 15:39:25 -06:00
Mbucari
0db3ee6fd7 Fix library scan bug 2023-03-29 15:38:57 -06:00
Robert McRackan
6aaf4f63d1 incr major ver 2023-03-29 15:58:57 -04:00
rmcrackan
ab392a9285 Merge pull request #558 from Mbucari/master
Refined Walkthrough
2023-03-29 15:54:15 -04:00
Mbucari
efc9ff4bd8 Disable buttons on new row 2023-03-29 13:31:39 -06:00
Mbucari
a52b466c85 Fix QuickFilter Walkthrough 2023-03-29 13:17:31 -06:00
Mbucari
5611431abf Quick Filters display moveup and movedown buttons appropriately 2023-03-29 13:06:18 -06:00
Mbucari
a75932d1f4 Refine Walkthrough 2023-03-29 11:35:17 -06:00
Mbucari
6c8464b650 Use HashSet 2023-03-29 11:32:07 -06:00
rmcrackan
ba4a1c5a51 Merge pull request #554 from Mbucari/master
Bug fixes and guided tour
2023-03-28 16:49:49 -04:00
Mbucari
3681c0f18f Final walkthrough tweaks 2023-03-28 14:08:51 -06:00
Mbucari
e365ba7296 Use AvaloniaList properties 2023-03-28 13:29:07 -06:00
Mbucari
2afb5365dd Add search and quick filters to walkthrough 2023-03-28 12:30:05 -06:00
Mbucari
00cf7693d5 Add code comments 2023-03-28 10:02:22 -06:00
MBucari
dac6877a06 Fix #556 2023-03-28 07:09:46 -06:00
MBucari
36005508a1 Allow users to cancel walkthrough 2023-03-27 20:24:15 -06:00
MBucari
d9e27fd32e Bring cover viewer to front 2023-03-27 19:56:50 -06:00
MBucari
d86bcbb414 Add usings 2023-03-27 19:52:26 -06:00
MBucari
00cbab5b58 Update window title 2023-03-27 19:51:10 -06:00
MBucari
807725f6ff Replace editable DataGridTextColumn with TextBox (#552) 2023-03-27 19:40:23 -06:00
MBucari
ec9356b36e Do not import orphaned episodes (#553) 2023-03-27 18:58:43 -06:00
MBucari
add31024da Improve book availability detection (#551) 2023-03-27 17:53:25 -06:00
MBucari
27d2ada5a4 Don't warn for blank password with external login 2023-03-27 17:23:46 -06:00
Mbucari
702219ee69 Add guided walkthrough 2023-03-27 16:18:21 -06:00
Mbucari
cdf1a01457 Do not launch settings dialog after installation 2023-03-27 13:18:37 -06:00
Mbucari
a71ccbac6e Add Series Order column 2023-03-27 12:13:56 -06:00
Mbucari
f8c6b836c3 Merge branch 'rmcrackan:master' into master 2023-03-27 11:15:19 -06:00
Michael Bucari-Tovo
090871f50d More migrations to Avalonia 11.0.0-preview6 2023-03-27 11:14:54 -06:00
Robert McRackan
e62f01d2a3 Incr ver so bug fixes can be released. New features will also be announced in upcoming new major ver 2023-03-27 08:48:53 -04:00
Mbucari
68af6a5ebb Merge branch 'rmcrackan:master' into master 2023-03-26 21:04:53 -06:00
Michael Bucari-Tovo
8bba8538d5 Recheck for partially downloaded files. 2023-03-26 20:54:29 -06:00
rmcrackan
2cd9b86930 Merge pull request #549 from Mbucari/master
Lots of Bug Fixes and 2 New Features.
2023-03-26 22:54:08 -04:00
MBucari
b876d90964 Remove AudibleApi from solution 2023-03-26 08:43:33 -06:00
Mbucari
49c91c273b Merge branch 'rmcrackan:master' into master 2023-03-26 08:08:36 -06:00
MBucari
c07bc88493 Update AudibleApi 2023-03-25 21:18:38 -06:00
MBucari
397a516dc1 Fix (#548) 2023-03-25 21:18:38 -06:00
Robert McRackan
1c2b51aa83 update dependencies 2023-03-25 22:25:37 -04:00
Mbucari
fc6f494f0d Add dark mode support 2023-03-25 16:33:11 -06:00
Mbucari
7289459170 Migrate to Avalonia 11.0.0-preview6 2023-03-22 13:44:25 -06:00
Mbucari
ed6f741a65 Fix SettingsFileIsValid 2023-03-22 11:46:11 -06:00
Mbucari
1783da3e2d Ensure series and episode DateAdded is never default (#543) 2023-03-22 11:02:57 -06:00
Mbucari
e7eac7bed3 Log DTO items even if validation fails 2023-03-22 11:00:51 -06:00
MBucari
9ae1f0399b Add SeriesViewDialog 2023-03-22 08:28:20 -06:00
MBucari
784ab73a36 Add context menu to Series grid entries (#536) 2023-03-19 12:04:51 -06:00
MBucari
99687e968e Create books directory if not found (#542) 2023-03-19 10:19:38 -06:00
MBucari
565c84c4ab Add series # to grid display (#529) 2023-03-17 22:11:04 -06:00
MBucari
18cf20ecad All books that pass the filter are counted as "visible" (#536) 2023-03-17 19:52:47 -06:00
MBucari
2725340994 Suppress VisibleCountChanged firing when updating grid 2023-03-17 17:51:39 -06:00
MBucari
56de1e7659 Preserve "expanded" status when updating library 2023-03-17 17:47:27 -06:00
Mbucari
fd16e97632 When book is unavailable, check other accounts (#535) 2023-03-17 14:06:02 -06:00
278 changed files with 8850 additions and 4875 deletions

View File

@@ -69,7 +69,7 @@ jobs:
LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj `
--configuration ${{ env.DOTNET_CONFIGURATION }} `
--output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} `
-p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml
-p:PublishProfile=LoadByOS/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml
dotnet publish `
LibationCli/LibationCli.csproj `
--configuration ${{ env.DOTNET_CONFIGURATION }} `

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,9 +1,12 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
### Disclaimer
The docker image is provided as-is. We hope it can be useful to you but it is not officially supported.
### Setup
In order to use the docker image, you'll need to provide it with a copy of the `AccountsSettings.json`, `Settings.json`, and `LibationContext.db` files. These files can usually be found in the Libation folder in your user's home directory. If you haven't run Libation yet, you'll need to launch it to generate these files and setup your accounts. Once you have them, copy these files to a new location, such as `/opt/libation/config`. Before using them we'll need to make a couple edits so that the filepaths referenced are correct when running from the docker image.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

BIN
Images/Plus Minus.psd Normal file
View File

Binary file not shown.

View File

@@ -2,7 +2,7 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Table of Contents

View File

@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="1.0.0" />
<PackageReference Include="AAXClean.Codecs" Version="1.0.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,10 +2,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>9.4.6.1</Version>
<Version>10.2.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="5.0.2" />
<PackageReference Include="Octokit" Version="5.0.4" />
<PackageReference Include="Serilog.Sinks.ZipFile" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />

View File

@@ -118,8 +118,15 @@ namespace AppScaffolding
private static void ensureSerilogConfig(Configuration config)
{
if (config.GetObject("Serilog") is not null)
if (config.GetObject("Serilog") is JObject serilog)
{
if (serilog["WriteTo"] is JArray sinks && sinks.FirstOrDefault(s => s["Name"].Value<string>() is "File") is JToken fileSink)
{
fileSink["Name"] = "ZipFile";
config.SetNonString(serilog.DeepClone(), "Serilog");
}
return;
}
var serilogObj = new JObject
{
@@ -129,7 +136,7 @@ namespace AppScaffolding
// new JObject { {"Name", "Console" } }, // this has caused more problems than it's solved
new JObject
{
{ "Name", "File" },
{ "Name", "ZipFile" },
{ "Args",
new JObject
{

View File

@@ -1,5 +1,4 @@
using NPOI.XWPF.UserModel;
using System;
using System;
using System.Text.RegularExpressions;
namespace AppScaffolding

View File

@@ -20,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();
@@ -94,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);
}
}
@@ -107,7 +107,8 @@ namespace ApplicationServices
if (accounts is null || accounts.Length == 0)
return (0, 0);
try
int newCount = 0;
try
{
lock (_lock)
{
@@ -133,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}");
@@ -166,16 +167,69 @@ namespace ApplicationServices
{
stop();
var putBreakPointHere = logOutput;
ScanEnd?.Invoke(null, null);
ScanEnd?.Invoke(null, newCount);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
}
}
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions)
public static async Task<int> ImportSingleToDbAsync(AudibleApi.Common.Item item, string accountId, string localeName)
{
ArgumentValidator.EnsureNotNull(item, "item");
ArgumentValidator.EnsureNotNull(accountId, "accountId");
ArgumentValidator.EnsureNotNull(localeName, "localeName");
var importItem = new ImportItem
{
DtoItem = item,
AccountId = accountId,
LocaleName = localeName
};
var importItems = new List<ImportItem> { importItem };
var validator = new LibraryValidator();
var exceptions = validator.Validate(importItems.Select(i => i.DtoItem));
if (exceptions?.Any() ?? false)
{
Log.Logger.Error(new AggregateException(exceptions), "Error validating library book. {@DebugInfo}", new { item, accountId, localeName });
return 0;
}
using var context = DbContexts.GetContext();
var bookImporter = new BookImporter(context);
await Task.Run(() => bookImporter.Import(importItems));
var book = await Task.Run(() => context.LibraryBooks.FirstOrDefault(lb => lb.Book.AudibleProductId == importItem.DtoItem.ProductId));
if (book is null)
{
book = new LibraryBook(bookImporter.Cache[importItem.DtoItem.ProductId], importItem.DtoItem.DateAdded, importItem.AccountId);
context.LibraryBooks.Add(book);
}
else
{
book.AbsentFromLastScan = false;
}
try
{
int qtyChanged = await Task.Run(() => SaveContext(context));
if (qtyChanged > 0)
await Task.Run(finalizeLibrarySizeChange);
return qtyChanged;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error adding single library book to DB. {@DebugInfo}", new { item, accountId, localeName });
return 0;
}
}
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions)
{
var tasks = new List<Task<List<ImportItem>>>();
await using LogArchiver archiver
await using LogArchiver archiver
= Log.Logger.IsDebugEnabled()
? new LogArchiver(System.IO.Path.Combine(Configuration.Instance.LibationFiles, "LibraryScans.zip"))
: default;
@@ -184,16 +238,24 @@ namespace ApplicationServices
foreach (var account in accounts)
{
// get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll)
var apiExtended = await apiExtendedfunc(account);
try
{
// get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll)
var apiExtended = await apiExtendedfunc(account);
// add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions, archiver));
// add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions, archiver));
}
catch(Exception ex)
{
//Catch to allow other accounts to continue scanning.
Log.Logger.Error(ex, "Failed to scan account");
}
}
// import library in parallel
var arrayOfLists = await Task.WhenAll(tasks);
var importItems = arrayOfLists.SelectMany(a => a).ToList();
var arrayOfLists = await Task.WhenAll(tasks);
var importItems = arrayOfLists.SelectMany(a => a).ToList();
return importItems;
}
@@ -208,26 +270,44 @@ namespace ApplicationServices
logTime($"pre scanAccountAsync {account.AccountName}");
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes);
if (archiver is not null)
try
{
var fileName = $"{DateTime.Now:u} {account.MaskedLogEntry}.json";
var items = await Task.Run(() => JArray.FromObject(dtoItems.Select(i => i.SourceJson)));
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes);
var scanFile = new JObject
{
{ "Account", account.MaskedLogEntry },
{ "ScannedDateTime", DateTime.Now.ToString("u") },
{ "Items", items}
};
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
await logDtoItemsAsync(dtoItems);
await archiver.AddFileAsync(fileName, scanFile);
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
}
catch(ImportValidationException ex)
{
await logDtoItemsAsync(ex.Items, ex.InnerExceptions.ToArray());
//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)
{
if (archiver is not null)
{
var fileName = $"{DateTime.Now:u} {account.MaskedLogEntry}.json";
var items = await Task.Run(() => JArray.FromObject(dtoItems.Select(i => i.SourceJson)));
var scanFile = new JObject
{
{ "Account", account.MaskedLogEntry },
{ "ScannedDateTime", DateTime.Now.ToString("u") },
};
if (exceptions?.Any() is true)
scanFile.Add("Exceptions", JArray.FromObject(exceptions));
scanFile.Add("Items", items);
await archiver.AddFileAsync(fileName, scanFile);
}
}
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
}
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)

View File

@@ -149,35 +149,25 @@ namespace AudibleUtilities
foreach (var parent in items.Where(i => i.IsSeriesParent))
{
var children = items.Where(i => i.IsEpisodes && i.Relationships.Any(r => r.Asin == parent.Asin));
setSeries(parent, children);
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 validators = new List<IValidator>();
validators.AddRange(getValidators());
foreach (var v in validators)
{
var exceptions = v.Validate(items);
if (exceptions is not null && exceptions.Any())
throw new AggregateException(exceptions);
}
var allExceptions = IValidator.GetAllValidators().SelectMany(v => v.Validate(items)).ToList();
if (allExceptions?.Count > 0)
throw new ImportValidationException(items, allExceptions);
return items;
}
private static List<IValidator> getValidators()
{
var type = typeof(IValidator);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) && !p.IsInterface);
return types.Select(t => Activator.CreateInstance(t) as IValidator).ToList();
}
#region episodes and podcasts
/// <summary>
@@ -232,8 +222,11 @@ namespace AudibleUtilities
finally { semaphore.Release(); }
}
private static void setSeries(Item parent, IEnumerable<Item> children)
public static void SetSeries(Item parent, IEnumerable<Item> children)
{
ArgumentValidator.EnsureNotNull(parent, nameof(parent));
ArgumentValidator.EnsureNotNull(children, nameof(children));
//A series parent will always have exactly 1 Series
parent.Series = new[]
{
@@ -246,7 +239,15 @@ namespace AudibleUtilities
};
if (parent.PurchaseDate == default)
parent.PurchaseDate = children.Select(c => c.PurchaseDate).Order().First();
{
parent.PurchaseDate = children.Select(c => c.PurchaseDate).Order().FirstOrDefault(d => d != default);
if (parent.PurchaseDate == default)
{
Serilog.Log.Logger.Warning("{series} doesn't have a purchase date. Using UtcNow", parent);
parent.PurchaseDate = DateTimeOffset.UtcNow;
}
}
foreach (var child in children)
{
@@ -267,4 +268,4 @@ namespace AudibleUtilities
}
#endregion
}
}
}

View File

@@ -8,7 +8,18 @@ namespace AudibleUtilities
public interface IValidator
{
IEnumerable<Exception> Validate(IEnumerable<Item> items);
public static IValidator[] GetAllValidators()
=> new IValidator[]
{
new LibraryValidator(),
new BookValidator(),
new CategoryValidator(),
new ContributorValidator(),
new SeriesValidator(),
};
}
public class LibraryValidator : IValidator
{
public IEnumerable<Exception> Validate(IEnumerable<Item> items)

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AudibleApi" Version="8.2.2.1" />
<PackageReference Include="AudibleApi" Version="8.3.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,15 @@
using AudibleApi.Common;
using System;
using System.Collections.Generic;
namespace AudibleUtilities
{
public class ImportValidationException : AggregateException
{
public List<Item> Items { get; }
public ImportValidationException(List<Item> items, IEnumerable<Exception> exceptions) : base(exceptions)
{
Items = items;
}
}
}

View File

@@ -12,12 +12,12 @@
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.2.2.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="7.1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -25,6 +25,8 @@ namespace DataLayer
Account = account;
}
public override string ToString() => $"{DateAdded:d} {Book}";
public void SetAccount(string account) => Account = account;
public override string ToString() => $"{DateAdded:d} {Book}";
}
}

View File

@@ -18,9 +18,9 @@ namespace DataLayer
/// <summary>True if exists and IsLiberated. Else false</summary>
public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames());
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames(true));
public static bool HasPdf(this Book book) => book.Supplements.Any();
public static string SeriesNames(this Book book)
public static string SeriesNames(this Book book, bool includeIndex = false)
{
if (book.SeriesLink is null)
return "";
@@ -28,7 +28,7 @@ namespace DataLayer
// first: alphabetical by name
var withNames = book.SeriesLink
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
.Select(s => s.Series.Name)
.Select(getSeriesNameString)
.OrderBy(a => a)
.ToList();
// then un-named are alpha by series id
@@ -40,7 +40,12 @@ namespace DataLayer
var all = withNames.Union(nullNames).ToList();
return string.Join(", ", all);
}
string getSeriesNameString(SeriesBook sb)
=> includeIndex && !string.IsNullOrWhiteSpace(sb.Order) && sb.Order != "-1"
? $"{sb.Series.Name} (#{sb.Order})"
: sb.Series.Name;
}
public static string[] CategoriesNames(this Book book)
=> book.Category is null ? new string[0]
: book.Category.ParentCategory is null ? new[] { book.Category.Name }

View File

@@ -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))

View File

@@ -41,32 +41,48 @@ namespace DtoImporterService
//
// CURRENT SOLUTION: don't re-insert
var newItems = importItems
.ExceptBy(DbContext.LibraryBooks.Select(lb => lb.Book.AudibleProductId), imp => imp.DtoItem.ProductId)
.ToList();
// if 2 accounts try to import the same book in the same transaction: error since we're only tracking and pulling by asin.
// just use the first
var hash = newItems.ToDictionarySafe(dto => dto.DtoItem.ProductId);
foreach (var kvp in hash)
//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 uniqueImportItems.Values)
{
var newItem = kvp.Value;
var libraryBook = new LibraryBook(
bookImporter.Cache[newItem.DtoItem.ProductId],
newItem.DtoItem.DateAdded,
newItem.AccountId)
if (existingEntries.TryGetValue(item.DtoItem.ProductId, out LibraryBook existing))
{
AbsentFromLastScan = isPlusTitleUnavailable(newItem)
};
if (existing.Account != item.AccountId)
{
//Book is absent from the existing LibraryBook's account. Use the alternate account.
existing.SetAccount(item.AccountId);
}
try
{
DbContext.LibraryBooks.Add(libraryBook);
existing.AbsentFromLastScan = isPlusTitleUnavailable(item);
}
catch (Exception ex)
else
{
Serilog.Log.Logger.Error(ex, "Error adding library book. {@DebugInfo}", new { libraryBook.Book, libraryBook.Account });
var libraryBook = new LibraryBook(
bookImporter.Cache[item.DtoItem.ProductId],
item.DtoItem.DateAdded,
item.AccountId)
{
AbsentFromLastScan = isPlusTitleUnavailable(item)
};
try
{
DbContext.LibraryBooks.Add(libraryBook);
qtyNew++;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Error adding library book. {@DebugInfo}", new { libraryBook.Book, libraryBook.Account });
}
}
}
@@ -77,22 +93,30 @@ namespace DtoImporterService
foreach (var nullBook in DbContext.LibraryBooks.AsEnumerable().Where(lb => lb.Book is null && lb.Account.In(scannedAccounts)))
nullBook.AbsentFromLastScan = true;
//Join importItems on LibraryBooks before iterating over LibraryBooks to avoid
//quadratic complexity caused by searching all of importItems for each LibraryBook.
//Join uses hashing, so complexity should approach O(N) instead of O(N^2).
var items_lbs
= importItems
.Join(DbContext.LibraryBooks, o => (o.AccountId, o.DtoItem.ProductId), i => (i.Account, i.Book?.AudibleProductId), (o, i) => (o, i));
foreach ((ImportItem item, LibraryBook lb) in items_lbs)
lb.AbsentFromLastScan = isPlusTitleUnavailable(item);
var qtyNew = hash.Count;
return qtyNew;
}
private static Dictionary<TKey, TSource> ToDictionarySafe<TKey, TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TSource, TSource> tieBreaker)
{
var dictionary = new Dictionary<TKey, TSource>();
foreach (TSource newItem in source)
{
TKey key = keySelector(newItem);
dictionary[key]
= dictionary.TryGetValue(key, out TSource existingItem)
? tieBreaker(existingItem, newItem)
: newItem;
}
return dictionary;
}
private static ImportItem tieBreak(ImportItem item1, ImportItem item2)
=> 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);
}
}

View File

@@ -147,7 +147,8 @@ namespace FileManager
private void AddPath(LongPath path)
{
path = path.LongPathName;
if (!File.Exists(path) && !Directory.Exists(path))
//Temporary files created when updating the db will disappear before their attributes can be read.
if (Path.GetFileName(path).Contains("LibationContext.db") || !File.Exists(path) && !Directory.Exists(path))
return;
if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))
AddUniqueFiles(FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption));

View File

@@ -7,6 +7,6 @@
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<FluentTheme/>
</Application.Styles>
</Application>

View File

@@ -66,13 +66,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview4" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview4" />
<PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview4" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@@ -7,7 +7,7 @@ namespace HangoverAvalonia
{
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
public Control Build(object data)
{
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);

View File

@@ -64,7 +64,8 @@ namespace HangoverBase
try
{
var sql = _commands.SqlInput().Trim();
var sql = _commands.SqlInput()?.Trim();
if (sql is null) return;
#region // explanation
// Routing statements to non-query is a convenience.

View File

@@ -1,17 +1,88 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:LibationAvalonia"
x:Class="LibationAvalonia.App">
xmlns:controls="using:LibationAvalonia.Controls"
x:Class="LibationAvalonia.App"
Name="Libation">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml"/>
<StyleInclude Source="/Assets/DataGridTheme.xaml"/>
<StyleInclude Source="/Assets/LibationStyles.xaml"/>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#abffab" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="LightCoral" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="PaleGreen" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="Khaki" />
<SolidColorBrush x:Key="HyperlinkNew" Color="Blue" />
<SolidColorBrush x:Key="HyperlinkVisited" Color="Purple" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="White" />
<SolidColorBrush x:Key="SystemOpaqueBase" Color="White" />
<SolidColorBrush x:Key="CancelRed" Color="FireBrick" />
<SolidColorBrush x:Key="IconFill" Color="#231F20" />
<SolidColorBrush x:Key="StoplightRed" Color="#F06060" />
<SolidColorBrush x:Key="StoplightYellow" Color="#F0E160" />
<SolidColorBrush x:Key="StoplightGreen" Color="#70FA70" />
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#bed2fa" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="#502727" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="#1c3e20" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="#4e4b15" />
<SolidColorBrush x:Key="HyperlinkNew" Color="CornflowerBlue" />
<SolidColorBrush x:Key="HyperlinkVisited" Color="Orchid" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="Black" />
<SolidColorBrush x:Key="SystemOpaqueBase" Color="Black" />
<SolidColorBrush x:Key="CancelRed" Color="#802727" />
<SolidColorBrush x:Key="IconFill" Color="#DCE0DF" />
<SolidColorBrush x:Key="StoplightRed" Color="#5F0707" />
<SolidColorBrush x:Key="StoplightYellow" Color="#5F5B1A" />
<SolidColorBrush x:Key="StoplightGreen" Color="#174E15" />
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml"/>
<StyleInclude Source="/Assets/DataGridFluentTheme.xaml"/>
<StyleInclude Source="/Assets/LibationVectorIcons.xaml"/>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="CaretBrush" Value="{DynamicResource SystemControlTransparentBrush}" />
<Style Selector="^ /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
</Style>
</Style>
<Style Selector="controls|LinkLabel">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkNew}"/>
<Setter Property="ForegroundVisited" Value="{DynamicResource HyperlinkVisited}"/>
</Style>
<Style Selector="Button">
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="About Libation" />
</NativeMenu>
</NativeMenu.Menu>
</Application>

View File

@@ -1,23 +1,27 @@
using Avalonia;
using ApplicationServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
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 System.Threading.Tasks;
using ReactiveUI;
using DataLayer;
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; }
@@ -37,18 +41,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
@@ -84,11 +85,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)
{
@@ -128,40 +147,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();
@@ -198,11 +183,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();
}
@@ -214,21 +211,24 @@ namespace LibationAvalonia
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
{
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is "Dark" ? ThemeVariant.Dark : ThemeVariant.Light;
//Reload colors for current theme
LoadStyles();
var mainWindow = new MainWindow();
desktop.MainWindow = MainWindow = mainWindow;
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
mainWindow.OnLoad();
mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult());
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
mainWindow.Show();
}
private static void LoadStyles()
{
ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush");
ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush");
ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush");
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush");
SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush");
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));
}
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

View File

@@ -0,0 +1,588 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="using:Avalonia.Collections">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" Opacity="0.4" Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
</ResourceDictionary>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" Opacity="0.4" Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
<ControlTheme x:Key="DataGridCellTextBlockTheme" TargetType="TextBlock">
<Setter Property="Margin" Value="12,0,12,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</ControlTheme>
<ControlTheme x:Key="DataGridCellTextBoxTheme" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Style Selector="^ /template/ DataValidationErrors">
<Setter Property="Theme" Value="{StaticResource TooltipDataValidationErrors}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridCell}" TargetType="DataGridCell">
<Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="CellBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid x:Name="PART_CellRoot" ColumnDefinitions="*,Auto">
<Rectangle x:Name="CurrencyVisual"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid Grid.Column="0" x:Name="FocusVisual" IsHitTestVisible="False"
IsVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<ContentPresenter Grid.Column="0" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}" />
<Rectangle Grid.Column="0" x:Name="InvalidVisualElement"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellInvalidBrush}"
StrokeThickness="1" />
<Rectangle Name="PART_RightGridLine"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:focus /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="True" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridColumnHeader}" TargetType="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="HeaderBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="PART_ColumnHeaderRoot" ColumnDefinitions="*,Auto">
<Grid Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
IsVisible="False"
Grid.Column="1"
Height="12"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Fill="{TemplateBinding Foreground}"
Stretch="Uniform" />
</Grid>
<Rectangle Name="VerticalSeparator"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False"
IsVisible="False">
<Rectangle x:Name="FocusVisualPrimary"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle x:Name="FocusVisualSecondary"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:focus-visible /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="^:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="^:dragIndicator">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="^:sortascending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconAscendingPath}" />
</Style>
<Style Selector="^:sortdescending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconDescendingPath}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="DataGridTopLeftColumnHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="TopLeftHeaderRoot"
RowDefinitions="*,*,Auto">
<Border Grid.RowSpan="2"
BorderThickness="0,0,1,0"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRowHeader}" TargetType="DataGridRowHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="AreSeparatorsVisible" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
<Border Grid.RowSpan="3"
Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding SeparatorBrush}"
BorderThickness="0,0,1,0">
<Grid Background="{TemplateBinding Background}">
<Rectangle x:Name="RowInvalidVisualElement"
Opacity="0"
Fill="{DynamicResource DataGridRowInvalidBrush}"
Stretch="Fill" />
<Rectangle x:Name="BackgroundRectangle"
Fill="{DynamicResource DataGridRowBackgroundBrush}"
Stretch="Fill" />
</Grid>
</Border>
<Rectangle x:Name="HorizontalSeparator"
Grid.Row="2"
Grid.ColumnSpan="2"
Height="1"
Margin="1,0,1,0"
HorizontalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<ContentPresenter Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRow}" TargetType="DataGridRow">
<Setter Property="Focusable" Value="False" />
<Setter Property="Background" Value="{Binding $parent[DataGrid].RowBackground}" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="RowBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<DataGridFrozenGrid Name="PART_Root"
ColumnDefinitions="Auto,*"
RowDefinitions="*,Auto,Auto">
<Rectangle Name="BackgroundRectangle"
Fill="{DynamicResource DataGridRowBackgroundBrush}"
Grid.RowSpan="2"
Grid.ColumnSpan="2" />
<Rectangle x:Name="InvalidVisualElement"
Opacity="0"
Grid.ColumnSpan="2"
Fill="{DynamicResource DataGridRowInvalidBrush}" />
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="3"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridCellsPresenter Name="PART_CellsPresenter"
Grid.Column="1"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridDetailsPresenter Name="PART_DetailsPresenter"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
<Rectangle Name="PART_BottomGridLine"
Grid.Row="2"
Grid.Column="1"
Height="1"
HorizontalAlignment="Stretch" />
</DataGridFrozenGrid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:invalid">
<Style Selector="^ /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="^ /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="^:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="^:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
</Style>
</ControlTheme>
<ControlTheme x:Key="FluentDataGridRowGroupExpanderButtonTheme" TargetType="ToggleButton">
<Setter Property="Template">
<ControlTemplate>
<Border Width="12"
Height="12"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path Fill="{TemplateBinding Foreground}"
Data="{StaticResource DataGridRowGroupHeaderIconClosedPath}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRowGroupHeader}" TargetType="DataGridRowGroupHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate x:DataType="collections:DataGridCollectionViewGroup">
<DataGridFrozenGrid Name="PART_Root"
Background="{TemplateBinding Background}"
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*"
RowDefinitions="*,Auto">
<Rectangle Name="PART_IndentSpacer"
Grid.Column="1" />
<ToggleButton Name="PART_ExpanderButton"
Grid.Column="2"
Width="12"
Height="12"
Margin="12,0,0,0"
Theme="{StaticResource FluentDataGridRowGroupExpanderButtonTheme}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
Focusable="False"
Foreground="{TemplateBinding Foreground}" />
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<TextBlock Name="PART_PropertyNameElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsPropertyNameVisible}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Margin="4,0,0,0"
Text="{Binding Key}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Name="PART_ItemCountElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsItemCountVisible}"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
<Rectangle x:Name="CurrencyVisual"
Grid.ColumnSpan="5"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
Grid.ColumnSpan="5"
IsVisible="False"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="2"
DataGridFrozenGrid.IsFrozen="True" />
<Rectangle x:Name="PART_BottomGridLine"
Grid.Row="1"
Grid.ColumnSpan="5"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGrid}" TargetType="DataGrid">
<Setter Property="RowBackground" Value="Transparent" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="DropLocationIndicatorTemplate">
<Template>
<Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
Width="2" />
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="DataGridBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,*,Auto,Auto"
ClipToBounds="True">
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Theme="{StaticResource DataGridTopLeftColumnHeader}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.Row="0" Grid.ColumnSpan="2" />
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.Row="0" Grid.ColumnSpan="3" Grid.Column="0"
VerticalAlignment="Bottom"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3" Grid.Column="0">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border x:Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:empty-columns">
<Style Selector="^ /template/ DataGridColumnHeader#PART_TopLeftCornerHeader">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>
</Styles.Resources>
</Styles>

View File

@@ -1,658 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush"
Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
</Styles.Resources>
<Style Selector="DataGridCell">
<Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="CellBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid x:Name="PART_CellRoot" ColumnDefinitions="*,Auto">
<Rectangle x:Name="CurrencyVisual"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<Rectangle x:Name="InvalidVisualElement"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellInvalidBrush}"
StrokeThickness="1" />
<Rectangle Name="PART_RightGridLine"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridCell > TextBlock#CellTextBlock">
<Setter Property="Margin" Value="{DynamicResource DataGridTextColumnCellTextBlockMargin}" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="DataGridCell /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridCell /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridCell > TextBox DataValidationErrors">
<Setter Property="Template" Value="{DynamicResource TooltipDataValidationContentTemplate}" />
<Setter Property="ErrorTemplate" Value="{DynamicResource TooltipDataValidationErrorTemplate}" />
</Style>
<Style Selector="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="Padding" Value="6,0,0,0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="HeaderBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="PART_ColumnHeaderRoot" ColumnDefinitions="*,Auto">
<Grid Grid.Column="0" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ColumnDefinitions="*,12">
<ContentPresenter Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
Grid.Column="1"
Height="12"
Width="8"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Fill="{TemplateBinding Foreground}"
Stretch="Uniform"
Margin="0,0,4,0"
Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z "/>
</Grid>
<Rectangle Name="VerticalSeparator"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False">
<Rectangle x:Name="FocusVisualPrimary"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle x:Name="FocusVisualSecondary"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridColumnHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridColumnHeader:focus-visible /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:dragIndicator">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="DataGridColumnHeader /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="False" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="0.9" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="DataGridColumnHeader:sortascending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader:sortdescending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="-0.9" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="DataGridRow">
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="RowBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<DataGridFrozenGrid Name="PART_Root"
ColumnDefinitions="Auto,*"
RowDefinitions="*,Auto,Auto">
<Rectangle Name="BackgroundRectangle"
Grid.RowSpan="2"
Grid.ColumnSpan="2" />
<Rectangle x:Name="InvalidVisualElement"
Grid.ColumnSpan="2"
Fill="{DynamicResource DataGridRowInvalidBrush}" />
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="3"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridCellsPresenter Name="PART_CellsPresenter"
Grid.Column="1"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridDetailsPresenter Name="PART_DetailsPresenter"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
<Rectangle Name="PART_BottomGridLine"
Grid.Row="2"
Grid.Column="1"
Height="1"
HorizontalAlignment="Stretch" />
</DataGridFrozenGrid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRow">
<Setter Property="Background" Value="{Binding $parent[DataGrid].RowBackground}" />
</Style>
<Style Selector="DataGridRow:nth-child(even)">
<Setter Property="Background" Value="{Binding $parent[DataGrid].AlternatingRowBackground}" />
</Style>
<Style Selector="DataGridRow /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridRowHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="AreSeparatorsVisible" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
<Border Grid.RowSpan="3"
Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding SeparatorBrush}"
BorderThickness="0,0,1,0">
<Grid Background="{TemplateBinding Background}">
<Rectangle x:Name="RowInvalidVisualElement"
Fill="{DynamicResource DataGridRowInvalidBrush}"
Stretch="Fill" />
<Rectangle x:Name="BackgroundRectangle"
Stretch="Fill" />
</Grid>
</Border>
<Rectangle x:Name="HorizontalSeparator"
Grid.Row="2"
Grid.ColumnSpan="2"
Height="1"
Margin="1,0,1,0"
HorizontalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<ContentPresenter Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowGroupHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<DataGridFrozenGrid Name="PART_Root"
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*"
RowDefinitions="*,Auto">
<Rectangle Name="IndentSpacer"
Grid.Column="1" />
<ToggleButton Name="ExpanderButton"
Grid.Column="2"
Width="12"
Height="12"
Margin="12,0,0,0"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
Focusable="False"
Foreground="{TemplateBinding Foreground}" />
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<TextBlock Name="PropertyNameElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsPropertyNameVisible}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Margin="4,0,0,0"
Text="{Binding Key}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Name="ItemCountElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsItemCountVisible}"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
<Rectangle x:Name="CurrencyVisual"
Grid.ColumnSpan="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
Grid.ColumnSpan="5"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="2"
DataGridFrozenGrid.IsFrozen="True" />
<Rectangle x:Name="PART_BottomGridLine"
Grid.Row="1"
Grid.ColumnSpan="5"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton">
<Setter Property="Template">
<ControlTemplate>
<Border Grid.Column="0"
Width="12"
Height="12"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
<Setter Property="Stretch" Value="UniformToFill" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{Binding $parent[DataGridRowGroupHeader].Background}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pointerover /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pressed /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridRowGroupHeader:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid">
<Setter Property="RowBackground" Value="Transparent" />
<Setter Property="AlternatingRowBackground" Value="Transparent" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="DropLocationIndicatorTemplate">
<Template>
<Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
Width="2" />
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="DataGridBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,*,Auto,Auto">
<Grid.Resources>
<ControlTemplate x:Key="TopLeftHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="TopLeftHeaderRoot"
RowDefinitions="*,*,Auto">
<Border Grid.RowSpan="2"
BorderThickness="0,0,1,0"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<Rectangle Grid.RowSpan="2"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="TopRightHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="RootElement" />
</ControlTemplate>
</Grid.Resources>
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Template="{StaticResource TopLeftHeaderTemplate}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.ColumnSpan="2" />
<!--<DataGridColumnHeader Name="PART_TopRightCornerHeader"
Grid.Column="2"
Template="{StaticResource TopRightHeaderTemplate}" />-->
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<!--<Rectangle Name="BottomLeftCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Row="2"
Grid.ColumnSpan="2" />-->
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border x:Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3"
Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeader#PART_TopLeftCornerHeader">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False" />
</Style>
</Styles>

View File

@@ -1,18 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<Color x:Key="SeriesEntryGridBackgroundColor">#cdffcd</Color>
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.5" Color="{StaticResource SeriesEntryGridBackgroundColor}" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="LightCoral" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="PaleGreen" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="Khaki" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="{StaticResource SystemAltHighColor}" />
<SolidColorBrush x:Key="ProcessQueueBookBorderBrush" Color="Gray" />
<SolidColorBrush x:Key="DisabledGrayBrush" Color="#60D3D3D3" />
</Styles.Resources>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="LightGray" />
<Setter Property="CaretBrush" Value="#00000000" />
</Style>
</Styles>

View File

@@ -0,0 +1,96 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="using:Avalonia.Collections">
<Styles.Resources>
<ResourceDictionary>
<StreamGeometry x:Key="LeftArrows">M30,0 H60 L30,50 L60,100 H30 L0,50 M85,0 H115 L85,50 L115,100 H85 L55,50 M140,0 H170 L140,50 L170,100 H140 L110,50</StreamGeometry>
<StreamGeometry x:Key="FirstButtonIcon">M0,0 H100 V12 H53 L100,46 H0 L47,12 H0</StreamGeometry>
<StreamGeometry x:Key="UpButtonIcon">M0,36.66 L50,0 L100,36.66</StreamGeometry>
<StreamGeometry x:Key="DownButtonIcon">M0,0 L100,0 L50,36.66</StreamGeometry>
<StreamGeometry x:Key="LastButtonIcon">M0,0 H100 L53,34 H100 V46 H0 V34 H47</StreamGeometry>
<StreamGeometry x:Key="CancelButtonIcon">M30,0 H50 V30 H80 V50 H50 V80 H30 V50 H0 V30 H30</StreamGeometry>
<StreamGeometry x:Key="QueuedIcon">M31,0 H49 V100 H31 M58,0 H76 V100 H58 M85,0 H103 V100 H85 M8,85 V122 H129 V85 H117 V109 H20 V85 H8 M0,36 V66 L24,51 M114,36 V66 L138,51</StreamGeometry>
<StreamGeometry x:Key="QueueCompletedIcon">M0,0 H100 V100 H0 V0 M2,50 L36,82 L 93,27 L81,15 L36,59 L14,38</StreamGeometry>
<StreamGeometry x:Key="QueueErrorIcon">M0,0 H100 V100 H0 V0 M15,71 L29,85 L50,64 L71,85 L85,71 L64,50 L85,29 L71,15 L50,36 L29,15 L15,29 L36,50</StreamGeometry>
<StreamGeometry x:Key="BookErrorIcon">M32,0 a 32,32 0 0 1 0,64 a 32,32 0 0 1 0,-64 m 0,4 a 28,28 0 0 1 0,56 a 28,28 0 0 1 0,-56 m-21,24 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1</StreamGeometry>
<RotateTransform x:Key="Rotate45Transform" Angle="45" />
<StreamGeometry x:Key="EditTagsIcon">
M39,35 L50,24 H11
A 11,11 0 0 0 0,35 V89 A 11,11 0 0 0 11,100 H64 A 11,11 0 0 0 75,89 V52 L64,63 V89 H11 V35
M 51,65 H36 V50
M 90.5,26.5 L55,62 L 39,45 L74,10
M 78,6 L81.5,2.5 A 8,8 0 0 1 91.5,2 L98.5,9 A 8,8 0 0 1 97.5,19.5 L94,23
</StreamGeometry>
<StreamGeometry x:Key="CollapseIcon">
M0,2 A 2,2 0 0 1 2,0 H62 A2,2 0 0 1 64,2 V62 A 2,2 0 0 1 62,64 H 2 A 2,2 0 0 1 0,62 V2
M 2,2 H62 V62 H2 V2
M11,28 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1
</StreamGeometry>
<StreamGeometry x:Key="VerticalBarIcon">M28,53 v-42 a 1,1 0 0 1 1,-1 h6 a 1,1 0 0 1 1,1 v42 a 1,1 0 0 1 -1,1 h-6 a 1,1 0 0 1 -1,-1</StreamGeometry>
<CombinedGeometry x:Key="ExpandIcon" Geometry1="{StaticResource CollapseIcon}" Geometry2="{StaticResource VerticalBarIcon}" />
<StreamGeometry x:Key="StoplightBodyIcon">
M0,12 A 12,12 0 0 1 12,0 H34 A 12,12 0 0 1 46,12 V88 A 12,12 0 0 1 34,100 H12 A 12,12 0 0 1 0,88 V12
M20,8 H26 A 12,12 0 0 1 26,32 H20 A 12,12 0 0 1 20,8
M20,38 H26 A 12,12 0 0 1 26,62 H20 A 12,12 0 0 1 20,38
M20,68 H26 A 12,12 0 0 1 26,92 H20 A 12,12 0 0 1 20,68
</StreamGeometry>
<StreamGeometry x:Key="PdfDownloadedIcon">
M4,38.5 H3 A 3,3 0 0 1 0,35.5 V21.4 A 3,3 0 0 1 3,18.4 H4 V2 A 2,2 0 0 1 6,0 H30.5 L41,12 V18.4 A 3,3 0 0 1 45,21.4 V35.5 A 3,3 0 0 1 42,38.5 H41 V48.5 A 2,2 0 0 1 39,50.5 H6 A 2,2 0 0 1 4,48.5
M6,38.5 H39 V48.5 H6 V38.5
M6,18.4 V2 H29 V12 A 1,1 0 0 0 30,13 H39 V18.4
M 4.3179,36 c 0,0 0.122,-14.969 0.122,-14.969 1.469,-0.194 2.939,-0.388 4.5,-0.362 1.561,0.026 3.214,0.27 4.357,0.944 1.143,0.674 1.775,1.776 2.015,2.959 0.24,1.184 0.087,2.449 -0.5,3.52 -0.587,1.071 -1.607,1.949 -2.816,2.352 -1.209,0.403 -2.607,0.332 -4.005,0.26 0,0 -0.031,5.265 -0.031,5.265 0,0 -3.673,0.122 -3.673,0.122 0,0 0.031,-0.092 0.031,-0.092
m 3.643,-12.428 c 0,0 0.031,4.286 0.031,4.286 0.735,0.051 1.47,0.102 2.107,-0.056 0.638,-0.158 1.178,-0.526 1.459,-1.122 0.281,-0.597 0.301,-1.423 0.01,-2.005 -0.291,-0.582 -0.893,-0.918 -1.546,-1.061 -0.653,-0.143 -1.357,-0.092 -1.709,-0.066 -0.352,0.026 -0.352,0.026 -0.352,0.026
m 9.428,12.428 c 2.265,0.245 4.531,0.49 6.674,0.066 2.143,-0.424 4.163,-1.515 5.285,-3.081 1.122,-1.566 1.347,-3.607 1.27,-5.306 -0.076,-1.699 -0.454,-3.056 -1.454,-4.219 -1,-1.163 -2.622,-2.133 -4.704,-2.505 -2.082,-0.373 -4.623,-0.148 -7.164,0.076 0,0 0.092,14.969 0.092,14.969
m 3.49,-12.398 c 0,0 0,9.673 0,9.673 0.888,0.02 1.776,0.041 2.653,-0.179 0.877,-0.219 1.745,-0.679 2.367,-1.541 0.622,-0.862 1,-2.127 0.98,-3.403 -0.02,-1.275 -0.439,-2.561 -1.193,-3.337 -0.755,-0.776 -1.847,-1.041 -2.704,-1.158 -0.857,-0.117 -1.48,-0.087 -2.102,-0.056
m 11.908,12.245 v-14.785 h8.969 v2.51 h-5.786 v3.612 h5.388 v2.51 h-5.449 v6.092
</StreamGeometry>
<StreamGeometry x:Key="PdfDownArrowIcon">
M29,44 V58.7498 H35.0491 A 1.5,1.5 0 0 1 36.1342,61.2861 L23.5607,73.8595 A 1.5,1.5 0 0 1 21.4393,73.8595 L8.8658,61.2861 A 1.5,1.5 0 0 1 9.9509,58.7498 H16 V44 A 1.5,1.5 0 0 1 17.5,42.5 H27.5 A 1.5,1.5 0 0 1 29,44
</StreamGeometry>
<CombinedGeometry x:Key="PdfNotDownloadedIcon" Geometry1="{StaticResource PdfDownloadedIcon}" Geometry2="{StaticResource PdfDownArrowIcon}" />
<StreamGeometry x:Key="ImportIcon">
M5.65,4.3 h-2.75 a2.9,2.25 0 0 0 -2.9,2.25 v7.2
a2.9,2.25 0 0 0 2.9,2.25 h10.2 a2.9,2.25 0 0 0 2.9,-2.25 v-7.2 a2.9,2.25 0 0 0 -2.9,-2.25
h-2.75 v1.6 h2.75 a1.3,0.65 0 0 1 1.3,0.65 v7.2 a1.3,0.65 0 0 1 -1.3,0.65 h-10.2 a1.3,0.65 0 0 1 -1.3,-0.65 v-7.2 a1.3,0.65 0 0 1 1.3,-0.65 h2.75 v-1.6
M7.2,0.8 a 0.8,0.8 0 0 1 1.6,0 v8 l0.9929,-0.9929 a 0.8,0.8 0 0 1 1.1314,1.1314 l-2.3586,2.3586
a 0.8,0.8 0 0 1 -1.1314,0 l-2.3586,-2.3586 a 0.8,0.8 0 0 1 1.1314,-1.1314 l0.9929,0.9929 v8
</StreamGeometry>
<StreamGeometry x:Key="LibationCheersIcon">
M139,2
A 192,200 0 0 0 103,84
A 222,334 41 0 0 241,320
V478
H160
A 16,16 0 0 0 160,510
H352
A16 16 0 0 0 352,478
H271
V320
A 222,334 -41 0 0 409,84
A 192,200 0 0 0 373,2
M355,32
A 192,200 0 0 1 381,127
A 187.5,334 -35 0 1 256,286
A 187.5,334 35 0 1 131,127
A 192,200 0 0 1 157,32
H355
M146,147
A 168,300 35 0 0 256,270
A 168,300 -35 0 0 366,128
S 360,50 280,110
S 192,128 147,147
</StreamGeometry>
</ResourceDictionary>
</Styles.Resources>
</Styles>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 573 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
using LibationAvalonia.Dialogs;
using LibationFileManager;
using System.Threading.Tasks;
@@ -13,7 +14,7 @@ namespace LibationAvalonia
=> GetBrushFromResources(name, Brushes.Transparent);
public static IBrush GetBrushFromResources(string name, IBrush defaultBrush)
{
if (App.Current.Styles.TryGetResource(name, out var value) && value is IBrush brush)
if (App.Current.TryGetResource(name, App.Current.ActualThemeVariant, out var value) && value is IBrush brush)
return brush;
return defaultBrush;
}
@@ -21,7 +22,7 @@ namespace LibationAvalonia
public static Task<DialogResult> ShowDialogAsync(this DialogWindow dialogWindow, Window owner = null)
=> dialogWindow.ShowDialog<DialogResult>(owner ?? App.MainWindow);
public static Window GetParentWindow(this IControl control) => control.VisualRoot as Window;
public static Window GetParentWindow(this Control control) => control.GetVisualRoot() as Window;
private static Bitmap defaultImage;

View File

@@ -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()
{

View File

@@ -3,9 +3,9 @@ using LibationUiBase.GridView;
namespace LibationAvalonia.Controls
{
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
{
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{
//Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary.
var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox;

View File

@@ -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>;
}
}

View File

@@ -4,7 +4,6 @@ using Avalonia.Data;
using Avalonia.Interactivity;
using DataLayer;
using ReactiveUI;
using System;
namespace LibationAvalonia.Controls
{
@@ -18,7 +17,7 @@ namespace LibationAvalonia.Controls
BindingTarget = MyRatingCellEditor.RatingProperty;
}
protected override IControl GenerateElement(DataGridCell cell, object dataItem)
protected override Control GenerateElement(DataGridCell cell, object dataItem)
{
var myRatingElement = new MyRatingCellEditor
{
@@ -41,7 +40,7 @@ namespace LibationAvalonia.Controls
return myRatingElement;
}
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{
var myRatingElement = new MyRatingCellEditor
{
@@ -57,12 +56,12 @@ namespace LibationAvalonia.Controls
return myRatingElement;
}
protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs)
protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs)
=> editingElement is MyRatingCellEditor myRating
? myRating.Rating
: DefaultRating;
protected override void CancelCellEdit(IControl editingElement, object uneditedValue)
protected override void CancelCellEdit(Control editingElement, object uneditedValue)
{
if (editingElement is MyRatingCellEditor myRating)
{

View File

@@ -1,12 +1,10 @@
using Avalonia.Controls;
using System;
using System.Linq;
namespace LibationAvalonia.Controls
{
public partial class DataGridTemplateColumnExt : DataGridTemplateColumn
{
protected override IControl GenerateElement(DataGridCell cell, object dataItem)
protected override Control GenerateElement(DataGridCell cell, object dataItem)
{
cell?.AttachContextMenu();
return base.GenerateElement(cell, dataItem);

View File

@@ -5,29 +5,30 @@
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="LibationAvalonia.Controls.DirectoryOrCustomSelectControl">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<controls:DirectorySelectControl
Grid.Column="1"
Grid.Row="0"
Name="directorySelectControl"
SubDirectory="{Binding $parent.SubDirectory}"
KnownDirectories="{Binding $parent.KnownDirectories}" />
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" Name="grid">
<controls:DirectorySelectControl
Grid.Column="1"
Grid.Row="0"
IsEnabled="{Binding KnownChecked}"
SelectedDirectory="{Binding SelectedDirectory, Mode=TwoWay}"
SubDirectory="{Binding $parent[1].SubDirectory}"
KnownDirectories="{Binding $parent[1].KnownDirectories}" />
<RadioButton
Grid.Column="0"
Grid.Row="0"
Name="knownDirRadio"
IsChecked="{Binding KnownChecked, Mode=TwoWay}" />
IsChecked="{Binding KnownChecked, Mode=TwoWay}"/>
<RadioButton
Grid.Column="0"
Grid.Row="1"
Name="customDirRadio"
IsChecked="{Binding CustomChecked, Mode=TwoWay}" />
IsChecked="{Binding CustomChecked, Mode=TwoWay}"/>
<Grid Grid.Column="1" Grid.Row="1" ColumnDefinitions="*,Auto">
<TextBox IsEnabled="{Binding CustomChecked}" Name="customDirTbox" Grid.Column="0" IsReadOnly="True" Text="{Binding CustomDir, Mode=TwoWay}" />
<Button Name="customDirBrowseBtn" Grid.Column="1" Content="..." Margin="5,0,0,0" Padding="10,0,10,0" VerticalAlignment="Stretch" />
<Grid Grid.Column="1" Grid.Row="1" ColumnDefinitions="*,Auto"
IsEnabled="{Binding CustomChecked}">
<TextBox Grid.Column="0" IsReadOnly="True" Text="{Binding CustomDir, Mode=TwoWay}" />
<Button Grid.Column="1" Content="..." Margin="5,0,0,0" Padding="10,0,10,0" Click="CustomDirBrowseBtn_Click" VerticalAlignment="Stretch" />
</Grid>
</Grid>
</UserControl>

View File

@@ -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
@@ -37,55 +36,49 @@ namespace LibationAvalonia.Controls
get => GetValue(SubDirectoryProperty);
set => SetValue(SubDirectoryProperty, value);
}
CustomState customStates = new();
private readonly DirectoryState directoryState = new();
public DirectoryOrCustomSelectControl()
{
InitializeComponent();
customDirBrowseBtn = this.Find<Button>(nameof(customDirBrowseBtn));
directorySelectControl = this.Find<DirectorySelectControl>(nameof(directorySelectControl));
grid.DataContext = directoryState;
this.Find<TextBox>(nameof(customDirTbox)).DataContext = customStates;
this.Find<RadioButton>(nameof(knownDirRadio)).DataContext = customStates;
this.Find<RadioButton>(nameof(customDirRadio)).DataContext = customStates;
customStates.PropertyChanged += CheckStates_PropertyChanged;
customDirBrowseBtn.Click += CustomDirBrowseBtn_Click;
directoryState.PropertyChanged += DirectoryState_PropertyChanged;
PropertyChanged += DirectoryOrCustomSelectControl_PropertyChanged;
directorySelectControl.PropertyChanged += DirectorySelectControl_PropertyChanged;
}
private class CustomState: ViewModels.ViewModelBase
private void DirectoryState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(DirectoryState.SelectedDirectory) or nameof(DirectoryState.KnownChecked) &&
directoryState.KnownChecked &&
directoryState.SelectedDirectory is Configuration.KnownDirectories kdir &&
kdir is not Configuration.KnownDirectories.None)
{
Directory = kdir is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute : Configuration.GetKnownDirectoryPath(kdir);
}
else if (e.PropertyName is nameof(DirectoryState.CustomDir) or nameof(DirectoryState.CustomChecked) &&
directoryState.CustomChecked &&
directoryState.CustomDir is not null)
{
Directory = directoryState.CustomDir;
}
}
private class DirectoryState : ViewModels.ViewModelBase
{
private string _customDir;
private string _subDirectory;
private bool _knownChecked;
private bool _customChecked;
public string CustomDir { get=> _customDir; set => this.RaiseAndSetIfChanged(ref _customDir, value); }
public bool KnownChecked
{
get => _knownChecked;
set
{
this.RaiseAndSetIfChanged(ref _knownChecked, value);
if (value)
CustomChecked = false;
else if (!CustomChecked)
CustomChecked = true;
}
}
public bool CustomChecked
{
get => _customChecked;
set
{
this.RaiseAndSetIfChanged(ref _customChecked, value);
if (value)
KnownChecked = false;
else if (!KnownChecked)
KnownChecked = true;
}
}
private Configuration.KnownDirectories? _selectedDirectory;
public string CustomDir { get => _customDir; set => this.RaiseAndSetIfChanged(ref _customDir, value); }
public string SubDirectory { get => _subDirectory; set => this.RaiseAndSetIfChanged(ref _subDirectory, value); }
public bool KnownChecked { get => _knownChecked; set => this.RaiseAndSetIfChanged(ref _knownChecked, value); }
public bool CustomChecked { get => _customChecked; set => this.RaiseAndSetIfChanged(ref _customChecked, value); }
public Configuration.KnownDirectories? SelectedDirectory { get => _selectedDirectory; set => this.RaiseAndSetIfChanged(ref _selectedDirectory, value); }
}
private async void CustomDirBrowseBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
@@ -97,48 +90,12 @@ namespace LibationAvalonia.Controls
var selectedFolders = await (VisualRoot as Window).StorageProvider.OpenFolderPickerAsync(options);
customStates.CustomDir =
selectedFolders
.SingleOrDefault()?.
TryGetUri(out var uri) is true
? uri.LocalPath
: customStates.CustomDir;
}
private void CheckStates_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(CustomState.CustomDir))
{
directorySelectControl.IsEnabled = !customStates.CustomChecked;
customDirBrowseBtn.IsEnabled = customStates.CustomChecked;
}
setDirectory();
}
private void DirectorySelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name == nameof(DirectorySelectControl.SelectedDirectory))
{
setDirectory();
}
}
private void setDirectory()
{
var selectedDir
= customStates.CustomChecked ? customStates.CustomDir
: directorySelectControl.SelectedDirectory is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute
: Configuration.GetKnownDirectoryPath(directorySelectControl.SelectedDirectory);
selectedDir ??= string.Empty;
Directory = customStates.CustomChecked ? selectedDir : System.IO.Path.Combine(selectedDir, SubDirectory ?? "");
directoryState.CustomDir = selectedFolders.SingleOrDefault()?.Path?.LocalPath ?? directoryState.CustomDir;
}
private void DirectoryOrCustomSelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name == nameof(Directory) && e.OldValue is null)
if (e.Property == DirectoryProperty)
{
var directory = Directory?.Trim() ?? "";
@@ -146,23 +103,23 @@ 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)
{
customStates.CustomChecked = true;
customStates.CustomDir = directory;
directoryState.CustomDir = noSubDir;
directoryState.CustomChecked = true;
}
else
{
customStates.KnownChecked = true;
directorySelectControl.SelectedDirectory = known;
directoryState.SelectedDirectory = known;
directoryState.KnownChecked = true;
}
}
else if (e.Property.Name == nameof(KnownDirectories))
directorySelectControl.KnownDirectories = KnownDirectories;
else if (e.Property.Name == nameof(SubDirectory))
directorySelectControl.SubDirectory = SubDirectory;
else if (e.Property == KnownDirectoriesProperty &&
KnownDirectories.Count > 0 &&
directoryState.SelectedDirectory is null or Configuration.KnownDirectories.None)
directoryState.SelectedDirectory = KnownDirectories[0];
}
private string RemoveSubDirectoryFromPath(string path)
@@ -180,10 +137,5 @@ namespace LibationAvalonia.Controls
return path;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@@ -8,6 +8,7 @@
<UserControl.Resources>
<controls:KnownDirectoryConverter x:Key="KnownDirectoryConverter" />
<controls:KnownDirectoryPath x:Key="KnownDirectoryPath" />
</UserControl.Resources>
@@ -20,19 +21,26 @@
<controls:WheelComboBox
HorizontalContentAlignment = "Stretch"
HorizontalAlignment = "Stretch"
Name="combo"
MinHeight="{Binding #displayPathTbox.MinHeight}"
SelectedItem="{Binding $parent[1].SelectedDirectory, Mode=TwoWay}"
Items="{Binding $parent[1].KnownDirectories}">
ItemsSource="{Binding $parent[1].KnownDirectories}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding, Converter={StaticResource KnownDirectoryConverter}}" />
<TextBlock Text="{Binding Converter={StaticResource KnownDirectoryConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</controls:WheelComboBox>
<TextBox Margin="0,10,0,10" IsReadOnly="True" Name="displayPathTbox" />
<TextBox Margin="0,10,0,10" IsReadOnly="True">
<TextBox.Text>
<MultiBinding Converter="{StaticResource KnownDirectoryPath}">
<MultiBinding.Bindings>
<Binding Path="#combo.SelectedItem"/>
<Binding Path="$parent[1].SubDirectory"/>
</MultiBinding.Bindings>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</UserControl>

View File

@@ -1,15 +1,13 @@
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;
namespace LibationAvalonia.Controls
{
@@ -27,6 +25,24 @@ namespace LibationAvalonia.Controls
return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
}
}
public class KnownDirectoryPath : IMultiValueConverter
{
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
if (values?.Count == 2 && values[0] is Configuration.KnownDirectories kdir && kdir is not Configuration.KnownDirectories.None)
{
var subdir = values[1] as string ?? "";
var path = kdir is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute : Configuration.GetKnownDirectoryPath(kdir);
return Path.Combine(path, subdir);
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
}
}
public partial class DirectorySelectControl : UserControl
{
@@ -40,8 +56,8 @@ namespace LibationAvalonia.Controls
Configuration.KnownDirectories.LibationFiles
};
public static readonly StyledProperty<Configuration.KnownDirectories> SelectedDirectoryProperty =
AvaloniaProperty.Register<DirectorySelectControl, Configuration.KnownDirectories>(nameof(SelectedDirectory));
public static readonly StyledProperty<Configuration.KnownDirectories?> SelectedDirectoryProperty =
AvaloniaProperty.Register<DirectorySelectControl, Configuration.KnownDirectories?>(nameof(SelectedDirectory));
public static readonly StyledProperty<List<Configuration.KnownDirectories>> KnownDirectoriesProperty =
AvaloniaProperty.Register<DirectorySelectControl, List<Configuration.KnownDirectories>>(nameof(KnownDirectories), DefaultKnownDirectories);
@@ -52,25 +68,6 @@ namespace LibationAvalonia.Controls
public DirectorySelectControl()
{
InitializeComponent();
displayPathTbox = this.Get<TextBox>(nameof(displayPathTbox));
displayPathTbox.Bind(TextBox.TextProperty, TextboxPath);
PropertyChanged += DirectorySelectControl_PropertyChanged;
}
private Subject<string> TextboxPath = new Subject<string>();
private void DirectorySelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name == nameof(SelectedDirectory))
{
TextboxPath.OnNext(
Path.Combine(
SelectedDirectory is Configuration.KnownDirectories.None ? string.Empty
: SelectedDirectory is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute
: Configuration.GetKnownDirectoryPath(SelectedDirectory)
, SubDirectory ?? string.Empty));
}
}
public List<Configuration.KnownDirectories> KnownDirectories
@@ -79,7 +76,7 @@ namespace LibationAvalonia.Controls
set => SetValue(KnownDirectoriesProperty, value);
}
public Configuration.KnownDirectories SelectedDirectory
public Configuration.KnownDirectories? SelectedDirectory
{
get => GetValue(SelectedDirectoryProperty);
set => SetValue(SelectedDirectoryProperty, value);
@@ -90,10 +87,5 @@ namespace LibationAvalonia.Controls
get => GetValue(SubDirectoryProperty);
set => SetValue(SubDirectoryProperty, value);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@@ -3,49 +3,53 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
x:Class="LibationAvalonia.Controls.GroupBox">
<Design.DataContext>
</Design.DataContext>
<ContentControl.Styles>
<Style Selector="controls|GroupBox Border">
<Setter Property="BorderBrush" Value="DarkGray" />
</Style>
<Style Selector="controls|GroupBox">
<Setter Property="BorderBrush" Value="{DynamicResource SystemBaseMediumLowColor}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="7,10,*,Auto">
<Grid ColumnDefinitions="Auto,Auto,*,Auto" RowDefinitions="Auto,*,Auto">
<Panel
Name="PART_LabelOffsetter"
Grid.Column="1"
Margin="8,9,0,0" />
<Grid
ZIndex="1"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1" Margin="8,0,0,0"
Grid.Column="2"
ColumnDefinitions="Auto,*"
VerticalAlignment="Top">
<TextBlock
Padding="4,0,4,0"
Background="{StaticResource SystemAltHighColor}"
Name="PART_Label"
Padding="4,0"
Background="{DynamicResource SystemAltHighColor}"
Text="{TemplateBinding Label}"
/>
</Grid>
<ContentPresenter
Margin="8,0,8,5"
Grid.Row="2"
Name="PART_ContentPresenter"
Margin="8,10,8,5"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Content="{TemplateBinding Content}"/>
<Border
BorderBrush="DarkGray"
BorderThickness="{TemplateBinding BorderWidth}"
CornerRadius="3"
Name="PART_Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Grid.Column="0"
Grid.ColumnSpan="3"
Grid.ColumnSpan="4"
Grid.Row="1"
Grid.RowSpan="3"/>
Grid.RowSpan="2"/>
</Grid>
</ControlTemplate>

View File

@@ -1,38 +1,24 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
namespace LibationAvalonia.Controls
{
public partial class GroupBox : ContentControl
{
public static readonly StyledProperty<Thickness> BorderWidthProperty =
AvaloniaProperty.Register<GroupBox, Thickness>(nameof(BorderWidth));
public static readonly StyledProperty<string> LabelProperty =
AvaloniaProperty.Register<GroupBox, string>(nameof(Label));
public GroupBox()
{
InitializeComponent();
BorderWidth = new Thickness(3);
Label = "This is a groupbox label";
}
public Thickness BorderWidth
{
get { return GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
public string Label
{
get { return GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@@ -3,10 +3,10 @@
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"
xmlns:controls="using:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Controls.LinkLabel">
<TextBlock.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="Blue"/>
<Style Selector="controls|LinkLabel">
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</TextBlock.Styles>

View File

@@ -1,20 +1,80 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using System;
using System.Windows.Input;
namespace LibationAvalonia.Controls
{
public partial class LinkLabel : TextBlock, IStyleable
public partial class LinkLabel : TextBlock, IStyleable, ICommandSource
{
Type IStyleable.StyleKey => typeof(TextBlock);
Type IStyleable.StyleKey => typeof(LinkLabel);
public static readonly StyledProperty<ICommand> CommandProperty =
AvaloniaProperty.Register<LinkLabel, ICommand>(nameof(Command), enableDataValidation: true);
public static readonly StyledProperty<object> CommandParameterProperty =
AvaloniaProperty.Register<LinkLabel, object>(nameof(CommandParameter));
public static readonly StyledProperty<IBrush> ForegroundVisitedProperty =
AvaloniaProperty.Register<LinkLabel, IBrush>(nameof(ForegroundVisited));
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
RoutedEvent.Register<Button, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble);
public ICommand Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public IBrush ForegroundVisited
{
get => GetValue(ForegroundVisitedProperty);
set => SetValue(ForegroundVisitedProperty, value);
}
public event EventHandler<RoutedEventArgs> Click
{
add => AddHandler(ClickEvent, value);
remove => RemoveHandler(ClickEvent, value);
}
private static readonly Cursor HandCursor = new Cursor(StandardCursorType.Hand);
private bool _commandCanExecute = true;
public LinkLabel()
{
InitializeComponent();
Tapped += LinkLabel_Tapped;
}
private void LinkLabel_Tapped(object sender, TappedEventArgs e)
{
Foreground = ForegroundVisited;
if (IsEffectivelyEnabled)
{
var args = new RoutedEventArgs(ClickEvent);
RaiseEvent(args);
if (!args.Handled && Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
args.Handled = true;
}
}
}
protected override void OnPointerEntered(PointerEventArgs e)
{
this.Cursor = HandCursor;
@@ -25,10 +85,33 @@ namespace LibationAvalonia.Controls
this.Cursor = Cursor.Default;
base.OnPointerExited(e);
}
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
private void InitializeComponent()
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception error)
{
AvaloniaXamlLoader.Load(this);
base.UpdateDataValidation(property, state, error);
if (property == CommandProperty)
{
if (state == BindingValueType.BindingError)
{
if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
}
}
}
public void CanExecuteChanged(object sender, EventArgs e)
{
var canExecute = Command == null || Command.CanExecute(CommandParameter);
if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
}
}
}

View File

@@ -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();

View File

@@ -0,0 +1,176 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia;
using LibationFileManager;
using System;
using System.Threading.Tasks;
namespace LibationAvalonia.Controls;
#nullable enable
public class NativeWebView : NativeControlHost, IWebView
{
private IWebViewAdapter? _webViewAdapter;
private Uri? _delayedSource;
private TaskCompletionSource _webViewReadyCompletion = new();
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
public event EventHandler? DOMContentLoaded;
public bool CanGoBack => _webViewAdapter?.CanGoBack ?? false;
public bool CanGoForward => _webViewAdapter?.CanGoForward ?? false;
public Uri? Source
{
get => _webViewAdapter?.Source ?? throw new InvalidOperationException("Control was not initialized");
set
{
if (_webViewAdapter is null)
{
_delayedSource = value;
return;
}
_webViewAdapter.Source = value;
}
}
public bool GoBack()
{
return _webViewAdapter?.GoBack() ?? throw new InvalidOperationException("Control was not initialized");
}
public bool GoForward()
{
return _webViewAdapter?.GoForward() ?? throw new InvalidOperationException("Control was not initialized");
}
public Task<string?> InvokeScriptAsync(string scriptName)
{
return _webViewAdapter is null
? throw new InvalidOperationException("Control was not initialized")
: _webViewAdapter.InvokeScriptAsync(scriptName);
}
public void Navigate(Uri url)
{
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
.Navigate(url);
}
public Task NavigateToString(string text)
{
return (_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
.NavigateToString(text);
}
public void Refresh()
{
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
.Refresh();
}
public void Stop()
{
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
.Stop();
}
public Task WaitForNativeHost()
{
return _webViewReadyCompletion.Task;
}
private class PlatformHandle : IPlatformHandle
{
public nint Handle { get; init; }
public string? HandleDescriptor { get; init; }
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
_webViewAdapter = InteropFactory.Create().CreateWebViewAdapter();
if (_webViewAdapter is null)
return base.CreateNativeControlCore(parent);
else
{
SubscribeOnEvents();
var handle = new PlatformHandle
{
Handle = _webViewAdapter.PlatformHandle.Handle,
HandleDescriptor = _webViewAdapter.PlatformHandle.HandleDescriptor
};
if (_delayedSource is not null)
{
_webViewAdapter.Source = _delayedSource;
}
_webViewReadyCompletion.TrySetResult();
return handle;
}
}
private void SubscribeOnEvents()
{
if (_webViewAdapter is not null)
{
_webViewAdapter.NavigationStarted += WebViewAdapterOnNavigationStarted;
_webViewAdapter.NavigationCompleted += WebViewAdapterOnNavigationCompleted;
_webViewAdapter.DOMContentLoaded += _webViewAdapter_DOMContentLoaded;
}
}
private void _webViewAdapter_DOMContentLoaded(object? sender, EventArgs e)
{
DOMContentLoaded?.Invoke(this, e);
}
private void WebViewAdapterOnNavigationStarted(object? sender, WebViewNavigationEventArgs e)
{
NavigationStarted?.Invoke(this, e);
}
private void WebViewAdapterOnNavigationCompleted(object? sender, WebViewNavigationEventArgs e)
{
NavigationCompleted?.Invoke(this, e);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty && change.NewValue is Rect rect)
{
var scaling = (float)(VisualRoot?.RenderScaling ?? 1.0f);
_webViewAdapter?.HandleResize((int)(rect.Width * scaling), (int)(rect.Height * scaling), scaling);
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (_webViewAdapter != null)
{
e.Handled = _webViewAdapter.HandleKeyDown((uint)e.Key, (uint)e.KeyModifiers);
}
base.OnKeyDown(e);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
if (_webViewAdapter is not null)
{
_webViewReadyCompletion = new TaskCompletionSource();
_webViewAdapter.NavigationStarted -= WebViewAdapterOnNavigationStarted;
_webViewAdapter.NavigationCompleted -= WebViewAdapterOnNavigationCompleted;
(_webViewAdapter as IDisposable)?.Dispose();
}
}
}

View File

@@ -0,0 +1,329 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="600"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:AudioSettingsVM"
x:Class="LibationAvalonia.Controls.Settings.Audio">
<Grid
Margin="5"
RowDefinitions="Auto,*,Auto"
ColumnDefinitions="*,*">
<Grid.Styles>
<Style Selector="CheckBox">
<Setter Property="Margin" Value="0,0,0,5" />
<Style Selector="^ > TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Style>
<Style Selector="RadioButton">
<Setter Property="Margin" Value="0,0,0,5" />
<Style Selector="^ TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Style>
</Grid.Styles>
<StackPanel
Grid.Row="0"
Grid.Column="0">
<CheckBox IsChecked="{CompiledBinding CreateCueSheet, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding CreateCueSheetText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding DownloadCoverArt, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding DownloadCoverArtText}" />
</CheckBox>
<Grid ColumnDefinitions="*,Auto">
<CheckBox IsChecked="{CompiledBinding DownloadClipsBookmarks, Mode=TwoWay}">
<TextBlock Text="Download Clips, Notes and Bookmarks as" />
</CheckBox>
<controls:WheelComboBox
Margin="5,0,0,0"
Grid.Column="1"
IsEnabled="{CompiledBinding DownloadClipsBookmarks}"
ItemsSource="{CompiledBinding ClipBookmarkFormats}"
SelectedItem="{CompiledBinding ClipBookmarkFormat}"/>
</Grid>
<CheckBox IsChecked="{CompiledBinding RetainAaxFile, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding RetainAaxFileText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding MergeOpeningAndEndCredits, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding MergeOpeningEndCreditsText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding AllowLibationFixup, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding AllowLibationFixupText}" />
</CheckBox>
</StackPanel>
<controls:GroupBox
Grid.Row="1"
Label="Audiobook Fix-ups"
IsEnabled="{CompiledBinding AllowLibationFixup}">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{CompiledBinding SplitFilesByChapter, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding SplitFilesByChapterText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding StripAudibleBrandAudio, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding StripAudibleBrandingText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding StripUnabridged, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding StripUnabridgedText}" />
</CheckBox>
<RadioButton IsChecked="{CompiledBinding !DecryptToLossy, Mode=TwoWay}">
<StackPanel VerticalAlignment="Center">
<TextBlock
Text="Download my books in the original audio format (Lossless)" />
<CheckBox
IsEnabled="{CompiledBinding !DecryptToLossy}"
IsChecked="{CompiledBinding MoveMoovToBeginning, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding MoveMoovToBeginningText}" />
</CheckBox>
</StackPanel>
</RadioButton>
<RadioButton IsChecked="{CompiledBinding DecryptToLossy, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Download my books as .MP3 files (transcode if necessary)" />
</RadioButton>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox
Grid.Column="1"
Grid.RowSpan="2"
Margin="10,0,0,0"
Label="Mp3 Encoding Options">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,*">
<Grid
Margin="0,5"
ColumnDefinitions="Auto,*">
<controls:GroupBox
Grid.Column="0"
Label="Target">
<Grid ColumnDefinitions="Auto,Auto">
<RadioButton
Margin="5"
Content="Bitrate"
IsChecked="{CompiledBinding LameTargetBitrate, Mode=TwoWay}"/>
<RadioButton
Grid.Column="1"
Margin="5"
Content="Quality"
IsChecked="{CompiledBinding !LameTargetBitrate, Mode=TwoWay}"/>
</Grid>
</controls:GroupBox>
<CheckBox
HorizontalAlignment="Right"
Grid.Column="1"
IsChecked="{CompiledBinding LameDownsampleMono, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Downsample to mono? (Recommended)" />
</CheckBox>
</Grid>
<Grid Grid.Row="1" Margin="0,5" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto">
<TextBlock Margin="0,0,0,5" Text="Max audio sample rate:" />
<controls:WheelComboBox
Grid.Row="1"
HorizontalAlignment="Stretch"
ItemsSource="{CompiledBinding SampleRates}"
SelectedItem="{CompiledBinding SelectedSampleRate, Mode=TwoWay}"/>
<TextBlock Margin="0,0,0,5" Grid.Column="2" Text="Encoder Quality:" />
<controls:WheelComboBox
Grid.Column="2"
Grid.Row="1"
HorizontalAlignment="Stretch"
ItemsSource="{CompiledBinding EncoderQualities}"
SelectedItem="{CompiledBinding SelectedEncoderQuality, Mode=TwoWay}"/>
</Grid>
<controls:GroupBox
Grid.Row="2"
Margin="0,5"
Label="Bitrate"
IsEnabled="{CompiledBinding LameTargetBitrate}" >
<StackPanel>
<Grid ColumnDefinitions="*,25,Auto">
<Slider
Grid.Column="0"
IsEnabled="{CompiledBinding !LameMatchSource}"
Value="{CompiledBinding LameBitrate, Mode=TwoWay}"
Minimum="16"
Maximum="320"
IsSnapToTickEnabled="True" TickFrequency="16"
Ticks="16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,272,288,304,320"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{CompiledBinding $parent[Slider].Value, Mode=OneWay, StringFormat='\{0:f0\} Kbps'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
Text="{CompiledBinding LameBitrate}" />
<TextBlock
Grid.Column="2"
Text=" Kbps" />
</Grid>
<Grid ColumnDefinitions="*,*">
<CheckBox
Grid.Column="0"
IsChecked="{CompiledBinding LameConstantBitrate, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Restrict Encoder to Constant Bitrate?" />
</CheckBox>
<CheckBox
Grid.Column="1"
HorizontalAlignment="Right"
IsChecked="{CompiledBinding LameMatchSource, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Match Source Bitrate?" />
</CheckBox>
</Grid>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox
Grid.Row="3"
Margin="0,5"
Label="Quality"
IsEnabled="{CompiledBinding !LameTargetBitrate}">
<Grid
ColumnDefinitions="*,Auto,25"
RowDefinitions="*,Auto">
<Slider
Grid.Column="0"
Grid.ColumnSpan="2"
Value="{CompiledBinding LameVBRQuality, Mode=TwoWay}"
Minimum="0"
Maximum="9"
IsSnapToTickEnabled="True" TickFrequency="1"
Ticks="0,1,2,3,4,5,6,7,8,9"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{CompiledBinding $parent[Slider].Value, Mode=OneWay, StringFormat='V\{0:f0\}'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<StackPanel
Grid.Column="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock Text="V" />
<TextBlock Text="{CompiledBinding LameVBRQuality}" />
</StackPanel>
<TextBlock
Grid.Column="0"
Grid.Row="1"
Text="Higher" />
<TextBlock
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Right"
Text="Lower" />
</Grid>
</controls:GroupBox>
<TextBlock
Grid.Row="4"
Margin="0,5"
VerticalAlignment="Bottom"
Text="Using L.A.M.E encoding engine"
FontStyle="Oblique" />
</Grid>
</controls:GroupBox>
<controls:GroupBox
Grid.Row="2"
Grid.ColumnSpan="2"
Margin="0,10,0,0"
IsEnabled="{CompiledBinding SplitFilesByChapter}"
Label="{CompiledBinding ChapterTitleTemplateText}">
<Grid ColumnDefinitions="*,Auto" Margin="0,8" >
<TextBox
Grid.Column="0"
FontSize="14"
IsReadOnly="True"
Text="{CompiledBinding ChapterTitleTemplate}" />
<Button
Grid.Column="1"
Content="Edit"
Padding="30,0"
Margin="10,0,0,0"
VerticalAlignment="Stretch"
Click="EditChapterTitleTemplateButton_Click" />
</Grid>
</controls:GroupBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,38 @@
using Avalonia.Controls;
using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels.Settings;
using LibationFileManager;
using System.Threading.Tasks;
namespace LibationAvalonia.Controls.Settings
{
public partial class Audio : UserControl
{
private AudioSettingsVM _viewModel => DataContext as AudioSettingsVM;
public Audio()
{
InitializeComponent();
if (Design.IsDesignMode)
{
_ = Configuration.Instance.LibationFiles;
DataContext = new AudioSettingsVM(Configuration.Instance);
}
}
public async void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_viewModel is null) return;
var newTemplate = await editTemplate(TemplateEditor<Templates.ChapterTitleTemplate>.CreateNameEditor(_viewModel.ChapterTitleTemplate));
if (newTemplate is not null)
_viewModel.ChapterTitleTemplate = newTemplate;
}
private async Task<string> editTemplate(ITemplateEditor template)
{
var form = new EditTemplateDialog(template);
if (await form.ShowDialog<DialogResult>(this.GetParentWindow()) == DialogResult.OK)
return template.EditingTemplate.TemplateText;
else return null;
}
}
}

View File

@@ -0,0 +1,182 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="700"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:DownloadDecryptSettingsVM"
x:Class="LibationAvalonia.Controls.Settings.DownloadDecrypt">
<Grid RowDefinitions="Auto,Auto,Auto,*">
<controls:GroupBox
Grid.Row="0"
Margin="5"
Label="{CompiledBinding BadBookGroupboxText}">
<Grid
ColumnDefinitions="*,*"
RowDefinitions="Auto,Auto">
<Grid.Styles>
<Style Selector="RadioButton">
<Setter Property="Margin" Value="0,5,0,5" />
<Style Selector="^ > TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Style>
</Grid.Styles>
<RadioButton
Grid.Column="0"
Grid.Row="0"
IsChecked="{CompiledBinding BadBookAsk, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding BadBookAskText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="0"
IsChecked="{CompiledBinding BadBookAbort, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding BadBookAbortText}" />
</RadioButton>
<RadioButton
Grid.Column="0"
Grid.Row="1"
IsChecked="{CompiledBinding BadBookRetry, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding BadBookRetryText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="1"
IsChecked="{CompiledBinding BadBookIgnore, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding BadBookIgnoreText}" />
</RadioButton>
</Grid>
</controls:GroupBox>
<controls:GroupBox
Margin="5"
Grid.Row="1"
Label="Custom File Naming">
<Grid
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
ColumnDefinitions="*,Auto">
<Grid.Styles>
<Style Selector="TextBox">
<Setter Property="Margin" Value="0,5,10,10" />
<Setter Property="FontSize" Value="14" />
<Setter Property="IsReadOnly" Value="True" />
</Style>
<Style Selector="Button">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,5,0,10" />
<Setter Property="Padding" Value="30,0" />
</Style>
</Grid.Styles>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="0,5,0,0"
Text="{CompiledBinding FolderTemplateText}" />
<TextBox
Grid.Row="1"
Grid.Column="0"
Text="{CompiledBinding FolderTemplate}" />
<Button
Grid.Row="1"
Grid.Column="1"
Content="Edit"
Click="EditFolderTemplateButton_Click" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="{CompiledBinding FileTemplateText}" />
<TextBox
Grid.Row="3"
Grid.Column="0"
Text="{CompiledBinding FileTemplate}" />
<Button
Grid.Row="3"
Grid.Column="1"
Content="Edit"
Click="EditFileTemplateButton_Click" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="{CompiledBinding ChapterFileTemplateText}" />
<TextBox
Grid.Row="5"
Grid.Column="0"
Text="{CompiledBinding ChapterFileTemplate}" />
<Button
Grid.Row="5"
Grid.Column="1"
Content="Edit"
Click="EditChapterFileTemplateButton_Click" />
<Button
Grid.Row="6"
Grid.Column="0"
Height="30"
Margin="0"
Content="{CompiledBinding EditCharReplacementText}"
Click="EditCharReplacementButton_Click" />
</Grid>
</controls:GroupBox>
<controls:GroupBox
Grid.Row="2"
Margin="5"
Label="Temporary Files Location">
<StackPanel
Margin="0,5" >
<TextBlock
Margin="0,0,0,10"
TextWrapping="Wrap"
Text="{CompiledBinding InProgressDescriptionText}" />
<controls:DirectoryOrCustomSelectControl
Directory="{CompiledBinding InProgressDirectory, Mode=TwoWay}"
KnownDirectories="{CompiledBinding KnownDirectories}" />
</StackPanel>
</controls:GroupBox>
<CheckBox
Grid.Row="3"
Margin="5"
VerticalAlignment="Top"
IsVisible="{CompiledBinding !Config.IsLinux}"
IsChecked="{CompiledBinding UseCoverAsFolderIcon, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{CompiledBinding UseCoverAsFolderIconText}" />
</CheckBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,62 @@
using Avalonia.Controls;
using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels.Settings;
using LibationFileManager;
using System.Threading.Tasks;
namespace LibationAvalonia.Controls.Settings
{
public partial class DownloadDecrypt : UserControl
{
private DownloadDecryptSettingsVM _viewModel => DataContext as DownloadDecryptSettingsVM;
public DownloadDecrypt()
{
InitializeComponent();
if (Design.IsDesignMode)
{
_ = Configuration.Instance.LibationFiles;
DataContext = new DownloadDecryptSettingsVM(Configuration.Instance);
}
}
public async void EditFolderTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_viewModel is null) return;
var newTemplate = await editTemplate(TemplateEditor<Templates.FolderTemplate>.CreateFilenameEditor(_viewModel.Config.Books, _viewModel.FolderTemplate));
if (newTemplate is not null)
_viewModel.FolderTemplate = newTemplate;
}
public async void EditFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_viewModel is null) return;
var newTemplate = await editTemplate(TemplateEditor<Templates.FileTemplate>.CreateFilenameEditor(_viewModel.Config.Books, _viewModel.FileTemplate));
if (newTemplate is not null)
_viewModel.FileTemplate = newTemplate;
}
public async void EditChapterFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_viewModel is null) return;
var newTemplate = await editTemplate(TemplateEditor<Templates.ChapterFileTemplate>.CreateFilenameEditor(_viewModel.Config.Books, _viewModel.ChapterFileTemplate));
if (newTemplate is not null)
_viewModel.ChapterFileTemplate = newTemplate;
}
public async void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_viewModel is null) return;
var form = new EditReplacementChars(_viewModel.Config);
await form.ShowDialog<DialogResult>(this.GetParentWindow());
}
private async Task<string> editTemplate(ITemplateEditor template)
{
var form = new EditTemplateDialog(template);
if (await form.ShowDialog<DialogResult>(this.GetParentWindow()) == DialogResult.OK)
return template.EditingTemplate.TemplateText;
else return null;
}
}
}

View File

@@ -0,0 +1,41 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="450"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:ImportSettingsVM"
x:Class="LibationAvalonia.Controls.Settings.Import">
<StackPanel Margin="5">
<StackPanel.Styles>
<Style Selector="CheckBox">
<Setter Property="Margin" Value="0,0,0,10" />
<Style Selector="^ > TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Style>
</StackPanel.Styles>
<CheckBox IsChecked="{CompiledBinding AutoScan, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding AutoScanText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding ShowImportedStats, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding ShowImportedStatsText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding ImportEpisodes, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding ImportEpisodesText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding DownloadEpisodes, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding DownloadEpisodesText}" />
</CheckBox>
<CheckBox IsChecked="{CompiledBinding AutoDownloadEpisodes, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding AutoDownloadEpisodesText}" />
</CheckBox>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,20 @@
using Avalonia.Controls;
using LibationAvalonia.ViewModels.Settings;
using LibationFileManager;
namespace LibationAvalonia.Controls.Settings
{
public partial class Import : UserControl
{
public Import()
{
InitializeComponent();
if (Design.IsDesignMode)
{
_ = Configuration.Instance.LibationFiles;
DataContext = new ImportSettingsVM(Configuration.Instance);
}
}
}
}

View File

@@ -0,0 +1,88 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="450"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:ImportantSettingsVM"
x:Class="LibationAvalonia.Controls.Settings.Important">
<Grid RowDefinitions="Auto,Auto,*">
<controls:GroupBox
Grid.Row="0"
Margin="5"
Label="Books Location">
<StackPanel>
<TextBlock
Margin="5"
Text="{CompiledBinding BooksText}" />
<controls:DirectoryOrCustomSelectControl Margin="0,10,0,10"
SubDirectory="Books"
Directory="{CompiledBinding BooksDirectory, Mode=TwoWay}"
KnownDirectories="{CompiledBinding KnownDirectories}" />
<CheckBox IsChecked="{CompiledBinding SavePodcastsToParentFolder, Mode=TwoWay}">
<TextBlock Text="{CompiledBinding SavePodcastsToParentFolderText}" />
</CheckBox>
</StackPanel>
</controls:GroupBox>
<StackPanel
Grid.Row="1" Margin="5"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
VerticalAlignment="Center"
Text="Logging level" />
<controls:WheelComboBox
Width="120"
Height="25"
HorizontalContentAlignment="Stretch"
SelectedItem="{CompiledBinding LoggingLevel, Mode=TwoWay}"
ItemsSource="{CompiledBinding LoggingLevels}" />
<Button
Margin="50,0,0,0"
Padding="20,0"
VerticalAlignment="Stretch"
Content="Open Log Folder"
Command="{CompiledBinding OpenLogFolderButton}" />
</StackPanel>
<Grid
Grid.Row="2"
ColumnDefinitions="Auto,Auto,*"
Margin="10"
VerticalAlignment="Bottom">
<TextBlock
Grid.Column="0"
FontSize="16"
VerticalAlignment="Center"
Text="Theme: "/>
<controls:WheelComboBox
Grid.Column="1"
MinWidth="80"
SelectedItem="{CompiledBinding ThemeVariant, Mode=TwoWay}"
ItemsSource="{CompiledBinding Themes}"/>
<TextBlock
Grid.Column="2"
FontSize="16"
FontWeight="Bold"
Margin="10,0"
VerticalAlignment="Center"
IsVisible="{CompiledBinding SelectionChanged}"
Text="Theme change takes effect on restart"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,26 @@
using Avalonia.Controls;
using Dinah.Core;
using FileManager;
using LibationAvalonia.ViewModels.Settings;
using LibationFileManager;
namespace LibationAvalonia.Controls.Settings
{
public partial class Important : UserControl
{
public Important()
{
InitializeComponent();
if (Design.IsDesignMode)
{
_ = Configuration.Instance.LibationFiles;
DataContext = new ImportantSettingsVM(Configuration.Instance);
}
}
public void OpenLogFolderButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Go.To.Folder(((LongPath)Configuration.Instance.LibationFiles).ShortPathName);
}
}
}

View File

@@ -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
{
@@ -26,10 +22,5 @@ namespace LibationAvalonia.Controls
base.OnPointerWheelChanged(e);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@@ -0,0 +1,73 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="520"
MinWidth="400" MinHeight="520"
Width="400" Height="520"
x:Class="LibationAvalonia.Dialogs.AboutDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
Title="About Libation"
Icon="/Assets/libation.ico">
<Grid Margin="10" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,*">
<controls:LinkLabel Grid.ColumnSpan="2" FontSize="16" FontWeight="Bold" Text="{Binding Version}" ToolTip.Tip="View Release Notes" Tapped="ViewReleaseNotes_Tapped" />
<controls:LinkLabel Grid.Column="1" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Right" Text="https://getlibation.com" Tapped="Link_getlibation"/>
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="0,20,0,0" IsEnabled="{Binding CanCheckForUpgrade}" Content="{Binding UpgradeButtonText}" Click="CheckForUpgrade_Click" />
<Canvas Grid.Row="2" Grid.ColumnSpan="2" Margin="0,30,0,20" Width="280" Height="220">
<Path Stretch="None" Fill="{DynamicResource IconFill}" Data="{DynamicResource LibationCheersIcon}">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="12" />
<ScaleTransform ScaleX="0.4" ScaleY="0.4" />
<TranslateTransform X="-160" Y="-150" />
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path Stretch="None" Fill="{DynamicResource IconFill}" Data="{DynamicResource LibationCheersIcon}">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1" ScaleY="1" />
<RotateTransform Angle="-12" />
<ScaleTransform ScaleX="0.4" ScaleY="0.4" />
<TranslateTransform X="23" Y="-150" />
</TransformGroup>
</Path.RenderTransform>
</Path>
</Canvas>
<controls:GroupBox Grid.Row="3" Label="Acknowledgements" Grid.ColumnSpan="2">
<StackPanel>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<controls:LinkLabel FontWeight="Bold" Text="rmcrackan" Tapped="Link_GithubUser" />
<TextBlock Grid.Column="1" Margin="10,0" Text="Creator" />
<controls:LinkLabel Grid.Row="1" FontWeight="Bold" Text="Mbucari" Tapped="Link_GithubUser" />
<TextBlock Grid.Row="1" Grid.Column="1" Margin="10,0" Text="Developer" />
</Grid>
<TextBlock Margin="0,10" FontSize="12" Text="Additional Contributions by:" TextDecorations="Underline"/>
<WrapPanel>
<WrapPanel.Styles>
<Style Selector="controls|LinkLabel">
<Setter Property="Margin" Value="5,0" />
<Setter Property="FontSize" Value="13" />
</Style>
</WrapPanel.Styles>
<controls:LinkLabel Text="pixil98" Tapped="Link_GithubUser" />
<controls:LinkLabel Text="hutattedonmyarm" Tapped="Link_GithubUser" />
<controls:LinkLabel Text="seanke" Tapped="Link_GithubUser" />
<controls:LinkLabel Text="wtanksleyjr" Tapped="Link_GithubUser" />
<controls:LinkLabel Text="Dr.Blank" Tapped="Link_GithubUser" />
<controls:LinkLabel Text="CharlieRussel" Tapped="Link_GithubUser" />
</WrapPanel>
</StackPanel>
</controls:GroupBox>
</Grid>
</Window>

View File

@@ -0,0 +1,80 @@
using Avalonia.Controls;
using LibationAvalonia.Controls;
using LibationAvalonia.ViewModels;
using LibationFileManager;
using LibationUiBase;
using ReactiveUI;
using System;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs
{
public partial class AboutDialog : DialogWindow
{
private readonly AboutVM _viewModel;
public AboutDialog() : base(saveAndRestorePosition:false)
{
if (Design.IsDesignMode)
_ = Configuration.Instance.LibationFiles;
InitializeComponent();
DataContext = _viewModel = new AboutVM();
}
private async void CheckForUpgrade_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var mainWindow = Owner as Views.MainWindow;
var upgrader = new Upgrader();
upgrader.DownloadProgress += async (_, e) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => mainWindow.ViewModel.DownloadProgress = e.ProgressPercentage);
upgrader.DownloadCompleted += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => mainWindow.ViewModel.DownloadProgress = null);
_viewModel.CanCheckForUpgrade = false;
Version latestVersion = null;
await upgrader.CheckForUpgradeAsync(OnUpgradeAvailable);
_viewModel.CanCheckForUpgrade = latestVersion is null;
_viewModel.UpgradeButtonText = latestVersion is null ? "Libation is up to date. Check Again." : $"Version {latestVersion:3} is available";
async Task OnUpgradeAvailable(UpgradeEventArgs e)
{
var notificationResult = await new UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
e.Ignore = notificationResult == DialogResult.Ignore;
e.InstallUpgrade = notificationResult == DialogResult.OK;
latestVersion = e.UpgradeProperties.LatestRelease;
}
}
private void Link_GithubUser(object sender, Avalonia.Input.TappedEventArgs e)
{
if (sender is LinkLabel lbl)
{
Dinah.Core.Go.To.Url($"ht" + $"tps://github.com/{lbl.Text.Replace('.','-')}");
}
}
private void Link_getlibation(object sender, Avalonia.Input.TappedEventArgs e) => Dinah.Core.Go.To.Url(AppScaffolding.LibationScaffolding.WebsiteUrl);
private void ViewReleaseNotes_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
=> Dinah.Core.Go.To.Url($"{AppScaffolding.LibationScaffolding.RepositoryUrl}/releases/tag/v{AppScaffolding.LibationScaffolding.BuildVersion.ToString(3)}");
}
public class AboutVM : ViewModelBase
{
public string Version { get; }
public bool CanCheckForUpgrade { get => canCheckForUpgrade; set => this.RaiseAndSetIfChanged(ref canCheckForUpgrade, value); }
public string UpgradeButtonText { get => upgradeButtonText; set => this.RaiseAndSetIfChanged(ref upgradeButtonText, value); }
private bool canCheckForUpgrade = true;
private string upgradeButtonText = "Check for Upgrade";
public AboutVM()
{
Version = $"Libation {AppScaffolding.LibationScaffolding.Variety} v{AppScaffolding.LibationScaffolding.BuildVersion}";
}
}
}

View File

@@ -33,9 +33,9 @@
<DataTemplate>
<Button
Width="60"
Height="30"
Content="X"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" />
@@ -49,9 +49,10 @@
<DataTemplate>
<Button
Width="60"
Height="30"
Content="Export"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}"
ToolTip.Tip="Export account authorization to audible-cli"
Click="ExportButton_Clicked" />
@@ -63,11 +64,14 @@
<DataGridCheckBoxColumn
Binding="{Binding LibraryScan, Mode=TwoWay}"
Header="Include in&#xa;library scan?"/>
<DataGridTextColumn
Width="2*"
Binding="{Binding AccountId, Mode=TwoWay}"
Header="Audible&#xa;email/login"/>
<DataGridTemplateColumn Width="2*" Header="Audible&#xa;email/login">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AccountId, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="Auto" Header="Locale">
<DataGridTemplateColumn.CellTemplate>
@@ -77,7 +81,7 @@
HorizontalContentAlignment = "Stretch"
HorizontalAlignment = "Stretch"
SelectedItem="{Binding SelectedLocale, Mode=TwoWay}"
Items="{Binding Locales}">
ItemsSource="{Binding Locales}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -92,10 +96,13 @@
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="3*"
Binding="{Binding AccountName, Mode=TwoWay}"
Header="Account Nickname&#xa;(optional)"/>
<DataGridTemplateColumn Width="3*" Header="Account Nickname&#xa;(optional)">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AccountName, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
@@ -106,14 +113,13 @@
<Button
Grid.Column="0"
Height="30"
Padding="5,5"
Content="Import from audible-cli"
Click="ImportButton_Clicked" />
<Button
Grid.Column="1"
Height="30"
Padding="30,3,30,3"
Padding="30,5"
Content="Save"
Click="SaveButton_Clicked" />
</Grid>

View File

@@ -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,26 +101,26 @@ 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" }
}
}
};
string audibleAppDataDir = GetAudibleCliAppDataPath();
if (Directory.Exists(audibleAppDataDir))
openFileDialogOptions.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir);
openFileDialogOptions.SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(audibleAppDataDir);
var selectedFiles = await StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
var selectedFile = selectedFiles.SingleOrDefault();
var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return;
if (selectedFile is null) return;
try
{
var jsonText = File.ReadAllText(uri.LocalPath);
var jsonText = File.ReadAllText(selectedFile);
var mkbAuth = Mkb79Auth.FromJson(jsonText);
var account = await mkbAuth.ToAccountAsync();
@@ -153,13 +135,13 @@ namespace LibationAvalonia.Dialogs
persister.AccountsSettings.Add(account);
AddAccountToGrid(account);
Accounts.Add(new AccountDto(account));
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
this,
$"An error occurred while importing an account from:\r\n{uri.LocalPath}\r\n\r\nIs the file encrypted?",
$"An error occurred while importing an account from:\r\n{selectedFile}\r\n\r\nIs the file encrypted?",
"Error Importing Account",
ex);
}
@@ -196,12 +178,6 @@ namespace LibationAvalonia.Dialogs
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void persist(AccountsSettings accountsSettings)
{
var existingAccounts = accountsSettings.Accounts;
@@ -293,20 +269,20 @@ namespace LibationAvalonia.Dialogs
string audibleAppDataDir = GetAudibleCliAppDataPath();
if (Directory.Exists(audibleAppDataDir))
options.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir);
options.SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(audibleAppDataDir);
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
var selectedFile = (await StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return;
if (selectedFile is null) return;
try
{
var mkbAuth = Mkb79Auth.FromAccount(account);
var jsonText = mkbAuth.ToJson();
File.WriteAllText(uri.LocalPath, jsonText);
File.WriteAllText(selectedFile, jsonText);
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{uri.LocalPath}", "Success!");
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{selectedFile}", "Success!");
}
catch (Exception ex)
{

View File

@@ -45,7 +45,6 @@
<controls:GroupBox
Label="Edit Tags"
Grid.Row="1"
BorderWidth="1"
Margin="10,0,10,0">
<StackPanel Orientation="Vertical">
@@ -63,7 +62,6 @@
<controls:GroupBox
Label="Liberated status: Whether the book/pdf has been downloaded"
Grid.Row="2"
BorderWidth="1"
Margin="10,10,10,10">
<StackPanel Orientation="Vertical">
@@ -95,7 +93,7 @@
Height="25"
VerticalAlignment="Center"
SelectedItem="{Binding BookLiberatedSelectedItem, Mode=TwoWay}"
Items="{Binding BookLiberatedItems}">
ItemsSource="{Binding BookLiberatedItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -116,8 +114,8 @@
Height="25"
Width="150"
VerticalAlignment="Center"
SelectedItem="{Binding PdfLiberatedSelectedItem, Mode=TwoWay}"
Items="{Binding PdfLiberatedItems}">
SelectedItem="{Binding PdfLiberatedSelectedItem, Mode=TwoWay}"
ItemsSource="{Binding PdfLiberatedItems}">
<ComboBox.ItemTemplate>
<DataTemplate>

View File

@@ -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,12 +60,7 @@ namespace LibationAvalonia.Dialogs
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose();
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
=> SaveAndClose();
private class BookDetailsDialogViewModel : ViewModelBase
{

View File

@@ -101,13 +101,13 @@
Grid.Column="0"
Grid.Row="0"
Content="Check All"
Click="CheckAll_Click"/>
Command="{Binding CheckAll}"/>
<Button
Grid.Column="0"
Grid.Row="1"
Content="Uncheck All"
Click="UncheckAll_Click"/>
Command="{Binding UncheckAll}"/>
<Button
Grid.Column="1"

View File

@@ -77,12 +77,12 @@ namespace LibationAvalonia.Dialogs
await setControlEnabled(sender, true);
}
public void CheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void CheckAll()
{
foreach (var record in bookRecordEntries)
foreach (var record in bookRecordEntries)
record.IsChecked = true;
}
public void UncheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void UncheckAll()
{
foreach (var record in bookRecordEntries)
record.IsChecked = false;
@@ -172,23 +172,23 @@ namespace LibationAvalonia.Dialogs
}
});
var selectedFile = await StorageProvider.SaveFilePickerAsync(saveFileDialog);
var selectedFile = (await StorageProvider.SaveFilePickerAsync(saveFileDialog))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return;
if (selectedFile is null) return;
var ext = System.IO.Path.GetExtension(uri.LocalPath).ToLowerInvariant();
var ext = System.IO.Path.GetExtension(selectedFile).ToLowerInvariant();
switch (ext)
{
case ".xlsx":
default:
await Task.Run(() => RecordExporter.ToXlsx(uri.LocalPath, records));
await Task.Run(() => RecordExporter.ToXlsx(selectedFile, records));
break;
case ".csv":
await Task.Run(() => RecordExporter.ToCsv(uri.LocalPath, records));
await Task.Run(() => RecordExporter.ToCsv(selectedFile, records));
break;
case ".json":
await Task.Run(() => RecordExporter.ToJson(uri.LocalPath, libraryBook, records));
await Task.Run(() => RecordExporter.ToJson(selectedFile, libraryBook, records));
break;
}
}

View File

@@ -5,8 +5,13 @@
mc:Ignorable="d" d:DesignWidth="540" d:DesignHeight="140"
x:Class="LibationAvalonia.Dialogs.DescriptionDisplayDialog"
SystemDecorations="None"
Title="DescriptionDisplay">
Title="DescriptionDisplay">
<Window.Styles>
<Style Selector="TextBox[IsReadOnly=true] /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource TextControlBackground}" />
</Style>
</Window.Styles>
<TextBox
Text="{Binding DescriptionText}"
IsReadOnly="True"
@@ -15,5 +20,5 @@
Name="DescriptionTextBox"
CaretBrush="{StaticResource SystemControlTransparentBrush}"
LostFocus="DescriptionTextBox_LostFocus" />
</Window>
</Window>

View File

@@ -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;
@@ -52,11 +49,5 @@ namespace LibationAvalonia.Dialogs
{
Close();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

Some files were not shown because too many files have changed in this diff Show More