Merge pull request #444 from Mbucari/master

Build and attach deb package
This commit is contained in:
rmcrackan 2023-01-12 08:45:14 -05:00 committed by GitHub
commit 89c3ea8311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 191 additions and 121 deletions

39
.github/workflows/build-deb.yml vendored Normal file
View File

@ -0,0 +1,39 @@
# build-deb.yml
# Reusable workflow that builds the Linux Debian package.
---
name: build_deb
on:
workflow_call:
inputs:
version:
type: string
description: 'Version number'
required: true
env:
FILE_NAME: "Libation.${{ inputs.version }}-linux-chardonnay"
jobs:
build_deb:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download Artifact
uses: actions/download-artifact@master
with:
name: "${{ env.FILE_NAME }}.tar.gz"
- name: Build .deb
id: deb
run: |
chmod +x ./.github/workflows/scripts/targz2deb.sh
./.github/workflows/scripts/targz2deb.sh "${{ env.FILE_NAME }}.tar.gz" ${{ inputs.version }}
- name: Publish .deb
uses: actions/upload-artifact@v3
with:
name: ${{ env.FILE_NAME }}.deb
path: ${{ env.FILE_NAME }}.deb
if-no-files-found: error

View File

@ -15,6 +15,10 @@ on:
description: 'Skip running unit tests' description: 'Skip running unit tests'
required: false required: false
default: true default: true
outputs:
version:
description: "The Libation version number"
value: ${{ jobs.build.outputs.version }}
env: env:
DOTNET_CONFIGURATION: 'Release' DOTNET_CONFIGURATION: 'Release'
@ -23,6 +27,8 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
strategy: strategy:
matrix: matrix:
os: [Linux, MacOS] os: [Linux, MacOS]
@ -66,6 +72,8 @@ jobs:
id: zip id: zip
working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.release_name }}
run: | run: |
delfiles=("libmp3lame.x86.dll" "libmp3lame.x64.dll" "ffmpegaac.x86.dll" "ffmpegaac.x64.dll" "ZipExtractor.exe")
for n in "${delfiles[@]}"; do rm "$n"; done
osbuild="$(echo '${{ matrix.os }}' | tr '[:upper:]' '[:lower:]')" osbuild="$(echo '${{ matrix.os }}' | tr '[:upper:]' '[:lower:]')"
artifact="Libation.${{ steps.get_version.outputs.version }}-${osbuild}-${{ matrix.release_name }}" artifact="Libation.${{ steps.get_version.outputs.version }}-${osbuild}-${{ matrix.release_name }}"
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"

View File

@ -15,6 +15,10 @@ on:
description: 'Skip running unit tests' description: 'Skip running unit tests'
required: false required: false
default: true default: true
outputs:
version:
description: "The Libation version number"
value: ${{ jobs.build.outputs.version }}
env: env:
DOTNET_CONFIGURATION: 'Release' DOTNET_CONFIGURATION: 'Release'
@ -23,6 +27,8 @@ env:
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
strategy: strategy:
matrix: matrix:
os: [Windows] os: [Windows]
@ -48,8 +54,7 @@ jobs:
if ("${{ inputs.version_override }}".length -gt 0) { if ("${{ inputs.version_override }}".length -gt 0) {
$version = "${{ inputs.version_override }}" $version = "${{ inputs.version_override }}"
} else { } else {
[xml]$appScaffolding = Get-Content -Path ./Source/AppScaffolding/AppScaffolding.csproj $version = (Select-Xml -Path "./Source/AppScaffolding/AppScaffolding.csproj" -XPath "/Project/PropertyGroup/Version").Node.InnerXML.Trim()
$version = $appScaffolding.Project.PropertyGroup.Version
} }
"version=$version" >> $env:GITHUB_OUTPUT "version=$version" >> $env:GITHUB_OUTPUT
@ -70,9 +75,12 @@ jobs:
id: zip id: zip
working-directory: ./Source/bin/Publish working-directory: ./Source/bin/Publish
run: | run: |
$dir = "${{ matrix.os }}-${{ matrix.release_name }}\"
$delfiles = @("libmp3lame.so", "ffmpegaac.so", "glass-with-glow_256.svg", "Libation.desktop")
foreach ($file in $delfiles){ if (test-path $dir$file){ Remove-Item $dir$file } }
$artifact="${{ matrix.prefix }}Libation.${{ steps.get_version.outputs.version }}-" + "${{ matrix.os }}".ToLower() + "-${{ matrix.release_name }}" $artifact="${{ matrix.prefix }}Libation.${{ steps.get_version.outputs.version }}-" + "${{ matrix.os }}".ToLower() + "-${{ matrix.release_name }}"
"artifact=$artifact" >> $env:GITHUB_OUTPUT "artifact=$artifact" >> $env:GITHUB_OUTPUT
Compress-Archive -Path "${{ matrix.os }}-${{ matrix.release_name }}\*" -DestinationPath "$artifact.zip" Compress-Archive -Path "${dir}*" -DestinationPath "$artifact.zip"
- name: Publish artifact - name: Publish artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -80,3 +88,4 @@ jobs:
name: ${{ steps.zip.outputs.artifact }}.zip name: ${{ steps.zip.outputs.artifact }}.zip
path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip
if-no-files-found: error if-no-files-found: error

View File

@ -28,3 +28,10 @@ jobs:
with: with:
version_override: ${{ inputs.version_override }} version_override: ${{ inputs.version_override }}
run_unit_tests: ${{ inputs.run_unit_tests }} run_unit_tests: ${{ inputs.run_unit_tests }}
linux_deb:
needs: [linux]
uses: ./.github/workflows/build-deb.yml
with:
version: ${{ needs.linux.outputs.version }}

View File

@ -116,7 +116,6 @@ Version: $VERSION
Architecture: all Architecture: all
Essential: no Essential: no
Priority: optional Priority: optional
Depends: ffmpeg
Maintainer: github.com/rmcrackan Maintainer: github.com/rmcrackan
Description: liberate your audiobooks Description: liberate your audiobooks
" >> "$FOLDER_DEBIAN/control" " >> "$FOLDER_DEBIAN/control"

View File

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

View File

@ -32,7 +32,7 @@ namespace AaxDecrypter
AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", ""); AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", "");
} }
if (DownloadOptions.FixupFile) if (DownloadOptions.FixupFile && !string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator))
AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator); AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator);
//Finishing configuring lame encoder. //Finishing configuring lame encoder.

View File

@ -93,7 +93,9 @@ namespace DataLayer
var starString = new string(STAR, fullStars); var starString = new string(STAR, fullStars);
if (score - fullStars >= 0.25f) if (score - fullStars >= 0.75f)
starString += STAR;
else if (score - fullStars >= 0.25f)
starString += HALF; starString += HALF;
return starString; return starString;

View File

@ -174,7 +174,10 @@ namespace DtoImporterService
if (item.PictureLarge is not null) if (item.PictureLarge is not null)
book.PictureLarge = item.PictureLarge; book.PictureLarge = item.PictureLarge;
book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars); book.UpdateProductRating(
(float)(item.Rating?.OverallDistribution?.AverageRating ?? 0),
(float)(item.Rating?.PerformanceDistribution?.AverageRating ?? 0),
(float)(item.Rating?.StoryDistribution?.AverageRating ?? 0));
// important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import // important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import
book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story); book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story);

View File

@ -2,14 +2,24 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using DataLayer; using DataLayer;
using LibationAvalonia.ViewModels;
using ReactiveUI; using ReactiveUI;
using System; using System;
namespace LibationAvalonia.Controls namespace LibationAvalonia.Controls
{ {
public class StarStringConverter : Avalonia.Data.Converters.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
=> value is Rating rating ? rating.ToStarString() : string.Empty;
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
=> throw new NotImplementedException();
}
public class DataGridMyRatingColumn : DataGridBoundColumn public class DataGridMyRatingColumn : DataGridBoundColumn
{ {
[Avalonia.Data.AssignBinding]
public Avalonia.Data.IBinding BackgroundBinding { get; set; }
private static Rating DefaultRating => new Rating(0, 0, 0); private static Rating DefaultRating => new Rating(0, 0, 0);
public DataGridMyRatingColumn() public DataGridMyRatingColumn()
{ {
@ -24,29 +34,20 @@ namespace LibationAvalonia.Controls
IsEditingMode = false IsEditingMode = false
}; };
ToolTip.SetTip(myRatingElement, "Click to change ratings");
cell?.AttachContextMenu(); cell?.AttachContextMenu();
if (!IsReadOnly)
ToolTip.SetTip(myRatingElement, "Click to change ratings");
if (Binding != null) if (Binding != null)
{ {
myRatingElement.Bind(BindingTarget, Binding); myRatingElement.Bind(BindingTarget, Binding);
} }
if (BackgroundBinding != null)
void setControlBackground(object dataContext)
{ {
if (dataContext is GridEntry ge) myRatingElement.Bind(MyRatingCellEditor.BackgroundProperty, BackgroundBinding);
myRatingElement.Background = ge.BackgroundBrush;
} }
setControlBackground(cell?.DataContext);
var subscriber =
cell
?.ObservableForProperty(g => g.DataContext)
?.Subscribe(ctx => setControlBackground(ctx?.Value));
myRatingElement.DetachedFromVisualTree += (_, _) => subscriber?.Dispose();
return myRatingElement; return myRatingElement;
} }
@ -57,6 +58,10 @@ namespace LibationAvalonia.Controls
Name = "CellMyRatingEditor", Name = "CellMyRatingEditor",
IsEditingMode = true IsEditingMode = true
}; };
if (BackgroundBinding != null)
{
myRatingElement.Bind(MyRatingCellEditor.BackgroundProperty, BackgroundBinding);
}
return myRatingElement; return myRatingElement;
} }

View File

@ -1,6 +1,8 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using DataLayer; using DataLayer;
using ReactiveUI;
using System;
using System.Linq; using System.Linq;
namespace LibationAvalonia.Controls namespace LibationAvalonia.Controls
@ -9,6 +11,7 @@ namespace LibationAvalonia.Controls
{ {
private const string SOLID_STAR = "★"; private const string SOLID_STAR = "★";
private const string HOLLOW_STAR = "☆"; private const string HOLLOW_STAR = "☆";
private const string HALF_STAR = "½";
public static readonly StyledProperty<Rating> RatingProperty = public static readonly StyledProperty<Rating> RatingProperty =
AvaloniaProperty.Register<MyRatingCellEditor, Rating>(nameof(Rating)); AvaloniaProperty.Register<MyRatingCellEditor, Rating>(nameof(Rating));
@ -19,39 +22,41 @@ namespace LibationAvalonia.Controls
public MyRatingCellEditor() public MyRatingCellEditor()
{ {
InitializeComponent(); InitializeComponent();
var subscriber = this.ObservableForProperty(p => p.Rating).Subscribe(o => DisplayStarRating(o.Value ?? new Rating(0, 0, 0)));
Unloaded += (_, _) => subscriber.Dispose();
if (Design.IsDesignMode) if (Design.IsDesignMode)
Rating = new Rating(5, 4, 3); Rating = new Rating(5, 4, 3);
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) private void DisplayStarRating(Rating rating)
{
if (change.Property.Name == nameof(Rating) && Rating is not null)
{ {
var blankValue = IsEditingMode ? HOLLOW_STAR : string.Empty; var blankValue = IsEditingMode ? HOLLOW_STAR : string.Empty;
int rating = 0; string getStar(float score, int starIndex)
=> Math.Floor(score) > starIndex ? SOLID_STAR
: score < starIndex ? blankValue
: score - starIndex < 0.25 ? blankValue
: score - starIndex > 0.75 ? SOLID_STAR
: HALF_STAR;
int starIndex = 0;
foreach (TextBlock star in panelOverall.Children) foreach (TextBlock star in panelOverall.Children)
star.Tag = star.Text = Rating.OverallRating > rating++ ? SOLID_STAR : blankValue; star.Tag = star.Text = getStar(rating.OverallRating, starIndex++);
rating = 0; starIndex = 0;
foreach (TextBlock star in panelPerform.Children) foreach (TextBlock star in panelPerform.Children)
star.Tag = star.Text = Rating.PerformanceRating > rating++ ? SOLID_STAR : blankValue; star.Tag = star.Text = getStar(rating.PerformanceRating, starIndex++);
rating = 0; starIndex = 0;
foreach (TextBlock star in panelStory.Children) foreach (TextBlock star in panelStory.Children)
star.Tag = star.Text = Rating.StoryRating > rating++ ? SOLID_STAR : blankValue; star.Tag = star.Text = getStar(rating.StoryRating, starIndex++);
SetVisible();
}
base.OnPropertyChanged(change);
}
private void SetVisible()
{
ratingsGrid.IsEnabled = IsEditingMode; ratingsGrid.IsEnabled = IsEditingMode;
tblockOverall.IsVisible = panelOverall.IsVisible = IsEditingMode || Rating?.OverallRating > 0; tblockOverall.IsVisible = panelOverall.IsVisible = IsEditingMode || rating.OverallRating > 0;
tblockPerform.IsVisible = panelPerform.IsVisible = IsEditingMode || Rating?.PerformanceRating > 0; tblockPerform.IsVisible = panelPerform.IsVisible = IsEditingMode || rating.PerformanceRating > 0;
tblockStory.IsVisible = panelStory.IsVisible = IsEditingMode || Rating?.StoryRating > 0; tblockStory.IsVisible = panelStory.IsVisible = IsEditingMode || rating.StoryRating > 0;
} }
public void Panel_PointerExited(object sender, Avalonia.Input.PointerEventArgs e) public void Panel_PointerExited(object sender, Avalonia.Input.PointerEventArgs e)

View File

@ -120,7 +120,7 @@ namespace LibationAvalonia.Dialogs
SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)), SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)),
FileTypeFilter = new FilePickerFileType[] FileTypeFilter = new FilePickerFileType[]
{ {
new("JSON files (*.json)") { Patterns = new[] { "json" } }, new("JSON files (*.json)") { Patterns = new[] { "*.json" } },
} }
}; };
@ -280,7 +280,7 @@ namespace LibationAvalonia.Dialogs
ShowOverwritePrompt = true, ShowOverwritePrompt = true,
FileTypeChoices = new FilePickerFileType[] FileTypeChoices = new FilePickerFileType[]
{ {
new("JSON files (*.json)") { Patterns = new[] { "json" } }, new("JSON files (*.json)") { Patterns = new[] { "*.json" } },
} }
}; };

View File

@ -27,7 +27,7 @@ namespace LibationAvalonia.Dialogs
userEditTbox = this.FindControl<TextBox>(nameof(userEditTbox)); userEditTbox = this.FindControl<TextBox>(nameof(userEditTbox));
if (Design.IsDesignMode) if (Design.IsDesignMode)
{ {
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); _ = Configuration.Instance.LibationFiles;
_viewModel = new(Configuration.Instance, Templates.File); _viewModel = new(Configuration.Instance, Templates.File);
_viewModel.resetTextBox(_viewModel.Template.DefaultTemplate); _viewModel.resetTextBox(_viewModel.Template.DefaultTemplate);
Title = $"Edit {_viewModel.Template.Name}"; Title = $"Edit {_viewModel.Template.Name}";

View File

@ -22,7 +22,7 @@ namespace LibationAvalonia.Dialogs
public SettingsDialog() public SettingsDialog()
{ {
if (Design.IsDesignMode) if (Design.IsDesignMode)
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); _ = Configuration.Instance.LibationFiles;
InitializeComponent(); InitializeComponent();
DataContext = settingsDisp = new(config); DataContext = settingsDisp = new(config);

View File

@ -44,8 +44,7 @@ namespace LibationAvalonia.ViewModels
public string Category { get; protected set; } public string Category { get; protected set; }
public string Misc { get; protected set; } public string Misc { get; protected set; }
public string Description { get; protected set; } public string Description { get; protected set; }
public string ProductRating { get; protected set; } public Rating ProductRating { get; protected set; }
public string MyRatingString => MyRating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
protected Rating _myRating; protected Rating _myRating;
public Rating MyRating public Rating MyRating
{ {
@ -68,7 +67,6 @@ namespace LibationAvalonia.ViewModels
public abstract bool IsSeries { get; } public abstract bool IsSeries { get; }
public abstract bool IsEpisode { get; } public abstract bool IsEpisode { get; }
public abstract bool IsBook { get; } public abstract bool IsBook { get; }
public abstract double Opacity { get; }
public IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : Brushes.Transparent; public IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : Brushes.Transparent;
#endregion #endregion

View File

@ -53,7 +53,6 @@ namespace LibationAvalonia.ViewModels
public override bool IsSeries => false; public override bool IsSeries => false;
public override bool IsEpisode => Parent is not null; public override bool IsEpisode => Parent is not null;
public override bool IsBook => Parent is null; public override bool IsBook => Parent is null;
public override double Opacity => Book.UserDefinedItem.Tags.ToLower().Contains("hidden") ? 0.4 : 1;
#endregion #endregion
@ -69,7 +68,7 @@ namespace LibationAvalonia.ViewModels
//the reference doesn't change. Clone the rating so that it updates within Avalonia properly. //the reference doesn't change. Clone the rating so that it updates within Avalonia properly.
_myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating); _myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating);
PurchaseDate = libraryBook.DateAdded.ToString("d"); PurchaseDate = libraryBook.DateAdded.ToString("d");
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); ProductRating = Book.Rating ?? new Rating(0, 0, 0);
Authors = Book.AuthorNames(); Authors = Book.AuthorNames();
Narrators = Book.NarratorNames(); Narrators = Book.NarratorNames();
Category = string.Join(" > ", Book.CategoriesNames()); Category = string.Join(" > ", Book.CategoriesNames());
@ -102,7 +101,6 @@ namespace LibationAvalonia.ViewModels
case nameof(udi.Tags): case nameof(udi.Tags):
Book.UserDefinedItem.Tags = udi.Tags; Book.UserDefinedItem.Tags = udi.Tags;
this.RaisePropertyChanged(nameof(BookTags)); this.RaisePropertyChanged(nameof(BookTags));
this.RaisePropertyChanged(nameof(Opacity));
break; break;
case nameof(udi.BookStatus): case nameof(udi.BookStatus):
Book.UserDefinedItem.BookStatus = udi.BookStatus; Book.UserDefinedItem.BookStatus = udi.BookStatus;

View File

@ -31,7 +31,7 @@ namespace LibationAvalonia.ViewModels
Queue.CompletedCountChanged += Queue_CompletedCountChanged; Queue.CompletedCountChanged += Queue_CompletedCountChanged;
if (Design.IsDesignMode) if (Design.IsDesignMode)
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); _ = Configuration.Instance.LibationFiles;
SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024; SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
} }

View File

@ -49,7 +49,6 @@ namespace LibationAvalonia.ViewModels
public override bool IsSeries => true; public override bool IsSeries => true;
public override bool IsEpisode => false; public override bool IsEpisode => false;
public override bool IsBook => false; public override bool IsBook => false;
public override double Opacity => 1;
#endregion #endregion
@ -71,7 +70,7 @@ namespace LibationAvalonia.ViewModels
//Ratings are changed using Update(), which is a problem for Avalonia data bindings because //Ratings are changed using Update(), which is a problem for Avalonia data bindings because
//the reference doesn't change. Clone the rating so that it updates within Avalonia properly. //the reference doesn't change. Clone the rating so that it updates within Avalonia properly.
_myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating); _myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating);
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); ProductRating = Book.Rating ?? new Rating(0, 0, 0);
Authors = Book.AuthorNames(); Authors = Book.AuthorNames();
Narrators = Book.NarratorNames(); Narrators = Book.NarratorNames();
Category = string.Join(" > ", Book.CategoriesNames()); Category = string.Join(" > ", Book.CategoriesNames());

View File

@ -19,10 +19,12 @@
CanUserReorderColumns="True"> CanUserReorderColumns="True">
<DataGrid.Styles> <DataGrid.Styles>
<Style Selector="DataGridCell > Panel"> <Style Selector="DataGridCell">
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Height" Value="80"/> <Setter Property="Height" Value="80"/>
</Style> </Style>
<Style Selector="DataGridCell > Panel">
<Setter Property="VerticalAlignment" Value="Stretch"/>
</Style>
<Style Selector="DataGridCell > Panel > TextBlock"> <Style Selector="DataGridCell > Panel > TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
@ -31,6 +33,10 @@
</Style> </Style>
</DataGrid.Styles> </DataGrid.Styles>
<DataGrid.Resources>
<controls:StarStringConverter x:Key="starStringConverter" />
</DataGrid.Resources>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn <DataGridTemplateColumn
@ -73,7 +79,7 @@
<controls:DataGridTemplateColumnExt MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}"> <controls:DataGridTemplateColumnExt MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Title}" /> <TextBlock Text="{Binding Title}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -83,7 +89,7 @@
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}"> <controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Authors}" /> <TextBlock Text="{Binding Authors}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -93,7 +99,7 @@
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}"> <controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Narrators}" /> <TextBlock Text="{Binding Narrators}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -103,7 +109,7 @@
<controls:DataGridTemplateColumnExt Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}"> <controls:DataGridTemplateColumnExt Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Length}" /> <TextBlock Text="{Binding Length}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -113,7 +119,7 @@
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Series}" /> <TextBlock Text="{Binding Series}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -123,7 +129,7 @@
<controls:DataGridTemplateColumnExt MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding LongDescription}"> <controls:DataGridTemplateColumnExt MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding LongDescription}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" > <Panel Background="{Binding BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
<TextBlock Text="{Binding Description}" FontSize="11" VerticalAlignment="Top" /> <TextBlock Text="{Binding Description}" FontSize="11" VerticalAlignment="Top" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -133,39 +139,45 @@
<controls:DataGridTemplateColumnExt Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}"> <controls:DataGridTemplateColumnExt Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Category}" /> <TextBlock Text="{Binding Category}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="115" Header="Product&#xA;Rating" CanUserSort="True" SortMemberPath="ProductRating" ClipboardContentBinding="{Binding ProductRating}"> <controls:DataGridMyRatingColumn
<DataGridTemplateColumn.CellTemplate> Header="Product&#xA;Rating"
<DataTemplate> IsReadOnly="true"
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> Width="115"
<TextBlock Text="{Binding ProductRating}" TextWrapping="NoWrap" FontSize="11" /> SortMemberPath="ProductRating" CanUserSort="True"
</Panel> BackgroundBinding="{Binding BackgroundBrush}"
</DataTemplate> ClipboardContentBinding="{Binding ProductRating, Converter={StaticResource starStringConverter}}"
</DataGridTemplateColumn.CellTemplate> Binding="{Binding ProductRating}" />
</controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="90" Header="Purchase&#xA;Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}"> <controls:DataGridTemplateColumnExt Width="90" Header="Purchase&#xA;Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding PurchaseDate}" /> <TextBlock Text="{Binding PurchaseDate}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridMyRatingColumn IsReadOnly="false" Width="115" Header="My Rating" CanUserSort="True" SortMemberPath="MyRating" ClipboardContentBinding="{Binding MyRatingString}" Binding="{Binding MyRating, Mode=TwoWay}" /> <controls:DataGridMyRatingColumn
Header="My Rating"
IsReadOnly="false"
Width="115"
SortMemberPath="MyRating" CanUserSort="True"
BackgroundBinding="{Binding BackgroundBrush}"
ClipboardContentBinding="{Binding MyRating, Converter={StaticResource starStringConverter}}"
Binding="{Binding MyRating, Mode=TwoWay}" />
<controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}"> <controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}"> <Panel Background="{Binding BackgroundBrush}">
<TextBlock Text="{Binding Misc}" TextWrapping="WrapWithOverflow" FontSize="10" /> <TextBlock Text="{Binding Misc}" TextWrapping="WrapWithOverflow" FontSize="10" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -187,6 +199,5 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -273,13 +273,13 @@ namespace LibationAvalonia.Views
#region Button Click Handlers #region Button Click Handlers
public void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) public async void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
{ {
var button = args.Source as Button; var button = args.Source as Button;
if (button.DataContext is SeriesEntry sEntry) if (button.DataContext is SeriesEntry sEntry)
{ {
_viewModel.ToggleSeriesExpanded(sEntry); await _viewModel.ToggleSeriesExpanded(sEntry);
//Expanding and collapsing reset the list, which will cause focus to shift //Expanding and collapsing reset the list, which will cause focus to shift
//to the topright cell. Reset focus onto the clicked button's cell. //to the topright cell. Reset focus onto the clicked button's cell.

View File

@ -83,19 +83,32 @@ namespace LibationFileManager
private readonly List<(PropertyChangedEventHandlerEx subscriber, PropertyChangedEventHandlerEx wrapper)> changedFilters = new(); private readonly List<(PropertyChangedEventHandlerEx subscriber, PropertyChangedEventHandlerEx wrapper)> changedFilters = new();
private readonly List<(PropertyChangingEventHandlerEx subscriber, PropertyChangingEventHandlerEx wrapper)> changingFilters = new(); private readonly List<(PropertyChangingEventHandlerEx subscriber, PropertyChangingEventHandlerEx wrapper)> changingFilters = new();
public PropertyChangeFilter() protected void OnPropertyChanged(string propertyName, object newValue)
{ {
PropertyChanging += Configuration_PropertyChanging; if (propertyChangedActions.ContainsKey(propertyName))
PropertyChanged += Configuration_PropertyChanged; {
//Invoke observables registered for propertyName
foreach (var action in propertyChangedActions[propertyName])
action.DynamicInvoke(newValue);
}
_propertyChanged?.Invoke(this, new(propertyName, newValue));
}
protected void OnPropertyChanging(string propertyName, object oldValue, object newValue)
{
if (propertyChangingActions.ContainsKey(propertyName))
{
//Invoke observables registered for propertyName
foreach (var action in propertyChangingActions[propertyName])
action.DynamicInvoke(oldValue, newValue);
}
_propertyChanging?.Invoke(this, new(propertyName, oldValue, newValue));
} }
#region Events #region Events
protected void OnPropertyChanged(string propertyName, object newValue)
=> _propertyChanged?.Invoke(this, new(propertyName, newValue));
protected void OnPropertyChanging(string propertyName, object oldValue, object newValue)
=> _propertyChanging?.Invoke(this, new(propertyName, oldValue, newValue));
private PropertyChangedEventHandlerEx _propertyChanged; private PropertyChangedEventHandlerEx _propertyChanged;
private PropertyChangingEventHandlerEx _propertyChanging; private PropertyChangingEventHandlerEx _propertyChanging;
@ -255,28 +268,6 @@ namespace LibationFileManager
throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}."); throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}.");
} }
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgsEx e)
{
if (propertyChangedActions.ContainsKey(e.PropertyName))
{
foreach (var action in propertyChangedActions[e.PropertyName])
{
action.DynamicInvoke(e.NewValue);
}
}
}
private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgsEx e)
{
if (propertyChangingActions.ContainsKey(e.PropertyName))
{
foreach (var action in propertyChangingActions[e.PropertyName])
{
action.DynamicInvoke(e.OldValue, e.NewValue);
}
}
}
private class Unsubscriber : IDisposable private class Unsubscriber : IDisposable
{ {
private List<Delegate> _observers; private List<Delegate> _observers;

View File

@ -208,11 +208,7 @@
#endregion #endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox releaseNotesTbox; private System.Windows.Forms.TextBox releaseNotesTbox;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.LinkLabel linkLabel3;
private System.Windows.Forms.LinkLabel linkLabel2;
private System.Windows.Forms.LinkLabel packageDlLink; private System.Windows.Forms.LinkLabel packageDlLink;
private System.Windows.Forms.Button dontRemindBtn; private System.Windows.Forms.Button dontRemindBtn;
private System.Windows.Forms.Button yesBtn; private System.Windows.Forms.Button yesBtn;