Skip to content

Commit 40427aa

Browse files
github-actions[bot]CopilotPureWeen
authored
[release/10.0.1xx-sr1] Fix SafeAreaEdges.SoftInput applying bottom padding when keyboard is hidden and inset consumption issue (#32448)
* Initial plan * - Fix Bottom Inset when keyboard is closed * Remove CurrentSettings element checks that were causing test timeouts Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Shane Neuville <shneuvil@microsoft.com> Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
1 parent 2c8cee6 commit 40427aa

File tree

6 files changed

+135
-23
lines changed

6 files changed

+135
-23
lines changed

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28986.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,72 @@ public Issue28986(TestDevice device) : base(device)
1313
{
1414
}
1515

16+
[Test]
17+
[Category(UITestCategories.SafeAreaEdges)]
18+
public void SoftInputDoesNotApplyBottomPaddingWhenKeyboardHidden()
19+
{
20+
// This test validates the fix for issue #31870
21+
// When SafeAreaEdges.Bottom is set to SoftInput and the keyboard is NOT showing,
22+
// there should be NO bottom padding from the navigation bar.
23+
App.WaitForElement("ContentGrid");
24+
25+
// First, set to None to get the baseline position (no safe area padding)
26+
App.Tap("GridResetNoneButton");
27+
var noneRect = App.WaitForElement("MainGrid").GetRect();
28+
29+
// Now set bottom edge to SoftInput (keyboard is still hidden)
30+
App.Tap("GridSetBottomSoftInputButton");
31+
32+
// Wait for layout to update and get the rect after setting SoftInput
33+
var softInputRect = App.WaitForElement("MainGrid").GetRect();
34+
35+
// Verify that the bottom position is the same as None (no padding applied)
36+
// The height should be the same because SoftInput should not add padding when keyboard is hidden
37+
Assert.That(softInputRect.Height, Is.EqualTo(noneRect.Height).Within(5),
38+
"MainGrid height should be the same with SoftInput as with None when keyboard is hidden (no bottom padding)");
39+
40+
// Also verify the Y position hasn't shifted
41+
Assert.That(softInputRect.Y, Is.EqualTo(noneRect.Y).Within(5),
42+
"MainGrid Y position should be the same with SoftInput as with None when keyboard is hidden");
43+
}
44+
45+
[Test]
46+
[Category(UITestCategories.SafeAreaEdges)]
47+
public void AllRegionStillAppliesBottomPaddingWhenKeyboardHidden()
48+
{
49+
// This test validates that the fix for #31870 doesn't break SafeAreaRegions.All
50+
// When SafeAreaEdges is set to All, it should respect safe area behavior consistently.
51+
// The specific assertion depends on whether the device has a navigation bar.
52+
App.WaitForElement("ContentGrid");
53+
54+
// First, set to None to get the baseline position (no safe area padding)
55+
App.Tap("GridResetNoneButton");
56+
var noneRect = App.WaitForElement("MainGrid").GetRect();
57+
58+
// Set bottom edge to SoftInput to test the specific behavior we fixed
59+
App.Tap("GridSetBottomSoftInputButton");
60+
App.WaitForElement("MainGrid");
61+
var softInputRect = App.WaitForElement("MainGrid").GetRect();
62+
63+
// Now set to All (should apply all safe area insets)
64+
App.Tap("GridResetAllButton");
65+
66+
// Get the rect after setting All
67+
var allRect = App.WaitForElement("MainGrid").GetRect();
68+
69+
// The key validation: All should have same or less height than None (never more)
70+
// And All should behave differently than SoftInput when keyboard is hidden
71+
// Note: Using LessThanOrEqualTo instead of LessThan because some test devices (e.g., emulators
72+
// without navigation bars) have no bottom safe area padding, resulting in equal heights.
73+
// This test validates behavior consistency rather than assuming specific padding values.
74+
Assert.That(allRect.Height, Is.LessThanOrEqualTo(noneRect.Height),
75+
"MainGrid height with All should be less than or equal to None (All respects safe area)");
76+
77+
// SoftInput should match None when keyboard is hidden (no bottom padding)
78+
Assert.That(softInputRect.Height, Is.EqualTo(noneRect.Height).Within(5),
79+
"MainGrid height with SoftInput should match None when keyboard is hidden");
80+
}
81+
1682
[Test]
1783
[Category(UITestCategories.SafeAreaEdges)]
1884
public void SafeAreaMainGridBasicFunctionality()
@@ -97,7 +163,6 @@ public void SafeAreaMainGridSequentialButtonTesting()
97163
Assert.That(finalAllPosition.Y, Is.EqualTo(allPosition.Y), "Final All position should match initial All position");
98164
}
99165

100-
#if TEST_FAILS_ON_ANDROID
101166
[Test]
102167
[Category(UITestCategories.SafeAreaEdges)]
103168
public void SafeAreaPerEdgeValidation()
@@ -128,6 +193,5 @@ public void SafeAreaPerEdgeValidation()
128193
Assert.That(containerPositionWithoutSoftInput.Height, Is.EqualTo(containerPosition.Height), "ContentGrid height should return to original when Soft Input is dismissed with Container edges");
129194
});
130195
}
131-
#endif
132196
}
133197
#endif

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28986_ContentPage.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ public void SafeAreaMainGridSequentialButtonTesting()
9898
}
9999

100100

101-
#if TEST_FAILS_ON_ANDROID
102101
[Test]
103102
[Category(UITestCategories.SafeAreaEdges)]
104103
public void SafeAreaPerEdgeValidation()
@@ -129,6 +128,5 @@ public void SafeAreaPerEdgeValidation()
129128
Assert.That(containerPositionWithoutSoftInput.Height, Is.EqualTo(containerPosition.Height), "ContentGrid height should return to original when Soft Input is dismissed with Container edges");
130129
});
131130
}
132-
#endif
133131
}
134132
#endif

src/Core/src/Platform/Android/MauiWindowInsetListener.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ public void TrackView(AView view)
304304

305305
public bool HasTrackedView => _trackedViews.Count > 0;
306306

307+
public bool IsViewTracked(AView view)
308+
{
309+
return _trackedViews.Contains(view);
310+
}
307311
public void ResetView(AView view)
308312
{
309313
if (view is IHandleWindowInsets customHandler)

src/Core/src/Platform/Android/SafeAreaExtensions.cs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
6262
var right = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(2, layout), baseSafeArea.Right, 2, isKeyboardShowing, keyboardInsets);
6363
var bottom = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(3, layout), baseSafeArea.Bottom, 3, isKeyboardShowing, keyboardInsets);
6464

65+
var globalWindowInsetsListener = MauiWindowInsetListener.FindListenerForView(view);
66+
bool hasTrackedViews = globalWindowInsetsListener?.HasTrackedView == true;
67+
68+
// If this view has no safe area padding to apply, pass insets through to children
69+
// instead of consuming them. This allows child views with SafeAreaEdges set
70+
// to properly handle the insets even when the parent has SafeAreaEdges.None
71+
// However, if this view was previously tracked (had padding before), we need to
72+
// continue processing to reset the padding to 0
73+
if (left == 0 && right == 0 && top == 0 && bottom == 0)
74+
{
75+
// Only pass through if this view hasn't been tracked yet
76+
// If it was tracked, we need to reset its padding
77+
if (globalWindowInsetsListener?.IsViewTracked(view) != true)
78+
{
79+
// Don't consume insets - pass them through for potential child views to handle
80+
return windowInsets;
81+
}
82+
}
83+
84+
6585
if (isKeyboardShowing &&
6686
context.GetActivity()?.Window is Window window &&
6787
window?.Attributes is WindowManagerLayoutParams attr)
@@ -77,9 +97,6 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
7797
}
7898
}
7999

80-
var globalWindowInsetsListener = MauiWindowInsetListener.FindListenerForView(view);
81-
bool hasTrackedViews = globalWindowInsetsListener?.HasTrackedView == true;
82-
83100
// Check intersection with view bounds to determine which edges actually need padding
84101
// If we don't have any tracked views yet we will find the first view to pad
85102
// in order to limit duplicate measures
@@ -253,15 +270,25 @@ internal static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double
253270
}
254271

255272
// Handle SoftInput specifically - only apply keyboard insets for bottom edge when keyboard is showing
256-
if (isKeyboardShowing && edge == 3)
257-
{
258-
if (SafeAreaEdges.IsSoftInput(safeAreaRegion))
259-
return keyBoardInsets.Bottom;
260-
261-
// if they keyboard is showing then we will just return 0 for the bottom inset
262-
// because that part of the view is covered by the keyboard so we don't want to pad the view
263-
return 0;
264-
}
273+
if (edge == 3)
274+
{
275+
if (SafeAreaEdges.IsOnlySoftInput(safeAreaRegion))
276+
{
277+
// SoftInput only applies padding when keyboard is showing
278+
return isKeyboardShowing ? keyBoardInsets.Bottom : 0;
279+
}
280+
281+
if (isKeyboardShowing)
282+
{
283+
// Return keyboard insets for any region that includes SoftInput
284+
if (SafeAreaEdges.IsSoftInput(safeAreaRegion))
285+
return keyBoardInsets.Bottom;
286+
287+
// if the keyboard is showing then we will just return 0 for the bottom inset
288+
// because that part of the view is covered by the keyboard so we don't want to pad the view
289+
return 0;
290+
}
291+
}
265292

266293
// All other regions respect safe area in some form
267294
// This includes:

src/Core/src/Platform/iOS/MauiView.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,29 @@ SafeAreaRegions GetSafeAreaRegionForEdge(int edge)
143143
return SafeAreaRegions.None;
144144
}
145145

146-
static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double originalSafeArea)
146+
// Note: This method was changed from static to instance to access _isKeyboardShowing field
147+
// which is needed to determine if SoftInput padding should be applied
148+
double GetSafeAreaForEdge(double originalSafeArea, int edge)
147149
{
150+
var safeAreaRegion = GetSafeAreaRegionForEdge(edge);
151+
148152
// Edge-to-edge content - no safe area padding
149153
if (safeAreaRegion == SafeAreaRegions.None)
150154
return 0;
151155

156+
// Handle SoftInput specifically - only apply padding when keyboard is actually showing
157+
if (edge == 3 && SafeAreaEdges.IsOnlySoftInput(safeAreaRegion))
158+
{
159+
// SoftInput only applies padding when keyboard is showing
160+
// When keyboard is hidden, return 0 to avoid showing home indicator padding
161+
if (!_isKeyboardShowing)
162+
return 0;
163+
}
164+
152165
// All other regions respect safe area in some form
153166
// This includes:
154167
// - Default: Platform default behavior
155-
// - All: Obey all safe area insets
168+
// - All: Obey all safe area insets
156169
// - SoftInput: Always pad for keyboard/soft input
157170
// - Container: Content flows under keyboard but stays out of bars/notch
158171
// - Any combination of the above flags
@@ -336,10 +349,10 @@ SafeAreaPadding GetAdjustedSafeAreaInsets()
336349
if (View is ISafeAreaView2)
337350
{
338351
// Apply safe area selectively per edge based on SafeAreaRegions
339-
var left = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(0), baseSafeArea.Left);
340-
var top = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(1), baseSafeArea.Top);
341-
var right = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(2), baseSafeArea.Right);
342-
var bottom = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(3), baseSafeArea.Bottom);
352+
var left = GetSafeAreaForEdge(baseSafeArea.Left, 0);
353+
var top = GetSafeAreaForEdge(baseSafeArea.Top, 1);
354+
var right = GetSafeAreaForEdge(baseSafeArea.Right, 2);
355+
var bottom = GetSafeAreaForEdge(baseSafeArea.Bottom, 3);
343356

344357
return new SafeAreaPadding(left, right, top, bottom);
345358
}

src/Core/src/Primitives/SafeAreaEdges.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,19 @@ internal static bool IsSoftInput(SafeAreaRegions region)
7878
return (region & SafeAreaRegions.SoftInput) == SafeAreaRegions.SoftInput;
7979
}
8080

81+
internal static bool IsOnlySoftInput(SafeAreaRegions region)
82+
{
83+
// Check if the region is ONLY SoftInput, not combined with other flags or All
84+
return region == SafeAreaRegions.SoftInput;
85+
}
86+
8187
internal static bool IsContainer(SafeAreaRegions region)
8288
{
8389
if (region == SafeAreaRegions.Default)
8490
return false;
8591
if (region == SafeAreaRegions.All)
8692
return true;
87-
return (region & SafeAreaRegions.Container) == SafeAreaRegions.Container;
93+
return (region & SafeAreaRegions.Container) == SafeAreaRegions.Container;
8894
}
8995

9096
/// <summary>

0 commit comments

Comments
 (0)