Skip to content
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
11 changes: 8 additions & 3 deletions src/Controls/src/Core/Shell/ShellNavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public bool ProposeNavigationOutsideGotoAsync(
if (AccumulateNavigatedEvents)
return true;

var proposedState = GetNavigationState(shellItem, shellSection, shellContent, stack, shellSection.Navigation.ModalStack);
var proposedState = GetNavigationState(shellItem, shellSection, shellContent, stack, shellSection.Navigation.ModalStack, isNavigateThroughTab: true);
var navArgs = ProposeNavigation(source, proposedState, canCancel, isAnimated);

if (navArgs.DeferralCount > 0)
Expand Down Expand Up @@ -523,7 +523,7 @@ topNavStackPage as BindableObject ??
};
}

public static ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> sectionStack, IReadOnlyList<Page> modalStack)
public static ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> sectionStack, IReadOnlyList<Page> modalStack, bool isNavigateThroughTab = false)
{
List<string> routeStack = new List<string>();

Expand Down Expand Up @@ -576,7 +576,12 @@ public static ShellNavigationState GetNavigationState(ShellItem shellItem, Shell
}

#if IOS || MACCATALYST
if (Shell.Current?.CurrentState?.Location is not null)
// This fix addresses #25599 (Navigating event showing same Current and Target when
// re-tapping a tab on iOS). It only applies when called from the Navigating event
// context (ProposeNavigationOutsideGotoAsync), not when updating Shell.CurrentState.
// Applying it in UpdateCurrentState would cause Shell.CurrentState to be stale
// after a GoToAsync deep-navigation (#34662).
if (isNavigateThroughTab && Shell.Current?.CurrentState?.Location is not null)
{
var currentRoute = Shell.Current?.CurrentState?.Location?.ToString();
if (!string.IsNullOrEmpty(currentRoute))
Expand Down
149 changes: 149 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34662.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34662, "Shell OnNavigated not called for route navigation", PlatformAffected.iOS | PlatformAffected.macOS)]
public class Issue34662 : Shell
{
public Issue34662()
{
// Page1 and Page2 are sub-routes under DashboardPage -- not ShellItems.
// This allows absolute navigation to "//DashboardPage/Page1/Page2".
Routing.RegisterRoute("Page1", typeof(Issue34662_Page1));
Routing.RegisterRoute("Page2", typeof(Issue34662_Page2));

Items.Add(new ShellContent
{
Title = "Login",
ContentTemplate = new DataTemplate(() => new Issue34662_LoginPage()),
Route = "LoginPage"
});

Items.Add(new ShellContent
{
Title = "Dashboard",
ContentTemplate = new DataTemplate(() => new Issue34662_DashboardPage()),
Route = "DashboardPage"
});
}

protected override void OnNavigated(ShellNavigatedEventArgs args)
{
base.OnNavigated(args);

// Capture CurrentState.Location inside OnNavigated.
// On 10.0.50 (bug): CurrentState = "//DashboardPage" (stale) after GoToAsync("//DashboardPage/Page1/Page2").
// On 10.0.41 (working): CurrentState = "//DashboardPage/Page1/Page2".
var currentState = CurrentState?.Location?.OriginalString;

// OnNavigated fires AFTER Page2.OnAppearing, so push the value directly to Page2's label.
var page2 = CurrentPage as Issue34662_Page2
?? (CurrentPage as NavigationPage)?.CurrentPage as Issue34662_Page2;
page2?.SetCurrentStateLocation(currentState);
}
}

public class Issue34662_LoginPage : ContentPage
{
public Issue34662_LoginPage()
{
Title = "Login";

var loginButton = new Button
{
Text = "Login -> //DashboardPage/Page1/Page2",
AutomationId = "LoginButton",
HorizontalOptions = LayoutOptions.Fill
};
loginButton.Clicked += OnLoginClicked;

Content = new VerticalStackLayout
{
Padding = new Thickness(40),
Spacing = 20,
VerticalOptions = LayoutOptions.Center,
Children =
{
new Label
{
Text = "Tap to navigate to //DashboardPage/Page1/Page2",
HorizontalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center
},
loginButton
}
};
}

private async void OnLoginClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//DashboardPage/Page1/Page2");
}
}

public class Issue34662_DashboardPage : ContentPage
{
public Issue34662_DashboardPage()
{
Title = "Dashboard";
Content = new Label
{
Text = "Dashboard Page",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
}
}

public class Issue34662_Page1 : ContentPage
{
public Issue34662_Page1()
{
Title = "Page 1";
Content = new Label
{
Text = "Page 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
}
}

public class Issue34662_Page2 : ContentPage
{
readonly Label _currentStateLabel;

public Issue34662_Page2()
{
Title = "Page 2";

// Set by OnNavigated via SetCurrentStateLocation.
// Shows Shell.CurrentState.Location captured inside OnNavigated.
// Bug on 10.0.50: shows "//DashboardPage" (stale) instead of "//DashboardPage/Page1/Page2".
_currentStateLabel = new Label
{
AutomationId = "OnNavigatedCurrentStateLabel",
HorizontalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center,
Text = "(not set)"
};

Content = new VerticalStackLayout
{
Padding = new Thickness(30),
Spacing = 16,
VerticalOptions = LayoutOptions.Center,
Children =
{
new Label { Text = "Page 2", HorizontalOptions = LayoutOptions.Center },
new Label { Text = "CurrentState.Location inside OnNavigated:", HorizontalOptions = LayoutOptions.Center },
_currentStateLabel
}
};
}

// Called directly from Issue34662.OnNavigated -- OnNavigated fires AFTER OnAppearing,
// so we cannot read CurrentState in OnAppearing and must receive it this way.
internal void SetCurrentStateLocation(string currentState)
{
_currentStateLabel.Text = currentState ?? "(null)";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue34662 : _IssuesUITest
{
public Issue34662(TestDevice device) : base(device) { }

public override string Issue => "Shell OnNavigated not called for route navigation";

[Test]
[Category(UITestCategories.Shell)]
public void ShellCurrentStateLocationCorrectAfterAbsoluteNavigation()
{
const string expectedCurrentState = "//DashboardPage/Page1/Page2";

App.WaitForElement("LoginButton");
App.Tap("LoginButton");
App.WaitForTextToBePresentInElement("OnNavigatedCurrentStateLabel", expectedCurrentState, TimeSpan.FromSeconds(5));

var currentStateText = App.WaitForElement("OnNavigatedCurrentStateLabel").GetText();
Assert.That(currentStateText, Is.EqualTo(expectedCurrentState),
"Shell.CurrentState.Location inside OnNavigated should be '//DashboardPage/Page1/Page2', not stale '//DashboardPage'");
}
}
Loading