diff --git a/src/Artemis.UI.Shared/Styles/Artemis.axaml b/src/Artemis.UI.Shared/Styles/Artemis.axaml
index 69759ddc7..d32872413 100644
--- a/src/Artemis.UI.Shared/Styles/Artemis.axaml
+++ b/src/Artemis.UI.Shared/Styles/Artemis.axaml
@@ -32,6 +32,7 @@
+
diff --git a/src/Artemis.UI.Shared/Styles/Control.axaml b/src/Artemis.UI.Shared/Styles/Control.axaml
new file mode 100644
index 000000000..b20c87ff5
--- /dev/null
+++ b/src/Artemis.UI.Shared/Styles/Control.axaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml
index 628846513..3dce3c328 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml
@@ -14,7 +14,8 @@
CornerRadius="8"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
- Command="{CompiledBinding NavigateToEntry}">
+ Command="{CompiledBinding NavigateToEntry}"
+ IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}">
-
+
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs
index 22e378a1d..c38c9aef3 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Reactive;
+using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,19 +16,26 @@ namespace Artemis.UI.Screens.Workshop.Entries;
public class EntryListViewModel : ActivatableViewModelBase
{
private readonly IRouter _router;
- private readonly ObservableAsPropertyHelper _entryIcon;
+ private readonly IWorkshopService _workshopService;
+ private ObservableAsPropertyHelper? _entryIcon;
public EntryListViewModel(IGetEntries_Entries_Items entry, IRouter router, IWorkshopService workshopService)
{
_router = router;
+ _workshopService = workshopService;
Entry = entry;
- EntryIcon = workshopService.GetEntryIcon(entry.Id, CancellationToken.None);
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
+
+ this.WhenActivated(d =>
+ {
+ _entryIcon = Observable.FromAsync(GetIcon).ToProperty(this, vm => vm.EntryIcon);
+ _entryIcon.DisposeWith(d);
+ });
}
public IGetEntries_Entries_Items Entry { get; }
- public Task EntryIcon { get; }
+ public Bitmap? EntryIcon => _entryIcon?.Value;
public ReactiveCommand NavigateToEntry { get; }
private async Task ExecuteNavigateToEntry()
@@ -46,4 +54,12 @@ public class EntryListViewModel : ActivatableViewModelBase
throw new ArgumentOutOfRangeException();
}
}
+
+ private async Task GetIcon(CancellationToken cancellationToken)
+ {
+ // Take at least 100ms to allow the UI to load and make the whole thing smooth
+ Task iconTask = _workshopService.GetEntryIcon(Entry.Id, cancellationToken);
+ await Task.Delay(100, cancellationToken);
+ return await iconTask;
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs
index 340c84ee4..23ddddfa2 100644
--- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -13,7 +14,9 @@ using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.WebClient.Workshop;
+using Avalonia.Threading;
using DryIoc.ImTools;
+using DynamicData;
using ReactiveUI;
using StrawberryShake;
@@ -26,7 +29,7 @@ public class ProfileListViewModel : RoutableScreen _showPagination;
private readonly ObservableAsPropertyHelper _isLoading;
- private List? _entries;
+ private SourceList _entries = new();
private int _page;
private int _loadedPage = -1;
private int _totalPages = 1;
@@ -45,9 +48,17 @@ public class ProfileListViewModel : RoutableScreen vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
CategoriesViewModel = categoriesViewModel;
-
+
+ _entries.Connect()
+ .ObserveOn(new AvaloniaSynchronizationContext(DispatcherPriority.SystemIdle))
+ .Transform(getEntryListViewModel)
+ .Bind(out ReadOnlyObservableCollection entries)
+ .Subscribe();
+ Entries = entries;
+
// Respond to page changes
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"workshop/profiles/{p}")));
+
// Respond to filter changes
this.WhenActivated(d => CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ =>
{
@@ -65,11 +76,7 @@ public class ProfileListViewModel : RoutableScreen? Entries
- {
- get => _entries;
- set => RaiseAndSetIfChanged(ref _entries, value);
- }
+ public ReadOnlyObservableCollection Entries { get; }
public int Page
{
@@ -99,8 +106,11 @@ public class ProfileListViewModel : RoutableScreen _getEntryListViewModel(n)).ToList();
TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) EntriesPerPage);
+ _entries.Edit(e =>
+ {
+ e.Clear();
+ e.AddRange(entries.Data.Entries.Items);
+ });
}
else
TotalPages = 1;
diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml b/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml
new file mode 100644
index 000000000..2c9ed6559
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+ by
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml.cs
new file mode 100644
index 000000000..8490b69b2
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Search/SearchResultView.axaml.cs
@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.Workshop.Search;
+
+public partial class SearchResultView : ReactiveUserControl
+{
+ public SearchResultView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchResultViewModel.cs b/src/Artemis.UI/Screens/Workshop/Search/SearchResultViewModel.cs
new file mode 100644
index 000000000..8136ce350
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Search/SearchResultViewModel.cs
@@ -0,0 +1,32 @@
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Artemis.UI.Shared;
+using Artemis.WebClient.Workshop;
+using Artemis.WebClient.Workshop.Services;
+using Avalonia.Media.Imaging;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.Workshop.Search;
+
+public class SearchResultViewModel : ActivatableViewModelBase
+{
+ private readonly IWorkshopService _workshopService;
+ private ObservableAsPropertyHelper? _entryIcon;
+
+ public SearchResultViewModel(ISearchEntries_SearchEntries entry, IWorkshopService workshopService)
+ {
+ _workshopService = workshopService;
+
+ Entry = entry;
+ this.WhenActivated(d =>
+ {
+ _entryIcon = Observable.FromAsync(c => _workshopService.GetEntryIcon(Entry.Id, c)).ToProperty(this, vm => vm.EntryIcon);
+ _entryIcon.DisposeWith(d);
+ });
+ }
+
+ public ISearchEntries_SearchEntries Entry { get; }
+ public Bitmap? EntryIcon => _entryIcon?.Value;
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml
index 171d4c400..8ac1d728f 100644
--- a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml
@@ -11,47 +11,31 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+ windowing:AppWindow.AllowInteractionInTitleBar="True" />
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs b/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs
index b3370ee8b..e27504efc 100644
--- a/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs
@@ -7,32 +7,40 @@ using Artemis.UI.Screens.Workshop.CurrentUser;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
+using Artemis.WebClient.Workshop.Services;
using ReactiveUI;
+using Serilog;
using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Search;
public class SearchViewModel : ViewModelBase
{
- public CurrentUserViewModel CurrentUserViewModel { get; }
+ private readonly ILogger _logger;
private readonly IRouter _router;
private readonly IWorkshopClient _workshopClient;
+ private readonly IWorkshopService _workshopService;
private EntryType? _entryType;
- private ISearchEntries_SearchEntries? _selectedEntry;
+ private bool _isLoading;
+ private SearchResultViewModel? _selectedEntry;
- public SearchViewModel(IWorkshopClient workshopClient, IRouter router, CurrentUserViewModel currentUserViewModel)
+ public SearchViewModel(ILogger logger, IWorkshopClient workshopClient, IWorkshopService workshopService, IRouter router, CurrentUserViewModel currentUserViewModel)
{
- CurrentUserViewModel = currentUserViewModel;
+ _logger = logger;
_workshopClient = workshopClient;
+ _workshopService = workshopService;
_router = router;
+ CurrentUserViewModel = currentUserViewModel;
SearchAsync = ExecuteSearchAsync;
this.WhenAnyValue(vm => vm.SelectedEntry).WhereNotNull().Subscribe(NavigateToEntry);
}
+ public CurrentUserViewModel CurrentUserViewModel { get; }
+
public Func>> SearchAsync { get; }
- public ISearchEntries_SearchEntries? SelectedEntry
+ public SearchResultViewModel? SelectedEntry
{
get => _selectedEntry;
set => RaiseAndSetIfChanged(ref _selectedEntry, value);
@@ -44,13 +52,19 @@ public class SearchViewModel : ViewModelBase
set => RaiseAndSetIfChanged(ref _entryType, value);
}
- private void NavigateToEntry(ISearchEntries_SearchEntries entry)
+ public bool IsLoading
+ {
+ get => _isLoading;
+ set => RaiseAndSetIfChanged(ref _isLoading, value);
+ }
+
+ private void NavigateToEntry(SearchResultViewModel searchResult)
{
string? url = null;
- if (entry.EntryType == WebClient.Workshop.EntryType.Profile)
- url = $"workshop/profiles/{entry.Id}";
- if (entry.EntryType == WebClient.Workshop.EntryType.Layout)
- url = $"workshop/layouts/{entry.Id}";
+ if (searchResult.Entry.EntryType == WebClient.Workshop.EntryType.Profile)
+ url = $"workshop/profiles/{searchResult.Entry.Id}";
+ if (searchResult.Entry.EntryType == WebClient.Workshop.EntryType.Layout)
+ url = $"workshop/layouts/{searchResult.Entry.Id}";
if (url != null)
Task.Run(() => _router.Navigate(url));
@@ -58,10 +72,25 @@ public class SearchViewModel : ViewModelBase
private async Task> ExecuteSearchAsync(string? input, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(input))
- return new List