1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Router - Require routable VMs to implement new RoutableScreen

This commit is contained in:
Robert 2023-09-02 15:52:17 +02:00
parent 318ec99ad4
commit c132edeb51
43 changed files with 284 additions and 287 deletions

View File

@ -0,0 +1,13 @@
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// For internal use.
/// </summary>
/// <seealso cref="RoutableHostScreen{TScreen}" />
/// <seealso cref="RoutableHostScreen{TScreen,TParam}" />
internal interface IRoutableHostScreen : IRoutableScreen
{
bool RecycleScreen { get; }
IRoutableScreen? InternalScreen { get; }
void InternalChangeScreen(IRoutableScreen? screen);
}

View File

@ -0,0 +1,14 @@
using System.Threading;
using System.Threading.Tasks;
using ReactiveUI;
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// For internal use.
/// </summary>
internal interface IRoutableScreen : IActivatableViewModel
{
Task InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken);
Task InternalOnClosing(NavigationArguments args);
}

View File

@ -0,0 +1,42 @@
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// Represents a view model to which routing can take place and which in turn can host another view model.
/// </summary>
/// <typeparam name="TScreen">The type of view model the screen can host.</typeparam>
public abstract class RoutableHostScreen<TScreen> : RoutableScreen, IRoutableHostScreen where TScreen : RoutableScreen
{
private bool _recycleScreen = true;
private TScreen? _screen;
/// <summary>
/// Gets the currently active child screen.
/// </summary>
public TScreen? Screen
{
get => _screen;
private set => RaiseAndSetIfChanged(ref _screen, value);
}
/// <inheritdoc />
public bool RecycleScreen
{
get => _recycleScreen;
protected set => RaiseAndSetIfChanged(ref _recycleScreen, value);
}
IRoutableScreen? IRoutableHostScreen.InternalScreen => Screen;
void IRoutableHostScreen.InternalChangeScreen(IRoutableScreen? screen)
{
if (screen == null)
{
Screen = null;
return;
}
if (screen is not TScreen typedScreen)
throw new ArtemisRoutingException($"Screen cannot be hosted, {screen.GetType().Name} is not assignable to {typeof(TScreen).Name}.");
Screen = typedScreen;
}
}

View File

@ -0,0 +1,44 @@
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// Represents a view model to which routing with parameters can take place and which in turn can host another view
/// model.
/// </summary>
/// <typeparam name="TScreen">The type of view model the screen can host.</typeparam>
/// <typeparam name="TParam">The type of parameters the screen expects. It must have a parameterless constructor.</typeparam>
public abstract class RoutableHostScreen<TScreen, TParam> : RoutableScreen<TParam>, IRoutableHostScreen where TScreen : RoutableScreen where TParam : new()
{
private bool _recycleScreen = true;
private TScreen? _screen;
/// <summary>
/// Gets the currently active child screen.
/// </summary>
public TScreen? Screen
{
get => _screen;
private set => RaiseAndSetIfChanged(ref _screen, value);
}
/// <inheritdoc />
public bool RecycleScreen
{
get => _recycleScreen;
protected set => RaiseAndSetIfChanged(ref _recycleScreen, value);
}
IRoutableScreen? IRoutableHostScreen.InternalScreen => Screen;
void IRoutableHostScreen.InternalChangeScreen(IRoutableScreen? screen)
{
if (screen == null)
{
Screen = null;
return;
}
if (screen is not TScreen typedScreen)
throw new ArtemisRoutingException($"Screen cannot be hosted, {screen.GetType().Name} is not assignable to {typeof(TScreen).Name}.");
Screen = typedScreen;
}
}

View File

@ -1,24 +1,55 @@
using System.Threading;
using System.Threading.Tasks;
using ReactiveUI;
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// For internal use.
/// Represents a view model to which routing can take place.
/// </summary>
/// <seealso cref="RoutableScreen{TScreen}"/>
/// <seealso cref="RoutableScreen{TScreen, TParam}"/>
internal interface IRoutableScreen : IActivatableViewModel
public abstract class RoutableScreen : ActivatableViewModelBase, IRoutableScreen
{
/// <summary>
/// Gets or sets a value indicating whether or not to reuse the child screen instance if the type has not changed.
/// Called before navigating to this screen.
/// </summary>
/// <remarks>Defaults to <see langword="true"/>.</remarks>
bool RecycleScreen { get; }
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task BeforeNavigating(NavigationArguments args)
{
return Task.CompletedTask;
}
object? InternalScreen { get; }
void InternalChangeScreen(object? screen);
Task InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken);
Task InternalOnClosing(NavigationArguments args);
/// <summary>
/// Called while navigating to this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used by other objects or threads to receive notice of
/// cancellation.
/// </param>
public virtual Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// Called before navigating away from this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task OnClosing(NavigationArguments args)
{
return Task.CompletedTask;
}
#region Overrides of RoutableScreen
async Task IRoutableScreen.InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
await OnNavigating(args, cancellationToken);
}
async Task IRoutableScreen.InternalOnClosing(NavigationArguments args)
{
await OnClosing(args);
}
#endregion
}

View File

@ -4,39 +4,15 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// Represents a view model to which routing with parameters can take place and which in turn can host another view
/// model.
/// Represents a view model to which routing with parameters can take place.
/// </summary>
/// <typeparam name="TScreen">The type of view model the screen can host.</typeparam>
/// <typeparam name="TParam">The type of parameters the screen expects. It must have a parameterless constructor.</typeparam>
public abstract class RoutableScreen<TScreen, TParam> : ActivatableViewModelBase, IRoutableScreen where TScreen : class where TParam : new()
public abstract class RoutableScreen<TParam> : RoutableScreen, IRoutableScreen where TParam : new()
{
private bool _recycleScreen = true;
private TScreen? _screen;
/// <summary>
/// Gets the currently active child screen.
/// </summary>
public TScreen? Screen
{
get => _screen;
private set => RaiseAndSetIfChanged(ref _screen, value);
}
/// <summary>
/// Called before navigating to this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task BeforeNavigating(NavigationArguments args)
{
return Task.CompletedTask;
}
/// <summary>
/// Called while navigating to this screen.
/// </summary>
@ -50,40 +26,7 @@ public abstract class RoutableScreen<TScreen, TParam> : ActivatableViewModelBase
{
return Task.CompletedTask;
}
/// <summary>
/// Called before navigating away from this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task OnClosing(NavigationArguments args)
{
return Task.CompletedTask;
}
/// <inheritdoc />
public bool RecycleScreen
{
get => _recycleScreen;
protected set => RaiseAndSetIfChanged(ref _recycleScreen, value);
}
#region Overrides of RoutableScreen
object? IRoutableScreen.InternalScreen => Screen;
void IRoutableScreen.InternalChangeScreen(object? screen)
{
if (screen == null)
{
Screen = null;
return;
}
if (screen is not TScreen typedScreen)
throw new ArtemisRoutingException($"Provided screen is not assignable to {typeof(TScreen).FullName}");
Screen = typedScreen;
}
async Task IRoutableScreen.InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
Func<object[], TParam> activator = GetParameterActivator();
@ -92,6 +35,7 @@ public abstract class RoutableScreen<TScreen, TParam> : ActivatableViewModelBase
throw new ArtemisRoutingException($"Did not retrieve the required amount of parameters, expects {_parameterPropertyCount}, got {args.SegmentParameters.Length}.");
TParam parameters = activator(args.SegmentParameters);
await OnNavigating(args, cancellationToken);
await OnNavigating(parameters, args, cancellationToken);
}
@ -100,8 +44,6 @@ public abstract class RoutableScreen<TScreen, TParam> : ActivatableViewModelBase
await OnClosing(args);
}
#endregion
#region Parameter generation
// ReSharper disable once StaticMemberInGenericType - That's intentional, each kind of TParam should have its own property count

View File

@ -1,87 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Routing;
/// <summary>
/// Represents a view model to which routing can take place and which in turn can host another view model.
/// </summary>
/// <typeparam name="TScreen">The type of view model the screen can host.</typeparam>
public abstract class RoutableScreen<TScreen> : ActivatableViewModelBase, IRoutableScreen where TScreen : class
{
private TScreen? _screen;
private bool _recycleScreen = true;
/// <summary>
/// Gets the currently active child screen.
/// </summary>
public TScreen? Screen
{
get => _screen;
private set => RaiseAndSetIfChanged(ref _screen, value);
}
/// <inheritdoc />
public bool RecycleScreen
{
get => _recycleScreen;
protected set => RaiseAndSetIfChanged(ref _recycleScreen, value);
}
/// <summary>
/// Called before navigating to this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task BeforeNavigating(NavigationArguments args)
{
return Task.CompletedTask;
}
/// <summary>
/// Called while navigating to this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
public virtual Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// Called before navigating away from this screen.
/// </summary>
/// <param name="args">Navigation arguments containing information about the navigation action.</param>
public virtual Task OnClosing(NavigationArguments args)
{
return Task.CompletedTask;
}
#region Overrides of RoutableScreen
object? IRoutableScreen.InternalScreen => Screen;
void IRoutableScreen.InternalChangeScreen(object? screen)
{
if (screen == null)
{
Screen = null;
return;
}
if (screen is not TScreen typedScreen)
throw new ArtemisRoutingException($"Screen cannot be hosted, {screen.GetType().Name} is not assignable to {typeof(TScreen).Name}.");
Screen = typedScreen;
}
async Task IRoutableScreen.InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
await OnNavigating(args, cancellationToken);
}
async Task IRoutableScreen.InternalOnClosing(NavigationArguments args)
{
await OnClosing(args);
}
#endregion
}

View File

@ -7,7 +7,7 @@ namespace Artemis.UI.Shared.Routing;
/// Represents a registration for a route and its associated view model.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model associated with the route.</typeparam>
public class RouteRegistration<TViewModel> : IRouterRegistration where TViewModel : ViewModelBase
public class RouteRegistration<TViewModel> : IRouterRegistration where TViewModel : RoutableScreen
{
/// <summary>
/// Initializes a new instance of the <see cref="RouteRegistration{TViewModel}" /> class.

View File

@ -74,19 +74,12 @@ internal class RouteResolution
};
}
public object GetViewModel(IContainer container)
public RoutableScreen GetViewModel(IContainer container)
{
if (ViewModel == null)
throw new ArtemisRoutingException("Cannot get a view model of a non-success route resolution");
object? viewModel = container.Resolve(ViewModel);
if (viewModel == null)
throw new ArtemisRoutingException($"Could not resolve view model of type {ViewModel}");
return viewModel;
return GetViewModel<RoutableScreen>(container);
}
public T GetViewModel<T>(IContainer container)
public T GetViewModel<T>(IContainer container) where T : RoutableScreen
{
if (ViewModel == null)
throw new ArtemisRoutingException("Cannot get a view model of a non-success route resolution");

View File

@ -50,7 +50,7 @@ public interface IRouter
/// </summary>
/// <param name="root">The root screen to set.</param>
/// <typeparam name="TScreen">The type of the root screen. It must be a class.</typeparam>
void SetRoot<TScreen>(RoutableScreen<TScreen> root) where TScreen : class;
void SetRoot<TScreen>(RoutableHostScreen<TScreen> root) where TScreen : RoutableScreen;
/// <summary>
/// Sets the root screen from which navigation takes place.
@ -58,7 +58,7 @@ public interface IRouter
/// <param name="root">The root screen to set.</param>
/// <typeparam name="TScreen">The type of the root screen. It must be a class.</typeparam>
/// <typeparam name="TParam">The type of the parameters for the root screen. It must have a parameterless constructor.</typeparam>
void SetRoot<TScreen, TParam>(RoutableScreen<TScreen, TParam> root) where TScreen : class where TParam : new();
void SetRoot<TScreen, TParam>(RoutableHostScreen<TScreen, TParam> root) where TScreen : RoutableScreen where TParam : new();
/// <summary>
/// Clears the route used by the previous window, so that it is not restored when the main window opens.

View File

@ -14,12 +14,12 @@ internal class Navigation
private readonly IContainer _container;
private readonly ILogger _logger;
private readonly IRoutableScreen _root;
private readonly IRoutableHostScreen _root;
private readonly RouteResolution _resolution;
private readonly RouterNavigationOptions _options;
private CancellationTokenSource _cts;
public Navigation(IContainer container, ILogger logger, IRoutableScreen root, RouteResolution resolution, RouterNavigationOptions options)
public Navigation(IContainer container, ILogger logger, IRoutableHostScreen root, RouteResolution resolution, RouterNavigationOptions options)
{
_container = container;
_logger = logger;
@ -54,21 +54,21 @@ internal class Navigation
_cts.Cancel();
}
private async Task NavigateResolution(RouteResolution resolution, NavigationArguments args, IRoutableScreen host)
private async Task NavigateResolution(RouteResolution resolution, NavigationArguments args, IRoutableHostScreen host)
{
if (Cancelled)
return;
// Reuse the screen if its type has not changed, if a new one must be created, don't do so on the UI thread
object screen;
IRoutableScreen screen;
if (_options.RecycleScreens && host.RecycleScreen && host.InternalScreen != null && host.InternalScreen.GetType() == resolution.ViewModel)
screen = host.InternalScreen;
else
screen = await Task.Run(() => resolution.GetViewModel(_container));
// If resolution has a child, ensure the screen can host it
if (resolution.Child != null && screen is not IRoutableScreen)
throw new ArtemisRoutingException($"Route resolved with a child but view model of type {resolution.ViewModel} is does mot implement {nameof(IRoutableScreen)}.");
if (resolution.Child != null && screen is not IRoutableHostScreen)
throw new ArtemisRoutingException($"Route resolved with a child but view model of type {resolution.ViewModel} is does mot implement {nameof(IRoutableHostScreen)}.");
// Only change the screen if it wasn't reused
if (!ReferenceEquals(host.InternalScreen, screen))
@ -87,27 +87,24 @@ internal class Navigation
if (CancelIfRequested(args, "ChangeScreen", screen))
return;
// If the screen implements some form of Navigable, activate it
// Navigate on the screen
args.SegmentParameters = resolution.Parameters ?? Array.Empty<object>();
if (screen is IRoutableScreen routableScreen)
try
{
try
{
await routableScreen.InternalOnNavigating(args, _cts.Token);
}
catch (Exception e)
{
Cancel();
if (e is not TaskCanceledException)
_logger.Error(e, "Failed to navigate to {Path}", resolution.Path);
}
if (CancelIfRequested(args, "OnNavigating", screen))
return;
await screen.InternalOnNavigating(args, _cts.Token);
}
catch (Exception e)
{
Cancel();
if (e is not TaskCanceledException)
_logger.Error(e, "Failed to navigate to {Path}", resolution.Path);
}
if (screen is IRoutableScreen childScreen)
if (CancelIfRequested(args, "OnNavigating", screen))
return;
if (screen is IRoutableHostScreen childScreen)
{
// Navigate the child too
if (resolution.Child != null)

View File

@ -14,15 +14,15 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
private readonly Stack<string> _backStack = new();
private readonly BehaviorSubject<string?> _currentRouteSubject;
private readonly Stack<string> _forwardStack = new();
private readonly Func<IRoutableScreen, RouteResolution, RouterNavigationOptions, Navigation> _getNavigation;
private readonly Func<IRoutableHostScreen, RouteResolution, RouterNavigationOptions, Navigation> _getNavigation;
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
private Navigation? _currentNavigation;
private IRoutableScreen? _root;
private IRoutableHostScreen? _root;
private string? _previousWindowRoute;
public Router(ILogger logger, IMainWindowService mainWindowService, Func<IRoutableScreen, RouteResolution, RouterNavigationOptions, Navigation> getNavigation)
public Router(ILogger logger, IMainWindowService mainWindowService, Func<IRoutableHostScreen, RouteResolution, RouterNavigationOptions, Navigation> getNavigation)
{
_logger = logger;
_mainWindowService = mainWindowService;
@ -45,21 +45,17 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
return RouteResolution.AsFailure(path);
}
private async Task<bool> RequestClose(object screen, NavigationArguments args)
private async Task<bool> RequestClose(IRoutableScreen screen, NavigationArguments args)
{
if (screen is not IRoutableScreen routableScreen)
return true;
await routableScreen.InternalOnClosing(args);
if (args.Cancelled)
{
_logger.Debug("Navigation to {Path} cancelled during RequestClose by {Screen}", args.Path, screen.GetType().Name);
// Drill down to child screens first
if (screen is IRoutableHostScreen hostScreen && hostScreen.InternalScreen != null && !await RequestClose(hostScreen.InternalScreen, args))
return false;
}
if (routableScreen.InternalScreen == null)
await screen.InternalOnClosing(args);
if (!args.Cancelled)
return true;
return await RequestClose(routableScreen.InternalScreen, args);
_logger.Debug("Navigation to {Path} cancelled during RequestClose by {Screen}", args.Path, screen.GetType().Name);
return false;
}
private bool PathEquals(string path, bool allowPartialMatch)
@ -161,13 +157,13 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
}
/// <inheritdoc />
public void SetRoot<TScreen>(RoutableScreen<TScreen> root) where TScreen : class
public void SetRoot<TScreen>(RoutableHostScreen<TScreen> root) where TScreen : RoutableScreen
{
_root = root;
}
/// <inheritdoc />
public void SetRoot<TScreen, TParam>(RoutableScreen<TScreen, TParam> root) where TScreen : class where TParam : new()
public void SetRoot<TScreen, TParam>(RoutableHostScreen<TScreen, TParam> root) where TScreen : RoutableScreen where TParam : new()
{
_root = root;
}

View File

@ -35,8 +35,9 @@ public static class Routes
{
Children = new List<IRouterRegistration>()
{
new RouteRegistration<LibraryInstalledViewModel>("installed"),
new RouteRegistration<LibrarySubmissionsViewModel>("submissions"),
new RouteRegistration<InstalledTabViewModel>("installed"),
new RouteRegistration<SubmissionsTabViewModel>("submissions"),
new RouteRegistration<SubmissionsDetailViewModel>("submissions/{entryId:guid}"),
}
}
}

View File

@ -1,12 +1,13 @@
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Home;
public class HomeViewModel : ViewModelBase, IMainScreenViewModel
public class HomeViewModel : RoutableScreen, IMainScreenViewModel
{
public HomeViewModel(ISettingsService settingsService, IWindowService windowService)
{

View File

@ -17,14 +17,13 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorViewModel : RoutableScreen<object, ProfileEditorViewModelParameters>, IMainScreenViewModel
public class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParameters>, IMainScreenViewModel
{
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService;

View File

@ -1,8 +1,9 @@
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
namespace Artemis.UI.Screens.Root;
public class BlankViewModel : ViewModelBase, IMainScreenViewModel
public class BlankViewModel : RoutableScreen, IMainScreenViewModel
{
/// <inheritdoc />
public ViewModelBase? TitleBarViewModel => null;

View File

@ -1,5 +1,6 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Shared.Routing;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
@ -14,7 +15,7 @@ public partial class RootView : ReactiveUserControl<RootViewModel>
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d));
}
private void Navigate(IMainScreenViewModel viewModel)
private void Navigate(RoutableScreen viewModel)
{
try
{

View File

@ -18,7 +18,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Root;
public class RootViewModel : RoutableScreen<IMainScreenViewModel>, IMainWindowProvider
public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProvider
{
private readonly ICoreService _coreService;
private readonly IDebugService _debugService;
@ -100,12 +100,10 @@ public class RootViewModel : RoutableScreen<IMainScreenViewModel>, IMainWindowPr
_router.GoForward();
}
private void UpdateTitleBarViewModel(IMainScreenViewModel? viewModel)
private void UpdateTitleBarViewModel(RoutableScreen? viewModel)
{
if (viewModel?.TitleBarViewModel != null)
TitleBarViewModel = viewModel.TitleBarViewModel;
else
TitleBarViewModel = _defaultTitleBarViewModel;
IMainScreenViewModel? mainScreenViewModel = viewModel as IMainScreenViewModel;
TitleBarViewModel = mainScreenViewModel?.TitleBarViewModel ?? _defaultTitleBarViewModel;
}
private void CurrentMainWindowOnClosing(object? sender, EventArgs e)

View File

@ -11,7 +11,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Settings;
public class SettingsViewModel : RoutableScreen<ActivatableViewModelBase>, IMainScreenViewModel
public class SettingsViewModel : RoutableHostScreen<RoutableScreen>, IMainScreenViewModel
{
private readonly IRouter _router;
private RouteViewModel? _selectedTab;

View File

@ -1,9 +1,9 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
namespace Artemis.UI.Screens.Settings;
public class AboutTabViewModel : ActivatableViewModelBase
public class AboutTabViewModel : RoutableScreen
{
public AboutTabViewModel()
{

View File

@ -8,7 +8,7 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Device;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using DynamicData;
@ -16,7 +16,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Settings;
public class DevicesTabViewModel : ActivatableViewModelBase
public class DevicesTabViewModel : RoutableScreen
{
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;

View File

@ -13,8 +13,8 @@ using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Services.Updating;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Threading;
@ -26,7 +26,7 @@ using Serilog.Events;
namespace Artemis.UI.Screens.Settings;
public class GeneralTabViewModel : ActivatableViewModelBase
public class GeneralTabViewModel : RoutableScreen
{
private readonly IAutoRunProvider? _autoRunProvider;
private readonly IDebugService _debugService;

View File

@ -9,18 +9,17 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings;
public class PluginsTabViewModel : ActivatableViewModelBase
public class PluginsTabViewModel : RoutableScreen
{
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;

View File

@ -22,7 +22,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Settings;
public class ReleasesTabViewModel : RoutableScreen<ReleaseDetailsViewModel>
public class ReleasesTabViewModel : RoutableHostScreen<ReleaseDetailsViewModel>
{
private readonly ILogger _logger;
private readonly IUpdateService _updateService;

View File

@ -18,7 +18,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Settings.Updating;
public class ReleaseDetailsViewModel : RoutableScreen<ViewModelBase, ReleaseDetailsViewModelParameters>
public class ReleaseDetailsViewModel : RoutableScreen<ReleaseDetailsViewModelParameters>
{
private readonly ObservableAsPropertyHelper<long> _fileSize;
private readonly ILogger _logger;

View File

@ -10,6 +10,7 @@ using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Avalonia;
using ReactiveUI;
@ -17,7 +18,7 @@ using SkiaSharp;
namespace Artemis.UI.Screens.SurfaceEditor;
public class SurfaceEditorViewModel : ActivatableViewModelBase, IMainScreenViewModel
public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
{
private readonly IDeviceService _deviceService;
private readonly IDeviceVmFactory _deviceVmFactory;

View File

@ -18,7 +18,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Entries;
public abstract class EntryListBaseViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopListParameters>, IWorkshopViewModel
public abstract class EntryListBaseViewModel : RoutableScreen<WorkshopListParameters>
{
private readonly INotificationService _notificationService;
private readonly IWorkshopClient _workshopClient;

View File

@ -14,7 +14,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Home;
public class WorkshopHomeViewModel : ActivatableViewModelBase, IWorkshopViewModel
public class WorkshopHomeViewModel : RoutableScreen
{
private readonly IWindowService _windowService;
private readonly IWorkshopService _workshopService;

View File

@ -10,7 +10,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Home;
public class WorkshopOfflineViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopOfflineParameters>, IWorkshopViewModel
public class WorkshopOfflineViewModel : RoutableScreen<WorkshopOfflineParameters>
{
private readonly IRouter _router;
private readonly IWorkshopService _workshopService;

View File

@ -9,7 +9,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Layout;
public class LayoutDetailsViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopDetailParameters>, IWorkshopViewModel
public class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
{
private readonly IWorkshopClient _client;
private IGetEntryById_Entry? _entry;

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.LibraryInstalledView">
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.InstalledTabView">
Installed entries management here 🫡
</UserControl>

View File

@ -5,9 +5,9 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class LibraryInstalledView : ReactiveUserControl<LibraryInstalledViewModel>
public partial class InstalledTabView : ReactiveUserControl<InstalledTabViewModel>
{
public LibraryInstalledView()
public InstalledTabView()
{
InitializeComponent();
}

View File

@ -1,8 +1,9 @@
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public class LibraryInstalledViewModel : ActivatableViewModelBase
public class InstalledTabViewModel : RoutableScreen
{
}

View File

@ -0,0 +1,8 @@
<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="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.SubmissionsDetailView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class SubmissionsDetailView : ReactiveUserControl<SubmissionsDetailViewModel>
{
public SubmissionsDetailView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Shared.Routing;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public class SubmissionsDetailViewModel : RoutableScreen<WorkshopDetailParameters>
{
public override Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{
Console.WriteLine(parameters.EntryId);
return Task.CompletedTask;
}
}

View File

@ -8,8 +8,8 @@
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.LibrarySubmissionsView"
x:DataType="tabs:LibrarySubmissionsViewModel">
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.SubmissionsTabView"
x:DataType="tabs:SubmissionsTabViewModel">
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" />
@ -54,7 +54,7 @@
Margin="0 0 0 5"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Command="{Binding $parent[tabs:LibrarySubmissionsView].DataContext.NavigateToEntry}"
Command="{Binding $parent[tabs:SubmissionsTabView].DataContext.NavigateToEntry}"
CommandParameter="{CompiledBinding}">
<Grid ColumnDefinitions="Auto,*,Auto">
<!-- Icon -->

View File

@ -5,9 +5,9 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class LibrarySubmissionsView : ReactiveUserControl<LibrarySubmissionsViewModel>
public partial class SubmissionsTabView : ReactiveUserControl<SubmissionsTabViewModel>
{
public LibrarySubmissionsView()
public SubmissionsTabView()
{
InitializeComponent();
}

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.CurrentUser;
using Artemis.UI.Screens.Workshop.SubmissionWizard;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
@ -17,7 +16,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public class LibrarySubmissionsViewModel : ActivatableViewModelBase, IWorkshopViewModel
public class SubmissionsTabViewModel : RoutableScreen
{
private readonly IWorkshopClient _client;
private readonly SourceCache<IGetSubmittedEntries_SubmittedEntries, Guid> _entries;
@ -25,7 +24,7 @@ public class LibrarySubmissionsViewModel : ActivatableViewModelBase, IWorkshopVi
private bool _isLoading = true;
private bool _workshopReachable;
public LibrarySubmissionsViewModel(IWorkshopClient client, IAuthenticationService authenticationService, IWindowService windowService, IWorkshopService workshopService, IRouter router)
public SubmissionsTabViewModel(IWorkshopClient client, IAuthenticationService authenticationService, IWindowService windowService, IWorkshopService workshopService, IRouter router)
{
_client = client;
_windowService = windowService;

View File

@ -12,7 +12,7 @@ using System;
namespace Artemis.UI.Screens.Workshop.Library;
public class WorkshopLibraryViewModel : RoutableScreen<ActivatableViewModelBase>, IWorkshopViewModel
public class WorkshopLibraryViewModel : RoutableHostScreen<RoutableScreen>
{
private RouteViewModel? _selectedTab;

View File

@ -18,7 +18,7 @@ using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Profile;
public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopDetailParameters>, IWorkshopViewModel
public class ProfileDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
{
private readonly IWorkshopClient _client;
private readonly ProfileEntryDownloadHandler _downloadHandler;

View File

@ -19,7 +19,6 @@ public class SearchViewModel : ViewModelBase
private readonly ILogger _logger;
private readonly IRouter _router;
private readonly IWorkshopClient _workshopClient;
private EntryType? _entryType;
private bool _isLoading;
private SearchResultViewModel? _selectedEntry;
@ -44,12 +43,6 @@ public class SearchViewModel : ViewModelBase
set => RaiseAndSetIfChanged(ref _selectedEntry, value);
}
public EntryType? EntryType
{
get => _entryType;
set => RaiseAndSetIfChanged(ref _entryType, value);
}
public bool IsLoading
{
get => _isLoading;
@ -76,7 +69,7 @@ public class SearchViewModel : ViewModelBase
return new List<object>();
IsLoading = true;
IOperationResult<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(input, EntryType, cancellationToken);
IOperationResult<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(input, null, cancellationToken);
return results.Data?.SearchEntries.Select(e => new SearchResultViewModel(e) as object) ?? new List<object>();
}
catch (Exception e)

View File

@ -1,38 +1,18 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.Home;
using Artemis.UI.Screens.Workshop.Home;
using Artemis.UI.Screens.Workshop.Search;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop;
public class WorkshopViewModel : RoutableScreen<IWorkshopViewModel>, IMainScreenViewModel
public class WorkshopViewModel : RoutableHostScreen<RoutableScreen>, IMainScreenViewModel
{
private readonly SearchViewModel _searchViewModel;
public WorkshopViewModel(SearchViewModel searchViewModel, WorkshopHomeViewModel homeViewModel)
{
_searchViewModel = searchViewModel;
TitleBarViewModel = searchViewModel;
HomeViewModel = homeViewModel;
}
public ViewModelBase TitleBarViewModel { get; }
public WorkshopHomeViewModel HomeViewModel { get; }
/// <inheritdoc />
public override Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken)
{
_searchViewModel.EntryType = Screen?.EntryType;
return Task.CompletedTask;
}
}
public interface IWorkshopViewModel
{
public EntryType? EntryType { get; }
}