diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 00000000..e1e3db3d --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -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 diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 3c0d1401..b9b7cf37 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -15,6 +15,10 @@ on: description: 'Skip running unit tests' required: false default: true + outputs: + version: + description: "The Libation version number" + value: ${{ jobs.build.outputs.version }} env: DOTNET_CONFIGURATION: 'Release' @@ -23,6 +27,8 @@ env: jobs: build: runs-on: ubuntu-latest + outputs: + version: ${{ steps.get_version.outputs.version }} strategy: matrix: os: [Linux, MacOS] @@ -66,6 +72,8 @@ jobs: id: zip working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} 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:]')" artifact="Libation.${{ steps.get_version.outputs.version }}-${osbuild}-${{ matrix.release_name }}" echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 8739bb6f..cfac74ec 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -15,6 +15,10 @@ on: description: 'Skip running unit tests' required: false default: true + outputs: + version: + description: "The Libation version number" + value: ${{ jobs.build.outputs.version }} env: DOTNET_CONFIGURATION: 'Release' @@ -23,6 +27,8 @@ env: jobs: build: runs-on: windows-latest + outputs: + version: ${{ steps.get_version.outputs.version }} strategy: matrix: os: [Windows] @@ -48,8 +54,7 @@ jobs: if ("${{ inputs.version_override }}".length -gt 0) { $version = "${{ inputs.version_override }}" } else { - [xml]$appScaffolding = Get-Content -Path ./Source/AppScaffolding/AppScaffolding.csproj - $version = $appScaffolding.Project.PropertyGroup.Version + $version = (Select-Xml -Path "./Source/AppScaffolding/AppScaffolding.csproj" -XPath "/Project/PropertyGroup/Version").Node.InnerXML.Trim() } "version=$version" >> $env:GITHUB_OUTPUT @@ -69,10 +74,13 @@ jobs: - name: Zip artifact id: zip 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=$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 uses: actions/upload-artifact@v3 @@ -80,3 +88,4 @@ jobs: name: ${{ steps.zip.outputs.artifact }}.zip path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip if-no-files-found: error + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b62f1b1..47e89f80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,3 +28,10 @@ jobs: with: version_override: ${{ inputs.version_override }} run_unit_tests: ${{ inputs.run_unit_tests }} + + linux_deb: + needs: [linux] + uses: ./.github/workflows/build-deb.yml + with: + version: ${{ needs.linux.outputs.version }} + diff --git a/Source/targz2deb.sh b/.github/workflows/scripts/targz2deb.sh similarity index 99% rename from Source/targz2deb.sh rename to .github/workflows/scripts/targz2deb.sh index 67c675ee..4143aef8 100644 --- a/Source/targz2deb.sh +++ b/.github/workflows/scripts/targz2deb.sh @@ -116,7 +116,6 @@ Version: $VERSION Architecture: all Essential: no Priority: optional -Depends: ffmpeg Maintainer: github.com/rmcrackan Description: liberate your audiobooks " >> "$FOLDER_DEBIAN/control" diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index 5724667b..4236ee60 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -13,7 +13,7 @@ - + diff --git a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs index dc4eeb66..c42fc4d4 100644 --- a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs +++ b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs @@ -32,7 +32,7 @@ namespace AaxDecrypter 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); //Finishing configuring lame encoder. diff --git a/Source/DataLayer/EntityExtensions.cs b/Source/DataLayer/EntityExtensions.cs index ef18a232..acf0b866 100644 --- a/Source/DataLayer/EntityExtensions.cs +++ b/Source/DataLayer/EntityExtensions.cs @@ -93,10 +93,12 @@ namespace DataLayer var starString = new string(STAR, fullStars); - if (score - fullStars >= 0.25f) - starString += HALF; + if (score - fullStars >= 0.75f) + starString += STAR; + else if (score - fullStars >= 0.25f) + starString += HALF; - return starString; + return starString; } } } diff --git a/Source/DtoImporterService/BookImporter.cs b/Source/DtoImporterService/BookImporter.cs index 6b884673..d9de490c 100644 --- a/Source/DtoImporterService/BookImporter.cs +++ b/Source/DtoImporterService/BookImporter.cs @@ -174,7 +174,10 @@ namespace DtoImporterService if (item.PictureLarge is not null) 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 book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story); diff --git a/Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs b/Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs index 3fefa38c..b563883e 100644 --- a/Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs +++ b/Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs @@ -2,14 +2,24 @@ using Avalonia.Controls; using Avalonia.Interactivity; using DataLayer; -using LibationAvalonia.ViewModels; using ReactiveUI; using System; 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 { + [Avalonia.Data.AssignBinding] + public Avalonia.Data.IBinding BackgroundBinding { get; set; } private static Rating DefaultRating => new Rating(0, 0, 0); public DataGridMyRatingColumn() { @@ -24,29 +34,20 @@ namespace LibationAvalonia.Controls IsEditingMode = false }; - ToolTip.SetTip(myRatingElement, "Click to change ratings"); cell?.AttachContextMenu(); + if (!IsReadOnly) + ToolTip.SetTip(myRatingElement, "Click to change ratings"); + if (Binding != null) { myRatingElement.Bind(BindingTarget, Binding); } - - void setControlBackground(object dataContext) + if (BackgroundBinding != null) { - if (dataContext is GridEntry ge) - myRatingElement.Background = ge.BackgroundBrush; + myRatingElement.Bind(MyRatingCellEditor.BackgroundProperty, BackgroundBinding); } - setControlBackground(cell?.DataContext); - - var subscriber = - cell - ?.ObservableForProperty(g => g.DataContext) - ?.Subscribe(ctx => setControlBackground(ctx?.Value)); - - myRatingElement.DetachedFromVisualTree += (_, _) => subscriber?.Dispose(); - return myRatingElement; } @@ -57,6 +58,10 @@ namespace LibationAvalonia.Controls Name = "CellMyRatingEditor", IsEditingMode = true }; + if (BackgroundBinding != null) + { + myRatingElement.Bind(MyRatingCellEditor.BackgroundProperty, BackgroundBinding); + } return myRatingElement; } diff --git a/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml b/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml index aa2e50f6..c477ba16 100644 --- a/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml +++ b/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml @@ -17,7 +17,7 @@ - + diff --git a/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs b/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs index 3c7e889f..c5100f14 100644 --- a/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs +++ b/Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls; using DataLayer; +using ReactiveUI; +using System; using System.Linq; namespace LibationAvalonia.Controls @@ -9,6 +11,7 @@ namespace LibationAvalonia.Controls { private const string SOLID_STAR = "★"; private const string HOLLOW_STAR = "☆"; + private const string HALF_STAR = "½"; public static readonly StyledProperty RatingProperty = AvaloniaProperty.Register(nameof(Rating)); @@ -19,39 +22,41 @@ namespace LibationAvalonia.Controls public MyRatingCellEditor() { InitializeComponent(); + + var subscriber = this.ObservableForProperty(p => p.Rating).Subscribe(o => DisplayStarRating(o.Value ?? new Rating(0, 0, 0))); + Unloaded += (_, _) => subscriber.Dispose(); + if (Design.IsDesignMode) 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; - foreach (TextBlock star in panelOverall.Children) - star.Tag = star.Text = Rating.OverallRating > rating++ ? SOLID_STAR : blankValue; + 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; - rating = 0; - foreach (TextBlock star in panelPerform.Children) - star.Tag = star.Text = Rating.PerformanceRating > rating++ ? SOLID_STAR : blankValue; + int starIndex = 0; + foreach (TextBlock star in panelOverall.Children) + star.Tag = star.Text = getStar(rating.OverallRating, starIndex++); - rating = 0; - foreach (TextBlock star in panelStory.Children) - star.Tag = star.Text = Rating.StoryRating > rating++ ? SOLID_STAR : blankValue; + starIndex = 0; + foreach (TextBlock star in panelPerform.Children) + star.Tag = star.Text = getStar(rating.PerformanceRating, starIndex++); - SetVisible(); - } - base.OnPropertyChanged(change); - } + starIndex = 0; + foreach (TextBlock star in panelStory.Children) + star.Tag = star.Text = getStar(rating.StoryRating, starIndex++); - private void SetVisible() - { ratingsGrid.IsEnabled = IsEditingMode; - tblockOverall.IsVisible = panelOverall.IsVisible = IsEditingMode || Rating?.OverallRating > 0; - tblockPerform.IsVisible = panelPerform.IsVisible = IsEditingMode || Rating?.PerformanceRating > 0; - tblockStory.IsVisible = panelStory.IsVisible = IsEditingMode || Rating?.StoryRating > 0; + tblockOverall.IsVisible = panelOverall.IsVisible = IsEditingMode || rating.OverallRating > 0; + tblockPerform.IsVisible = panelPerform.IsVisible = IsEditingMode || rating.PerformanceRating > 0; + tblockStory.IsVisible = panelStory.IsVisible = IsEditingMode || rating.StoryRating > 0; } public void Panel_PointerExited(object sender, Avalonia.Input.PointerEventArgs e) diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs index 9dfc6e3f..41967e0d 100644 --- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs @@ -120,7 +120,7 @@ namespace LibationAvalonia.Dialogs SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)), 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, FileTypeChoices = new FilePickerFileType[] { - new("JSON files (*.json)") { Patterns = new[] { "json" } }, + new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, } }; diff --git a/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml.cs index 33a039f9..6a3b7213 100644 --- a/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml.cs @@ -27,7 +27,7 @@ namespace LibationAvalonia.Dialogs userEditTbox = this.FindControl(nameof(userEditTbox)); if (Design.IsDesignMode) { - AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); + _ = Configuration.Instance.LibationFiles; _viewModel = new(Configuration.Instance, Templates.File); _viewModel.resetTextBox(_viewModel.Template.DefaultTemplate); Title = $"Edit {_viewModel.Template.Name}"; diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs index 0af59ebd..468be111 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs @@ -22,7 +22,7 @@ namespace LibationAvalonia.Dialogs public SettingsDialog() { if (Design.IsDesignMode) - AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); + _ = Configuration.Instance.LibationFiles; InitializeComponent(); DataContext = settingsDisp = new(config); diff --git a/Source/LibationAvalonia/ViewModels/GridEntry.cs b/Source/LibationAvalonia/ViewModels/GridEntry.cs index 4219699e..b20f12cd 100644 --- a/Source/LibationAvalonia/ViewModels/GridEntry.cs +++ b/Source/LibationAvalonia/ViewModels/GridEntry.cs @@ -44,8 +44,7 @@ namespace LibationAvalonia.ViewModels public string Category { get; protected set; } public string Misc { get; protected set; } public string Description { get; protected set; } - public string ProductRating { get; protected set; } - public string MyRatingString => MyRating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + public Rating ProductRating { get; protected set; } protected Rating _myRating; public Rating MyRating { @@ -68,7 +67,6 @@ namespace LibationAvalonia.ViewModels public abstract bool IsSeries { get; } public abstract bool IsEpisode { get; } public abstract bool IsBook { get; } - public abstract double Opacity { get; } public IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : Brushes.Transparent; #endregion diff --git a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs index c79f4cfd..e00c9461 100644 --- a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs +++ b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs @@ -53,7 +53,6 @@ namespace LibationAvalonia.ViewModels public override bool IsSeries => false; public override bool IsEpisode => Parent is not null; public override bool IsBook => Parent is null; - public override double Opacity => Book.UserDefinedItem.Tags.ToLower().Contains("hidden") ? 0.4 : 1; #endregion @@ -69,7 +68,7 @@ namespace LibationAvalonia.ViewModels //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); PurchaseDate = libraryBook.DateAdded.ToString("d"); - ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + ProductRating = Book.Rating ?? new Rating(0, 0, 0); Authors = Book.AuthorNames(); Narrators = Book.NarratorNames(); Category = string.Join(" > ", Book.CategoriesNames()); @@ -102,7 +101,6 @@ namespace LibationAvalonia.ViewModels case nameof(udi.Tags): Book.UserDefinedItem.Tags = udi.Tags; this.RaisePropertyChanged(nameof(BookTags)); - this.RaisePropertyChanged(nameof(Opacity)); break; case nameof(udi.BookStatus): Book.UserDefinedItem.BookStatus = udi.BookStatus; diff --git a/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs index 2c483c3e..05b92134 100644 --- a/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs @@ -31,7 +31,7 @@ namespace LibationAvalonia.ViewModels Queue.CompletedCountChanged += Queue_CompletedCountChanged; if (Design.IsDesignMode) - AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); + _ = Configuration.Instance.LibationFiles; SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024; } diff --git a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs index c0ca2965..9f2d9749 100644 --- a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs +++ b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs @@ -49,7 +49,6 @@ namespace LibationAvalonia.ViewModels public override bool IsSeries => true; public override bool IsEpisode => false; public override bool IsBook => false; - public override double Opacity => 1; #endregion @@ -71,7 +70,7 @@ namespace LibationAvalonia.ViewModels //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. _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(); Narrators = Book.NarratorNames(); Category = string.Join(" > ", Book.CategoriesNames()); diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index 28967546..87dfb645 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -19,10 +19,12 @@ CanUserReorderColumns="True"> - + + + + + - + @@ -83,7 +89,7 @@ - + @@ -93,7 +99,7 @@ - + @@ -103,7 +109,7 @@ - + @@ -113,7 +119,7 @@ - + @@ -123,7 +129,7 @@ - + @@ -133,39 +139,45 @@ - + - - - - - - - - - + - + - + - + @@ -187,6 +199,5 @@ - diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 4d1854b8..7d3a3bc3 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -273,13 +273,13 @@ namespace LibationAvalonia.Views #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; 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 //to the topright cell. Reset focus onto the clicked button's cell. diff --git a/Source/LibationFileManager/PropertyChangeFilter.cs b/Source/LibationFileManager/PropertyChangeFilter.cs index c30bfa0f..5fe0d6e4 100644 --- a/Source/LibationFileManager/PropertyChangeFilter.cs +++ b/Source/LibationFileManager/PropertyChangeFilter.cs @@ -79,23 +79,36 @@ namespace LibationFileManager { private readonly Dictionary> propertyChangedActions = new(); private readonly Dictionary> propertyChangingActions = new(); - + private readonly List<(PropertyChangedEventHandlerEx subscriber, PropertyChangedEventHandlerEx wrapper)> changedFilters = new(); private readonly List<(PropertyChangingEventHandlerEx subscriber, PropertyChangingEventHandlerEx wrapper)> changingFilters = new(); - public PropertyChangeFilter() + protected void OnPropertyChanged(string propertyName, object newValue) { - PropertyChanging += Configuration_PropertyChanging; - PropertyChanged += Configuration_PropertyChanged; + if (propertyChangedActions.ContainsKey(propertyName)) + { + //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 - 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 PropertyChangingEventHandlerEx _propertyChanging; @@ -255,28 +268,6 @@ namespace LibationFileManager 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 List _observers; diff --git a/Source/LibationWinForms/Dialogs/UpgradeNotificationDialog.Designer.cs b/Source/LibationWinForms/Dialogs/UpgradeNotificationDialog.Designer.cs index 581e0b9e..f1d8ca56 100644 --- a/Source/LibationWinForms/Dialogs/UpgradeNotificationDialog.Designer.cs +++ b/Source/LibationWinForms/Dialogs/UpgradeNotificationDialog.Designer.cs @@ -208,11 +208,7 @@ #endregion - private System.Windows.Forms.Label label1; 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.Button dontRemindBtn; private System.Windows.Forms.Button yesBtn;