Get favourites working

This commit is contained in:
TheXamlGuy
2024-05-19 17:52:15 +01:00
parent 68cdbc5519
commit 5ded5897c1
22 changed files with 232 additions and 30 deletions
+1 -2
View File
@@ -2,8 +2,7 @@
x:Class="Bitvault.Avalonia.App" x:Class="Bitvault.Avalonia.App"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:ui="using:FluentAvalonia.UI.Controls">
RequestedThemeVariant="Default">
<Application.Styles> <Application.Styles>
<ThemeResources PreferSystemTheme="True" PreferUserAccentColor="True" /> <ThemeResources PreferSystemTheme="True" PreferUserAccentColor="True" />
<Style Selector="ui|SettingsExpanderItem"> <Style Selector="ui|SettingsExpanderItem">
+3 -1
View File
@@ -94,6 +94,7 @@ public partial class App : Application
services.AddTemplate<ItemCommandHeaderViewModel, ItemCommandHeaderView>("ItemCommandHeader"); services.AddTemplate<ItemCommandHeaderViewModel, ItemCommandHeaderView>("ItemCommandHeader");
services.AddTemplate<FavouriteItemActionViewModel, FavouriteItemActionView>();
services.AddTemplate<ConfirmItemActionViewModel, ConfirmItemActionView>(); services.AddTemplate<ConfirmItemActionViewModel, ConfirmItemActionView>();
services.AddTemplate<DismissItemActionViewModel, DismissItemActionView>(); services.AddTemplate<DismissItemActionViewModel, DismissItemActionView>();
services.AddTemplate<ArchiveItemActionViewModel, ArchiveItemActionView>(); services.AddTemplate<ArchiveItemActionViewModel, ArchiveItemActionView>();
@@ -108,7 +109,8 @@ public partial class App : Application
services.AddHandler<ConfirmItemHandler>(ServiceLifetime.Scoped); services.AddHandler<ConfirmItemHandler>(ServiceLifetime.Scoped);
services.AddHandler<ArchiveItemHandler>(ServiceLifetime.Scoped); services.AddHandler<ArchiveItemHandler>(ServiceLifetime.Scoped);
services.AddHandler<UnarchiveItemHandler>(ServiceLifetime.Scoped); services.AddHandler<UnarchiveItemHandler>(ServiceLifetime.Scoped);
services.AddHandler<FavouriteItemHandler>(ServiceLifetime.Scoped);
services.AddHandler<UnfavouriteItemHandler>(ServiceLifetime.Scoped);
}); });
})!); })!);
@@ -17,6 +17,6 @@
FontFamily="{DynamicResource FluentThemeFontFamily}" FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16" FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}" Foreground="{DynamicResource IconForegroundBrush}"
Text="&#xE066;" /> Text="&#xE073;" />
</Button> </Button>
</UserControl> </UserControl>
@@ -14,8 +14,8 @@
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
FontFamily="{DynamicResource FluentThemeFontFamily}" FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="18" FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}" Foreground="{DynamicResource IconForegroundBrush}"
Text="&#xF295;" /> Text="&#xE3EA;" />
</Button> </Button>
</UserControl> </UserControl>
@@ -16,6 +16,6 @@
FontFamily="{DynamicResource FluentThemeFontFamily}" FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16" FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}" Foreground="{DynamicResource IconForegroundBrush}"
Text="&#xF36A;" /> Text="&#xE607;" />
</Button> </Button>
</UserControl> </UserControl>
+1 -1
View File
@@ -16,6 +16,6 @@
FontFamily="{DynamicResource FluentThemeFontFamily}" FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16" FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}" Foreground="{DynamicResource IconForegroundBrush}"
Text="&#xE5B3;" /> Text="&#xE759;" />
</Button> </Button>
</UserControl> </UserControl>
@@ -0,0 +1,30 @@
<UserControl
x:Class="Bitvault.Avalonia.FavouriteItemActionView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Bitvault"
x:DataType="vm:FavouriteItemActionViewModel">
<Button
Width="{StaticResource ButtonWidth}"
Height="{StaticResource ButtonHeight}"
VerticalAlignment="Center"
Command="{Binding InvokeCommand}"
ToolTip.Tip="Edit">
<Grid>
<TextBlock
VerticalAlignment="Center"
FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}"
IsVisible="{Binding !Value}"
Text="&#xEF61;" />
<TextBlock
VerticalAlignment="Center"
FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16"
Foreground="{DynamicResource IconForegroundBrush}"
IsVisible="{Binding Value}"
Text="&#xEF60;" />
</Grid>
</Button>
</UserControl>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace Bitvault.Avalonia;
public partial class FavouriteItemActionView : UserControl
{
public FavouriteItemActionView() => InitializeComponent();
}
+14 -1
View File
@@ -5,6 +5,9 @@
xmlns:vm="using:Bitvault" xmlns:vm="using:Bitvault"
x:DataType="vm:ItemNavigationViewModel" x:DataType="vm:ItemNavigationViewModel"
IsSelected="{Binding Selected}"> IsSelected="{Binding Selected}">
<ListBoxItem.Resources>
<SolidColorBrush x:Key="StarredIconForegroundBrush" Color="#FFEDB120" />
</ListBoxItem.Resources>
<Interaction.Behaviors> <Interaction.Behaviors>
<DataTriggerBehavior Binding="{Binding Selected}" Value="True"> <DataTriggerBehavior Binding="{Binding Selected}" Value="True">
<NavigateAction <NavigateAction
@@ -17,6 +20,7 @@
Scope="self"> Scope="self">
<NavigateAction.ParameterBindings> <NavigateAction.ParameterBindings>
<ParameterBinding Key="Archived" Value="{Binding Archived}" /> <ParameterBinding Key="Archived" Value="{Binding Archived}" />
<ParameterBinding Key="Favourite" Value="{Binding Favourite}" />
</NavigateAction.ParameterBindings> </NavigateAction.ParameterBindings>
</NavigateAction> </NavigateAction>
</DataTriggerBehavior> </DataTriggerBehavior>
@@ -25,8 +29,17 @@
<PersonPicture <PersonPicture
Grid.Column="0" Grid.Column="0"
Height="34" Height="34"
Margin="0"
DisplayName="{Binding Name}" /> DisplayName="{Binding Name}" />
<TextBlock
Grid.Column="0"
Margin="0,0,-2,10"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
FontFamily="{DynamicResource FluentThemeFontFamily}"
FontSize="16"
Foreground="{DynamicResource StarredIconForegroundBrush}"
IsVisible="{Binding Favourite}"
Text="&#xEF60;" />
<StackPanel Grid.Column="1" Margin="12,12,6,12"> <StackPanel Grid.Column="1" Margin="12,12,6,12">
<TextBlock FontWeight="SemiBold" Text="{Binding Name}" /> <TextBlock FontWeight="SemiBold" Text="{Binding Name}" />
<TextBlock Opacity="0.7" Text="{Binding Name}" /> <TextBlock Opacity="0.7" Text="{Binding Name}" />
@@ -18,19 +18,19 @@ public class AggerateContainerViewModelHandler(IMediator mediator,
bool selected = true; bool selected = true;
if (await mediator.Handle<RequestEventArgs<QueryContainerConfiguration>, if (await mediator.Handle<RequestEventArgs<QueryContainerConfiguration>,
IReadOnlyCollection<(int Id, string? Name)>>(Request.As(new QueryContainerConfiguration IReadOnlyCollection<(int Id, string? Name, bool Favourite, bool Archived)>>(Request.As(new QueryContainerConfiguration
{ {
Filter = configuration.Filter, Filter = configuration.Filter,
Query = configuration.Query Query = configuration.Query
})) is IReadOnlyCollection<(int Id, string? Name)> results) })) is IReadOnlyCollection<(int Id, string? Name, bool Favourite, bool Archived)> results)
{ {
foreach ((int Id, string? Name) in results) foreach ((int Id, string? Name, bool Favourite, bool Archived) in results)
{ {
IServiceScope serviceScope = serviceProvider.CreateScope(); IServiceScope serviceScope = serviceProvider.CreateScope();
IServiceFactory serviceFactory = serviceScope.ServiceProvider.GetRequiredService<IServiceFactory>(); IServiceFactory serviceFactory = serviceScope.ServiceProvider.GetRequiredService<IServiceFactory>();
IValueStore<Item> valueStore = serviceScope.ServiceProvider.GetRequiredService<IValueStore<Item>>(); IValueStore<Item> valueStore = serviceScope.ServiceProvider.GetRequiredService<IValueStore<Item>>();
if (serviceFactory.Create<ItemNavigationViewModel>(Id, Name, "Description", selected, configuration.Filter == "Archive") is ItemNavigationViewModel viewModel) if (serviceFactory.Create<ItemNavigationViewModel>(Id, Name, "Description", selected, Favourite, Archived) is ItemNavigationViewModel viewModel)
{ {
Item item = new() { Id = Id, Name = Name }; Item item = new() { Id = Id, Name = Name };
valueStore.Set(item); valueStore.Set(item);
-1
View File
@@ -14,4 +14,3 @@ public partial class ArchiveItemActionViewModel(IServiceProvider provider,
[RelayCommand] [RelayCommand]
public void Invoke() => Publisher.Publish(Archive.As<Item>()); public void Invoke() => Publisher.Publish(Archive.As<Item>());
} }
+10
View File
@@ -0,0 +1,10 @@
namespace Bitvault;
public record Favourite
{
public static FavouriteEventArgs<TValue> As<TValue>(TValue value) =>
new(value);
public static FavouriteEventArgs<TValue> As<TValue>() where TValue : new() =>
new(new TValue());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Bitvault;
public record FavouriteEventArgs<TValue>(TValue Value);
+30
View File
@@ -0,0 +1,30 @@
using CommunityToolkit.Mvvm.Input;
using Toolkit.Foundation;
namespace Bitvault;
public partial class FavouriteItemActionViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscription subscriber,
IDisposer disposer,
bool value = false) : Observable<bool>(provider, factory, mediator, publisher, subscriber, disposer, value),
IRemovable
{
[RelayCommand]
public void Invoke()
{
if (!Value)
{
Value = true;
Publisher.Publish(Favourite.As<Item>());
}
else
{
Value = false;
Publisher.Publish(Unfavourite.As<Item>());
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using Bitvault.Data;
using Microsoft.EntityFrameworkCore;
using Toolkit.Foundation;
namespace Bitvault;
public class FavouriteItemHandler(IValueStore<Item> valueStore,
IDbContextFactory<ContainerDbContext> dbContextFactory) :
INotificationHandler<FavouriteEventArgs<Item>>
{
public async Task Handle(FavouriteEventArgs<Item> args)
{
try
{
if (valueStore.Value is Item item)
{
await Task.Run(async () =>
{
using ContainerDbContext context = await dbContextFactory.CreateDbContextAsync();
if (await context.FindAsync<ItemEntry>(item.Id) is ItemEntry result)
{
result.State = 1;
await context.SaveChangesAsync();
}
});
}
}
catch
{
}
}
}
+16 -10
View File
@@ -15,10 +15,13 @@ public partial class ItemNavigationViewModel(IServiceProvider provider,
string name, string name,
string description, string description,
bool selected, bool selected,
bool favourite,
bool archived) : bool archived) :
Observable(provider, factory, mediator, publisher, subscriber, disposer), Observable(provider, factory, mediator, publisher, subscriber, disposer),
INotificationHandler<ArchiveEventArgs<Item>>, INotificationHandler<ArchiveEventArgs<Item>>,
INotificationHandler<UnarchiveEventArgs<Item>>, INotificationHandler<UnarchiveEventArgs<Item>>,
INotificationHandler<FavouriteEventArgs<Item>>,
INotificationHandler<UnfavouriteEventArgs<Item>>,
ISelectable, ISelectable,
IRemovable IRemovable
{ {
@@ -28,6 +31,9 @@ public partial class ItemNavigationViewModel(IServiceProvider provider,
[ObservableProperty] [ObservableProperty]
private string? description = description; private string? description = description;
[ObservableProperty]
private bool favourite = favourite;
[ObservableProperty] [ObservableProperty]
private int id = id; private int id = id;
@@ -42,15 +48,15 @@ public partial class ItemNavigationViewModel(IServiceProvider provider,
public IContentTemplate Template { get; set; } = template; public IContentTemplate Template { get; set; } = template;
public Task Handle(ArchiveEventArgs<Item> args) public Task Handle(ArchiveEventArgs<Item> args) =>
{ Task.Run(Dispose);
Dispose();
return Task.CompletedTask;
}
public Task Handle(UnarchiveEventArgs<Item> args) public Task Handle(UnarchiveEventArgs<Item> args) =>
{ Task.Run(Dispose);
Dispose();
return Task.CompletedTask; public Task Handle(FavouriteEventArgs<Item> args) =>
} Task.FromResult(Favourite = true);
public Task Handle(UnfavouriteEventArgs<Item> args) =>
Task.FromResult(Favourite = false);
} }
+2
View File
@@ -18,6 +18,7 @@ public partial class ItemViewModel :
ISubscription subscriber, ISubscription subscriber,
IDisposer disposer, IDisposer disposer,
IContentTemplate template, IContentTemplate template,
bool favourite = false,
bool archived = false) : base(provider, factory, mediator, publisher, subscriber, disposer) bool archived = false) : base(provider, factory, mediator, publisher, subscriber, disposer)
{ {
Template = template; Template = template;
@@ -27,6 +28,7 @@ public partial class ItemViewModel :
{ {
Publisher.Publish(Notify.As(Factory.Create<CommandCollection>(new List<IDisposable> Publisher.Publish(Notify.As(Factory.Create<CommandCollection>(new List<IDisposable>
{ {
Factory.Create<FavouriteItemActionViewModel>(favourite),
Factory.Create<EditItemActionViewModel>(), Factory.Create<EditItemActionViewModel>(),
Factory.Create<ArchiveItemActionViewModel>(), Factory.Create<ArchiveItemActionViewModel>(),
}))); })));
+7 -5
View File
@@ -6,12 +6,12 @@ using Toolkit.Foundation;
namespace Bitvault; namespace Bitvault;
public class QueryContainerHandler(IDbContextFactory<ContainerDbContext> dbContextFactory) : public class QueryContainerHandler(IDbContextFactory<ContainerDbContext> dbContextFactory) :
IHandler<RequestEventArgs<QueryContainerConfiguration>, IReadOnlyCollection<(int Id, string? Name)>> IHandler<RequestEventArgs<QueryContainerConfiguration>, IReadOnlyCollection<(int Id, string? Name, bool Favourite, bool Archived)>>
{ {
public async Task<IReadOnlyCollection<(int Id, string? Name)>> Handle(RequestEventArgs<QueryContainerConfiguration> args, public async Task<IReadOnlyCollection<(int Id, string? Name, bool Favourite, bool Archived)>> Handle(RequestEventArgs<QueryContainerConfiguration> args,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
List<(int Id, string? Name)> items = []; List<(int Id, string? Name, bool Favourite, bool Archived)> items = [];
if (args.Value is QueryContainerConfiguration queryConfiguration) if (args.Value is QueryContainerConfiguration queryConfiguration)
{ {
@@ -44,13 +44,15 @@ public class QueryContainerHandler(IDbContextFactory<ContainerDbContext> dbConte
return await context.Set<ItemEntry>().Where(predicate).Select(x => new return await context.Set<ItemEntry>().Where(predicate).Select(x => new
{ {
x.Id, x.Id,
x.Name x.Name,
Favourite = x.State == 1,
Archived = x.State == 2
}).OrderBy(x => x.Name).ToListAsync(); }).OrderBy(x => x.Name).ToListAsync();
}); });
foreach (var result in results) foreach (var result in results)
{ {
items.Add(new(result.Id, result.Name)); items.Add(new(result.Id, result.Name, result.Favourite, result.Archived));
} }
} }
+10
View File
@@ -0,0 +1,10 @@
namespace Bitvault;
public record Unfavourite
{
public static UnfavouriteEventArgs<TValue> As<TValue>(TValue value) =>
new(value);
public static UnfavouriteEventArgs<TValue> As<TValue>() where TValue : new() =>
new(new TValue());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Bitvault;
public record UnfavouriteEventArgs<TValue>(TValue Value);
@@ -0,0 +1,17 @@
using CommunityToolkit.Mvvm.Input;
using Toolkit.Foundation;
namespace Bitvault;
public partial class UnfavouriteItemActionViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscription subscriber,
IDisposer disposer) : Observable(provider, factory, mediator, publisher, subscriber, disposer),
IRemovable
{
[RelayCommand]
public void Invoke() => Publisher.Publish(Unfavourite.As<Item>());
}
+34
View File
@@ -0,0 +1,34 @@
using Bitvault.Data;
using Microsoft.EntityFrameworkCore;
using Toolkit.Foundation;
namespace Bitvault;
public class UnfavouriteItemHandler(IValueStore<Item> valueStore,
IDbContextFactory<ContainerDbContext> dbContextFactory) :
INotificationHandler<UnfavouriteEventArgs<Item>>
{
public async Task Handle(UnfavouriteEventArgs<Item> args)
{
try
{
if (valueStore.Value is Item item)
{
await Task.Run(async () =>
{
using ContainerDbContext context = await dbContextFactory.CreateDbContextAsync();
if (await context.FindAsync<ItemEntry>(item.Id) is ItemEntry result)
{
result.State = 0;
await context.SaveChangesAsync();
}
});
}
}
catch
{
}
}
}