Compare commits

...

84 Commits

Author SHA1 Message Date
Robert McRackan
da9cb3371f incr ver 2023-05-23 13:06:09 -04:00
rmcrackan
91d0f8020e Merge pull request #606 from Mbucari/master
Corectly read and write locales
2023-05-23 13:04:35 -04:00
Mbucari
156726ca95 Corectly read and write locales 2023-05-23 09:41:28 -06:00
rmcrackan
3dad4c194b Update README.md 2023-05-20 23:12:23 -04:00
Mbucari
6025a7538a Merge pull request #604 from Mbucari/master
Fix rpm upgrade
2023-05-19 16:39:04 -06:00
Mbucari
824f65baae Fix rpm upgrade 2023-05-19 16:37:00 -06:00
Mbucari
9372a7318b inc ver 2023-05-19 13:46:26 -06:00
Mbucari
ddd032c16d Fix null string in integer fields 2023-05-19 13:36:22 -06:00
Mbucari
9aaf523240 Update InstallOnMac.md 2023-05-19 13:07:33 -06:00
Mbucari
8cbdeb38fa Update InstallOnLinux.md 2023-05-19 13:05:29 -06:00
Mbucari
a9258a1811 Update GettingStarted.md 2023-05-19 13:01:25 -06:00
Robert McRackan
0dbc42c407 incr ver 2023-05-19 14:43:17 -04:00
rmcrackan
2c91de1b3b Merge pull request #603 from Mbucari/master
New searches, new linux release, new Avalonia build
2023-05-19 14:23:05 -04:00
Mbucari
607cd07b74 Change IInteropFunctions.ReleaseIdentifier to ReleaseIdString 2023-05-19 12:08:22 -06:00
Mbucari
64d080336c Use correct package manager 2023-05-19 11:30:09 -06:00
Mbucari
fd510861c6 Add AbsentFromLastScan (#601) LastDownloaded (#602) search 2023-05-19 11:09:57 -06:00
Mbucari
3fdfbb9e26 Improve episode sequence detection (#600) 2023-05-19 11:07:42 -06:00
Mbucari
3e74898dac Merge branch 'rmcrackan:master' into master 2023-05-19 09:31:47 -06:00
Mbucari
d6fe3013ab RPM build 2023-05-19 09:30:11 -06:00
Robert McRackan
265794bae0 update dependencies 2023-05-19 11:24:47 -04:00
Mbucari
7586f7a159 Upgrade Avalonia to v11.0.0-preview8 2023-05-15 12:58:45 -06:00
Mbucari
5dfddfb549 Use avres DataGrid theme and only replace DataGridColumnHeader 2023-05-15 12:51:46 -06:00
Mbucari
98bb06378a Update Avalonia to v11.0.0-preview8 2023-05-15 10:54:56 -06:00
Robert McRackan
429367d21c incr ver 2023-04-17 21:39:18 -04:00
Mbucari
ea9e36fd76 Merge pull request #588 from Mbucari/master
Use old activation bytes if present.
2023-04-17 19:34:26 -06:00
MBucari
fe534b335b Update dependencies 2023-04-17 19:32:52 -06:00
MBucari
6db3a8fbf3 Use old activation bytes if present. 2023-04-17 16:09:47 -06:00
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
185 changed files with 5304 additions and 3961 deletions

View File

@@ -15,6 +15,21 @@ on:
description: 'Skip running unit tests'
required: false
default: true
runs_on:
type: string
description: 'The GitHub hosted runner to use'
required: true
OS:
type: string
description: >
The operating system targeted by the build.
There must be a corresponding Bundle_$OS.sh script file in ./Scripts
required: true
architecture:
type: string
description: 'CPU architecture targeted by the build.'
required: true
env:
DOTNET_CONFIGURATION: 'Release'
@@ -23,11 +38,8 @@ env:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
arch: [x64, arm64]
name: '${{ inputs.OS }}-${{ inputs.architecture }}'
runs-on: ${{ inputs.runs_on }}
steps:
- uses: actions/checkout@v3
- name: Setup .NET
@@ -48,6 +60,7 @@ jobs:
version="$(grep -Eio -m 1 '<Version>.*</Version>' ./Source/AppScaffolding/AppScaffolding.csproj | sed -r 's/<\/?Version>//g')"
fi
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Unit test
if: ${{ inputs.run_unit_tests }}
working-directory: ./Source
@@ -56,52 +69,64 @@ jobs:
- name: Publish
id: publish
working-directory: ./Source
run: |
os=${{ matrix.os }}
target_os="$(echo ${os/-latest/} | sed 's/ubuntu/linux/')"
display_os="$(echo ${target_os/macos/macOS} | sed 's/linux/Linux/')"
run: |
if [[ "${{ inputs.OS }}" == "MacOS" ]]
then
display_os="macOS"
RUNTIME_ID="osx-${{ inputs.architecture }}"
else
display_os="Linux"
RUNTIME_ID="linux-${{ inputs.architecture }}"
fi
OUTPUT="bin/Publish/${display_os}-${{ inputs.architecture }}-${{ env.RELEASE_NAME }}"
echo "display_os=${display_os}" >> $GITHUB_OUTPUT
RUNTIME_IDENTIFIER="$(echo ${target_os/macos/osx})-${{ matrix.arch }}"
echo "$RUNTIME_IDENTIFIER"
echo "Runtime Identifier: $RUNTIME_ID"
echo "Output Directory: $OUTPUT"
dotnet publish \
LibationAvalonia/LibationAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--runtime $RUNTIME_ID \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
--output $OUTPUT \
-p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
LoadByOS/${display_os}ConfigApp/${display_os}ConfigApp.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--runtime $RUNTIME_ID \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
--output $OUTPUT \
-p:PublishProfile=LoadByOS/Properties/${display_os}ConfigApp/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
LibationCli/LibationCli.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--runtime $RUNTIME_ID \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
--output $OUTPUT \
-p:PublishProfile=LibationCli/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
HangoverAvalonia/HangoverAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--runtime $RUNTIME_ID \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
--output $OUTPUT \
-p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
- name: Build bundle
id: bundle
working-directory: ./Source/bin/Publish/${{ steps.publish.outputs.display_os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }}
working-directory: ./Source/bin/Publish/${{ steps.publish.outputs.display_os }}-${{ inputs.architecture }}-${{ env.RELEASE_NAME }}
run: |
BUNDLE_DIR=$(pwd)
echo "Bundle dir: ${BUNDLE_DIR}"
cd ..
SCRIPT=../../../Scripts/Bundle_${{ steps.publish.outputs.display_os }}.sh
SCRIPT=../../../Scripts/Bundle_${{ inputs.OS }}.sh
chmod +rx ${SCRIPT}
${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ matrix.arch }}"
${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ inputs.architecture }}"
artifact=$(ls ./bundle)
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
- name: Publish bundle
uses: actions/upload-artifact@v3
with:
name: ${{ steps.bundle.outputs.artifact }}
path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }}
if-no-files-found: error
if-no-files-found: error
retention-days: 7

View File

@@ -22,6 +22,7 @@ env:
jobs:
build:
name: '${{ matrix.os }}-${{ matrix.release_name }}'
runs-on: windows-latest
strategy:
matrix:
@@ -69,7 +70,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 }} `
@@ -110,4 +111,4 @@ jobs:
name: ${{ steps.zip.outputs.artifact }}.zip
path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip
if-no-files-found: error
retention-days: 7

View File

@@ -17,14 +17,34 @@ on:
default: true
jobs:
windows:
uses: ./.github/workflows/build-windows.yml
with:
version_override: ${{ inputs.version_override }}
run_unit_tests: ${{ inputs.run_unit_tests }}
linux:
strategy:
matrix:
OS: [Redhat, Debian]
architecture: [x64, arm64]
uses: ./.github/workflows/build-linux.yml
with:
version_override: ${{ inputs.version_override }}
runs_on: ubuntu-latest
OS: ${{ matrix.OS }}
architecture: ${{ matrix.architecture }}
run_unit_tests: ${{ inputs.run_unit_tests }}
macos:
strategy:
matrix:
architecture: [x64, arm64]
uses: ./.github/workflows/build-linux.yml
with:
version_override: ${{ inputs.version_override }}
runs_on: macos-latest
OS: MacOS
architecture: ${{ matrix.architecture }}
run_unit_tests: ${{ inputs.run_unit_tests }}

View File

@@ -1,8 +1,10 @@
{
"WindowsClassic": "Classic-Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip",
"WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip",
"LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-amd64\\.deb",
"MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-x64\\.tgz",
"LinuxAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-arm64\\.deb",
"MacOSAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-arm64\\.tgz"
"WindowsClassic": "Classic-Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip",
"WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip",
"LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-amd64\\.deb",
"LinuxAvalonia_RPM": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-amd64\\.rpm",
"MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-x64\\.tgz",
"LinuxAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-arm64\\.deb",
"LinuxAvalonia_Arm64_RPM": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-arm64\\.rpm",
"MacOSAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-arm64\\.tgz"
}

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**.
@@ -33,7 +33,7 @@ Classic is Windows only. It has an older look because it's built with older, dul
Extract the zip file to a folder and then run `Libation.exe` from inside of that folder. Do not put it in Program Files. The inability to edit files from there causes problems with configuration and updating.
* [Ubuntu Linux](InstallOnLinux.md)
* [Linux](InstallOnLinux.md)
* [MacOS](InstallOnMac.md)
### Create Accounts

View File

@@ -1,21 +1,29 @@
## [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**.
### Install and Run Libation on Ubuntu
New Libation releases are automatically packed into a debian package and are available from the Libation repository's releases page.
New Libation releases are automatically packed into .deb and .rpm package and are available from the Libation repository's releases page.
Run this command in your terminal to dowbnload and install Libation, replacing the url with the Latest Libation .deb package url:
```Console
wget -O libation.deb https://github.com/rmcrackan/Libation/releases/download/vX.X.X/Libation.X.X.X-linux-chardonnay.deb &&
sudo apt install ./libation.deb
```
Run this command in your terminal to dowbnload and install Libation, replacing the url with the latest Libation package url:
You should now see Libation among your applications.
- Debian
```Console
wget -O libation.deb https://github.com/rmcrackan/Libation/releases/download/vX.X.X/Libation.X.X.X-linux-chardonnay.deb &&
sudo apt install ./libation.deb
```
- Redhat and CentOS
```Console
wget -O libation.rpm https://github.com/rmcrackan/Libation/releases/download/vX.X.X/Libation.X.X.X-linux-chardonnay.rpm &&
sudo yum install ./libation.rpm
```
If your desktop uses gtk, you should now see Libation among your applications.
Additionally, you may launch Libation, LibationCli, and Hangover (the Libation recovery app) via the command line using 'libation, libationcli', and 'hangover' aliases respectively.

View File

@@ -1,12 +1,14 @@
## [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**.
# Run Libation on MacOS
This walkthrough should get you up and running with Libation on your Mac.
## Supports macOS 10.15 (Catalina) and above
## Install Libation
- Download the file from the latest release and extract it.

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

@@ -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
@@ -49,12 +49,12 @@
* Customizable saved filters for common searches
* Open source
* Supports most regions: US, UK, Canada, Germany, France, Australia, Japan, India, and Spain
* Fully supported in Windows, Mac, and Linux
<a name="theBad"/>
### The bad
* Only fully supported in Windows. (Mac and Linux are in beta)
* Large file size
* Made by a programmer, not a designer so the goals are function rather than beauty. And it shows

145
Scripts/Bundle_Redhat.sh Normal file
View File

@@ -0,0 +1,145 @@
#!/bin/bash
BIN_DIR=$1; shift
VERSION=$1; shift
ARCH=$1; shift
if [ -z "$BIN_DIR" ]
then
echo "This script must be called with a the Libation Linux bins directory as an argument."
exit
fi
if [ ! -d "$BIN_DIR" ]
then
echo "The directory \"$BIN_DIR\" does not exist."
exit
fi
if [ -z "$VERSION" ]
then
echo "This script must be called with the Libation version number as an argument."
exit
fi
if [ -z "$ARCH" ]
then
echo "This script must be called with the Libation cpu architecture as an argument."
exit
fi
contains() { case "$1" in *"$2"*) true ;; *) false ;; esac }
if ! contains "$BIN_DIR" "$ARCH"
then
echo "This script must be called with a Libation binaries for ${ARCH}."
exit
fi
BASEDIR=$(pwd)
delfiles=('libmp3lame.arm64.dylib' 'libmp3lame.x64.dylib' 'libmp3lame.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.arm64.dylib' 'ffmpegaac.x64.dylib' 'ffmpegaac.x64.dll' 'ffmpegaac.x86.dll' 'LinuxConfigApp' 'LinuxConfigApp.deps.json' 'LinuxConfigApp.runtimeconfig.json')
if [[ "$ARCH" == "x64" ]]
then
delfiles+=('libmp3lame.arm64.so' 'ffmpegaac.arm64.so')
ARCH_RPM="x86_64"
ARCH="amd64"
else
delfiles+=('libmp3lame.x64.so' 'ffmpegaac.x64.so')
ARCH_RPM="aarch64"
fi
notinstalled=('libcoreclrtraceptprovider.so' 'libation_glass.svg' 'Libation.desktop')
mkdir -p ~/rpmbuild/SPECS
mkdir ~/rpmbuild/BUILD
mkdir ~/rpmbuild/RPMS
echo "Name: libation
Version: ${VERSION}
Release: 1
Summary: Liberate your Audible Library
License: GPLv3+
URL: https://github.com/rmcrackan/Libation
Source0: https://github.com/rmcrackan/Libation
Requires: bash
%define __os_install_post %{nil}
%description
Liberate your Audible Library
%install
mkdir -p %{buildroot}%{_libdir}/%{name}
mkdir -p %{buildroot}%{_datadir}/icons/hicolor/scalable/apps
mkdir -p %{buildroot}%{_datadir}/applications
if test -f 'libcoreclrtraceptprovider.so'; then
rm 'libcoreclrtraceptprovider.so'
fi
install -m 666 libation_glass.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/libation.svg
install -m 666 Libation.desktop %{buildroot}%{_datadir}/applications/Libation.desktop
rm libation_glass.svg
rm Libation.desktop
install * %{buildroot}%{_libdir}/%{name}/
%post
if [ \$1 -eq 1 ] ; then
# Initial installation
touch %{_libdir}/%{name}/appsettings.json
chmod 666 %{_libdir}/%{name}/appsettings.json
ln -s %{_libdir}/%{name}/Libation %{_bindir}/libation
ln -s %{_libdir}/%{name}/Hangover %{_bindir}/hangover
ln -s %{_libdir}/%{name}/LibationCli %{_bindir}/libationcli
gtk-update-icon-cache -f %{_datadir}/icons/hicolor/
if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then
echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p
fi
fi
%postun
if [ \$1 -eq 0 ] ; then
# Uninstall
rm %{_bindir}/libation
rm %{_bindir}/hangover
rm %{_bindir}/libationcli
fi
%files
%{_datadir}/icons/hicolor/scalable/apps/libation.svg
%{_datadir}/applications/Libation.desktop" >> ~/rpmbuild/SPECS/libation.spec
cd "$BIN_DIR"
for f in *; do
if [[ " ${delfiles[*]} " =~ " ${f} " ]]; then
echo "Deleting $f"
elif [[ ! " ${notinstalled[*]} " =~ " ${f} " ]]; then
echo "%{_libdir}/%{name}/${f}" >> ~/rpmbuild/SPECS/libation.spec
cp $f ~/rpmbuild/BUILD/
else
cp $f ~/rpmbuild/BUILD/
fi
done
cd ~/rpmbuild/SPECS/
rpmbuild -bb --target $ARCH_RPM libation.spec
cd $BASEDIR
RPM_FILE=$(ls ~/rpmbuild/RPMS/${ARCH_RPM})
mkdir bundle
mv ~/rpmbuild/RPMS/${ARCH_RPM}/$RPM_FILE "./bundle/Libation.${VERSION}-linux-chardonnay-${ARCH}.rpm"

View File

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

View File

@@ -32,7 +32,11 @@ namespace AaxDecrypter
protected bool Step_GetMetadata()
{
AaxFile = new AaxFile(InputFileStream);
AaxFile.SetDecryptionKey(DownloadOptions.AudibleKey, DownloadOptions.AudibleIV);
if (DownloadOptions.AudibleKey?.Length == 8 && DownloadOptions.AudibleIV is null)
AaxFile.SetDecryptionKey(DownloadOptions.AudibleKey);
else
AaxFile.SetDecryptionKey(DownloadOptions.AudibleKey, DownloadOptions.AudibleIV);
if (DownloadOptions.StripUnabridged)
{

View File

@@ -182,7 +182,6 @@ namespace AaxDecrypter
FileUtility.SaferDelete(jsonDownloadState);
if (!string.IsNullOrEmpty(DownloadOptions.AudibleKey) &&
!string.IsNullOrEmpty(DownloadOptions.AudibleIV) &&
DownloadOptions.RetainEncryptedFile)
{
string aaxPath = Path.ChangeExtension(tempFilePath, ".aax");

View File

@@ -2,10 +2,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>10.0.2.1</Version>
<Version>10.3.2.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="5.0.2" />
<PackageReference Include="Octokit" Version="6.0.0" />
<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
{
@@ -323,7 +330,18 @@ namespace AppScaffolding
//Download the release index
var bts = await gitHubClient.Repository.Content.GetRawContent(ownerAccount, repoName, ".releaseindex.json");
var releaseIndex = JObject.Parse(System.Text.Encoding.ASCII.GetString(bts));
var regexPattern = releaseIndex.Value<string>(ReleaseIdentifier.ToString());
string regexPattern;
try
{
regexPattern = releaseIndex.Value<string>(InteropFactory.Create().ReleaseIdString);
}
catch
{
regexPattern = releaseIndex.Value<string>(ReleaseIdentifier.ToString());
}
var regex = new System.Text.RegularExpressions.Regex(regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-the-latest-release

View File

@@ -249,8 +249,26 @@ namespace AudibleUtilities
}
}
foreach (var child in children)
int lastEpNum = -1, dupeCount = 0;
foreach (var child in children.OrderBy(i => i.EpisodeNumber).ThenBy(i => i.PublicationDateTime))
{
string sequence;
if (child.EpisodeNumber is null)
{
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive for malformed data from audible
sequence = parent.Relationships.FirstOrDefault(r => r.Asin == child.Asin)?.Sort?.ToString() ?? "0";
}
else
{
//multipart episodes may have the same episode number
if (child.EpisodeNumber == lastEpNum)
dupeCount++;
else
lastEpNum = child.EpisodeNumber.Value;
sequence = (lastEpNum + dupeCount).ToString();
}
// use parent's 'DateAdded'. DateAdded is just a convenience prop for: PurchaseDate.UtcDateTime
child.PurchaseDate = parent.PurchaseDate;
// parent is essentially a series
@@ -259,8 +277,7 @@ namespace AudibleUtilities
new Series
{
Asin = parent.Asin,
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive for malformed data from audible
Sequence = parent.Relationships.FirstOrDefault(r => r.Asin == child.Asin)?.Sort?.ToString() ?? "0",
Sequence = sequence,
Title = parent.TitleWithSubtitle
}
};

View File

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

View File

@@ -43,6 +43,9 @@ namespace AudibleUtilities
[JsonProperty("locale_code")]
public string LocaleCode { get; private set; }
[JsonProperty("with_username")]
public bool WithUsername { get; private set; }
[JsonProperty("activation_bytes")]
public string ActivationBytes { get; private set; }
@@ -68,7 +71,8 @@ namespace AudibleUtilities
}
[JsonIgnore] public ISystemDateTime SystemDateTime { get; } = new SystemDateTime();
[JsonIgnore] public Locale Locale => Localization.Get(LocaleCode);
[JsonIgnore]
public Locale Locale => Localization.Locales.Where(l => l.WithUsername == WithUsername).Single(l => l.CountryCode == LocaleCode);
[JsonIgnore] public string DeviceSerialNumber => DeviceInfo.DeviceSerialNumber;
[JsonIgnore] public string DeviceType => DeviceInfo.DeviceType;
[JsonIgnore] public string AmazonAccountId => CustomerInfo.UserId;
@@ -177,6 +181,7 @@ namespace AudibleUtilities
DevicePrivateKey = account.IdentityTokens.PrivateKey,
AccessTokenExpires = account.IdentityTokens.ExistingAccessToken.Expires,
LocaleCode = account.Locale.CountryCode,
WithUsername = account.Locale.WithUsername,
RefreshToken = account.IdentityTokens.RefreshToken.Value,
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
WebsiteCookies = new(account.IdentityTokens.Cookies),

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.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

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

@@ -24,7 +24,7 @@
<ItemsRepeater IsVisible="True"
VerticalCacheLength="1.2"
HorizontalCacheLength="1"
Items="{Binding CheckboxItems}"
ItemsSource="{Binding CheckboxItems}"
ItemTemplate="{StaticResource elementFactory}" />
</ScrollViewer>
</UserControl>

View File

@@ -66,13 +66,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" />
<!--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-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" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@@ -1,7 +1,9 @@
<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/>
@@ -51,15 +53,15 @@
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml"/>
<StyleInclude Source="/Assets/DataGridFluentTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="/Assets/LibationVectorIcons.xaml"/>
<StyleInclude Source="/Assets/DataGridColumnHeader.xaml"/>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
@@ -68,5 +70,19 @@
<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

@@ -2,6 +2,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
@@ -13,6 +14,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using ReactiveUI;
using DataLayer;
namespace LibationAvalonia
{
@@ -24,7 +27,6 @@ namespace LibationAvalonia
public static IBrush ProcessQueueBookCancelledBrush { get; private set; }
public static IBrush ProcessQueueBookDefaultBrush { get; private set; }
public static IBrush SeriesEntryGridBackgroundBrush { get; private set; }
public static IBrush HyperlinkVisited { get; private set; }
public static IAssetLoader AssetLoader { get; private set; }
@@ -215,9 +217,8 @@ namespace LibationAvalonia
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();
}
@@ -228,7 +229,6 @@ namespace LibationAvalonia
ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookCancelledBrush));
SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources(nameof(SeriesEntryGridBackgroundBrush));
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookDefaultBrush));
HyperlinkVisited = AvaloniaUtils.GetBrushFromResources(nameof(HyperlinkVisited));
}
}
}

View File

@@ -0,0 +1,104 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="using:Avalonia.Collections">
<Styles.Resources>
<!--
Based on Fluent template from v11.0.0-preview8
Modified sort arrow positioning to make more room for header text
-->
<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="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>
</Styles.Resources>
</Styles>

View File

@@ -1,588 +0,0 @@
<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

@@ -64,6 +64,33 @@
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

@@ -24,7 +24,7 @@
<ItemsRepeater IsVisible="True"
VerticalCacheLength="1.2"
HorizontalCacheLength="1"
Items="{Binding CheckboxItems}"
ItemsSource="{Binding CheckboxItems}"
ItemTemplate="{StaticResource elementFactory}" />
</ScrollViewer>
</UserControl>

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

@@ -36,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;
private Configuration.KnownDirectories? _selectedDirectory;
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;
}
}
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)
@@ -96,43 +90,12 @@ namespace LibationAvalonia.Controls
var selectedFolders = await (VisualRoot as Window).StorageProvider.OpenFolderPickerAsync(options);
customStates.CustomDir = selectedFolders.SingleOrDefault()?.Path?.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() ?? "";
@@ -144,19 +107,19 @@ namespace LibationAvalonia.Controls
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)

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

@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reactive.Subjects;
namespace LibationAvalonia.Controls
{
@@ -26,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
{
@@ -39,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);
@@ -51,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
@@ -78,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);

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"
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,27 +1,19 @@
using Avalonia;
using Avalonia.Controls;
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
{

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="{DynamicResource HyperlinkNew}"/>
<Style Selector="controls|LinkLabel">
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</TextBlock.Styles>

View File

@@ -1,14 +1,57 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
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();
@@ -17,7 +60,19 @@ namespace LibationAvalonia.Controls
private void LinkLabel_Tapped(object sender, TappedEventArgs e)
{
Foreground = App.HyperlinkVisited;
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)
@@ -30,5 +85,33 @@ namespace LibationAvalonia.Controls
this.Cursor = Cursor.Default;
base.OnPointerExited(e);
}
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception error)
{
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

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

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

@@ -23,7 +23,7 @@
CanUserSortColumns="False"
AutoGenerateColumns="False"
IsReadOnly="False"
Items="{Binding Accounts}"
ItemsSource="{Binding Accounts}"
GridLinesVisibility="All">
<DataGrid.Columns>
@@ -64,14 +64,11 @@
<DataGridCheckBoxColumn
Binding="{Binding LibraryScan, Mode=TwoWay}"
Header="Include in&#xa;library scan?"/>
<DataGridTemplateColumn Width="2*" Header="Audible&#xa;email/login">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AccountId, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="2*"
Binding="{Binding AccountId, Mode=TwoWay}"
Header="Audible&#xa;email/login"/>
<DataGridTemplateColumn Width="Auto" Header="Locale">
<DataGridTemplateColumn.CellTemplate>
@@ -81,7 +78,7 @@
HorizontalContentAlignment = "Stretch"
HorizontalAlignment = "Stretch"
SelectedItem="{Binding SelectedLocale, Mode=TwoWay}"
Items="{Binding Locales}">
ItemsSource="{Binding Locales}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -96,13 +93,10 @@
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="3*" Header="Account Nickname&#xa;(optional)">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AccountName, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="Auto"
Binding="{Binding AccountName, Mode=TwoWay}"
Header="Account Nickname&#xa;(optional)"/>
</DataGrid.Columns>
</DataGrid>
@@ -113,14 +107,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

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

@@ -24,7 +24,7 @@
CanUserSortColumns="True"
AutoGenerateColumns="False"
IsReadOnly="False"
Items="{Binding DataGridCollectionView}"
ItemsSource="{Binding DataGridCollectionView}"
GridLinesVisibility="All">
<DataGrid.Styles>
@@ -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)
record.IsChecked = true;
}
public void UncheckAll_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void UncheckAll()
{
foreach (var record in bookRecordEntries)
record.IsChecked = false;

View File

@@ -41,9 +41,9 @@ namespace LibationAvalonia.Dialogs
}
protected virtual void SaveAndClose() => Close(DialogResult.OK);
protected virtual Task SaveAndCloseAsync() => Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(SaveAndClose);
protected virtual async Task SaveAndCloseAsync() => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(SaveAndClose);
protected virtual void CancelAndClose() => Close(DialogResult.Cancel);
protected virtual Task CancelAndCloseAsync() => Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(CancelAndClose);
protected virtual async Task CancelAndCloseAsync() => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(CancelAndClose);
private async void DialogWindow_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
{

View File

@@ -23,7 +23,7 @@
CanUserSortColumns="False"
AutoGenerateColumns="False"
IsReadOnly="False"
Items="{Binding Filters}"
ItemsSource="{Binding Filters}"
GridLinesVisibility="All">
<DataGrid.Columns>
@@ -44,14 +44,11 @@
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="Filter">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding FilterString, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="*"
IsReadOnly="False"
Binding="{Binding FilterString, Mode=TwoWay}"
Header="Filter"/>
<DataGridTemplateColumn Header="Move&#xa;Up">
<DataGridTemplateColumn.CellTemplate>
@@ -99,10 +96,10 @@
<Button
Grid.Column="1"
Height="30"
Padding="30,3,30,3"
Padding="30,5"
Name="saveBtn"
Content="Save"
Click="SaveButton_Clicked" />
Command="{Binding SaveAndClose}" />
</Grid>
</Grid>
</Window>

View File

@@ -38,7 +38,7 @@ namespace LibationAvalonia.Dialogs
if (!accounts.Any())
return;
ControlToFocusOnShow = this.FindControl<Button>(nameof(SaveButton_Clicked));
ControlToFocusOnShow = this.FindControl<Button>(nameof(saveBtn));
var allFilters = QuickFilters.Filters.Select(f => new Filter { FilterString = f }).ToList();
allFilters.Add(new Filter());
@@ -100,10 +100,5 @@ namespace LibationAvalonia.Dialogs
Filters.Insert(index + 1, filter);
}
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
SaveAndClose();
}
}
}

View File

@@ -23,8 +23,8 @@
IsReadOnly="False"
BeginningEdit="ReplacementGrid_BeginningEdit"
CellEditEnding="ReplacementGrid_CellEditEnding"
KeyDown="ReplacementGrid_KeyDown"
Items="{Binding replacements}">
KeyDown="ReplacementGrid_KeyDown"
ItemsSource="{Binding replacements}">
<DataGrid.Columns>
@@ -62,9 +62,9 @@
Margin="5"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Click="Defaults_Click" Content="Defaults" />
<Button Margin="0,0,10,0" Click="LoFiDefaults_Click" Content="LoFi Defaults" />
<Button Click="Barebones_Click" Content="Barebones" />
<Button Margin="0,0,10,0" Command="{Binding Defaults}" Content="Defaults" />
<Button Margin="0,0,10,0" Command="{Binding LoFiDefaults}" Content="LoFi Defaults" />
<Button Command="{Binding Barebones}" Content="Barebones" />
</StackPanel>
<StackPanel
@@ -73,8 +73,8 @@
Margin="5"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Click="Cancel_Click" Content="Cancel" />
<Button Padding="20,5,20,6" Click="Save_Click" Content="Save" />
<Button Margin="0,0,10,0" Command="{Binding Close}" Content="Cancel" />
<Button Padding="20,5,20,6" Command="{Binding SaveAndClose}" Content="Save" />
</StackPanel>
</Grid>

View File

@@ -35,16 +35,12 @@ namespace LibationAvalonia.Dialogs
LoadTable(config.ReplacementCharacters.Replacements);
}
public void Defaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void Defaults()
=> LoadTable(ReplacementCharacters.Default.Replacements);
public void LoFiDefaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void LoFiDefaults()
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
public void Barebones_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public void Barebones()
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
public void Save_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose();
public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> Close();
protected override void SaveAndClose()
{

View File

@@ -33,7 +33,7 @@
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Content="Reset to Default"
Click="ResetButton_Click" />
Command="{Binding ResetToDefault}"/>
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
@@ -45,7 +45,7 @@
GridLinesVisibility="All"
AutoGenerateColumns="False"
DoubleTapped="EditTemplateViewModel_DoubleTapped"
Items="{Binding ListItems}" >
ItemsSource="{Binding ListItems}" >
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Tag">
@@ -69,8 +69,7 @@
</DataGrid.Columns>
</DataGrid>
<Grid
Grid.Column="1"
Margin="5,0,5,0"

View File

@@ -19,14 +19,14 @@ namespace LibationAvalonia.Dialogs
public EditTemplateDialog()
{
AvaloniaXamlLoader.Load(this);
userEditTbox = this.FindControl<TextBox>(nameof(userEditTbox));
InitializeComponent();
if (Design.IsDesignMode)
{
_ = Configuration.Instance.LibationFiles;
var editor = TemplateEditor<Templates.FileTemplate>.CreateFilenameEditor(Configuration.Instance.Books, Configuration.Instance.FileTemplate);
_viewModel = new(Configuration.Instance, editor);
_viewModel.resetTextBox(editor.EditingTemplate.TemplateText);
_viewModel.ResetTextBox(editor.EditingTemplate.TemplateText);
Title = $"Edit {editor.TemplateName}";
DataContext = _viewModel;
}
@@ -37,7 +37,7 @@ namespace LibationAvalonia.Dialogs
ArgumentValidator.EnsureNotNull(templateEditor, nameof(templateEditor));
_viewModel = new EditTemplateViewModel(Configuration.Instance, templateEditor);
_viewModel.resetTextBox(templateEditor.EditingTemplate.TemplateText);
_viewModel.ResetTextBox(templateEditor.EditingTemplate.TemplateText);
Title = $"Edit {templateEditor.TemplateName}";
DataContext = _viewModel;
}
@@ -67,9 +67,6 @@ namespace LibationAvalonia.Dialogs
public async void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
public void ResetButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> _viewModel.resetTextBox(_viewModel.TemplateEditor.DefaultTemplate);
private class EditTemplateViewModel : ViewModels.ViewModelBase
{
private readonly Configuration config;
@@ -115,7 +112,8 @@ namespace LibationAvalonia.Dialogs
public AvaloniaList<Tuple<string, string, string>> ListItems { get; set; }
public void resetTextBox(string value) => UserTemplateText = value;
public void ResetTextBox(string value) => UserTemplateText = value;
public void ResetToDefault() => ResetTextBox(TemplateEditor.DefaultTemplate);
public async Task<bool> Validate()
{

View File

@@ -27,6 +27,6 @@
Margin="5"
Padding="30,3,30,3"
Content="Save"
Click="SaveButton_Click" />
Command="{Binding SaveButtonAsync}" />
</Grid>
</Window>

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls;
using LibationFileManager;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs
{
@@ -27,9 +28,8 @@ namespace LibationAvalonia.Dialogs
DataContext = dirSelectOptions = new();
}
public async void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public async Task SaveButtonAsync()
{
var libationDir = dirSelectOptions.Directory;
if (!System.IO.Directory.Exists(libationDir))

View File

@@ -2,21 +2,21 @@
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="120"
mc:Ignorable="d" d:DesignWidth="550" d:DesignHeight="135"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Dialogs.LiberatedStatusBatchAutoDialog"
Title="Liberated status: Whether the book has been downloaded"
MinHeight="100" MaxHeight="165"
MinWidth="600" MaxWidth="800"
Width="600"
MinHeight="135" MaxHeight="135"
MinWidth="550" MaxWidth="550"
Width="550" Height="135"
WindowStartupLocation="CenterOwner"
Icon="/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,Auto">
<Grid Margin="10" RowDefinitions="Auto,Auto">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
Orientation="Vertical">
<CheckBox
Margin="0,0,0,10"
@@ -26,12 +26,6 @@
TextWrapping="Wrap"
Text="If the audio file can be found, set download status to 'Downloaded'" />
</CheckBox>
</StackPanel>
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding SetNotDownloaded, Mode=TwoWay}">
@@ -43,12 +37,10 @@
</StackPanel>
<Button
Grid.Row="2"
Padding="30,0,30,0"
Margin="10,0,10,10"
Grid.Row="1"
Padding="30,5"
HorizontalAlignment="Right"
Height="25"
Content="Save"
Click="SaveButton_Clicked"/>
Command="{Binding SaveAndClose}"/>
</Grid>
</Window>

View File

@@ -10,8 +10,5 @@ namespace LibationAvalonia.Dialogs
InitializeComponent();
DataContext = this;
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose();
}
}

View File

@@ -2,25 +2,25 @@
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="120"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="100"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Dialogs.LiberatedStatusBatchManualDialog"
Title="Liberated status: Whether the book has been downloaded"
MinWidth="400" MinHeight="120"
MaxWidth="400" MaxHeight="120"
Width="400" Height="120"
MinWidth="400" MinHeight="100"
MaxWidth="400" MaxHeight="100"
Width="400" Height="100"
WindowStartupLocation="CenterOwner"
Icon="/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,Auto">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<TextBlock
Grid.Row="0"
Margin="10,10,10,0"
Grid.ColumnSpan="2"
Margin="10"
Text="To download again next time: change to Not Downloaded&#xa;To not download: change to Downloaded"/>
<StackPanel
Margin="10"
Margin="10,0"
Grid.Row="1"
Orientation="Horizontal">
@@ -36,7 +36,7 @@
Height="25"
VerticalAlignment="Center"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
Items="{Binding BookStatuses}">
ItemsSource="{Binding BookStatuses}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -49,14 +49,15 @@
</ComboBox.ItemTemplate>
</controls:WheelComboBox>
</StackPanel>
<Button
Grid.Row="2"
Padding="30,0,30,0"
Margin="10,0,10,10"
Grid.Row="1"
Grid.Column="1"
Margin="10,0"
Padding="30,5"
VerticalAlignment="Stretch"
HorizontalAlignment="Right"
Height="25"
Content="Save"
Click="SaveButton_Clicked"/>
Click="SaveButton_Clicked" />
</Grid>
</Window>

View File

@@ -16,7 +16,7 @@
<TextBlock Text="IDs Found: " />
<TextBlock Text="{Binding FoundAsins}" />
</StackPanel>
<ListBox Margin="0,5,0,0" Grid.Row="1" Grid.ColumnSpan="2" Name="foundAudiobooksLB" Items="{Binding FoundFiles}" AutoScrollToSelectedItem="true">
<ListBox Margin="0,5,0,0" Grid.Row="1" Grid.ColumnSpan="2" Name="foundAudiobooksLB" ItemsSource="{Binding FoundFiles}" AutoScrollToSelectedItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto,*">

View File

@@ -89,7 +89,7 @@ namespace LibationAvalonia.Dialogs
FilePathCache.Insert(book);
var lb = context.GetLibraryBook_Flat_NoTracking(book.Id);
if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
if (lb?.Book?.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
FileFound?.Invoke(this, book);

View File

@@ -2,9 +2,11 @@
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="240" d:DesignHeight="120"
MinWidth="240" MinHeight="120"
MaxWidth="240" MaxHeight="120"
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="140"
MinWidth="240" MinHeight="140"
MaxWidth="240" MaxHeight="140"
Width="240" Height="140"
WindowStartupLocation="CenterOwner"
x:Class="LibationAvalonia.Dialogs.Login.ApprovalNeededDialog"
Title="Approval Alert Detected"
Icon="/Assets/libation.ico">

View File

@@ -4,7 +4,7 @@ namespace LibationAvalonia.Dialogs.Login
{
public partial class ApprovalNeededDialog : DialogWindow
{
public ApprovalNeededDialog()
public ApprovalNeededDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();
}

View File

@@ -1,5 +1,7 @@
using AudibleApi;
using AudibleUtilities;
using Avalonia.Threading;
using LibationFileManager;
using System;
using System.Threading.Tasks;
@@ -23,9 +25,24 @@ namespace LibationAvalonia.Dialogs.Login
public async Task<ChoiceOut> StartAsync(ChoiceIn choiceIn)
{
if (Configuration.IsWindows && Environment.OSVersion.Version.Major >= 10)
{
try
{
var weblogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
if (await weblogin.ShowDialog<DialogResult>(App.MainWindow) is DialogResult.OK)
return ChoiceOut.External(weblogin.ResponseUrl);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"Failed to run {nameof(WebLoginDialog)}");
}
}
var dialog = new LoginChoiceEagerDialog(_account);
if (await dialog.ShowDialogAsync() is not DialogResult.OK || string.IsNullOrWhiteSpace(dialog.Password))
if (await dialog.ShowDialogAsync() is not DialogResult.OK ||
(dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password)))
return null;
switch (dialog.LoginMethod)

View File

@@ -5,6 +5,8 @@
mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="250"
MinWidth="220" MinHeight="250"
MaxWidth="220" MaxHeight="250"
Width="220" Height="250"
WindowStartupLocation="CenterOwner"
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
Title="CAPTCHA"
Icon="/Assets/libation.ico">

View File

@@ -13,7 +13,7 @@ namespace LibationAvalonia.Dialogs.Login
public string Answer => _viewModel.Answer;
private readonly CaptchaDialogViewModel _viewModel;
public CaptchaDialog()
public CaptchaDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();
passwordBox = this.FindControl<TextBox>(nameof(passwordBox));

View File

@@ -5,6 +5,7 @@
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="120"
MinWidth="300" MinHeight="120"
Width="300" Height="120"
WindowStartupLocation="CenterOwner"
x:Class="LibationAvalonia.Dialogs.Login.LoginCallbackDialog"
Title="Audible Login"
Icon="/Assets/libation.ico">

View File

@@ -11,7 +11,7 @@ namespace LibationAvalonia.Dialogs.Login
public Account Account { get; }
public string Password { get; set; }
public LoginCallbackDialog()
public LoginCallbackDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();

View File

@@ -2,9 +2,9 @@
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="350" d:DesignHeight="200"
MinWidth="350" MinHeight="200"
Width="350" Height="200"
mc:Ignorable="d" d:DesignWidth="360" d:DesignHeight="200"
MinWidth="370" MinHeight="200"
Width="370" Height="200"
WindowStartupLocation="CenterOwner"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Dialogs.Login.LoginChoiceEagerDialog"
@@ -35,7 +35,7 @@
Grid.Row="2"
Grid.Column="0"
Margin="0,5,0,5"
ColumnDefinitions="Auto,*">
ColumnDefinitions="Auto,*,Auto">
<TextBlock
Grid.Column="0"
@@ -46,6 +46,12 @@
Grid.Column="1"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}" />
<Button
Margin="5,0"
Grid.Column="2"
VerticalAlignment="Stretch"
Content="Submit"
Command="{Binding SaveAndCloseAsync}" />
</Grid>
<StackPanel
@@ -54,7 +60,7 @@
<controls:LinkLabel
Tapped="ExternalLoginLink_Tapped"
Text="Or click here to log in with your browser." />
Text="Trouble logging in? Click here to log in with your browser." />
<TextBlock
TextWrapping="Wrap"

View File

@@ -12,7 +12,7 @@ namespace LibationAvalonia.Dialogs.Login
public string Password { get; set; }
public LoginMethod LoginMethod { get; private set; }
public LoginChoiceEagerDialog()
public LoginChoiceEagerDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();

View File

@@ -14,7 +14,7 @@ namespace LibationAvalonia.Dialogs.Login
public string ExternalLoginUrl { get; }
public string ResponseUrl { get; set; }
public LoginExternalDialog()
public LoginExternalDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();
@@ -53,7 +53,7 @@ namespace LibationAvalonia.Dialogs.Login
public async void CopyUrlToClipboard_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await Application.Current.Clipboard.SetTextAsync(ExternalLoginUrl);
=> await App.MainWindow.Clipboard.SetTextAsync(ExternalLoginUrl);
public void LaunchInBrowser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> Go.To.Url(ExternalLoginUrl);

View File

@@ -4,7 +4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
MinWidth="400" MinHeight="200"
MaxWidth="400" MaxHeight="200"
MaxWidth="400" MaxHeight="400"
Width="400" Height="200"
WindowStartupLocation="CenterOwner"
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
Title="Two-Step Verification"
Icon="/Assets/libation.ico">

View File

@@ -14,7 +14,7 @@ namespace LibationAvalonia.Dialogs.Login
public string SelectedValue { get; private set; }
private RbValues Values { get; } = new();
public MfaDialog()
public MfaDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();

View File

@@ -0,0 +1,13 @@
<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="800" d:DesignHeight="450"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Dialogs.Login.WebLoginDialog"
Width="500" Height="800"
WindowStartupLocation="CenterOwner"
Icon="/Assets/libation.ico"
Title="Audible Login">
<controls:NativeWebView Name="webView" />
</Window>

View File

@@ -0,0 +1,54 @@
using Avalonia.Controls;
using Dinah.Core;
using System;
namespace LibationAvalonia.Dialogs.Login
{
public partial class WebLoginDialog : Window
{
public string ResponseUrl { get; private set; }
private readonly string accountID;
public WebLoginDialog()
{
InitializeComponent();
webView.NavigationStarted += WebView_NavigationStarted;
webView.DOMContentLoaded += WebView_NavigationCompleted;
}
public WebLoginDialog(string accountID, string loginUrl) : this()
{
this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
webView.Source = new Uri(ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl)));
}
private void WebView_NavigationStarted(object sender, LibationFileManager.WebViewNavigationEventArgs e)
{
if (e.Request?.AbsolutePath.Contains("/ap/maplanding") is true)
{
ResponseUrl = e.Request.ToString();
Close(DialogResult.OK);
}
}
private async void WebView_NavigationCompleted(object sender, EventArgs e)
{
await webView.InvokeScriptAsync(getScript(accountID));
}
private static string getScript(string accountID) => $$"""
(function() {
var inputs = document.getElementsByTagName('input');
for (index = 0; index < inputs.length; ++index) {
if (inputs[index].name.includes('email')) {
inputs[index].value = '{{accountID}}';
}
if (inputs[index].name.includes('password')) {
inputs[index].focus();
}
}
})()
""";
}
}

View File

@@ -5,6 +5,8 @@
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
MinWidth="200" MinHeight="200"
MaxWidth="200" MaxHeight="200"
Width="200" Height="200"
WindowStartupLocation="CenterOwner"
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
Title="2FA Code"
Icon="/Assets/libation.ico">

View File

@@ -9,7 +9,7 @@ namespace LibationAvalonia.Dialogs.Login
public string Prompt { get; } = "For added security, please enter the One Time Password (OTP) generated by your Authenticator App";
public _2faCodeDialog()
public _2faCodeDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();
_2FABox = this.FindControl<TextBox>(nameof(_2FABox));

View File

@@ -40,7 +40,7 @@
<Button Grid.Column="1" IsVisible="{Binding HasButton2}" MinWidth="75" MinHeight="28" Name="Button2" Click="Button2_Click" Margin="5">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Button2Text}"/>
</Button>
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="28" Name="Button3" Click="Button3_Click" Content="Cancel" Margin="5">
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="28" Name="Button3" Click="Button3_Click" Margin="5">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Button3Text}"/>
</Button>
</StackPanel>

View File

@@ -2,7 +2,7 @@
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="500" d:DesignHeight="185"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="200"
x:Class="LibationAvalonia.Dialogs.ScanAccountsDialog"
MinWidth="500" MinHeight="160"
Width="500" Height="200"
@@ -10,7 +10,7 @@
WindowStartupLocation="CenterOwner"
Icon="/Assets/libation.ico">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,Auto">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,*,Auto">
<Grid.Styles>
<Style Selector="Button:focus">
@@ -23,20 +23,19 @@
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="10,10,10,0"
Margin="10"
Text="Check the accounts to scan and import.&#xa;To change default selections, go to: Settings > Accounts"/>
<ScrollViewer
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="10,0"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
Margin="10"
MinHeight="90"
MaxHeight="90">
VerticalScrollBarVisibility="Auto">
<ListBox Items="{Binding Accounts}">
<ListBox ItemsSource="{Binding Accounts}">
<ListBox.ItemTemplate>
<DataTemplate>
@@ -62,21 +61,19 @@
<Button
Grid.Row="2"
Grid.Column="0"
Padding="20,0,20,0"
Margin="10,0,10,10"
Height="25"
Padding="20,5"
Margin="10"
Content="Edit Accounts"
Click="EditAccountsButton_Clicked"/>
Command="{Binding EditAccountsAsync}"/>
<Button
Grid.Row="2"
Grid.Column="1"
Padding="30,0,30,0"
Margin="10,0,10,10"
Padding="30,5"
Margin="10"
HorizontalAlignment="Right"
Height="25"
Content="Import"
Name="ImportButton"
Click="ImportButton_Clicked"/>
Command="{Binding SaveAndClose}"/>
</Grid>
</Window>

View File

@@ -4,6 +4,7 @@ using Avalonia.Interactivity;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs
{
@@ -25,7 +26,7 @@ namespace LibationAvalonia.Dialogs
InitializeComponent();
this.HideMinMaxBtns();
this.Opened += ScanAccountsDialog_Opened;
ControlToFocusOnShow = this.FindControl<Button>(nameof(ImportButton));
LoadAccounts();
}
@@ -46,12 +47,7 @@ namespace LibationAvalonia.Dialogs
DataContext = this;
}
private void ScanAccountsDialog_Opened(object sender, System.EventArgs e)
{
this.FindControl<Button>(nameof(ImportButton)).Focus();
}
public async void EditAccountsButton_Clicked(object sender, RoutedEventArgs e)
public async Task EditAccountsAsync()
{
if (await new AccountsDialog().ShowDialog<DialogResult>(this) == DialogResult.OK)
{
@@ -67,7 +63,5 @@ namespace LibationAvalonia.Dialogs
base.SaveAndClose();
}
public void ImportButton_Clicked(object sender, RoutedEventArgs e) => SaveAndClose();
}
}

View File

@@ -2,10 +2,10 @@
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="950" d:DesignHeight="650"
MinWidth="950" MinHeight="650"
MaxWidth="950" MaxHeight="650"
Width="950" Height="650"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
MinWidth="800" MinHeight="650"
MaxWidth="800" MaxHeight="650"
Width="800" Height="650"
x:Class="LibationAvalonia.Dialogs.SearchSyntaxDialog"
Title="Filter Options"
WindowStartupLocation="CenterOwner"
@@ -16,48 +16,55 @@
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Margin="10"
<Grid.Styles>
<Style Selector="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="10" />
</Style>
</Grid.Styles>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="4"
Text="Full Lucene query syntax is supported&#xa;Fields with similar names are synomyns (eg: Author, Authors, AuthorNames)&#xa;&#xa;TAG FORMAT: [tagName]" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="STRING FIELDS" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="NUMBER FIELDS" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="1"
Grid.Column="2"
Text="BOOLEAN (TRUE/FALSE) FIELDS" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="1"
Grid.Column="3"
Text="ID FIELDS" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="{Binding StringFields}" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="2"
Grid.Column="1"
Text="{Binding NumberFields}" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="2"
Grid.Column="2"
Text="{Binding BoolFields}" />
<TextBlock Margin="10"
<TextBlock
Grid.Row="2"
Grid.Column="3"
Text="{Binding IdFields}" />

View File

@@ -25,6 +25,9 @@ Find books between 1-100 minutes long
length:[1 TO 100]
Find books exactly 1 hr long
length:60
Find books published from 2020-1-1 to
2023-12-31
datepublished:[20200101 TO 20231231]
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields());

View File

@@ -3,10 +3,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="750"
MinWidth="900" MinHeight="700"
Width="900" Height="700"
MinWidth="900" MinHeight="750"
Width="900" Height="750"
x:Class="LibationAvalonia.Dialogs.SettingsDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
xmlns:settings="clr-namespace:LibationAvalonia.Controls.Settings"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:SettingsVM"
Title="Edit Settings"
Icon="/Assets/libation.ico">
@@ -24,789 +27,58 @@
<TabControl Name="tabControl" Grid.Column="0">
<TabControl.Styles>
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Height" Value="28"/>
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Height" Value="30"/>
</Style>
<Style Selector="TabControl /template/ ContentPresenter#PART_SelectedContentHost">
<Setter Property="BorderBrush" Value="{DynamicResource SystemBaseLowColor}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="TabItem">
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Height" Value="40"/>
<Setter Property="MinHeight" Value="45"/>
<Setter Property="Height" Value="45"/>
<Setter Property="Padding" Value="8,2,8,10"/>
</Style>
<Style Selector="TabItem#Header TextBlock">
<Setter Property="MinHeight" Value="5"/>
<Style Selector="^ > TextBlock" >
<Setter Property="FontSize" Value="14" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Style>
</TabControl.Styles>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Important Settings"/>
<TextBlock Text="Important Settings"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<Grid RowDefinitions="Auto,Auto,*">
<controls:GroupBox
Grid.Row="0"
Margin="5"
BorderWidth="1"
Label="Books Location">
<StackPanel>
<TextBlock
Margin="5"
Text="{Binding ImportantSettings.BooksText}" />
<controls:DirectoryOrCustomSelectControl Margin="0,10,0,10"
SubDirectory="Books"
Directory="{Binding ImportantSettings.BooksDirectory, Mode=TwoWay}"
KnownDirectories="{Binding ImportantSettings.KnownDirectories}" />
<CheckBox IsChecked="{Binding ImportantSettings.SavePodcastsToParentFolder, Mode=TwoWay}">
<TextBlock Text="{Binding ImportantSettings.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="150"
Height="25"
HorizontalContentAlignment="Stretch"
SelectedItem="{Binding ImportantSettings.LoggingLevel, Mode=TwoWay}"
Items="{Binding ImportantSettings.LoggingLevels}" />
<Button
Margin="50,0,0,0"
Padding="20,3,20,3"
Content="Open Log Folder"
Click="OpenLogFolderButton_Click" />
</StackPanel>
<!--
<CheckBox
Grid.Row="2"
Margin="5"
VerticalAlignment="Bottom"
IsChecked="{Binding ImportantSettings.BetaOptIn, Mode=TwoWay}">
<TextBlock Text="{Binding ImportantSettings.BetaOptInText}" />
</CheckBox>
-->
<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"
SelectedItem="{Binding ImportantSettings.ThemeVariant, Mode=TwoWay}"
Items="{Binding ImportantSettings.Themes}" />
<TextBlock
Grid.Column="2"
FontSize="16"
FontWeight="Bold"
Margin="10,0,0,0"
VerticalAlignment="Center"
IsVisible="{Binding ImportantSettings.SelectionChanged}"
Text="Theme change takes effect on restart"/>
</Grid>
</Grid>
</Border>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Import Library"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<StackPanel Margin="5">
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding ImportSettings.AutoScan, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding ImportSettings.AutoScanText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding ImportSettings.ShowImportedStats, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding ImportSettings.ShowImportedStatsText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding ImportSettings.ImportEpisodes, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding ImportSettings.ImportEpisodesText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding ImportSettings.DownloadEpisodes, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding ImportSettings.DownloadEpisodesText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,10"
IsChecked="{Binding ImportSettings.AutoDownloadEpisodes, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding ImportSettings.AutoDownloadEpisodesText}" />
</CheckBox>
</StackPanel>
</Border>
<settings:Important DataContext="{CompiledBinding ImportantSettings}" />
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Download/Decrypt"/>
<TextBlock Text="Import Library"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<Grid RowDefinitions="Auto,Auto,Auto,*">
<controls:GroupBox
Grid.Row="0"
Margin="5"
BorderWidth="1"
Label="{Binding DownloadDecryptSettings.BadBookGroupboxText}">
<Grid
ColumnDefinitions="*,*"
RowDefinitions="Auto,Auto">
<RadioButton
Grid.Column="0"
Grid.Row="0"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookAsk, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding DownloadDecryptSettings.BadBookAskText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="0"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookAbort, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding DownloadDecryptSettings.BadBookAbortText}" />
</RadioButton>
<RadioButton
Grid.Column="0"
Grid.Row="1"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookRetry, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding DownloadDecryptSettings.BadBookRetryText}" />
</RadioButton>
<RadioButton
Grid.Column="1"
Grid.Row="1"
Margin="0,5,0,5"
IsChecked="{Binding DownloadDecryptSettings.BadBookIgnore, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding DownloadDecryptSettings.BadBookIgnoreText}" />
</RadioButton>
</Grid>
</controls:GroupBox>
<controls:GroupBox
Margin="5"
Grid.Row="1"
BorderWidth="1"
Label="Custom File Naming">
<Grid
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
ColumnDefinitions="*,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="0,5,0,0"
Text="{Binding DownloadDecryptSettings.FolderTemplateText}" />
<TextBox
Grid.Row="1"
Grid.Column="0"
Margin="0,5,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.FolderTemplate}" />
<Button
Grid.Row="1"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditFolderTemplateButton_Click" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="{Binding DownloadDecryptSettings.FileTemplateText}" />
<TextBox
Grid.Row="3"
Grid.Column="0"
Margin="0,5,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.FileTemplate}" />
<Button
Grid.Row="3"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditFileTemplateButton_Click" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="{Binding DownloadDecryptSettings.ChapterFileTemplateText}" />
<TextBox
Grid.Row="5"
Grid.Column="0"
Margin="0,5,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding DownloadDecryptSettings.ChapterFileTemplate}" />
<Button
Grid.Row="5"
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditChapterFileTemplateButton_Click" />
<Button
Grid.Row="6"
Grid.Column="0"
Content="{Binding DownloadDecryptSettings.EditCharReplacementText}"
Height="30"
Padding="30,3,30,3"
Click="EditCharReplacementButton_Click" />
</Grid>
</controls:GroupBox>
<controls:GroupBox
Grid.Row="2"
Margin="5"
BorderWidth="1"
Label="Temporary Files Location">
<StackPanel
Margin="5" >
<TextBlock
Margin="0,0,0,10"
Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" />
<controls:DirectoryOrCustomSelectControl
Directory="{Binding DownloadDecryptSettings.InProgressDirectory, Mode=TwoWay}"
KnownDirectories="{Binding DownloadDecryptSettings.KnownDirectories}" />
</StackPanel>
</controls:GroupBox>
<CheckBox
Grid.Row="3"
Margin="5"
VerticalAlignment="Top"
IsVisible="{Binding !IsLinux}"
IsChecked="{Binding DownloadDecryptSettings.UseCoverAsFolderIcon, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding DownloadDecryptSettings.UseCoverAsFolderIconText}" />
</CheckBox>
</Grid>
</Border>
<settings:Import DataContext="{CompiledBinding ImportSettings}" />
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
FontSize="14"
VerticalAlignment="Center"
Text="Audio File Settings"/>
<TextBlock Text="Download/Decrypt"/>
</TabItem.Header>
<Border
Grid.Column="0"
Grid.Row="0"
BorderThickness="2"
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<settings:DownloadDecrypt DataContext="{CompiledBinding DownloadDecryptSettings}" />
</TabItem>
<Grid
RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
<TabItem>
<StackPanel
Margin="5"
Grid.Row="0"
Grid.Column="0">
<TabItem.Header>
<TextBlock Text="Audio File Settings"/>
</TabItem.Header>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.CreateCueSheet, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.CreateCueSheetText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.DownloadCoverArt, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.DownloadCoverArtText}" />
</CheckBox>
<StackPanel Orientation="Horizontal">
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.DownloadClipsBookmarks, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Download Clips, Notes and Bookmarks as" />
</CheckBox>
<controls:WheelComboBox
Margin="5,0,0,0"
IsEnabled="{Binding AudioSettings.DownloadClipsBookmarks}"
Items="{Binding AudioSettings.ClipBookmarkFormats}"
SelectedItem="{Binding AudioSettings.ClipBookmarkFormat}"/>
</StackPanel>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.RetainAaxFile, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.RetainAaxFileText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.MergeOpeningAndEndCredits, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.MergeOpeningEndCreditsText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.AllowLibationFixup, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.AllowLibationFixupText}" />
</CheckBox>
<controls:GroupBox
BorderWidth="1"
Label="Audiobook Fix-ups"
IsEnabled="{Binding AudioSettings.AllowLibationFixup}">
<StackPanel Orientation="Vertical">
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.SplitFilesByChapter, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.SplitFilesByChapterText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.StripAudibleBrandAudio, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.StripAudibleBrandingText}" />
</CheckBox>
<CheckBox
Margin="0,0,0,5"
IsChecked="{Binding AudioSettings.StripUnabridged, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.StripUnabridgedText}" />
</CheckBox>
<RadioButton
Margin="0,5,0,5"
IsChecked="{Binding !AudioSettings.DecryptToLossy, Mode=TwoWay}">
<StackPanel >
<TextBlock
TextWrapping="Wrap"
Text="Download my books in the original audio format (Lossless)" />
<CheckBox
Margin="0,0,0,5"
IsEnabled="{Binding !AudioSettings.DecryptToLossy}"
IsChecked="{Binding AudioSettings.MoveMoovToBeginning, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="{Binding AudioSettings.MoveMoovToBeginningText}" />
</CheckBox>
</StackPanel>
</RadioButton>
<RadioButton
Margin="0,5,0,5"
IsChecked="{Binding AudioSettings.DecryptToLossy, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Download my books as .MP3 files (transcode if necessary)" />
</RadioButton>
</StackPanel>
</controls:GroupBox>
</StackPanel>
<StackPanel
Grid.Row="0"
Grid.Column="1">
<controls:GroupBox
BorderWidth="1"
Label="Mp3 Encoding Options">
<StackPanel Orientation="Vertical">
<Grid
Margin="5,5,5,0"
ColumnDefinitions="Auto,*">
<controls:GroupBox
BorderWidth="1"
Grid.Column="0"
Label="Target">
<StackPanel Orientation="Horizontal">
<RadioButton
Margin="10"
Content="Bitrate"
IsChecked="{Binding AudioSettings.LameTargetBitrate, Mode=TwoWay}"/>
<RadioButton
Margin="10"
Content="Quality"
IsChecked="{Binding !AudioSettings.LameTargetBitrate, Mode=TwoWay}"/>
</StackPanel>
</controls:GroupBox>
<CheckBox
HorizontalAlignment="Right"
Grid.Column="1"
IsChecked="{Binding AudioSettings.LameDownsampleMono, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Downsample to mono? (Recommended)" />
</CheckBox>
</Grid>
<Grid Margin="5,5,5,0" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto">
<TextBlock Margin="0,0,0,5" Text="Max audio sample rate:" />
<controls:WheelComboBox
Grid.Row="1"
HorizontalAlignment="Stretch"
Items="{Binding AudioSettings.SampleRates}"
SelectedItem="{Binding AudioSettings.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"
Items="{Binding AudioSettings.EncoderQualities}"
SelectedItem="{Binding AudioSettings.SelectedEncoderQuality, Mode=TwoWay}"/>
</Grid>
<controls:GroupBox
Margin="5,5,5,0"
BorderWidth="1"
Label="Bitrate"
IsEnabled="{Binding AudioSettings.LameTargetBitrate}" >
<StackPanel>
<Grid ColumnDefinitions="*,25,Auto">
<Slider
Grid.Column="0"
IsEnabled="{Binding !AudioSettings.LameMatchSource}"
Value="{Binding AudioSettings.LameBitrate, Mode=TwoWay}"
Minimum="16"
Maximum="320"
IsSnapToTickEnabled="True" TickFrequency="16"
Ticks="16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,272,288,304,320"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='\{0:f0\} Kbps'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
Text="{Binding AudioSettings.LameBitrate}" />
<TextBlock
Grid.Column="2"
Text=" Kbps" />
</Grid>
<Grid ColumnDefinitions="*,*">
<CheckBox
Grid.Column="0"
IsChecked="{Binding AudioSettings.LameConstantBitrate, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Restrict Encoder to Constant Bitrate?" />
</CheckBox>
<CheckBox
Grid.Column="1"
HorizontalAlignment="Right"
IsChecked="{Binding AudioSettings.LameMatchSource, Mode=TwoWay}">
<TextBlock
TextWrapping="Wrap"
Text="Match Source Bitrate?" />
</CheckBox>
</Grid>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox
Margin="5,5,5,0"
BorderWidth="1"
Label="Quality"
IsEnabled="{Binding !AudioSettings.LameTargetBitrate}">
<Grid
ColumnDefinitions="*,*,25"
RowDefinitions="*,Auto">
<Slider
Grid.Column="0"
Grid.ColumnSpan="2"
Value="{Binding AudioSettings.LameVBRQuality, Mode=TwoWay}"
Minimum="0"
Maximum="9"
IsSnapToTickEnabled="True" TickFrequency="1"
Ticks="0,1,2,3,4,5,6,7,8,9"
TickPlacement="Outside">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='V\{0:f0\}'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<StackPanel
Grid.Column="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock Text="V" />
<TextBlock Text="{Binding AudioSettings.LameVBRQuality}" />
</StackPanel>
<TextBlock
Grid.Column="0"
Grid.Row="1"
Margin="10,0,0,0"
Text="Higher" />
<TextBlock
Grid.Column="1"
Grid.Row="1"
Margin="0,0,10,0"
HorizontalAlignment="Right"
Text="Lower" />
</Grid>
</controls:GroupBox>
<TextBlock
Margin="5,5,5,5"
Text="Using L.A.M.E encoding engine"
FontStyle="Italic" />
</StackPanel>
</controls:GroupBox>
</StackPanel>
<controls:GroupBox
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="5"
BorderWidth="1" IsEnabled="{Binding AudioSettings.SplitFilesByChapter}"
Label="{Binding AudioSettings.ChapterTitleTemplateText}">
<Grid ColumnDefinitions="*,Auto">
<TextBox
Grid.Column="0"
Margin="0,10,10,10"
FontSize="14"
IsReadOnly="True"
Text="{Binding AudioSettings.ChapterTitleTemplate}" />
<Button
Grid.Column="1"
Content="Edit"
Height="30"
Padding="30,3,30,3"
Click="EditChapterTitleTemplateButton_Click" />
</Grid>
</controls:GroupBox>
</Grid>
</Border>
<settings:Audio DataContext="{CompiledBinding AudioSettings}" />
</TabItem>
</TabControl>
</Grid>

View File

@@ -1,20 +1,13 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Dinah.Core;
using FileManager;
using LibationAvalonia.ViewModels.Settings;
using LibationFileManager;
using LibationUiBase;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs
{
public partial class SettingsDialog : DialogWindow
{
private SettingsPages settingsDisp;
private SettingsVM settingsDisp;
private readonly Configuration config = Configuration.Instance;
public SettingsDialog()
@@ -28,8 +21,17 @@ namespace LibationAvalonia.Dialogs
protected override async Task SaveAndCloseAsync()
{
if (!await settingsDisp.SaveSettingsAsync(config))
#region validation
if (string.IsNullOrWhiteSpace(settingsDisp.ImportantSettings.BooksDirectory))
{
await MessageBox.Show(this.GetParentWindow(), "Cannot set Books Location to blank", "Location is blank", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
#endregion
settingsDisp.SaveSettings(config);
await MessageBox.VerboseLoggingWarning_ShowIfTrue();
await base.SaveAndCloseAsync();
@@ -37,494 +39,5 @@ namespace LibationAvalonia.Dialogs
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
public void OpenLogFolderButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Go.To.Folder(((LongPath)Configuration.Instance.LibationFiles).ShortPathName);
}
public async void EditFolderTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(TemplateEditor<Templates.FolderTemplate>.CreateFilenameEditor(config.Books, settingsDisp.DownloadDecryptSettings.FolderTemplate));
if (newTemplate is not null)
settingsDisp.DownloadDecryptSettings.FolderTemplate = newTemplate;
}
public async void EditFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(TemplateEditor<Templates.FileTemplate>.CreateFilenameEditor(config.Books, settingsDisp.DownloadDecryptSettings.FileTemplate));
if (newTemplate is not null)
settingsDisp.DownloadDecryptSettings.FileTemplate = newTemplate;
}
public async void EditChapterFileTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(TemplateEditor<Templates.ChapterFileTemplate>.CreateFilenameEditor(config.Books, settingsDisp.DownloadDecryptSettings.ChapterFileTemplate));
if (newTemplate is not null)
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
}
public async void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var form = new EditReplacementChars(config);
await form.ShowDialog<DialogResult>(this);
}
public async void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var newTemplate = await editTemplate(TemplateEditor<Templates.ChapterTitleTemplate>.CreateNameEditor(settingsDisp.AudioSettings.ChapterTitleTemplate));
if (newTemplate is not null)
settingsDisp.AudioSettings.ChapterTitleTemplate = newTemplate;
}
private async Task<string> editTemplate(ITemplateEditor template)
{
var form = new EditTemplateDialog(template);
if (await form.ShowDialog<DialogResult>(this) == DialogResult.OK)
return template.EditingTemplate.TemplateText;
else return null;
}
}
internal interface ISettingsDisplay
{
void LoadSettings(Configuration config);
Task<bool> SaveSettingsAsync(Configuration config);
}
public class SettingsPages : ISettingsDisplay
{
public SettingsPages(Configuration config)
{
LoadSettings(config);
}
public bool IsLinux => Configuration.IsLinux;
public bool IsWindows => Configuration.IsWindows;
public ImportantSettings ImportantSettings { get; private set; }
public ImportSettings ImportSettings { get; private set; }
public DownloadDecryptSettings DownloadDecryptSettings { get; private set; }
public AudioSettings AudioSettings { get; private set; }
public void LoadSettings(Configuration config)
{
ImportantSettings = new(config);
ImportSettings = new(config);
DownloadDecryptSettings = new(config);
AudioSettings = new(config);
}
public async Task<bool> SaveSettingsAsync(Configuration config)
{
var result = await ImportantSettings.SaveSettingsAsync(config);
result &= await ImportSettings.SaveSettingsAsync(config);
result &= await DownloadDecryptSettings.SaveSettingsAsync(config);
result &= await AudioSettings.SaveSettingsAsync(config);
return result;
}
}
public class ImportantSettings : ViewModels.ViewModelBase, ISettingsDisplay
{
public ImportantSettings(Configuration config)
{
LoadSettings(config);
}
public void LoadSettings(Configuration config)
{
BooksDirectory = config.Books.PathWithoutPrefix;
SavePodcastsToParentFolder = config.SavePodcastsToParentFolder;
LoggingLevel = config.LogLevel;
BetaOptIn = config.BetaOptIn;
ThemeVariant = InitialThemeVariant
= Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is nameof(Avalonia.Styling.ThemeVariant.Dark)
? nameof(Avalonia.Styling.ThemeVariant.Dark)
: nameof(Avalonia.Styling.ThemeVariant.Light);
}
public async Task<bool> SaveSettingsAsync(Configuration config)
{
#region validation
if (string.IsNullOrWhiteSpace(BooksDirectory))
{
await MessageBox.Show("Cannot set Books Location to blank", "Location is blank", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
#endregion
LongPath lonNewBooks = BooksDirectory;
if (!System.IO.Directory.Exists(lonNewBooks))
System.IO.Directory.CreateDirectory(lonNewBooks);
config.Books = BooksDirectory;
config.SavePodcastsToParentFolder = SavePodcastsToParentFolder;
config.LogLevel = LoggingLevel;
config.BetaOptIn = BetaOptIn;
Configuration.Instance.SetString(ThemeVariant, nameof(ThemeVariant));
return true;
}
public List<Configuration.KnownDirectories> KnownDirectories { get; } = new()
{
Configuration.KnownDirectories.UserProfile,
Configuration.KnownDirectories.AppDir,
Configuration.KnownDirectories.MyDocs
};
public string BooksText { get; } = Configuration.GetDescription(nameof(Configuration.Books));
public string SavePodcastsToParentFolderText { get; } = Configuration.GetDescription(nameof(Configuration.SavePodcastsToParentFolder));
public Serilog.Events.LogEventLevel[] LoggingLevels { get; } = Enum.GetValues<Serilog.Events.LogEventLevel>();
public string BetaOptInText { get; } = Configuration.GetDescription(nameof(Configuration.BetaOptIn));
public string[] Themes { get; } = { nameof(Avalonia.Styling.ThemeVariant.Light), nameof(Avalonia.Styling.ThemeVariant.Dark) };
public string BooksDirectory { get; set; }
public bool SavePodcastsToParentFolder { get; set; }
public Serilog.Events.LogEventLevel LoggingLevel { get; set; }
public bool BetaOptIn { get; set; }
private string themeVariant;
public string ThemeVariant
{
get => themeVariant;
set
{
this.RaiseAndSetIfChanged(ref themeVariant, value);
SelectionChanged = ThemeVariant != InitialThemeVariant;
this.RaisePropertyChanged(nameof(SelectionChanged));
}
}
public string InitialThemeVariant { get; private set; }
public bool SelectionChanged { get; private set; }
}
public class ImportSettings : ISettingsDisplay
{
public ImportSettings(Configuration config)
{
LoadSettings(config);
}
public void LoadSettings(Configuration config)
{
AutoScan = config.AutoScan;
ShowImportedStats = config.ShowImportedStats;
ImportEpisodes = config.ImportEpisodes;
DownloadEpisodes = config.DownloadEpisodes;
AutoDownloadEpisodes = config.AutoDownloadEpisodes;
}
public Task<bool> SaveSettingsAsync(Configuration config)
{
config.AutoScan = AutoScan;
config.ShowImportedStats = ShowImportedStats;
config.ImportEpisodes = ImportEpisodes;
config.DownloadEpisodes = DownloadEpisodes;
config.AutoDownloadEpisodes = AutoDownloadEpisodes;
return Task.FromResult(true);
}
public string AutoScanText { get; } = Configuration.GetDescription(nameof(Configuration.AutoScan));
public string ShowImportedStatsText { get; } = Configuration.GetDescription(nameof(Configuration.ShowImportedStats));
public string ImportEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.ImportEpisodes));
public string DownloadEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadEpisodes));
public string AutoDownloadEpisodesText { get; } = Configuration.GetDescription(nameof(Configuration.AutoDownloadEpisodes));
public bool AutoScan { get; set; }
public bool ShowImportedStats { get; set; }
public bool ImportEpisodes { get; set; }
public bool DownloadEpisodes { get; set; }
public bool AutoDownloadEpisodes { get; set; }
}
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsDisplay
{
private bool _badBookAsk;
private bool _badBookAbort;
private bool _badBookRetry;
private bool _badBookIgnore;
private string _folderTemplate;
private string _fileTemplate;
private string _chapterFileTemplate;
public DownloadDecryptSettings(Configuration config)
{
LoadSettings(config);
}
public List<Configuration.KnownDirectories> KnownDirectories { get; } = new()
{
Configuration.KnownDirectories.WinTemp,
Configuration.KnownDirectories.UserProfile,
Configuration.KnownDirectories.AppDir,
Configuration.KnownDirectories.MyDocs,
Configuration.KnownDirectories.LibationFiles
};
public string InProgressDirectory { get; set; }
public void LoadSettings(Configuration config)
{
BadBookAsk = config.BadBook is Configuration.BadBookAction.Ask;
BadBookAbort = config.BadBook is Configuration.BadBookAction.Abort;
BadBookRetry = config.BadBook is Configuration.BadBookAction.Retry;
BadBookIgnore = config.BadBook is Configuration.BadBookAction.Ignore;
FolderTemplate = config.FolderTemplate;
FileTemplate = config.FileTemplate;
ChapterFileTemplate = config.ChapterFileTemplate;
InProgressDirectory = config.InProgress;
UseCoverAsFolderIcon = config.UseCoverAsFolderIcon;
}
public Task<bool> SaveSettingsAsync(Configuration config)
{
config.BadBook
= BadBookAbort ? Configuration.BadBookAction.Abort
: BadBookRetry ? Configuration.BadBookAction.Retry
: BadBookIgnore ? Configuration.BadBookAction.Ignore
: Configuration.BadBookAction.Ask;
config.FolderTemplate = FolderTemplate;
config.FileTemplate = FileTemplate;
config.ChapterFileTemplate = ChapterFileTemplate;
config.InProgress = InProgressDirectory;
config.UseCoverAsFolderIcon = UseCoverAsFolderIcon;
return Task.FromResult(true);
}
public string UseCoverAsFolderIconText { get; } = Configuration.GetDescription(nameof(Configuration.UseCoverAsFolderIcon));
public string BadBookGroupboxText { get; } = Configuration.GetDescription(nameof(Configuration.BadBook));
public string BadBookAskText { get; } = Configuration.BadBookAction.Ask.GetDescription();
public string BadBookAbortText { get; } = Configuration.BadBookAction.Abort.GetDescription();
public string BadBookRetryText { get; } = Configuration.BadBookAction.Retry.GetDescription();
public string BadBookIgnoreText { get; } = Configuration.BadBookAction.Ignore.GetDescription();
public string FolderTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate));
public string FileTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.FileTemplate));
public string ChapterFileTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
public string EditCharReplacementText { get; } = Configuration.GetDescription(nameof(Configuration.ReplacementCharacters));
public string InProgressDescriptionText { get; } = Configuration.GetDescription(nameof(Configuration.InProgress));
public string FolderTemplate { get => _folderTemplate; set { this.RaiseAndSetIfChanged(ref _folderTemplate, value); } }
public string FileTemplate { get => _fileTemplate; set { this.RaiseAndSetIfChanged(ref _fileTemplate, value); } }
public string ChapterFileTemplate { get => _chapterFileTemplate; set { this.RaiseAndSetIfChanged(ref _chapterFileTemplate, value); } }
public bool UseCoverAsFolderIcon { get; set; }
public bool BadBookAsk
{
get => _badBookAsk;
set
{
this.RaiseAndSetIfChanged(ref _badBookAsk, value);
if (value)
{
BadBookAbort = false;
BadBookRetry = false;
BadBookIgnore = false;
}
}
}
public bool BadBookAbort
{
get => _badBookAbort;
set
{
this.RaiseAndSetIfChanged(ref _badBookAbort, value);
if (value)
{
BadBookAsk = false;
BadBookRetry = false;
BadBookIgnore = false;
}
}
}
public bool BadBookRetry
{
get => _badBookRetry;
set
{
this.RaiseAndSetIfChanged(ref _badBookRetry, value);
if (value)
{
BadBookAsk = false;
BadBookAbort = false;
BadBookIgnore = false;
}
}
}
public bool BadBookIgnore
{
get => _badBookIgnore;
set
{
this.RaiseAndSetIfChanged(ref _badBookIgnore, value);
if (value)
{
BadBookAsk = false;
BadBookAbort = false;
BadBookRetry = false;
}
}
}
}
public class AudioSettings : ViewModels.ViewModelBase, ISettingsDisplay
{
private bool _downloadClipsBookmarks;
private bool _decryptToLossy;
private bool _splitFilesByChapter;
private bool _allowLibationFixup;
private bool _lameTargetBitrate;
private bool _lameMatchSource;
private int _lameBitrate;
private int _lameVBRQuality;
private string _chapterTitleTemplate;
public SampleRateSelection SelectedSampleRate { get; set; }
public NAudio.Lame.EncoderQuality SelectedEncoderQuality { get; set; }
public AvaloniaList<SampleRateSelection> SampleRates { get; }
= new(
new[]
{
AAXClean.SampleRate.Hz_44100,
AAXClean.SampleRate.Hz_32000,
AAXClean.SampleRate.Hz_24000,
AAXClean.SampleRate.Hz_22050,
AAXClean.SampleRate.Hz_16000,
AAXClean.SampleRate.Hz_12000,
}
.Select(s => new SampleRateSelection(s)));
public AvaloniaList<NAudio.Lame.EncoderQuality> EncoderQualities { get; }
= new(
new[]
{
NAudio.Lame.EncoderQuality.High,
NAudio.Lame.EncoderQuality.Standard,
NAudio.Lame.EncoderQuality.Fast,
});
public AudioSettings(Configuration config)
{
LoadSettings(config);
}
public void LoadSettings(Configuration config)
{
CreateCueSheet = config.CreateCueSheet;
AllowLibationFixup = config.AllowLibationFixup;
DownloadCoverArt = config.DownloadCoverArt;
RetainAaxFile = config.RetainAaxFile;
DownloadClipsBookmarks = config.DownloadClipsBookmarks;
ClipBookmarkFormat = config.ClipsBookmarksFileFormat;
SplitFilesByChapter = config.SplitFilesByChapter;
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
StripAudibleBrandAudio = config.StripAudibleBrandAudio;
StripUnabridged = config.StripUnabridged;
ChapterTitleTemplate = config.ChapterTitleTemplate;
DecryptToLossy = config.DecryptToLossy;
MoveMoovToBeginning = config.MoveMoovToBeginning;
LameTargetBitrate = config.LameTargetBitrate;
LameDownsampleMono = config.LameDownsampleMono;
LameConstantBitrate = config.LameConstantBitrate;
LameMatchSource = config.LameMatchSourceBR;
LameBitrate = config.LameBitrate;
LameVBRQuality = config.LameVBRQuality;
SelectedSampleRate = SampleRates.FirstOrDefault(s => s.SampleRate == config.MaxSampleRate);
SelectedEncoderQuality = config.LameEncoderQuality;
}
public Task<bool> SaveSettingsAsync(Configuration config)
{
config.CreateCueSheet = CreateCueSheet;
config.AllowLibationFixup = AllowLibationFixup;
config.DownloadCoverArt = DownloadCoverArt;
config.RetainAaxFile = RetainAaxFile;
config.DownloadClipsBookmarks = DownloadClipsBookmarks;
config.ClipsBookmarksFileFormat = ClipBookmarkFormat;
config.SplitFilesByChapter = SplitFilesByChapter;
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
config.StripAudibleBrandAudio = StripAudibleBrandAudio;
config.StripUnabridged = StripUnabridged;
config.ChapterTitleTemplate = ChapterTitleTemplate;
config.DecryptToLossy = DecryptToLossy;
config.MoveMoovToBeginning = MoveMoovToBeginning;
config.LameTargetBitrate = LameTargetBitrate;
config.LameDownsampleMono = LameDownsampleMono;
config.LameConstantBitrate = LameConstantBitrate;
config.LameMatchSourceBR = LameMatchSource;
config.LameBitrate = LameBitrate;
config.LameVBRQuality = LameVBRQuality;
config.LameEncoderQuality = SelectedEncoderQuality;
config.MaxSampleRate = SelectedSampleRate?.SampleRate ?? config.MaxSampleRate;
return Task.FromResult(true);
}
public AvaloniaList<Configuration.ClipBookmarkFormat> ClipBookmarkFormats { get; } = new(Enum<Configuration.ClipBookmarkFormat>.GetValues());
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
public string RetainAaxFileText { get; } = Configuration.GetDescription(nameof(Configuration.RetainAaxFile));
public string SplitFilesByChapterText { get; } = Configuration.GetDescription(nameof(Configuration.SplitFilesByChapter));
public string MergeOpeningEndCreditsText { get; } = Configuration.GetDescription(nameof(Configuration.MergeOpeningAndEndCredits));
public string StripAudibleBrandingText { get; } = Configuration.GetDescription(nameof(Configuration.StripAudibleBrandAudio));
public string StripUnabridgedText { get; } = Configuration.GetDescription(nameof(Configuration.StripUnabridged));
public string ChapterTitleTemplateText { get; } = Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate));
public string MoveMoovToBeginningText { get; } = Configuration.GetDescription(nameof(Configuration.MoveMoovToBeginning));
public bool CreateCueSheet { get; set; }
public bool DownloadCoverArt { get; set; }
public bool RetainAaxFile { get; set; }
public bool DownloadClipsBookmarks { get => _downloadClipsBookmarks; set => this.RaiseAndSetIfChanged(ref _downloadClipsBookmarks, value); }
public Configuration.ClipBookmarkFormat ClipBookmarkFormat { get; set; }
public bool MergeOpeningAndEndCredits { get; set; }
public bool StripAudibleBrandAudio { get; set; }
public bool StripUnabridged { get; set; }
public bool DecryptToLossy { get => _decryptToLossy; set => this.RaiseAndSetIfChanged(ref _decryptToLossy, value); }
public bool MoveMoovToBeginning { get; set; }
public bool LameDownsampleMono { get; set; } = Design.IsDesignMode;
public bool LameConstantBitrate { get; set; } = Design.IsDesignMode;
public bool SplitFilesByChapter { get => _splitFilesByChapter; set { this.RaiseAndSetIfChanged(ref _splitFilesByChapter, value); } }
public bool LameTargetBitrate { get => _lameTargetBitrate; set { this.RaiseAndSetIfChanged(ref _lameTargetBitrate, value); } }
public bool LameMatchSource { get => _lameMatchSource; set { this.RaiseAndSetIfChanged(ref _lameMatchSource, value); } }
public int LameBitrate { get => _lameBitrate; set { this.RaiseAndSetIfChanged(ref _lameBitrate, value); } }
public int LameVBRQuality { get => _lameVBRQuality; set { this.RaiseAndSetIfChanged(ref _lameVBRQuality, value); } }
public string ChapterTitleTemplate { get => _chapterTitleTemplate; set { this.RaiseAndSetIfChanged(ref _chapterTitleTemplate, value); } }
public bool AllowLibationFixup
{
get => _allowLibationFixup;
set
{
this.RaiseAndSetIfChanged(ref _allowLibationFixup, value);
if (!_allowLibationFixup)
{
SplitFilesByChapter = false;
StripAudibleBrandAudio = false;
StripUnabridged = false;
DecryptToLossy = false;
this.RaisePropertyChanged(nameof(SplitFilesByChapter));
this.RaisePropertyChanged(nameof(StripAudibleBrandAudio));
this.RaisePropertyChanged(nameof(StripUnabridged));
this.RaisePropertyChanged(nameof(DecryptToLossy));
}
}
}
}
}

View File

@@ -2,33 +2,32 @@
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="630" d:DesignHeight="110"
mc:Ignorable="d" d:DesignWidth="630" d:DesignHeight="90"
x:Class="LibationAvalonia.Dialogs.TagsBatchDialog"
MinWidth="630" MinHeight="110"
MaxWidth="630" MaxHeight="110"
MinWidth="630" MinHeight="90"
MaxWidth="630" MaxHeight="90"
Width="630" Height="110"
Title="Replace Tags"
WindowStartupLocation="CenterOwner"
Icon="/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,Auto">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto" Margin="10">
<TextBlock
Grid.Row="0"
Margin="10,10,10,0"
Grid.ColumnSpan="2"
Margin="0,0,0,10"
Text="Tags are separated by a space. Each tag can contain letters, numbers, and underscores"/>
<TextBox
Grid.Row="1"
Margin="10"
MinHeight="25"
Name="EditTagsTb"
Text="{Binding NewTags, Mode=TwoWay}" />
<Button
Grid.Row="2"
Padding="30,0,30,0"
Margin="10,0,10,10"
HorizontalAlignment="Right"
Height="25"
Grid.Row="1"
Grid.Column="1"
Margin="10,0,0,0"
Padding="20,3"
VerticalAlignment="Stretch"
Content="Save"
Click="SaveButton_Clicked"/>
Command="{Binding SaveAndClose}"/>
</Grid>
</Window>

View File

@@ -12,8 +12,5 @@ namespace LibationAvalonia.Dialogs
DataContext = this;
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose();
}
}

View File

@@ -52,12 +52,12 @@
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Content="Restore"
Click="Restore_Click" />
Command="{Binding RestoreCheckedAsync}"/>
<Button
IsEnabled="{Binding ControlsEnabled}"
Grid.Column="3"
Click="EmptyTrash_Click" >
Command="{Binding PermanentlyDeleteCheckedAsync}" >
<TextBlock
TextAlignment="Center"
Text="Permanently Delete&#xa;from Libation" />

View File

@@ -16,21 +16,20 @@ namespace LibationAvalonia.Dialogs
{
public partial class TrashBinDialog : Window
{
TrashBinViewModel _viewModel;
public TrashBinDialog()
{
InitializeComponent();
this.RestoreSizeAndLocation(Configuration.Instance);
DataContext = _viewModel = new();
DataContext = new TrashBinViewModel();
this.Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
}
public async void EmptyTrash_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await _viewModel.PermanentlyDeleteCheckedAsync();
public async void Restore_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await _viewModel.RestoreCheckedAsync();
KeyBindings.Add(new Avalonia.Input.KeyBinding
{
Gesture = new Avalonia.Input.KeyGesture(Avalonia.Input.Key.Escape),
Command = ReactiveCommand.Create(Close)
});
}
}
public class TrashBinViewModel : ViewModelBase, IDisposable

View File

@@ -21,7 +21,6 @@
<controls:GroupBox
Grid.Row="1"
BorderWidth="2"
Label="Release Information"
Margin="0,10,0,10">

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using LibationFileManager;
using System;
using System.Linq;
@@ -111,12 +112,12 @@ namespace LibationAvalonia
public static void HideMinMaxBtns(this Window form)
{
if (Design.IsDesignMode || !Configuration.IsWindows)
if (Design.IsDesignMode || !Configuration.IsWindows || form.TryGetPlatformHandle() is not IPlatformHandle handle)
return;
var handle = form.PlatformImpl.Handle.Handle;
var currentStyle = GetWindowLong(handle, GWL_STYLE);
SetWindowLong(handle, GWL_STYLE, currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
var currentStyle = GetWindowLong(handle.Handle, GWL_STYLE);
SetWindowLong(handle.Handle, GWL_STYLE, currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
}
const long WS_MINIMIZEBOX = 0x00020000L;

View File

@@ -3,7 +3,8 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>Assets/libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName>
@@ -16,6 +17,7 @@
<PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -30,8 +32,8 @@
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
<None Remove="Assets\DataGridFluentTheme.xaml" />
<None Remove=".gitignore" />
<None Remove="Assets\DataGridColumnHeader.xaml" />
<None Remove="Assets\img-coverart-prod-unavailable_300x300.jpg" />
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
@@ -51,8 +53,8 @@
<Compile Update="Views\LiberateStatusButton.axaml.cs">
<DependentUpon>LiberateStatusButton.axaml</DependentUpon>
</Compile>
<Compile Update="Views\MainWindow.*.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>
<Compile Update="ViewModels\MainVM.*.cs">
<DependentUpon>MainVM.cs</DependentUpon>
</Compile>
</ItemGroup>
@@ -68,13 +70,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview6" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview8" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview8" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,44 @@
using Avalonia;
using Avalonia.Input;
namespace LibationAvalonia
{
internal class MacAccessKeyHandler : AccessKeyHandler
{
protected override void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is Key.LWin or Key.RWin)
{
var newArgs = new KeyEventArgs { Key = Key.LeftAlt, Handled = e.Handled };
base.OnPreviewKeyDown(sender, newArgs);
e.Handled = newArgs.Handled;
}
else if (e.Key is not Key.LeftAlt and not Key.RightAlt)
base.OnPreviewKeyDown(sender, e);
}
protected override void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key is Key.LWin or Key.RWin)
{
var newArgs = new KeyEventArgs { Key = Key.LeftAlt, Handled = e.Handled };
base.OnPreviewKeyUp(sender, newArgs);
e.Handled = newArgs.Handled;
}
else if (e.Key is not Key.LeftAlt and not Key.RightAlt)
base.OnPreviewKeyDown(sender, e);
}
protected override void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
{
var newArgs = new KeyEventArgs { Key = e.Key, Handled = e.Handled, KeyModifiers = KeyModifiers.Alt };
base.OnKeyDown(sender, newArgs);
e.Handled = newArgs.Handled;
}
else if (!e.KeyModifiers.HasFlag(KeyModifiers.Alt))
base.OnPreviewKeyDown(sender, e);
}
}
}

View File

@@ -14,6 +14,7 @@ namespace LibationAvalonia
{
static class Program
{
[STAThread]
static void Main(string[] args)
{

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