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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32941.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 32941, "Label Overlapped by Android Status Bar When Using SafeAreaEdges=Container in .NET MAUI", PlatformAffected.Android)]
public class Issue32941 : TestShell
{
protected override void Init()
{
var shellContent1 = new ShellContent
{
Title = "Home",
Route = "MainPage",
Content = new Issue32941_MainPage()
};
var shellContent2 = new ShellContent
{
Title = "SignOut",
Route = "SignOutPage",
Content = new Issue32941_SignOutPage()
};
Items.Add(shellContent1);
Items.Add(shellContent2);
}
}

public class Issue32941_MainPage : ContentPage
{
public Issue32941_MainPage()
{
var goToSignOutButton = new Button
{
Text = "Go to SignOut",
AutomationId = "GoToSignOutButton"
};
goToSignOutButton.Clicked += async (s, e) => await Shell.Current.GoToAsync("//SignOutPage", false);

Content = new VerticalStackLayout
{
Spacing = 20,
Padding = new Thickness(20),
Children =
{
new Label
{
Text = "Main Page",
FontSize = 24,
AutomationId = "MainPageLabel"
},
goToSignOutButton
}
};
}
}

public class Issue32941_SignOutPage : ContentPage
{
public Issue32941_SignOutPage()
{
Shell.SetNavBarIsVisible(this, false);
SafeAreaEdges = new SafeAreaEdges(SafeAreaRegions.Container);

var backButton = new Button
{
Text = "Back to Main",
AutomationId = "BackButton"
};
backButton.Clicked += async (s, e) => await Shell.Current.GoToAsync("//MainPage", true);

Content = new VerticalStackLayout
{
BackgroundColor = Colors.White,
Children =
{
new Label
{
Text = "SignOut / Session Expiry Page",
FontSize = 24,
BackgroundColor = Colors.Yellow,
AutomationId = "SignOutLabel"
},
backButton
}
};
}
}
55 changes: 55 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue33034.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 33034, "SafeAreaEdges works correctly only on the first tab in Shell. Other tabs have content colliding with the display cutout in the landscape mode.", PlatformAffected.Android)]
public class Issue33034 : TestShell
{
protected override void Init()
{
var tabBar = new TabBar();
var tab = new Tab { Title = "Tabs" };

tab.Items.Add(new ShellContent
{
Title = "First Tab",
AutomationId = "FirstTab",
ContentTemplate = new DataTemplate(typeof(Issue33034TabContent)),
Route = "tab1"
});

tab.Items.Add(new ShellContent
{
Title = "Second Tab",
AutomationId = "SecondTab",
ContentTemplate = new DataTemplate(typeof(Issue33034TabContent)),
Route = "tab2"
});

tabBar.Items.Add(tab);
Items.Add(tabBar);
}
}

public class Issue33034TabContent : ContentPage
{
public Issue33034TabContent()
{
// Full-width label to detect safe area padding on either side
var edgeLabel = new Label
{
Text = "EDGE LABEL",
AutomationId = "EdgeLabel",
FontSize = 18,
FontAttributes = FontAttributes.Bold,
BackgroundColor = Colors.Red,
TextColor = Colors.White,
HorizontalOptions = LayoutOptions.Fill,
HorizontalTextAlignment = TextAlignment.Center
};

Content = new VerticalStackLayout
{
Children = { edgeLabel }
};
}
}
Comment on lines +32 to +54
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Issue33034TabContent page is missing the SafeAreaEdges property, but the test is specifically designed to validate SafeAreaEdges behavior across multiple tabs. Without setting SafeAreaEdges on the ContentPage, the test cannot properly validate the fix. Add the SafeAreaEdges property to ensure the test validates the intended behavior.

Copilot uses AI. Check for mistakes.

48 changes: 48 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue33038.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 33038, "Layout breaks on first navigation until soft keyboard appears/disappears", PlatformAffected.Android)]
public class Issue33038 : TestShell
{
protected override void Init()
{
FlyoutBehavior = FlyoutBehavior.Disabled;
Items.Add(new ShellContent { Title = "Start", Route = "start", Content = new Issue33038_StartPage() });
Items.Add(new ShellContent { Title = "SignIn", Route = "signin", Content = new Issue33038_SignInPage() });
}
}

public class Issue33038_StartPage : ContentPage
{
public Issue33038_StartPage()
{
var goToSignInButton = new Button { Text = "Go to SignIn", AutomationId = "GoToSignInButton" };
goToSignInButton.Clicked += async (s, e) => await Shell.Current.GoToAsync("//signin", false);

Content = new VerticalStackLayout
{
VerticalOptions = LayoutOptions.Center,
Spacing = 20,
Children = { new Label { Text = "Start Page", AutomationId = "StartPageLabel" }, goToSignInButton }
};
}
}

public class Issue33038_SignInPage : ContentPage
{
public Issue33038_SignInPage()
{
Shell.SetNavBarIsVisible(this, false);
SafeAreaEdges = new SafeAreaEdges(SafeAreaRegions.Container);

Content = new VerticalStackLayout
{
Spacing = 16,
Padding = new Thickness(20),
Children =
{
new Label { Text = "Sign In Page", BackgroundColor = Colors.Yellow, AutomationId = "SignInLabel" },
new Entry { Placeholder = "Email", AutomationId = "EmailEntry" }
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#if ANDROID || IOS // SafeAreaEdges not supported on Catalyst and Windows

using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue32941 : _IssuesUITest
{
public Issue32941(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "Label Overlapped by Android Status Bar When Using SafeAreaEdges=Container in .NET MAUI";

[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void ShellContentShouldRespectSafeAreaEdges_After_Navigation()
{
App.WaitForElement("MainPageLabel");
App.Tap("GoToSignOutButton");
App.WaitForElement("SignOutLabel");

// Get the position of the label
var labelRect = App.FindElement("SignOutLabel").GetRect();

// The label should be positioned below the status bar (Y coordinate should be > 0)
// On Android with notch, status bar is typically 24-88dp depending on device
// The label should have adequate top padding from SafeAreaEdges=Container
Assert.That(labelRect.Y, Is.GreaterThan(0), "Label should not be at Y=0 (would be under status bar)");

// Verify the label is not overlapped by checking it has reasonable top spacing
// A label at Y < 20 is likely overlapped by the status bar
Assert.That(labelRect.Y, Is.GreaterThanOrEqualTo(20),
"Label Y position should be at least 20 pixels from top to avoid status bar overlap");
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#if ANDROID || IOS // SafeAreaEdges not supported on Catalyst and Windows

using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue33034 : _IssuesUITest
{
public override string Issue => "SafeAreaEdges works correctly only on the first tab in Shell. Other tabs have content colliding with the display cutout in the landscape mode.";

public Issue33034(TestDevice device) : base(device) { }

[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaShouldWorkOnAllShellTabs()
{
App.WaitForElement("EdgeLabel");
App.SetOrientationLandscape();
var initialRect = App.WaitForElement("EdgeLabel").GetRect();

App.TapTab("Second Tab");
App.WaitForElement("EdgeLabel");
App.TapTab("First Tab");
var afterSwitchRect = App.WaitForElement("EdgeLabel").GetRect();

Assert.That(afterSwitchRect.X, Is.EqualTo(initialRect.X).Within(5));
Assert.That(afterSwitchRect.Width, Is.EqualTo(initialRect.Width).Within(5));
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if ANDROID || IOS // SafeAreaEdges not supported on Catalyst and Windows

using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue33038 : _IssuesUITest
{
public Issue33038(TestDevice testDevice) : base(testDevice) { }

public override string Issue => "Layout breaks on first navigation until soft keyboard appears/disappears";

[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void LayoutShouldBeCorrectOnFirstNavigation()
{
App.WaitForElement("StartPageLabel");
App.Tap("GoToSignInButton");
App.WaitForElement("SignInLabel");
VerifyScreenshot();
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/Core/src/Platform/Android/SafeAreaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
var screenWidth = realMetrics.WidthPixels;
var screenHeight = realMetrics.HeightPixels;

// Check if view extends beyond screen bounds - this indicates the view
// is still being positioned (e.g., during Shell fragment transitions).
// In this case, consume all insets to prevent children from processing
// invalid data, and request a re-apply after the view settles.
bool viewExtendsBeyondScreen = viewRight > screenWidth || viewBottom > screenHeight ||
viewLeft < 0 || viewTop < 0;

if (viewExtendsBeyondScreen)
{
// Request insets to be reapplied after the next layout pass
// when the view should be properly positioned.
// Don't return early - let processing continue with current insets
// to avoid visual popping, the re-apply will correct any issues.
view.Post(() => ViewCompat.RequestApplyInsets(view));
}

// Calculate actual overlap for each edge
// Top: how much the view extends into the top safe area
// If the viewTop is < 0 that means that it's most likely
Expand Down
Loading