Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,88 @@ public Issue28986(TestDevice device) : base(device)
{
}

#if ANDROID
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SoftInputDoesNotApplyBottomPaddingWhenKeyboardHidden()
{
// This test validates the fix for issue #31870
// When SafeAreaEdges.Bottom is set to SoftInput and the keyboard is NOT showing,
// there should be NO bottom padding from the navigation bar.
App.WaitForElement("ContentGrid");

// First, set to None to get the baseline position (no safe area padding)
App.Tap("GridResetNoneButton");
var noneRect = App.WaitForElement("MainGrid").GetRect();

// Now set bottom edge to SoftInput (keyboard is still hidden)
App.Tap("GridSetBottomSoftInputButton");

// Wait for layout to update
App.WaitForElement("MainGrid");

// Get the current settings to verify SoftInput is set
var currentSettings = App.FindElement("CurrentSettings").GetText();
Assert.That(currentSettings, Does.Contain("Bottom:SoftInput"),
"Bottom edge should be set to SoftInput");

// Get the rect after setting SoftInput
var softInputRect = App.WaitForElement("MainGrid").GetRect();

// Verify that the bottom position is the same as None (no padding applied)
// The height should be the same because SoftInput should not add padding when keyboard is hidden
Assert.That(softInputRect.Height, Is.EqualTo(noneRect.Height).Within(5),
"MainGrid height should be the same with SoftInput as with None when keyboard is hidden (no bottom padding)");

// Also verify the Y position hasn't shifted
Assert.That(softInputRect.Y, Is.EqualTo(noneRect.Y).Within(5),
"MainGrid Y position should be the same with SoftInput as with None when keyboard is hidden");
}

[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void AllRegionStillAppliesBottomPaddingWhenKeyboardHidden()
{
// This test validates that the fix for #31870 doesn't break SafeAreaRegions.All
// When SafeAreaEdges is set to All, it should respect safe area behavior consistently.
// The specific assertion depends on whether the device has a navigation bar.
App.WaitForElement("ContentGrid");

// First, set to None to get the baseline position (no safe area padding)
App.Tap("GridResetNoneButton");
var noneRect = App.WaitForElement("MainGrid").GetRect();

// Set bottom edge to SoftInput to test the specific behavior we fixed
App.Tap("GridSetBottomSoftInputButton");
App.WaitForElement("MainGrid");
var softInputRect = App.WaitForElement("MainGrid").GetRect();

// Now set to All (should apply all safe area insets)
App.Tap("GridResetAllButton");
App.WaitForElement("MainGrid");

// Get the current settings to verify All is set
var currentSettings = App.FindElement("CurrentSettings").GetText();
Assert.That(currentSettings, Does.Contain("All (Full safe area)"),
"SafeAreaEdges should be set to All");

// Get the rect after setting All
var allRect = App.WaitForElement("MainGrid").GetRect();

// The key validation: All should have same or less height than None (never more)
// And All should behave differently than SoftInput when keyboard is hidden
// Note: Using LessThanOrEqualTo instead of LessThan because some test devices (e.g., emulators
// without navigation bars) have no bottom safe area padding, resulting in equal heights.
// This test validates behavior consistency rather than assuming specific padding values.
Assert.That(allRect.Height, Is.LessThanOrEqualTo(noneRect.Height),
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment clarifying why LessThanOrEqualTo is used (to avoid future maintainers reverting it).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added clarifying comment in commit 4e8a92c explaining why LessThanOrEqualTo is used instead of LessThan - some test devices (like emulators without navigation bars) have no bottom safe area padding, so the assertion needs to handle both cases while still validating behavior consistency.

"MainGrid height with All should be less than or equal to None (All respects safe area)");

// SoftInput should match None when keyboard is hidden (no bottom padding)
Assert.That(softInputRect.Height, Is.EqualTo(noneRect.Height).Within(5),
"MainGrid height with SoftInput should match None when keyboard is hidden");
}
#endif

[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaMainGridBasicFunctionality()
Expand Down Expand Up @@ -128,6 +210,6 @@ public void SafeAreaPerEdgeValidation()
Assert.That(containerPositionWithoutSoftInput.Height, Is.EqualTo(containerPosition.Height), "ContentGrid height should return to original when Soft Input is dismissed with Container edges");
});
}
#endif
#endif
}
#endif
24 changes: 17 additions & 7 deletions src/Core/src/Platform/Android/SafeAreaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
}
else
{
newWindowInsets = windowInsets;
newWindowInsets = windowInsets;
}

// Fallback: return the base safe area for legacy views
Expand All @@ -253,14 +253,24 @@ internal static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double
}

// Handle SoftInput specifically - only apply keyboard insets for bottom edge when keyboard is showing
if (isKeyboardShowing && edge == 3)
if (edge == 3)
{
if (SafeAreaEdges.IsSoftInput(safeAreaRegion))
return keyBoardInsets.Bottom;
if (SafeAreaEdges.IsOnlySoftInput(safeAreaRegion))
{
// SoftInput only applies padding when keyboard is showing
return isKeyboardShowing ? keyBoardInsets.Bottom : 0;
}

// if they keyboard is showing then we will just return 0 for the bottom inset
// because that part of the view is covered by the keyboard so we don't want to pad the view
return 0;
if (isKeyboardShowing)
{
// Return keyboard insets for any region that includes SoftInput
if (SafeAreaEdges.IsSoftInput(safeAreaRegion))
return keyBoardInsets.Bottom;

// if the keyboard is showing then we will just return 0 for the bottom inset
// because that part of the view is covered by the keyboard so we don't want to pad the view
return 0;
}
}

// All other regions respect safe area in some form
Expand Down
23 changes: 17 additions & 6 deletions src/Core/src/Platform/iOS/MauiView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,27 @@ SafeAreaRegions GetSafeAreaRegionForEdge(int edge)
return SafeAreaRegions.None;
}

static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double originalSafeArea)
// Note: This method was changed from static to instance to access _isKeyboardShowing field
// which is needed to determine if SoftInput padding should be applied
double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double originalSafeArea, int edge)
{
// Edge-to-edge content - no safe area padding
if (safeAreaRegion == SafeAreaRegions.None)
return 0;

// Handle SoftInput specifically - only apply padding when keyboard is actually showing
if (edge == 3 && SafeAreaEdges.IsOnlySoftInput(safeAreaRegion))
{
// SoftInput only applies padding when keyboard is showing
// When keyboard is hidden, return 0 to avoid showing home indicator padding
if (!_isKeyboardShowing)
return 0;
}

// All other regions respect safe area in some form
// This includes:
// - Default: Platform default behavior
// - All: Obey all safe area insets
// - All: Obey all safe area insets
// - SoftInput: Always pad for keyboard/soft input
// - Container: Content flows under keyboard but stays out of bars/notch
// - Any combination of the above flags
Expand Down Expand Up @@ -336,10 +347,10 @@ SafeAreaPadding GetAdjustedSafeAreaInsets()
if (View is ISafeAreaView2)
{
// Apply safe area selectively per edge based on SafeAreaRegions
var left = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(0), baseSafeArea.Left);
var top = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(1), baseSafeArea.Top);
var right = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(2), baseSafeArea.Right);
var bottom = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(3), baseSafeArea.Bottom);
var left = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(0), baseSafeArea.Left, 0);
var top = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(1), baseSafeArea.Top, 1);
var right = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(2), baseSafeArea.Right, 2);
var bottom = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(3), baseSafeArea.Bottom, 3);

return new SafeAreaPadding(left, right, top, bottom);
}
Expand Down
8 changes: 7 additions & 1 deletion src/Core/src/Primitives/SafeAreaEdges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,19 @@ internal static bool IsSoftInput(SafeAreaRegions region)
return (region & SafeAreaRegions.SoftInput) == SafeAreaRegions.SoftInput;
}

internal static bool IsOnlySoftInput(SafeAreaRegions region)
{
// Check if the region is ONLY SoftInput, not combined with other flags or All
return region == SafeAreaRegions.SoftInput;
}

internal static bool IsContainer(SafeAreaRegions region)
{
if (region == SafeAreaRegions.Default)
return false;
if (region == SafeAreaRegions.All)
return true;
return (region & SafeAreaRegions.Container) == SafeAreaRegions.Container;
return (region & SafeAreaRegions.Container) == SafeAreaRegions.Container;
}

/// <summary>
Expand Down
Loading