diff --git a/Source/LibationAvalonia/App.axaml b/Source/LibationAvalonia/App.axaml index 13a64b07..9edde65f 100644 --- a/Source/LibationAvalonia/App.axaml +++ b/Source/LibationAvalonia/App.axaml @@ -20,6 +20,13 @@ + + + + 0 + + + @@ -28,17 +35,11 @@ - - - - - - @@ -47,29 +48,26 @@ - - - - - - - - + - - + - + + + + + + @@ -106,7 +104,7 @@ - + diff --git a/Source/LibationAvalonia/App.axaml.cs b/Source/LibationAvalonia/App.axaml.cs index 55e2a137..3201b953 100644 --- a/Source/LibationAvalonia/App.axaml.cs +++ b/Source/LibationAvalonia/App.axaml.cs @@ -2,7 +2,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; using LibationAvalonia.Dialogs; @@ -13,19 +12,22 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Avalonia.Threading; +using Dinah.Core; +using LibationAvalonia.Themes; +using Avalonia.Data.Core.Plugins; +using System.Linq; +#nullable enable namespace LibationAvalonia { public class App : Application { - public static MainWindow MainWindow { get; private set; } - public static IBrush ProcessQueueBookFailedBrush { get; private set; } - public static IBrush ProcessQueueBookCompletedBrush { get; private set; } - public static IBrush ProcessQueueBookCancelledBrush { get; private set; } - public static IBrush ProcessQueueBookDefaultBrush { get; private set; } - public static IBrush SeriesEntryGridBackgroundBrush { get; private set; } + public static Task>? LibraryTask { get; set; } + public static ChardonnayTheme? DefaultThemeColors { get; private set; } + public static MainWindow? MainWindow { get; private set; } + public static Uri AssetUriBase { get; } = new("avares://Libation/Assets/"); + public static new Application Current => Application.Current ?? throw new InvalidOperationException("The Avalonia app hasn't started yet."); - public static readonly Uri AssetUriBase = new("avares://Libation/Assets/"); public static Stream OpenAsset(string assetRelativePath) => AssetLoader.Open(new Uri(AssetUriBase, assetRelativePath)); @@ -34,12 +36,16 @@ namespace LibationAvalonia AvaloniaXamlLoader.Load(this); } - public static Task> LibraryTask; - public override void OnFrameworkInitializationCompleted() { + DefaultThemeColors = ChardonnayTheme.GetLiveTheme(); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + // Avoid duplicate validations from both Avalonia and the CommunityToolkit. + // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins + DisableAvaloniaDataAnnotationValidation(); + var config = Configuration.Instance; if (!config.LibationSettingsAreValid) @@ -69,11 +75,23 @@ namespace LibationAvalonia base.OnFrameworkInitializationCompleted(); } - - private async void Setup_Closing(object sender, System.ComponentModel.CancelEventArgs e) + private void DisableAvaloniaDataAnnotationValidation() { - var setupDialog = sender as SetupDialog; - var desktop = ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; + // Get an array of plugins to remove + var dataValidationPluginsToRemove = + BindingPlugins.DataValidators.OfType().ToArray(); + + // remove each entry found + foreach (var plugin in dataValidationPluginsToRemove) + { + BindingPlugins.DataValidators.Remove(plugin); + } + } + + private async void Setup_Closing(object? sender, System.ComponentModel.CancelEventArgs e) + { + if (sender is not SetupDialog setupDialog || ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) + return; try { @@ -87,7 +105,7 @@ namespace LibationAvalonia if (setupDialog.Config.LibationSettingsAreValid) { - string theme = setupDialog.SelectedTheme.Content as string; + string? theme = setupDialog.SelectedTheme.Content as string; setupDialog.Config.SetString(theme, nameof(ThemeVariant)); @@ -143,7 +161,7 @@ namespace LibationAvalonia desktop.MainWindow = libationFilesDialog; libationFilesDialog.Show(); - void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e) + void WindowClosing(object? sender, System.ComponentModel.CancelEventArgs e) { libationFilesDialog.Closing -= WindowClosing; e.Cancel = true; @@ -201,16 +219,9 @@ namespace LibationAvalonia private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop) { - Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) switch - { - nameof(ThemeVariant.Dark) => ThemeVariant.Dark, - nameof(ThemeVariant.Light) => ThemeVariant.Light, - // "System" - _ => ThemeVariant.Default - }; + Configuration.Instance.PropertyChanged += ThemeVariant_PropertyChanged; + OpenAndApplyTheme(Configuration.Instance.GetString(propertyName: nameof(ThemeVariant))); - //Reload colors for current theme - LoadStyles(); var mainWindow = new MainWindow(); desktop.MainWindow = MainWindow = mainWindow; mainWindow.Loaded += MainWindow_Loaded; @@ -218,19 +229,23 @@ namespace LibationAvalonia mainWindow.Show(); } - private static async void MainWindow_Loaded(object sender, Avalonia.Interactivity.RoutedEventArgs e) + [PropertyChangeFilter(nameof(ThemeVariant))] + private static void ThemeVariant_PropertyChanged(object sender, PropertyChangedEventArgsEx e) + => OpenAndApplyTheme(e.NewValue as string); + + private static void OpenAndApplyTheme(string? themeVariant) { - var library = await LibraryTask; - await Dispatcher.UIThread.InvokeAsync(() => MainWindow.OnLibraryLoadedAsync(library)); + using var themePersister = ChardonnayThemePersister.Create(); + themePersister?.Target.ApplyTheme(themeVariant); } - private static void LoadStyles() + private static async void MainWindow_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookFailedBrush)); - ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookCompletedBrush)); - ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookCancelledBrush)); - SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources(nameof(SeriesEntryGridBackgroundBrush)); - ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources(nameof(ProcessQueueBookDefaultBrush)); + if (LibraryTask is not null && MainWindow is not null) + { + var library = await LibraryTask; + await Dispatcher.UIThread.InvokeAsync(() => MainWindow.OnLibraryLoadedAsync(library)); + } } } } diff --git a/Source/LibationAvalonia/Assets/DataGridColumnHeader.xaml b/Source/LibationAvalonia/Assets/DataGridColumnHeader.xaml deleted file mode 100644 index 8c82dc2d..00000000 --- a/Source/LibationAvalonia/Assets/DataGridColumnHeader.xaml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/LibationAvalonia/AvaloniaUtils.cs b/Source/LibationAvalonia/AvaloniaUtils.cs index 714491bf..11508b70 100644 --- a/Source/LibationAvalonia/AvaloniaUtils.cs +++ b/Source/LibationAvalonia/AvaloniaUtils.cs @@ -1,8 +1,8 @@ -using Avalonia.Controls; -using Avalonia.Media; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Media.Imaging; using Avalonia.VisualTree; -using LibationAvalonia.Dialogs; using LibationFileManager; using System.Threading.Tasks; @@ -11,17 +11,21 @@ namespace LibationAvalonia { internal static class AvaloniaUtils { - public static IBrush GetBrushFromResources(string name) - => GetBrushFromResources(name, Brushes.Transparent); - public static IBrush GetBrushFromResources(string name, IBrush defaultBrush) + public static T DynamicResource(this T control, AvaloniaProperty prop, object resourceKey) where T : Control { - if ((App.Current?.TryGetResource(name, App.Current.ActualThemeVariant, out var value) ?? false) && value is IBrush brush) - return brush; - return defaultBrush; + control[!prop] = new DynamicResourceExtension(resourceKey); + return control; } - public static Task ShowDialogAsync(this DialogWindow dialogWindow, Window? owner = null) - => dialogWindow.ShowDialog(owner ?? App.MainWindow); + public static Task ShowDialogAsync(this Dialogs.DialogWindow dialogWindow, Window? owner = null) + => ((owner ?? App.MainWindow) is Window window) + ? dialogWindow.ShowDialog(window) + : Task.FromResult(DialogResult.None); + + public static Task ShowDialogAsync(this Dialogs.Login.WebLoginDialog dialogWindow, Window? owner = null) + => ((owner ?? App.MainWindow) is Window window) + ? dialogWindow.ShowDialog(window) + : Task.FromResult(DialogResult.None); public static Window? GetParentWindow(this Control control) => control.GetVisualRoot() as Window; diff --git a/Source/LibationAvalonia/Controls/GroupBox.axaml b/Source/LibationAvalonia/Controls/GroupBox.axaml index da3b0e32..c73a52bf 100644 --- a/Source/LibationAvalonia/Controls/GroupBox.axaml +++ b/Source/LibationAvalonia/Controls/GroupBox.axaml @@ -28,7 +28,7 @@ diff --git a/Source/LibationAvalonia/Controls/Settings/Important.axaml b/Source/LibationAvalonia/Controls/Settings/Important.axaml index 6653b82a..7745a507 100644 --- a/Source/LibationAvalonia/Controls/Settings/Important.axaml +++ b/Source/LibationAvalonia/Controls/Settings/Important.axaml @@ -158,15 +158,24 @@ + Text="Theme:"/> + diff --git a/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs b/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs index f7ccc4d0..a1e43395 100644 --- a/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs +++ b/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs @@ -1,4 +1,5 @@ using ApplicationServices; +using Avalonia; using Avalonia.Controls; using DataLayer; using LibationAvalonia.ViewModels; @@ -12,6 +13,16 @@ namespace LibationAvalonia.Views { public static event QueueItemPositionButtonClicked PositionButtonClicked; public static event QueueItemCancelButtonClicked CancelButtonClicked; + + public static readonly StyledProperty ProcessBookStatusProperty = + AvaloniaProperty.Register(nameof(ProcessBookStatus), enableDataValidation: true); + + public ProcessBookStatus ProcessBookStatus + { + get => GetValue(ProcessBookStatusProperty); + set => SetValue(ProcessBookStatusProperty, value); + } + public ProcessBookControl() { InitializeComponent(); @@ -23,6 +34,7 @@ namespace LibationAvalonia.Views context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"), LogMe.RegisterForm(default(ILogForm)) ); + return; } } diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml index 8c3f3815..3691b2e4 100644 --- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml @@ -35,7 +35,7 @@ Process Queue - + Queue Log - + diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs index 0b7761d8..de001556 100644 --- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs @@ -9,13 +9,15 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; +#nullable enable namespace LibationAvalonia.Views { public partial class ProcessQueueControl : UserControl { - private TrackedQueue Queue => _viewModel.Queue; - private ProcessQueueViewModel _viewModel => DataContext as ProcessQueueViewModel; + private TrackedQueue? Queue => _viewModel?.Queue; + private ProcessQueueViewModel? _viewModel => DataContext as ProcessQueueViewModel; public ProcessQueueControl() { @@ -25,6 +27,7 @@ namespace LibationAvalonia.Views ProcessBookControl.CancelButtonClicked += ProcessBookControl2_CancelButtonClicked; #region Design Mode Testing +#if DEBUG if (Design.IsDesignMode) { var vm = new ProcessQueueViewModel(); @@ -85,6 +88,7 @@ namespace LibationAvalonia.Views vm.Queue.MoveNext(); return; } +#endif #endregion } @@ -98,53 +102,59 @@ namespace LibationAvalonia.Views private async void ProcessBookControl2_CancelButtonClicked(ProcessBookViewModel item) { if (item is not null) + { await item.CancelAsync(); - Queue.RemoveQueued(item); + Queue?.RemoveQueued(item); + } } private void ProcessBookControl2_ButtonClicked(ProcessBookViewModel item, QueuePosition queueButton) { - Queue.MoveQueuePosition(item, queueButton); + Queue?.MoveQueuePosition(item, queueButton); } public async void CancelAllBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - Queue.ClearQueue(); - if (Queue.Current is not null) + Queue?.ClearQueue(); + if (Queue?.Current is not null) await Queue.Current.CancelAsync(); } public void ClearFinishedBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - Queue.ClearCompleted(); + Queue?.ClearCompleted(); - if (!_viewModel.Running) + if (_viewModel?.Running is false) _viewModel.RunningTime = string.Empty; } public void ClearLogBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - _viewModel.LogEntries.Clear(); + _viewModel?.LogEntries.Clear(); } private async void LogCopyBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - string logText = string.Join("\r\n", _viewModel.LogEntries.Select(r => $"{r.LogDate.ToShortDateString()} {r.LogDate.ToShortTimeString()}\t{r.LogMessage}")); - await App.MainWindow.Clipboard.SetTextAsync(logText); + if (_viewModel is ProcessQueueViewModel vm) + { + string logText = string.Join("\r\n", vm.LogEntries.Select(r => $"{r.LogDate.ToShortDateString()} {r.LogDate.ToShortTimeString()}\t{r.LogMessage}")); + if (App.MainWindow?.Clipboard?.SetTextAsync(logText) is Task setter) + await setter; + } } private async void cancelAllBtn_Click(object sender, EventArgs e) { - Queue.ClearQueue(); - if (Queue.Current is not null) + Queue?.ClearQueue(); + if (Queue?.Current is not null) await Queue.Current.CancelAsync(); } private void btnClearFinished_Click(object sender, EventArgs e) { - Queue.ClearCompleted(); + Queue?.ClearCompleted(); - if (!_viewModel.Running) + if (_viewModel?.Running is false) _viewModel.RunningTime = string.Empty; } @@ -155,7 +165,7 @@ namespace LibationAvalonia.Views { public static readonly DecimalConverter Instance = new(); - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is string sourceText && targetType.IsAssignableTo(typeof(decimal?))) { @@ -172,7 +182,7 @@ namespace LibationAvalonia.Views return 0; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is decimal val) { @@ -184,7 +194,7 @@ namespace LibationAvalonia.Views : val.ToString("F2") ) + " MB/s"; } - return value.ToString(); + return value?.ToString(); } } } diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index fd90a1c1..90517420 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -18,6 +18,7 @@ ItemsSource="{Binding GridEntries}" CanUserSortColumns="True" BorderThickness="3" CanUserResizeColumns="True" + LoadingRow="ProductsDisplay_LoadingRow" CanUserReorderColumns="True"> @@ -93,7 +94,7 @@ - + @@ -103,7 +104,7 @@ - + @@ -113,7 +114,7 @@ - + @@ -123,7 +124,7 @@ - + @@ -133,7 +134,7 @@ - + @@ -143,7 +144,7 @@ - + @@ -153,7 +154,7 @@ - + @@ -163,7 +164,7 @@ - + @@ -177,14 +178,13 @@ MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}" SortMemberPath="ProductRating" CanUserSort="True" OpacityBinding="{CompiledBinding Liberate.Opacity}" - BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" ClipboardContentBinding="{CompiledBinding ProductRating}" Binding="{CompiledBinding ProductRating}" /> - + @@ -198,14 +198,13 @@ MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}" SortMemberPath="MyRating" CanUserSort="True" OpacityBinding="{CompiledBinding Liberate.Opacity}" - BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" ClipboardContentBinding="{CompiledBinding MyRating}" Binding="{CompiledBinding MyRating, Mode=TwoWay}" /> - + @@ -215,7 +214,7 @@ - + diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 6f6948ab..7097aa56 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -1,6 +1,8 @@ using ApplicationServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Input.Platform; +using Avalonia.Media; using Avalonia.Platform.Storage; using Avalonia.Styling; using DataLayer; @@ -17,16 +19,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +#nullable enable namespace LibationAvalonia.Views { public partial class ProductsDisplay : UserControl { - public event EventHandler LiberateClicked; - public event EventHandler LiberateSeriesClicked; - public event EventHandler ConvertToMp3Clicked; + public event EventHandler? LiberateClicked; + public event EventHandler? LiberateSeriesClicked; + public event EventHandler? ConvertToMp3Clicked; - private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel; - ImageDisplayDialog imageDisplayDialog; + private ProductsDisplayViewModel? _viewModel => DataContext as ProductsDisplayViewModel; + ImageDisplayDialog? imageDisplayDialog; public ProductsDisplay() { @@ -52,6 +55,8 @@ namespace LibationAvalonia.Views Configuration.Instance.PropertyChanged += Configuration_GridScaleChanged; Configuration.Instance.PropertyChanged += Configuration_FontChanged; + #region Design Mode Testing +#if DEBUG if (Design.IsDesignMode) { using var context = DbContexts.GetContext(); @@ -80,6 +85,8 @@ namespace LibationAvalonia.Views setFontScale(1); return; } +#endif + #endregion setGridScale(Configuration.Instance.GridScaleFactor); setFontScale(Configuration.Instance.GridFontScaleFactor); @@ -91,6 +98,14 @@ namespace LibationAvalonia.Views } } + private void ProductsDisplay_LoadingRow(object sender, DataGridRowEventArgs e) + { + if (e.Row.DataContext is LibraryBookEntry entry && entry.Liberate.IsEpisode) + e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SeriesEntryGridBackgroundBrush"); + else + e.Row.Background = Brushes.Transparent; + } + private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) { if (sender is DataGridColumn col && e.Property == DataGridColumn.IsVisibleProperty) @@ -105,13 +120,15 @@ namespace LibationAvalonia.Views [PropertyChangeFilter(nameof(Configuration.GridScaleFactor))] private void Configuration_GridScaleChanged(object sender, Dinah.Core.PropertyChangedEventArgsEx e) { - setGridScale((float)e.NewValue); + if (e.NewValue is float value) + setGridScale(value); } [PropertyChangeFilter(nameof(Configuration.GridFontScaleFactor))] private void Configuration_FontChanged(object sender, Dinah.Core.PropertyChangedEventArgsEx e) { - setFontScale((float)e.NewValue); + if (e.NewValue is float value) + setFontScale(value); } private readonly Style rowHeightStyle; @@ -171,17 +188,18 @@ namespace LibationAvalonia.Views #region Cell Context Menu - public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args) + public void ProductsGrid_CellContextMenuStripNeeded(object? sender, DataGridCellContextMenuStripNeededEventArgs args) { var entry = args.GridEntry; var ctx = new GridContextMenu(entry, '_'); - if (args.Column.SortMemberPath is not "Liberate" and not "Cover") + if (args.Column.SortMemberPath is not "Liberate" and not "Cover" + && App.MainWindow?.Clipboard is IClipboard clipboard) { args.ContextMenuItems.Add(new MenuItem { Header = ctx.CopyCellText, - Command = ReactiveCommand.CreateFromTask(() => App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents)) + Command = ReactiveCommand.CreateFromTask(() => clipboard?.SetTextAsync(args.CellClipboardContents) ?? Task.CompletedTask) }); args.ContextMenuItems.Add(new Separator()); } @@ -240,13 +258,14 @@ namespace LibationAvalonia.Views { try { - var window = this.GetParentWindow(); + if (this.GetParentWindow() is not Window window) + return; var openFileDialogOptions = new FilePickerOpenOptions { Title = ctx.LocateFileDialogTitle, AllowMultiple = false, - SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix), + SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books?.PathWithoutPrefix!), FileTypeFilter = new FilePickerFileType[] { new("All files (*.*)") { Patterns = new[] { "*" } }, @@ -303,7 +322,7 @@ namespace LibationAvalonia.Views { var template = ctx.CreateTemplateEditor(libraryBook, existingTemplate); var form = new EditTemplateDialog(template); - if (await form.ShowDialog(this.GetParentWindow()) == DialogResult.OK) + if (this.GetParentWindow() is Window window && await form.ShowDialog(window) == DialogResult.OK) { setNewTemplate(template.EditingTemplate.TemplateText); } @@ -340,12 +359,12 @@ namespace LibationAvalonia.Views #region View Bookmarks/Clips - if (!entry.Liberate.IsSeries) + if (!entry.Liberate.IsSeries && VisualRoot is Window window) { args.ContextMenuItems.Add(new MenuItem { Header = ctx.ViewBookmarksText, - Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window)) + Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(window)) }); } @@ -389,6 +408,9 @@ namespace LibationAvalonia.Views var HeaderCell_PI = typeof(DataGridColumn).GetProperty("HeaderCell", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (HeaderCell_PI is null) + return; + foreach (var column in productsGrid.Columns) { var itemName = column.SortMemberPath; @@ -406,8 +428,9 @@ namespace LibationAvalonia.Views } ); - var headercell = HeaderCell_PI.GetValue(column) as DataGridColumnHeader; - headercell.ContextMenu = contextMenu; + var headerCell = HeaderCell_PI.GetValue(column) as DataGridColumnHeader; + if (headerCell is not null) + headerCell.ContextMenu = contextMenu; column.IsVisible = gridColumnsVisibilities.GetValueOrDefault(itemName, true); } @@ -425,30 +448,30 @@ namespace LibationAvalonia.Views } } - private void ContextMenu_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e) + private void ContextMenu_ContextMenuOpening(object? sender, System.ComponentModel.CancelEventArgs e) { - var contextMenu = sender as ContextMenu; + if (sender is not ContextMenu contextMenu) + return; foreach (var mi in contextMenu.Items.OfType()) { - if (mi.Tag is DataGridColumn column) + if (mi.Tag is DataGridColumn column && mi.Icon is CheckBox cbox) { - var cbox = mi.Icon as CheckBox; cbox.IsChecked = column.IsVisible; } } } - private void ContextMenu_MenuClosed(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void ContextMenu_MenuClosed(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - var contextMenu = sender as ContextMenu; + if (sender is not ContextMenu contextMenu) + return; var config = Configuration.Instance; var dictionary = config.GridColumnsVisibilities; foreach (var mi in contextMenu.Items.OfType()) { - if (mi.Tag is DataGridColumn column) + if (mi.Tag is DataGridColumn column && mi.Icon is CheckBox cbox) { - var cbox = mi.Icon as CheckBox; column.IsVisible = cbox.IsChecked == true; dictionary[column.SortMemberPath] = cbox.IsChecked == true; } @@ -463,7 +486,7 @@ namespace LibationAvalonia.Views config.GridColumnsVisibilities = dictionary; } - private void ProductsGrid_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e) + private void ProductsGrid_ColumnDisplayIndexChanged(object? sender, DataGridColumnEventArgs e) { var config = Configuration.Instance; @@ -478,9 +501,10 @@ namespace LibationAvalonia.Views public async void LiberateButton_Click(object sender, EventArgs e) { - var button = sender as LiberateStatusButton; + if (sender is not LiberateStatusButton button) + return; - if (button.DataContext is ISeriesEntry sEntry) + if (button.DataContext is ISeriesEntry sEntry && _viewModel is not null) { await _viewModel.ToggleSeriesExpanded(sEntry); @@ -518,7 +542,7 @@ namespace LibationAvalonia.Views var picDef = new PictureDefinition(gEntry.LibraryBook.Book.PictureLarge ?? gEntry.LibraryBook.Book.PictureId, PictureSize.Native); - void PictureCached(object sender, PictureCachedEventArgs e) + void PictureCached(object? sender, PictureCachedEventArgs e) { if (e.Definition.PictureId == picDef.PictureId) imageDisplayDialog.SetCoverBytes(e.Picture); @@ -558,7 +582,7 @@ namespace LibationAvalonia.Views DescriptionText = gEntry.Description, }; - void CloseWindow(object o, DataGridRowEventArgs e) + void CloseWindow(object? o, DataGridRowEventArgs e) { displayWindow.Close(); } @@ -572,13 +596,13 @@ namespace LibationAvalonia.Views } } - BookDetailsDialog bookDetailsForm; + BookDetailsDialog? bookDetailsForm; public void OnTagsButtonClick(object sender, Avalonia.Interactivity.RoutedEventArgs args) { var button = args.Source as Button; - if (button.DataContext is ILibraryBookEntry lbEntry && VisualRoot is Window window) + if (button?.DataContext is ILibraryBookEntry lbEntry && VisualRoot is Window window) { if (bookDetailsForm is null || !bookDetailsForm.IsVisible) { diff --git a/Source/LibationUiBase/GridView/EntryStatus.cs b/Source/LibationUiBase/GridView/EntryStatus.cs index 871a4e6c..1e8bfc0d 100644 --- a/Source/LibationUiBase/GridView/EntryStatus.cs +++ b/Source/LibationUiBase/GridView/EntryStatus.cs @@ -61,7 +61,6 @@ namespace LibationUiBase.GridView || PdfStatus is not null and not LiberatedStatus.Liberated ); public double Opacity => !IsSeries && Book.UserDefinedItem.Tags.ContainsInsensitive("hidden") ? 0.4 : 1; - public abstract object BackgroundBrush { get; } public object ButtonImage => GetLiberateIcon(); public string ToolTip => GetTooltip(); private Book Book { get; } diff --git a/Source/LibationWinForms/GridView/WinFormsEntryStatus.cs b/Source/LibationWinForms/GridView/WinFormsEntryStatus.cs index 0a24361a..48ab8901 100644 --- a/Source/LibationWinForms/GridView/WinFormsEntryStatus.cs +++ b/Source/LibationWinForms/GridView/WinFormsEntryStatus.cs @@ -7,7 +7,7 @@ namespace LibationWinForms.GridView public class WinFormsEntryStatus : EntryStatus, IEntryStatus { private static readonly Color SERIES_BG_COLOR = Color.FromArgb(230, 255, 230); - public override object BackgroundBrush => IsEpisode ? SERIES_BG_COLOR : SystemColors.ControlLightLight; + public Color BackgroundBrush => IsEpisode ? SERIES_BG_COLOR : SystemColors.ControlLightLight; private WinFormsEntryStatus(LibraryBook libraryBook) : base(libraryBook) { } public static EntryStatus Create(LibraryBook libraryBook) => new WinFormsEntryStatus(libraryBook);