diff --git a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs index 5fcb7a5bf9b3..05937889f271 100644 --- a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs @@ -209,6 +209,49 @@ public async Task RotationConsistent() var platformRotation = await InvokeOnMainThreadAsync(() => handler.PlatformView.Rotation); Assert.Equal(expected, platformRotation); } + + [Fact] + [Description("BottomNavigationView should extend to screen bottom in Edge-to-Edge mode (Issue 33344)")] + public async Task BottomNavigationViewExtendsToScreenBottom() + { + SetupBuilder(); + + var tabbedPage = new TabbedPage + { + Children = + { + new ContentPage() { Title = "Page1" }, + new ContentPage() { Title = "Page2" } + }, + BarBackgroundColor = Colors.Orange + }; + + Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.TabbedPage + .SetToolbarPlacement(tabbedPage, Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.ToolbarPlacement.Bottom); + + await CreateHandlerAndAddToWindow(tabbedPage, async handler => + { + var bottomNavView = GetBottomNavigationView(handler); + Assert.NotNull(bottomNavView); + + // Wait for layout to complete + await AssertEventually(() => bottomNavView.Height > 0); + + var location = new int[2]; + bottomNavView.GetLocationOnScreen(location); + var bottomNavBottom = location[1] + bottomNavView.Height; + + var decorView = MauiContext.Context.GetActivity()?.Window?.DecorView; + Assert.NotNull(decorView); + + decorView.GetLocationOnScreen(location); + var screenHeight = location[1] + decorView.Height; + + Assert.True(Math.Abs(screenHeight - bottomNavBottom) < 2, + $"BottomNavigationView should extend to screen bottom. Expected bottom at {screenHeight}px, but was at {bottomNavBottom}px (gap of {screenHeight - bottomNavBottom}px)"); + }); + } + BottomNavigationView GetBottomNavigationView(IPlatformViewHandler tabViewHandler) { var layout = tabViewHandler.PlatformView.FindParent((view) => view is CoordinatorLayout) diff --git a/src/Core/src/Platform/Android/MauiWindowInsetListener.cs b/src/Core/src/Platform/Android/MauiWindowInsetListener.cs index 9763ec85ed32..dd26fea690a1 100644 --- a/src/Core/src/Platform/Android/MauiWindowInsetListener.cs +++ b/src/Core/src/Platform/Android/MauiWindowInsetListener.cs @@ -264,37 +264,30 @@ public MauiWindowInsetListener() : base(DispatchModeStop) } } - // Handle bottom navigation - var hasBottomNav = v.FindViewById(Resource.Id.navigationlayout_bottomtabs)?.MeasuredHeight > 0; + var bottomTabContainer = v.FindViewById(Resource.Id.navigationlayout_bottomtabs); + var hasBottomNav = bottomTabContainer?.MeasuredHeight > 0; + var contentView = v.FindViewById(Resource.Id.navigationlayout_content); + if (hasBottomNav) { var bottomInset = Math.Max(systemBars?.Bottom ?? 0, displayCutout?.Bottom ?? 0); - v.SetPadding(0, 0, 0, bottomInset); + + // Only pad the bottom of contentView to prevent content from sliding under the + // BottomNavigationView + system navigation bar. Left/right are intentionally + // excluded: landscape cutout padding on the content area is handled by + // SafeAreaExtensions which applies per-view overlap logic. + contentView?.SetPadding(0, 0, 0, bottomInset); } else { - v.SetPadding(0, 0, 0, 0); + // Reset contentView padding when bottom navigation is removed dynamically + contentView?.SetPadding(0, 0, 0, 0); } - // Create new insets with consumed values - var newSystemBars = Insets.Of( - systemBars?.Left ?? 0, - appBarHasContent ? 0 : systemBars?.Top ?? 0, - systemBars?.Right ?? 0, - hasBottomNav ? 0 : systemBars?.Bottom ?? 0 - ) ?? Insets.None; - - var newDisplayCutout = Insets.Of( - displayCutout?.Left ?? 0, - appBarHasContent ? 0 : displayCutout?.Top ?? 0, - displayCutout?.Right ?? 0, - hasBottomNav ? 0 : displayCutout?.Bottom ?? 0 - ) ?? Insets.None; - - return new WindowInsetsCompat.Builder(insets) - ?.SetInsets(WindowInsetsCompat.Type.SystemBars(), newSystemBars) - ?.SetInsets(WindowInsetsCompat.Type.DisplayCutout(), newDisplayCutout) - ?.Build() ?? insets; + // Pass insets through unconsumed so child views receive them intact. + // Bottom: BottomNavigationView needs the nav bar inset to extend its background in Edge-to-Edge mode (Issue #33344). + // Top: SafeAreaExtensions handles per-view overlap, avoiding double-padding. + return insets; } public void TrackView(AView view)