Skip to content

Commit

Permalink
Fix Modal page offset measuring for AdjustPan and AdjustResize (#12661)
Browse files Browse the repository at this point in the history
* Watch for margin

* Fix modal margins and measuring when soft keyboard opens

* - fix offsets

* - force tests to setup testing scenario

* Update ModalNavigationManager.Android.cs

* - add additional comments
  • Loading branch information
PureWeen committed Feb 16, 2023
1 parent 33d6642 commit fe07490
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,17 @@ protected virtual void LoadView(IShellContext shellContext)
// so we want to delay loading to the latest possible point in time so
// it doesn't delay initial startup.
GenericGlobalLayoutListener ggll = null;
ggll = new GenericGlobalLayoutListener(InitialLoad);
sfl.ViewTreeObserver.AddOnGlobalLayoutListener(ggll);
ggll = new GenericGlobalLayoutListener(InitialLoad, sfl);

void InitialLoad()
void InitialLoad(GenericGlobalLayoutListener listener, AView view)
{
OnFlyoutViewLayoutChanging();

if (_flyoutContentView == null || ggll == null)
return;

var listener = ggll;
ggll = null;

// Once initial load has finished let's just attach to Layout Changing
sfl.ViewTreeObserver.RemoveOnGlobalLayoutListener(listener);
listener.Invalidate();
sfl.LayoutChanging += OnFlyoutViewLayoutChanging;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ public ShellToolbarTracker(IShellContext shellContext, AToolbar toolbar, DrawerL
_drawerLayout = drawerLayout ?? throw new ArgumentNullException(nameof(drawerLayout));
_appBar = _platformToolbar.Parent.GetParentOfType<AppBarLayout>();

_globalLayoutListener = new GenericGlobalLayoutListener(() => UpdateNavBarHasShadow(Page));
_appBar.ViewTreeObserver.AddOnGlobalLayoutListener(_globalLayoutListener);
_globalLayoutListener = new GenericGlobalLayoutListener((_,_) => UpdateNavBarHasShadow(Page), _appBar);
_platformToolbar.SetNavigationOnClickListener(this);
((IShellController)ShellContext.Shell).AddFlyoutBehaviorObserver(this);
ShellContext.Shell.Toolbar.PropertyChanged += OnToolbarPropertyChanged;
Expand Down Expand Up @@ -174,9 +173,6 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_appBar.IsAlive() && _appBar.ViewTreeObserver.IsAlive())
_appBar.ViewTreeObserver.RemoveOnGlobalLayoutListener(_globalLayoutListener);

_globalLayoutListener.Invalidate();

if (_backButtonBehavior != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using System;
using Android.Views;
using AView = Android.Views.View;
using Object = Java.Lang.Object;

namespace Microsoft.Maui.Controls.Platform
{
internal class GenericGlobalLayoutListener : Object, ViewTreeObserver.IOnGlobalLayoutListener
{
Action _callback;
Action<GenericGlobalLayoutListener, AView?>? _callback;
WeakReference<AView>? _targetView;

public GenericGlobalLayoutListener(Action callback)
public GenericGlobalLayoutListener(Action<GenericGlobalLayoutListener, AView?> callback, AView? targetView = null)
{
_callback = callback;

if (targetView?.ViewTreeObserver != null)
{
_targetView = new WeakReference<AView>(targetView);
targetView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
}
}

public void OnGlobalLayout()
{
_callback?.Invoke();
AView? targetView = null;
_targetView?.TryGetTarget(out targetView);
_callback?.Invoke(this, targetView);
}

protected override void Dispose(bool disposing)
Expand All @@ -29,6 +39,16 @@ protected override void Dispose(bool disposing)
internal void Invalidate()
{
_callback = null;

if (_targetView != null &&
_targetView.TryGetTarget(out var targetView) &&
targetView.IsAlive() &&
targetView.ViewTreeObserver != null)
{
targetView.ViewTreeObserver.RemoveOnGlobalLayoutListener(this);
}

_targetView = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,39 @@
using Android.Views.Animations;
using AndroidX.Activity;
using AndroidX.AppCompat.App;
using AndroidX.AppCompat.Widget;
using AndroidX.Core.View;
using AndroidX.Fragment.App;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using AView = Android.Views.View;

namespace Microsoft.Maui.Controls.Platform
{
internal partial class ModalNavigationManager
{
ViewGroup GetModalParentView()
ViewGroup? _modalParentView;

// This is only here for the device tests to use.
// With the device tests we have a `FakeActivityRootView` and a `WindowTestFragment`
// that we use to replicate the `DecorView` and `MainActivity`
// The tests will set this to the `FakeActivityRootView` so that the `modals`
// are part of the correct testing space.
// If/When we move to opening new activities we can remove this code.
internal void SetModalParentView(ViewGroup viewGroup)
{
var currentRootView = GetCurrentRootView() as ViewGroup;

if (_window?.PlatformActivity?.GetWindow() == _window)
{
currentRootView = _window?.PlatformActivity?.Window?.DecorView as ViewGroup;
}
_modalParentView = viewGroup;
}

return currentRootView ??
ViewGroup GetModalParentView()
{
return _modalParentView ??
_window?.PlatformActivity?.Window?.DecorView as ViewGroup ??
throw new InvalidOperationException("Root View Needs to be set");
}

bool _navAnimationInProgress;
internal const string CloseContextActionsSignalName = "Xamarin.CloseContextActions";
Page CurrentPage => _navModel.CurrentPage;

// AFAICT this is specific to ListView and Context Items
internal bool NavAnimationInProgress
Expand Down Expand Up @@ -198,6 +207,10 @@ sealed class ModalContainer : ViewGroup
ModalFragment _modalFragment;
FragmentManager? _fragmentManager;
NavigationRootManager? NavigationRootManager => _modalFragment.NavigationRootManager;
int _currentRootViewHeight = 0;
int _currentRootViewWidth = 0;
GenericGlobalLayoutListener? _rootViewLayoutListener;
AView? _rootView;

AView GetWindowRootView() =>
_windowMauiContext
Expand All @@ -213,7 +226,6 @@ public ModalContainer(
{
_windowMauiContext = windowMauiContext;
Modal = modal;

_backgroundView = new AView(_windowMauiContext.Context);
UpdateBackgroundColor();
AddView(_backgroundView);
Expand All @@ -229,22 +241,110 @@ public ModalContainer(
.BeginTransaction()
.Add(this.Id, _modalFragment)
.Commit();
}

protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
UpdateMargin();
UpdateRootView(GetWindowRootView());
}

protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
UpdateRootView(null);
}

void UpdateRootView(AView? rootView)
{
if (_rootView.IsAlive() && _rootView != null)
{
_rootView.LayoutChange -= OnRootViewLayoutChanged;
_rootView = null;
}

if (rootView.IsAlive() && rootView != null)
{
rootView.LayoutChange += OnRootViewLayoutChanged;
_rootView = rootView;
_currentRootViewHeight = _rootView.MeasuredHeight;
_currentRootViewWidth = _rootView.MeasuredWidth;
}
}

// If the RootView changes sizes that means we also need to change sizes
// This will typically happen when the user is opening the soft keyboard
// which sometimes causes the available window size to change
void OnRootViewLayoutChanged(object? sender, LayoutChangeEventArgs e)
{
if (Modal == null || sender is not AView view)
return;

var modalStack = Modal?.Navigation?.ModalStack;
if (modalStack == null ||
modalStack.Count == 0 ||
modalStack[modalStack.Count - 1] != Modal)
{
return;
}

if ((_currentRootViewHeight != view.MeasuredHeight || _currentRootViewWidth != view.MeasuredWidth)
&& this.ViewTreeObserver != null)
{
// When the keyboard closes Android calls layout but doesn't call remeasure.
// MY guess is that this is due to the modal not being part of the FitSystemWindowView
// The modal is added to the decor view so its dimensions don't get updated.
// So, here we are waiting for the layout pass to finish and then we remeasure the modal
//
// For .NET 8 we'll convert this all over to using a DialogFragment
// which means we can delete most of the awkward code here
_currentRootViewHeight = view.MeasuredHeight;
_currentRootViewWidth = view.MeasuredWidth;
if (!this.IsInLayout)
{
this.InvalidateMeasure(Modal);
return;
}

_rootViewLayoutListener ??= new GenericGlobalLayoutListener((listener, view) =>
{
if (view != null && !this.IsInLayout)
{
listener.Invalidate();
_rootViewLayoutListener = null;
this.InvalidateMeasure(Modal);
}
}, this);
}
}

void UpdateMargin()
{
// This sets up the modal container to be offset from the top of window the same
// amount as the view it's covering. This will make it so the
// ModalContainer takes into account the statusbar or lack thereof
var rootView = GetWindowRootView();
int y = (int)rootView.GetLocationOnScreenPx().Y;
// ModalContainer takes into account the StatusBar or lack thereof
var decorView = Context?.GetActivity()?.Window?.DecorView;

if (this.LayoutParameters is ViewGroup.MarginLayoutParams mlp &&
mlp.TopMargin != y)
if (decorView != null && this.LayoutParameters is ViewGroup.MarginLayoutParams mlp)
{
mlp.TopMargin = y;
var windowInsets = ViewCompat.GetRootWindowInsets(decorView);
if (windowInsets != null)
{
var barInsets = windowInsets.GetInsetsIgnoringVisibility(WindowInsetsCompat.Type.SystemBars());

if (mlp.TopMargin != barInsets.Top)
mlp.TopMargin = barInsets.Top;

if (mlp.LeftMargin != barInsets.Left)
mlp.LeftMargin = barInsets.Left;

if (mlp.RightMargin != barInsets.Right)
mlp.RightMargin = barInsets.Right;

if (mlp.BottomMargin != barInsets.Bottom)
mlp.BottomMargin = barInsets.Bottom;
}
}
}

Expand All @@ -262,8 +362,8 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
return;
}

var rootView = GetWindowRootView();
UpdateMargin();
var rootView = GetWindowRootView();

widthMeasureSpec = MeasureSpecMode.Exactly.MakeMeasureSpec(rootView.MeasuredWidth);
heightMeasureSpec = MeasureSpecMode.Exactly.MakeMeasureSpec(rootView.MeasuredHeight);
Expand Down Expand Up @@ -314,6 +414,10 @@ public void Destroy()

Modal.Handler = null;

UpdateRootView(null);
_rootViewLayoutListener?.Invalidate();
_rootViewLayoutListener = null;

_fragmentManager
.BeginTransaction()
.Remove(_modalFragment)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ public override AView OnCreateView(ALayoutInflater inflater, AViewGroup containe
FakeActivityRootView.AddView(handler.PlatformViewUnderTest);
handler.PlatformViewUnderTest.LayoutParameters = new FitWindowsFrameLayout.LayoutParams(AViewGroup.LayoutParams.MatchParent, AViewGroup.LayoutParams.MatchParent);

if (_window is Window window)
{
window.ModalNavigationManager.SetModalParentView(FakeActivityRootView);
}

return FakeActivityRootView;
}

Expand Down
Loading

0 comments on commit fe07490

Please sign in to comment.