diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs
index ed0ded1d7..8c9422400 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -13,6 +13,8 @@ namespace Artemis.Core;
///
public static class Utilities
{
+ private static bool _shuttingDown;
+
///
/// Call this before even initializing the Core to make sure the folders required for operation are in place
///
@@ -33,7 +35,11 @@ public static void PrepareFirstLaunch()
///
public static void Shutdown()
{
+ if (_shuttingDown)
+ return;
+
// Request a graceful shutdown, whatever UI we're running can pick this up
+ _shuttingDown = true;
OnShutdownRequested();
}
@@ -45,9 +51,13 @@ public static void Shutdown()
/// A list of extra arguments to pass to Artemis when restarting
public static void Restart(bool elevate, TimeSpan delay, params string[] extraArgs)
{
+ if (_shuttingDown)
+ return;
+
if (!OperatingSystem.IsWindows() && elevate)
throw new ArtemisCoreException("Elevation on non-Windows platforms is not supported.");
+ _shuttingDown = true;
OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList()));
}
@@ -106,12 +116,12 @@ public static void CreateAccessibleDirectory(string path)
/// Occurs when the core has requested an application shutdown
///
public static event EventHandler? ShutdownRequested;
-
+
///
/// Occurs when the core has requested an application restart
///
public static event EventHandler? RestartRequested;
-
+
///
/// Occurs when the core has requested a pending application update to be applied
///
@@ -151,7 +161,7 @@ private static void OnShutdownRequested()
{
ShutdownRequested?.Invoke(null, EventArgs.Empty);
}
-
+
private static void OnUpdateRequested(UpdateEventArgs e)
{
UpdateRequested?.Invoke(null, e);
diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs
index c87a86be1..3b787549c 100644
--- a/src/Artemis.Storage/StorageManager.cs
+++ b/src/Artemis.Storage/StorageManager.cs
@@ -9,6 +9,7 @@ public static class StorageManager
{
private static bool _ranMigrations;
private static bool _inUse;
+ private static object _factoryLock = new();
///
/// Creates a backup of the database if the last backup is older than 10 minutes
@@ -39,19 +40,22 @@ public static void CreateBackup(string dataFolder)
File.Copy(database, Path.Combine(backupFolder, $"artemis-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db"));
}
-
+
public static ArtemisDbContext CreateDbContext(string dataFolder)
{
- _inUse = true;
+ lock (_factoryLock)
+ {
+ _inUse = true;
- ArtemisDbContext dbContext = new() {DataFolder = dataFolder};
- if (_ranMigrations)
- return dbContext;
+ ArtemisDbContext dbContext = new() {DataFolder = dataFolder};
+ if (_ranMigrations)
+ return dbContext;
- dbContext.Database.Migrate();
- dbContext.Database.ExecuteSqlRaw("PRAGMA optimize");
- _ranMigrations = true;
+ dbContext.Database.Migrate();
+ dbContext.Database.ExecuteSqlRaw("PRAGMA optimize");
+ _ranMigrations = true;
- return dbContext;
+ return dbContext;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs
index 4353a4eef..8973793b0 100644
--- a/src/Artemis.UI/Routing/Routes.cs
+++ b/src/Artemis.UI/Routing/Routes.cs
@@ -20,57 +20,64 @@ namespace Artemis.UI.Routing;
public static class Routes
{
- public static List ArtemisRoutes = new()
- {
+ public static List ArtemisRoutes =
+ [
new RouteRegistration("blank"),
new RouteRegistration("home"),
new RouteRegistration("workshop")
{
- Children = new List
- {
+ Children =
+ [
new RouteRegistration("offline/{message:string}"),
new RouteRegistration("entries")
{
- Children = new List
- {
- new RouteRegistration("plugins/{page:int}"),
- new RouteRegistration("plugins/details/{entryId:long}"),
- new RouteRegistration("profiles/{page:int}"),
- new RouteRegistration("profiles/details/{entryId:long}"),
- new RouteRegistration("layouts/{page:int}"),
- new RouteRegistration("layouts/details/{entryId:long}"),
- }
+ Children =
+ [
+ new RouteRegistration("plugins")
+ {
+ Children = [new RouteRegistration("details/{entryId:long}")]
+ },
+ new RouteRegistration("profiles")
+ {
+ Children = [new RouteRegistration("details/{entryId:long}")]
+ },
+ new RouteRegistration("layouts")
+ {
+ Children = [new RouteRegistration("details/{entryId:long}")]
+ },
+ ]
},
+
new RouteRegistration("library")
{
- Children = new List
- {
+ Children =
+ [
new RouteRegistration("installed"),
new RouteRegistration("submissions"),
- new RouteRegistration("submissions/{entryId:long}"),
- }
+ new RouteRegistration("submissions/{entryId:long}")
+ ]
}
- }
+ ]
},
+
new RouteRegistration("surface-editor"),
new RouteRegistration("settings")
{
- Children = new List
- {
+ Children =
+ [
new RouteRegistration("general"),
new RouteRegistration("plugins"),
new RouteRegistration("devices"),
new RouteRegistration("releases")
{
- Children = new List
- {
- new RouteRegistration("{releaseId:guid}")
- }
+ Children = [new RouteRegistration("{releaseId:guid}")]
},
+
new RouteRegistration("account"),
new RouteRegistration("about")
- }
+ ]
},
+
new RouteRegistration("profile-editor/{profileConfigurationId:guid}")
- };
+ ];
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/Layout/LayoutProviders/WorkshopLayoutViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/Layout/LayoutProviders/WorkshopLayoutViewModel.cs
index 65c9c8d93..231093e79 100644
--- a/src/Artemis.UI/Screens/Device/Tabs/Layout/LayoutProviders/WorkshopLayoutViewModel.cs
+++ b/src/Artemis.UI/Screens/Device/Tabs/Layout/LayoutProviders/WorkshopLayoutViewModel.cs
@@ -64,7 +64,7 @@ public async Task BrowseLayouts()
if (!await _windowService.ShowConfirmContentDialog("Open workshop", "Do you want to close this window and view the workshop?"))
return false;
- await _router.Navigate("workshop/entries/layouts/1");
+ await _router.Navigate("workshop/entries/layouts");
return true;
}
diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs
index 3b7de1c0c..d2f8e9a23 100644
--- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs
+++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs
@@ -41,9 +41,9 @@ public SidebarViewModel(IRouter router, IProfileService profileService, IWindowS
new(MaterialIconKind.HomeOutline, "Home", "home"),
new(MaterialIconKind.TestTube, "Workshop", "workshop", null, new ObservableCollection
{
- new(MaterialIconKind.FolderVideo, "Profiles", "workshop/entries/profiles/1", "workshop/entries/profiles"),
- new(MaterialIconKind.KeyboardVariant, "Layouts", "workshop/entries/layouts/1", "workshop/entries/layouts"),
- new(MaterialIconKind.Connection, "Plugins", "workshop/entries/plugins/1", "workshop/entries/plugins"),
+ new(MaterialIconKind.FolderVideo, "Profiles", "workshop/entries/profiles", "workshop/entries/profiles"),
+ new(MaterialIconKind.KeyboardVariant, "Layouts", "workshop/entries/layouts", "workshop/entries/layouts"),
+ new(MaterialIconKind.Connection, "Plugins", "workshop/entries/plugins", "workshop/entries/plugins"),
new(MaterialIconKind.Bookshelf, "Library", "workshop/library"),
}),
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs
index 904348bc6..e76014c34 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs
@@ -24,9 +24,9 @@ public EntriesViewModel(IRouter router)
Tabs = new ObservableCollection
{
- new("Profiles", "workshop/entries/profiles/1", "workshop/entries/profiles"),
- new("Layouts", "workshop/entries/layouts/1", "workshop/entries/layouts"),
- new("Plugins", "workshop/entries/plugins/1", "workshop/entries/plugins"),
+ new("Profiles", "workshop/entries/profiles", "workshop/entries/profiles"),
+ new("Layouts", "workshop/entries/layouts", "workshop/entries/layouts"),
+ new("Plugins", "workshop/entries/plugins", "workshop/entries/plugins"),
};
this.WhenActivated(d =>
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml
index d5c3b34f9..8fdc91d29 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml
@@ -2,8 +2,6 @@
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"
- xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
- xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListInputView"
@@ -25,15 +23,6 @@
Download count
-
- Show per page
-
- 10
- 20
- 50
- 100
-
-
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputViewModel.cs
index f34348a8b..c327e3eb2 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputViewModel.cs
@@ -9,7 +9,6 @@ namespace Artemis.UI.Screens.Workshop.Entries.List;
public partial class EntryListInputViewModel : ViewModelBase
{
private static string? _lastSearch;
- private readonly PluginSetting _entriesPerPage;
private readonly PluginSetting _sortBy;
private string? _search;
[Notify] private string _searchWatermark = "Search";
@@ -18,9 +17,7 @@ public partial class EntryListInputViewModel : ViewModelBase
public EntryListInputViewModel(ISettingsService settingsService)
{
_search = _lastSearch;
- _entriesPerPage = settingsService.GetSetting("Workshop.EntriesPerPage", 10);
_sortBy = settingsService.GetSetting("Workshop.SortBy", 10);
- _entriesPerPage.AutoSave = true;
_sortBy.AutoSave = true;
}
@@ -33,17 +30,7 @@ public string? Search
_lastSearch = value;
}
}
-
- public int EntriesPerPage
- {
- get => _entriesPerPage.Value;
- set
- {
- _entriesPerPage.Value = value;
- this.RaisePropertyChanged();
- }
- }
-
+
public int SortBy
{
get => _sortBy.Value;
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
index 4d831ba29..92322920d 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
@@ -1,17 +1,16 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.Categories;
-using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.WebClient.Workshop;
-using Avalonia.Threading;
using DynamicData;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
@@ -19,21 +18,20 @@
namespace Artemis.UI.Screens.Workshop.Entries.List;
-public abstract partial class EntryListViewModel : RoutableScreen
+public abstract partial class EntryListViewModel : RoutableHostScreen
{
private readonly SourceList _entries = new();
- private readonly ObservableAsPropertyHelper _isLoading;
private readonly INotificationService _notificationService;
- private readonly string _route;
- private readonly ObservableAsPropertyHelper _showPagination;
private readonly IWorkshopClient _workshopClient;
- [Notify] private int _page;
- [Notify] private int _loadedPage = -1;
- [Notify] private int _totalPages = 1;
+ private readonly string _route;
+ private IGetEntriesv2_EntriesV2_PageInfo? _currentPageInfo;
+
+ [Notify] private bool _initializing = true;
+ [Notify] private bool _fetchingMore;
+ [Notify] private int _entriesPerFetch;
protected EntryListViewModel(string route,
IWorkshopClient workshopClient,
- IRouter router,
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
@@ -42,46 +40,37 @@ protected EntryListViewModel(string route,
_route = route;
_workshopClient = workshopClient;
_notificationService = notificationService;
- _showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
- _isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
CategoriesViewModel = categoriesViewModel;
InputViewModel = entryListInputViewModel;
_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($"{_route}/{p}")));
-
this.WhenActivated(d =>
{
// Respond to filter query input changes
- InputViewModel.WhenAnyValue(vm => vm.Search).Skip(1).Throttle(TimeSpan.FromMilliseconds(200)).Subscribe(_ => RefreshToStart()).DisposeWith(d);
- InputViewModel.WhenAnyValue(vm => vm.SortBy, vm => vm.EntriesPerPage).Skip(1).Subscribe(_ => RefreshToStart()).DisposeWith(d);
- CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ => RefreshToStart()).DisposeWith(d);
+ InputViewModel.WhenAnyValue(vm => vm.Search).Skip(1).Throttle(TimeSpan.FromMilliseconds(200)).Subscribe(_ => Reset()).DisposeWith(d);
+ CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ => Reset()).DisposeWith(d);
});
}
- public bool ShowPagination => _showPagination.Value;
- public bool IsLoading => _isLoading.Value;
-
public CategoriesViewModel CategoriesViewModel { get; }
public EntryListInputViewModel InputViewModel { get; }
public ReadOnlyObservableCollection Entries { get; }
-
- public override async Task OnNavigating(WorkshopListParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
- {
- Page = Math.Max(1, parameters.Page);
- await Task.Delay(200, cancellationToken);
- if (!cancellationToken.IsCancellationRequested)
- await Query(cancellationToken);
+ public override async Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken)
+ {
+ if (_entries.Count == 0)
+ {
+ await Task.Delay(250, cancellationToken);
+ await FetchMore(cancellationToken);
+ Initializing = false;
+ }
}
public override Task OnClosing(NavigationArguments args)
@@ -92,6 +81,43 @@ public override Task OnClosing(NavigationArguments args)
return base.OnClosing(args);
}
+ public async Task FetchMore(CancellationToken cancellationToken)
+ {
+ if (FetchingMore || _currentPageInfo != null && !_currentPageInfo.HasNextPage)
+ return;
+
+ FetchingMore = true;
+
+ int entriesPerFetch = _entries.Count == 0 ? _entriesPerFetch * 2 : _entriesPerFetch;
+ string? search = string.IsNullOrWhiteSpace(InputViewModel.Search) ? null : InputViewModel.Search;
+ EntryFilterInput filter = GetFilter();
+ IReadOnlyList sort = GetSort();
+
+ try
+ {
+ IOperationResult entries = await _workshopClient.GetEntriesv2.ExecuteAsync(search, filter, sort, entriesPerFetch, _currentPageInfo?.EndCursor, cancellationToken);
+ entries.EnsureNoErrors();
+
+ _currentPageInfo = entries.Data?.EntriesV2?.PageInfo;
+ if (entries.Data?.EntriesV2?.Edges != null)
+ _entries.Edit(e => e.AddRange(entries.Data.EntriesV2.Edges.Select(edge => edge.Node)));
+
+ InputViewModel.TotalCount = entries.Data?.EntriesV2?.TotalCount ?? 0;
+ }
+ catch (Exception e)
+ {
+ _notificationService.CreateNotification()
+ .WithTitle("Failed to load entries")
+ .WithMessage(e.Message)
+ .WithSeverity(NotificationSeverity.Error)
+ .Show();
+ }
+
+ finally
+ {
+ FetchingMore = false;
+ }
+ }
protected virtual EntryFilterInput GetFilter()
{
@@ -117,59 +143,10 @@ protected virtual IReadOnlyList GetSort()
};
}
- private void RefreshToStart()
+ private void Reset()
{
- // Reset to page one, will trigger a query
- if (Page != 1)
- Page = 1;
- // If already at page one, force a query
- else
- Task.Run(() => Query(CancellationToken.None));
- }
-
- private async Task Query(CancellationToken cancellationToken)
- {
- try
- {
- string? search = string.IsNullOrWhiteSpace(InputViewModel.Search) ? null : InputViewModel.Search;
- EntryFilterInput filter = GetFilter();
- IReadOnlyList sort = GetSort();
- IOperationResult entries = await _workshopClient.GetEntries.ExecuteAsync(
- search,
- filter,
- InputViewModel.EntriesPerPage * (Page - 1),
- InputViewModel.EntriesPerPage,
- sort,
- cancellationToken
- );
- entries.EnsureNoErrors();
-
- if (entries.Data?.Entries?.Items != null)
- {
- TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) InputViewModel.EntriesPerPage);
- InputViewModel.TotalCount = entries.Data.Entries.TotalCount;
- _entries.Edit(e =>
- {
- e.Clear();
- e.AddRange(entries.Data.Entries.Items);
- });
- }
- else
- {
- TotalPages = 1;
- }
- }
- catch (Exception e)
- {
- _notificationService.CreateNotification()
- .WithTitle("Failed to load entries")
- .WithMessage(e.Message)
- .WithSeverity(NotificationSeverity.Error)
- .Show();
- }
- finally
- {
- LoadedPage = Page;
- }
+ _entries.Clear();
+ _currentPageInfo = null;
+ Task.Run(() => FetchMore(CancellationToken.None));
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml
index 3f8aa658a..1c0c4182c 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml
@@ -2,8 +2,9 @@
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"
- xmlns:pagination="clr-namespace:Artemis.UI.Shared.Pagination;assembly=Artemis.UI.Shared"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
+ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:ui="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Tabs.LayoutListView"
x:DataType="tabs:LayoutListViewModel">
@@ -15,51 +16,52 @@
-
-
-
-
- Categories
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Looks like your current filters gave no results
-
- Modify or clear your filters to view other device layouts
-
-
+
+
+
+
+
+ Categories
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Looks like your current filters gave no results
+
+ Modify or clear your filters to view other device layouts
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs
index 6b574bb29..a003f47bb 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs
@@ -1,4 +1,11 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+using Artemis.UI.Shared.Routing;
+using Avalonia.Controls;
using Avalonia.ReactiveUI;
+using Avalonia.Threading;
+using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
@@ -7,5 +14,30 @@ public partial class LayoutListView : ReactiveUserControl
public LayoutListView()
{
InitializeComponent();
+ EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch();
+
+ this.WhenActivated(d =>
+ {
+ UpdateEntriesPerFetch();
+ ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d);
+ });
+ }
+
+ private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ // When near the bottom of EntriesScrollViewer, call FetchMore on the view model
+ if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100)
+ ViewModel?.FetchMore(CancellationToken.None);
+ }
+
+ private void Navigate(RoutableScreen viewModel)
+ {
+ Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle);
+ }
+
+ private void UpdateEntriesPerFetch()
+ {
+ if (ViewModel != null)
+ ViewModel.EntriesPerFetch = (int) (EntriesScrollViewer.Viewport.Height / 120);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
index ca0cdbb99..11c97846c 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
@@ -10,12 +10,11 @@ namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
public class LayoutListViewModel : List.EntryListViewModel
{
public LayoutListViewModel(IWorkshopClient workshopClient,
- IRouter router,
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func getEntryListViewModel)
- : base("workshop/entries/layouts", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
+ : base("workshop/entries/layouts", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search layouts";
}
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml
index e52c30641..7b6cb1f0c 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml
@@ -3,11 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
- xmlns:pagination="clr-namespace:Artemis.UI.Shared.Pagination;assembly=Artemis.UI.Shared"
+ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:ui="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Tabs.PluginListView"
x:DataType="tabs:PluginListViewModel">
-
+
-
-
-
-
- Categories
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Looks like your current filters gave no results
-
- Modify or clear your filters to view other plugins
-
-
+
+
+
+
+
+
+ Categories
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Looks like your current filters gave no results
+
+ Modify or clear your filters to view other plugins
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs
index 2e32eea93..8cfaa1696 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs
@@ -1,7 +1,11 @@
-using Avalonia;
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+using Artemis.UI.Shared.Routing;
using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
+using Avalonia.Threading;
+using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
@@ -10,5 +14,30 @@ public partial class PluginListView : ReactiveUserControl
public PluginListView()
{
InitializeComponent();
+ EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch();
+
+ this.WhenActivated(d =>
+ {
+ UpdateEntriesPerFetch();
+ ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d);
+ });
+ }
+
+ private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ // When near the bottom of EntriesScrollViewer, call FetchMore on the view model
+ if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100)
+ ViewModel?.FetchMore(CancellationToken.None);
+ }
+
+ private void Navigate(RoutableScreen viewModel)
+ {
+ Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle);
+ }
+
+ private void UpdateEntriesPerFetch()
+ {
+ if (ViewModel != null)
+ ViewModel.EntriesPerFetch = (int) (EntriesScrollViewer.Viewport.Height / 120);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
index 7af13b4e4..c7ea484a6 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
@@ -10,12 +10,11 @@ namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
public class PluginListViewModel : EntryListViewModel
{
public PluginListViewModel(IWorkshopClient workshopClient,
- IRouter router,
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func getEntryListViewModel)
- : base("workshop/entries/plugins", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
+ : base("workshop/entries/plugins", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search plugins";
}
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml
index cd6e6641b..03028d4b8 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml
@@ -2,9 +2,10 @@
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"
- xmlns:pagination="clr-namespace:Artemis.UI.Shared.Pagination;assembly=Artemis.UI.Shared"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
- mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
+ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:ui="clr-namespace:Artemis.UI"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Tabs.ProfileListView"
x:DataType="tabs:ProfileListViewModel">
@@ -15,51 +16,52 @@
-
-
-
-
- Categories
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Looks like your current filters gave no results
-
- Modify or clear your filters to view some awesome profiles
-
-
+
+
+
+
+
+ Categories
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Looks like your current filters gave no results
+
+ Modify or clear your filters to view some awesome profiles
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs
index 62adad88b..b25ba45f3 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs
@@ -1,4 +1,11 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+using Artemis.UI.Shared.Routing;
+using Avalonia.Controls;
using Avalonia.ReactiveUI;
+using Avalonia.Threading;
+using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
@@ -7,5 +14,30 @@ public partial class ProfileListView : ReactiveUserControl
public ProfileListView()
{
InitializeComponent();
+ EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch();
+
+ this.WhenActivated(d =>
+ {
+ UpdateEntriesPerFetch();
+ ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d);
+ });
+ }
+
+ private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ // When near the bottom of EntriesScrollViewer, call FetchMore on the view model
+ if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100)
+ ViewModel?.FetchMore(CancellationToken.None);
+ }
+
+ private void Navigate(RoutableScreen viewModel)
+ {
+ Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle);
+ }
+
+ private void UpdateEntriesPerFetch()
+ {
+ if (ViewModel != null)
+ ViewModel.EntriesPerFetch = (int) (EntriesScrollViewer.Viewport.Height / 120);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
index d8ae07a17..09ed5410b 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
@@ -10,12 +10,11 @@ namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
public class ProfileListViewModel : List.EntryListViewModel
{
public ProfileListViewModel(IWorkshopClient workshopClient,
- IRouter router,
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func getEntryListViewModel)
- : base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
+ : base("workshop/entries/profiles", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search profiles";
}
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
index c2ed7ebb8..4d535f0ba 100644
--- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
@@ -41,7 +41,7 @@
-