1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
Robert 2ee170b803 Workshop - Fixed deep linking to an entry
Workshop - Added the ability to upload new releases to existing submissions
2023-09-04 20:30:57 +02:00

138 lines
4.4 KiB
C#

using System;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
using DryIoc;
using ReactiveUI;
using Serilog;
namespace Artemis.UI.Shared.Routing;
internal class Navigation
{
private readonly IContainer _container;
private readonly ILogger _logger;
private readonly IRoutableHostScreen _root;
private readonly RouteResolution _resolution;
private readonly RouterNavigationOptions _options;
private CancellationTokenSource _cts;
public Navigation(IContainer container, ILogger logger, IRoutableHostScreen root, RouteResolution resolution, RouterNavigationOptions options)
{
_container = container;
_logger = logger;
_root = root;
_resolution = resolution;
_options = options;
_cts = new CancellationTokenSource();
}
public bool Cancelled => _cts.IsCancellationRequested;
public bool Completed { get; private set; }
public async Task Navigate(NavigationArguments args)
{
if (_options.EnableLogging)
_logger.Information("Navigating to {Path}", _resolution.Path);
_cts = new CancellationTokenSource();
await NavigateResolution(_resolution, args, _root);
if (!Cancelled && _options.EnableLogging)
_logger.Information("Navigated to {Path}", _resolution.Path);
}
public void Cancel()
{
if (Cancelled || Completed)
return;
if (_options.EnableLogging)
_logger.Information("Cancelled navigation to {Path}", _resolution.Path);
_cts.Cancel();
}
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
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 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))
{
try
{
host.InternalChangeScreen(screen);
}
catch (Exception e)
{
Cancel();
if (e is not TaskCanceledException)
_logger.Error(e, "Failed to navigate to {Path}", resolution.Path);
}
}
if (CancelIfRequested(args, "ChangeScreen", screen))
return;
// Navigate on the screen
args.SegmentParameters = resolution.Parameters ?? Array.Empty<object>();
try
{
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 (CancelIfRequested(args, "OnNavigating", screen))
return;
if (screen is IRoutableHostScreen childScreen)
{
// Navigate the child too
if (resolution.Child != null)
await NavigateResolution(resolution.Child, args, childScreen);
// Make sure there is no child
else if (childScreen.InternalScreen != null)
childScreen.InternalChangeScreen(null);
}
Completed = true;
}
public bool PathEquals(string path, RouterNavigationOptions options)
{
return options.PathEquals(_resolution.Path, path);
}
private bool CancelIfRequested(NavigationArguments args, string stage, object screen)
{
if (Cancelled)
return true;
if (!args.Cancelled)
return false;
_logger.Debug("Navigation to {Path} during {Stage} by {Screen}", args.Path, stage, screen.GetType().Name);
Cancel();
return true;
}
}