mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
139 lines
4.5 KiB
C#
139 lines
4.5 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 IRoutableScreen _root;
|
|
private readonly RouteResolution _resolution;
|
|
private readonly RouterNavigationOptions _options;
|
|
private CancellationTokenSource _cts;
|
|
|
|
public Navigation(IContainer container, ILogger logger, IRoutableScreen 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)
|
|
{
|
|
_logger.Information("Navigating to {Path}", _resolution.Path);
|
|
_cts = new CancellationTokenSource();
|
|
await NavigateResolution(_resolution, args, _root);
|
|
|
|
if (!Cancelled)
|
|
_logger.Information("Navigated to {Path}", _resolution.Path);
|
|
}
|
|
|
|
public void Cancel()
|
|
{
|
|
if (Cancelled || Completed)
|
|
return;
|
|
|
|
_logger.Information("Cancelled navigation to {Path}", _resolution.Path);
|
|
_cts.Cancel();
|
|
}
|
|
|
|
private async Task NavigateResolution(RouteResolution resolution, NavigationArguments args, IRoutableScreen 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;
|
|
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)}.");
|
|
|
|
// Only change the screen if it wasn't reused
|
|
if (!ReferenceEquals(host.InternalScreen, screen))
|
|
{
|
|
try
|
|
{
|
|
host.InternalChangeScreen(screen);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Cancel();
|
|
_logger.Error(e, "Failed to navigate to {Path}", resolution.Path);
|
|
}
|
|
}
|
|
|
|
if (CancelIfRequested(args, "ChangeScreen", screen))
|
|
return;
|
|
|
|
// If the screen implements some form of Navigable, activate it
|
|
args.SegmentParameters = resolution.Parameters ?? Array.Empty<object>();
|
|
if (screen is IRoutableScreen routableScreen)
|
|
{
|
|
try
|
|
{
|
|
await routableScreen.InternalOnNavigating(args, _cts.Token);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Cancel();
|
|
_logger.Error(e, "Failed to navigate to {Path}", resolution.Path);
|
|
}
|
|
|
|
if (CancelIfRequested(args, "OnNavigating", screen))
|
|
return;
|
|
}
|
|
|
|
if (screen is IRoutableScreen 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, bool allowPartialMatch)
|
|
{
|
|
if (allowPartialMatch)
|
|
return _resolution.Path.StartsWith(path, StringComparison.InvariantCultureIgnoreCase);
|
|
return string.Equals(_resolution.Path, path, StringComparison.InvariantCultureIgnoreCase);
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |