Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delay navigation by 60ms to let user animation run on the screen when… #36

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Source/Nalu.Maui.Navigation/Internals/NavigationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class NavigationService : INavigationService, IDisposable
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly AsyncLocal<StrongBox<bool>> _isNavigating = new();
private readonly LeakDetector? _leakDetector;
private readonly TimeProvider _timeProvider;
private IShellProxy? _shellProxy;

public IShellProxy ShellProxy => _shellProxy ?? throw new InvalidOperationException("You must use NaluShell to navigate with INavigationService.");
Expand All @@ -20,6 +21,7 @@ public NavigationService(INavigationConfiguration configuration, IServiceProvide
{
Configuration = configuration;
_serviceProvider = serviceProvider;
_timeProvider = serviceProvider.GetService<TimeProvider>() ?? TimeProvider.System;

var trackLeaks
= (Configuration.LeakDetectorState == NavigationLeakDetectorState.EnabledWithDebugger && Debugger.IsAttached) ||
Expand Down Expand Up @@ -69,6 +71,11 @@ public async Task<bool> GoToAsync(INavigationInfo navigation)

return await ExecuteNavigationAsync(async () =>
{
if (navigation.Behavior?.HasFlag(NavigationBehavior.Immediate) != true)
{
await Task.Delay(TimeSpan.FromMilliseconds(60), _timeProvider).ConfigureAwait(true);
}

shellProxy.BeginNavigation();
try
{
Expand Down
8 changes: 8 additions & 0 deletions Source/Nalu.Maui.Navigation/NavigationBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ public enum NavigationBehavior
/// When popping a page, the <see cref="ILeavingGuard"/>s will be ignored.
/// </summary>
IgnoreGuards = 0x04,

/// <summary>
/// Immediately navigates to the target page without waiting 60 milliseconds.
/// </summary>
/// <remarks>
/// Default behavior is to wait 60 milliseconds before navigating to the target page to let touch be displayed.
/// </remarks>
Immediate = 0x08,
}
140 changes: 70 additions & 70 deletions Source/Nalu.Maui.Navigation/ShellInfo/ShellProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,32 @@ public async Task CommitNavigationAsync(Action? completeAction = null)
return;
}

var contentChanged = _contentChanged;
_navigationTarget = null;
_contentChanged = false;
_navigationCurrentSection = null;

try
{
_shell.SetIsNavigating(true);

var contentChanged = _contentChanged;
_navigationTarget = null;
_contentChanged = false;
_navigationCurrentSection = null;

await _shell.GoToAsync(targetState, true).ConfigureAwait(true);
await Task.Yield();

if (contentChanged)
{
// Wait for the animation to complete
// I know this is a hack, but I don't see any other way to do this
// given `shell.GoToAsync` does not wait for the animation to complete
await Task.Delay(600).ConfigureAwait(true);
}

completeAction?.Invoke();
}
finally
{
_shell.SetIsNavigating(false);
}

await Task.Yield();

if (contentChanged)
{
// Wait for the animation to complete
// I know this is a hack, but I don't see any other way to do this
// given `shell.GoToAsync` does not wait for the animation to complete
await Task.Delay(500).ConfigureAwait(true);
}

completeAction?.Invoke();
}

public IShellContentProxy GetContent(string segmentName) => _contentsBySegmentName[segmentName];
Expand All @@ -98,87 +98,87 @@ public IShellContentProxy FindContent(params string[] names)
throw new KeyNotFoundException($"Could not find content with segment name '{name}'");
}

public Color GetToolbarIconColor(Page page) => Shell.GetTitleColor(page.IsSet(Shell.TitleColorProperty) ? page : _shell);
public Color GetToolbarIconColor(Page page) =>
Shell.GetTitleColor(page.IsSet(Shell.TitleColorProperty) ? page : _shell);

public async Task PushAsync(string segmentName, Page page)
{
try
{
_shell.SetIsNavigating(true);
var baseRoute = _navigationTarget ?? _shell.CurrentState.Location.OriginalString;
var finalRoute = $"{baseRoute}/{segmentName}";

var baseRoute = _navigationTarget ?? _shell.CurrentState.Location.OriginalString;
var finalRoute = $"{baseRoute}/{segmentName}";
var pageTypeRouteFactory = _routeFactory.GetRouteFactory(page.GetType());
pageTypeRouteFactory.Push(page);

var pageTypeRouteFactory = _routeFactory.GetRouteFactory(page.GetType());
pageTypeRouteFactory.Push(page);

if (!_registeredSegments.Contains(segmentName))
{
Routing.RegisterRoute(segmentName, pageTypeRouteFactory);
_registeredSegments.Add(segmentName);
}
if (!_registeredSegments.Contains(segmentName))
{
Routing.RegisterRoute(segmentName, pageTypeRouteFactory);
_registeredSegments.Add(segmentName);
}

if (_navigationTarget != null)
if (_navigationTarget != null)
{
_navigationTarget = finalRoute;
}
else
{
try
{
_navigationTarget = finalRoute;
_shell.SetIsNavigating(true);
await _shell.GoToAsync(finalRoute).ConfigureAwait(true);
}
else
finally
{
await _shell.GoToAsync(finalRoute).ConfigureAwait(true);
_shell.SetIsNavigating(false);
}
}
finally
{
_shell.SetIsNavigating(false);
}
}

public async Task PopAsync(IShellSectionProxy? section = null)
{
try
{
_shell.SetIsNavigating(true);
section ??= CurrentItem.CurrentSection;
section ??= CurrentItem.CurrentSection;

if (section == _navigationCurrentSection && _navigationTarget != null)
if (section == _navigationCurrentSection && _navigationTarget != null)
{
var previousSegmentEnd = _navigationTarget.LastIndexOf('/');
_navigationTarget = _navigationTarget[..previousSegmentEnd];
}
else
{
try
{
var previousSegmentEnd = _navigationTarget.LastIndexOf('/');
_navigationTarget = _navigationTarget[..previousSegmentEnd];
_shell.SetIsNavigating(true);
await section.PopAsync().ConfigureAwait(true);
}
else
finally
{
await section.PopAsync().ConfigureAwait(true);
_shell.SetIsNavigating(false);
}
}
finally
{
_shell.SetIsNavigating(false);
}
}

public async Task SelectContentAsync(string segmentName)
{
try
var contentProxy = (ShellContentProxy)GetContent(segmentName);
if (CurrentItem.CurrentSection.CurrentContent == contentProxy)
{
_shell.SetIsNavigating(true);
var contentProxy = (ShellContentProxy)GetContent(segmentName);
if (CurrentItem.CurrentSection.CurrentContent == contentProxy)
{
return;
}
return;
}

_contentChanged = true;
var content = contentProxy.Content;
var section = (ShellSection)content.Parent;
var item = (ShellItem)section.Parent;
_contentChanged = true;
var content = contentProxy.Content;
var section = (ShellSection)content.Parent;
var item = (ShellItem)section.Parent;

if (_navigationTarget is not null)
{
_navigationTarget = contentProxy.Parent.GetNavigationStack(contentProxy).LastOrDefault()?.Route
?? $"//{item.Route}/{section.Route}/{content.Route}";
return;
}
if (_navigationTarget is not null)
{
_navigationTarget = contentProxy.Parent.GetNavigationStack(contentProxy).LastOrDefault()?.Route
?? $"//{item.Route}/{section.Route}/{content.Route}";
return;
}

try
{
_shell.SetIsNavigating(true);
if (section.CurrentItem != content)
{
section.CurrentItem = content;
Expand All @@ -194,7 +194,7 @@ public async Task SelectContentAsync(string segmentName)
_shell.CurrentItem = item;
}

await Task.Delay(250).ConfigureAwait(true);
await Task.Delay(500).ConfigureAwait(true);
}
finally
{
Expand Down