Add theme preview dialog

This commit is contained in:
Michael Bucari-Tovo 2025-03-19 16:26:14 -06:00
parent 9043ea6334
commit 733a091ebd
6 changed files with 227 additions and 69 deletions

View File

@ -0,0 +1,52 @@
<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="650"
xmlns:views="clr-namespace:LibationAvalonia.Views"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
x:Class="LibationAvalonia.Controls.ThemePreviewControl">
<Grid RowDefinitions="Auto,Auto,*">
<controls:GroupBox>
<WrapPanel>
<RadioButton Margin="5,0" Content="This is an option" IsChecked="True" />
<RadioButton Margin="5,0" Content="This is another option" />
<CheckBox Margin="5,0" Content="This is a check box" />
<controls:WheelComboBox
Margin="5,0"
ItemsSource="{Binding ComboBoxItems}"
SelectedIndex="{Binding ComboBoxSelectedIndex}" />
<TextBox Margin="5,0" Text="This is an editable text box" />
<TextBox Margin="5,0" Text="This is a read-only text box" IsReadOnly="True" />
<NumericUpDown Margin="5,0" Value="100" />
<controls:LinkLabel VerticalAlignment="Center" Margin="5,0" Text="This is an unvisited link" />
<controls:LinkLabel VerticalAlignment="Center" Margin="5,0" Text="This is a visited link" Foreground="{DynamicResource HyperlinkVisited}" />
<StackPanel Margin="5,0" Height="15" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Path">
<Setter Property="Stretch" Value="Uniform" />
<Setter Property="Margin" Value="3,0" />
<Setter Property="Fill" Value="{DynamicResource IconFill}" />
</Style>
</StackPanel.Styles>
<Path Data="{StaticResource QueuedIcon}" />
<Path Data="{StaticResource QueueCompletedIcon}" />
<Path Data="{StaticResource QueueErrorIcon}"/>
</StackPanel>
</WrapPanel>
</controls:GroupBox>
<WrapPanel Orientation="Horizontal" Grid.Row="1">
<views:ProcessBookControl DataContext="{Binding QueuedBook}" ProcessBookStatus="{Binding Status}" />
<views:ProcessBookControl DataContext="{Binding WorkingBook}" ProcessBookStatus="{Binding Status}" />
<views:ProcessBookControl DataContext="{Binding CompletedBook}" ProcessBookStatus="{Binding Status}" />
<views:ProcessBookControl DataContext="{Binding CancelledBook}" ProcessBookStatus="{Binding Status}" />
<views:ProcessBookControl DataContext="{Binding FailedBook}" ProcessBookStatus="{Binding Status}" />
</WrapPanel>
<views:ProductsDisplay
Grid.Row="2"
DataContext="{Binding ProductsDisplay}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,94 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using DataLayer;
using Dinah.Core.ErrorHandling;
using LibationAvalonia.ViewModels;
using LibationFileManager;
using NPOI.Util.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace LibationAvalonia.Controls;
public partial class ThemePreviewControl : UserControl
{
public ProductsDisplayViewModel ProductsDisplay { get; set; }
public string[] ComboBoxItems { get; } = Enumerable.Range(1, 9).Select(n => $"Combo box item {n}").ToArray();
public int ComboBoxSelectedIndex { get; set; }
public ProcessBookViewModel QueuedBook { get; }
public ProcessBookViewModel WorkingBook { get; }
public ProcessBookViewModel CompletedBook { get; }
public ProcessBookViewModel CancelledBook { get; }
public ProcessBookViewModel FailedBook { get; }
public ThemePreviewControl()
{
InitializeComponent();
List<LibraryBook> sampleEntries;
sampleEntries = CreateMockBooks().ToList();
if (Design.IsDesignMode)
{
using var ms1 = new MemoryStream();
App.OpenAsset("img-coverart-prod-unavailable_80x80.jpg").CopyTo(ms1);
PictureStorage.SetDefaultImage(PictureSize._80x80, ms1.ToArray());
}
QueuedBook = new ProcessBookViewModel(sampleEntries[0], null) { Status = ProcessBookStatus.Queued };
WorkingBook = new ProcessBookViewModel(sampleEntries[0], null) { Status = ProcessBookStatus.Working };
CompletedBook = new ProcessBookViewModel(sampleEntries[0], null) { Status = ProcessBookStatus.Completed };
CancelledBook = new ProcessBookViewModel(sampleEntries[0], null) { Status = ProcessBookStatus.Cancelled };
FailedBook = new ProcessBookViewModel(sampleEntries[0], null) { Status = ProcessBookStatus.Failed };
//Set the current processable so that the empty queue doesn't try to advance.
QueuedBook.AddDownloadPdf();
WorkingBook.AddDownloadPdf();
typeof(ProcessBookViewModel).GetProperty(nameof(ProcessBookViewModel.Progress)).SetValue(WorkingBook, 50);
ProductsDisplay = new ProductsDisplayViewModel();
_ = ProductsDisplay.BindToGridAsync(sampleEntries);
DataContext = this;
}
private IEnumerable<LibraryBook> CreateMockBooks()
{
var author = new Contributor("Some Author", "asin_contributor");
var narrator = new Contributor("Some Narrator", "asin_narrator");
var book1 = new Book(new AudibleProductId("asin_book1"), "Some Book 1", "The Theming", "Demo Book Entry", 525600, ContentType.Product, [author], [narrator], "us");
var book2 = new Book(new AudibleProductId("asin_book2"), "Some Book 2", "The Theming", "Demo Book Entry", 525600, ContentType.Product, [author], [narrator], "us");
var book3 = new Book(new AudibleProductId("asin_book3"), "Some Book 3", "The Theming", "Demo Book Entry", 525600, ContentType.Product, [author], [narrator], "us");
var book4 = new Book(new AudibleProductId("asin_book4"), "Some Book 4", "The Theming", "Demo Book Entry", 525600, ContentType.Product, [author], [narrator], "us");
var seriesParent = new Book(new AudibleProductId("asin_series"), "Some Series", "", "Demo Series Entry", 0, ContentType.Parent, [author], [narrator], "us");
var episode = new Book(new AudibleProductId("asin_episode"), "Some Episode", "Episode 1", "Demo Episode Entry", 56, ContentType.Episode, [author], [narrator], "us");
var series = new Series(new AudibleSeriesId(seriesParent.AudibleProductId), seriesParent.Title);
seriesParent.UpsertSeries(series, "");
episode.UpsertSeries(series, "1");
book1.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
book4.UserDefinedItem.BookStatus = LiberatedStatus.Error;
//Set the backing field directly to preserve LiberatedStatus.PartialDownload
typeof(UserDefinedItem).GetField("_bookStatus", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(book2.UserDefinedItem, LiberatedStatus.PartialDownload);
yield return new LibraryBook(book1, System.DateTime.Now.AddDays(4), "someone@email.co");
yield return new LibraryBook(book2, System.DateTime.Now.AddDays(3), "someone@email.co");
yield return new LibraryBook(book3, System.DateTime.Now.AddDays(2), "someone@email.co") { AbsentFromLastScan = true };
yield return new LibraryBook(book4, System.DateTime.Now.AddDays(1), "someone@email.co");
yield return new LibraryBook(seriesParent, System.DateTime.Now, "someone@email.co");
yield return new LibraryBook(episode, System.DateTime.Now, "someone@email.co");
}
private class MockProcessable : FileLiberator.Processable
{
public override string Name => nameof(MockProcessable);
public override Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) => Task.FromResult(new StatusHandler());
public override bool Validate(LibraryBook libraryBook) => false;
}
}

View File

@ -2,69 +2,74 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="850"
Width="450" Height="450" Width="450" Height="450"
x:Class="LibationAvalonia.Dialogs.ThemePickerDialog" x:Class="LibationAvalonia.Dialogs.ThemePickerDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
Title="Theme Editor"> Title="Theme Editor">
<Grid
RowDefinitions="*,Auto"> <Grid ColumnDefinitions="Auto,*">
<DataGrid <controls:ThemePreviewControl Name="ThemePickerPreviewControl" Margin="5" Grid.Column="1" />
GridLinesVisibility="All"
Margin="5"
IsReadOnly="False"
ItemsSource="{Binding ThemeColors}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Color">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ColorPicker
IsHexInputVisible="True"
Color="{Binding ThemeColor, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding ThemeItemName, Mode=TwoWay}"
Header="Theme Item"/>
</DataGrid.Columns>
</DataGrid>
<Grid <Grid
Grid.Row="1" RowDefinitions="*,Auto">
ColumnDefinitions="Auto,Auto,Auto,*,Auto"> <DataGrid
GridLinesVisibility="All"
<Grid.Styles> Margin="5"
<Style Selector="Button"> IsReadOnly="False"
<Setter Property="Height" Value="30" /> ItemsSource="{Binding ThemeColors}">
<Setter Property="Padding" Value="20,0" /> <DataGrid.Columns>
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Styles>
<Button
Grid.Column="0"
Content="Cancel"
Command="{Binding CancelAndClose}" />
<Button
Grid.Column="1"
Content="Reset"
Command="{Binding ResetColors}" />
<Button
Grid.Column="2"
Content="Defaults"
Command="{Binding LoadDefaultColors}" />
<Button
Grid.Column="4"
Content="Save"
Command="{Binding SaveAndCloseAsync}" />
<DataGridTemplateColumn Width="Auto" Header="Color">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ColorPicker
IsHexInputVisible="True"
Color="{Binding ThemeColor, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="Auto"
Binding="{Binding ThemeItemName, Mode=TwoWay}"
Header="Theme Item"/>
</DataGrid.Columns>
</DataGrid>
<Grid
Grid.Row="1"
ColumnDefinitions="Auto,Auto,Auto,*,Auto">
<Grid.Styles>
<Style Selector="Button">
<Setter Property="Height" Value="30" />
<Setter Property="Padding" Value="20,0" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Styles>
<Button
Grid.Column="0"
Content="Cancel"
Command="{Binding CancelAndClose}" />
<Button
Grid.Column="1"
Content="Reset"
Command="{Binding ResetColors}" />
<Button
Grid.Column="2"
Content="Defaults"
Command="{Binding LoadDefaultColors}" />
<Button
Grid.Column="4"
Content="Save"
Command="{Binding SaveAndCloseAsync}" />
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</Window> </Window>

View File

@ -221,6 +221,7 @@
Name="productsDisplay" Name="productsDisplay"
DataContext="{CompiledBinding ProductsDisplay}" DataContext="{CompiledBinding ProductsDisplay}"
LiberateClicked="ProductsDisplay_LiberateClicked" LiberateClicked="ProductsDisplay_LiberateClicked"
TagsButtonClicked="ProductsDisplay_TagsButtonClicked"
LiberateSeriesClicked="ProductsDisplay_LiberateSeriesClicked" LiberateSeriesClicked="ProductsDisplay_LiberateSeriesClicked"
ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" /> ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" />
</SplitView> </SplitView>

View File

@ -4,6 +4,7 @@ using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using DataLayer; using DataLayer;
using FileManager; using FileManager;
using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels; using LibationAvalonia.ViewModels;
using LibationFileManager; using LibationFileManager;
using LibationUiBase.GridView; using LibationUiBase.GridView;
@ -137,6 +138,18 @@ namespace LibationAvalonia.Views
public void ProductsDisplay_LiberateSeriesClicked(object _, ISeriesEntry series) => ViewModel.LiberateSeriesClicked(series); public void ProductsDisplay_LiberateSeriesClicked(object _, ISeriesEntry series) => ViewModel.LiberateSeriesClicked(series);
public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook); public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook);
BookDetailsDialog bookDetailsForm;
public void ProductsDisplay_TagsButtonClicked(object _, LibraryBook libraryBook)
{
if (bookDetailsForm is null || !bookDetailsForm.IsVisible)
{
bookDetailsForm = new BookDetailsDialog(libraryBook);
bookDetailsForm.Show(this);
}
else
bookDetailsForm.LibraryBook = libraryBook;
}
public async void filterSearchTb_KeyPress(object _, KeyEventArgs e) public async void filterSearchTb_KeyPress(object _, KeyEventArgs e)
{ {
if (e.Key == Key.Return) if (e.Key == Key.Return)

View File

@ -27,6 +27,7 @@ namespace LibationAvalonia.Views
public event EventHandler<LibraryBook>? LiberateClicked; public event EventHandler<LibraryBook>? LiberateClicked;
public event EventHandler<ISeriesEntry>? LiberateSeriesClicked; public event EventHandler<ISeriesEntry>? LiberateSeriesClicked;
public event EventHandler<LibraryBook>? ConvertToMp3Clicked; public event EventHandler<LibraryBook>? ConvertToMp3Clicked;
public event EventHandler<LibraryBook>? TagsButtonClicked;
private ProductsDisplayViewModel? _viewModel => DataContext as ProductsDisplayViewModel; private ProductsDisplayViewModel? _viewModel => DataContext as ProductsDisplayViewModel;
ImageDisplayDialog? imageDisplayDialog; ImageDisplayDialog? imageDisplayDialog;
@ -103,7 +104,7 @@ namespace LibationAvalonia.Views
if (e.Row.DataContext is LibraryBookEntry<AvaloniaEntryStatus> entry && entry.Liberate.IsEpisode) if (e.Row.DataContext is LibraryBookEntry<AvaloniaEntryStatus> entry && entry.Liberate.IsEpisode)
e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SeriesEntryGridBackgroundBrush"); e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SeriesEntryGridBackgroundBrush");
else else
e.Row.Background = Brushes.Transparent; e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SystemRegionColor");
} }
private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
@ -596,21 +597,13 @@ namespace LibationAvalonia.Views
} }
} }
BookDetailsDialog? bookDetailsForm;
public void OnTagsButtonClick(object sender, Avalonia.Interactivity.RoutedEventArgs args) public void OnTagsButtonClick(object sender, Avalonia.Interactivity.RoutedEventArgs args)
{ {
var button = args.Source as Button; var button = args.Source as Button;
if (button?.DataContext is ILibraryBookEntry lbEntry && VisualRoot is Window window) if (button?.DataContext is ILibraryBookEntry lbEntry)
{ {
if (bookDetailsForm is null || !bookDetailsForm.IsVisible) TagsButtonClicked?.Invoke(this, lbEntry.LibraryBook);
{
bookDetailsForm = new BookDetailsDialog(lbEntry.LibraryBook);
bookDetailsForm.Show(window);
}
else
bookDetailsForm.LibraryBook = lbEntry.LibraryBook;
} }
} }