From b4f3eed8d3a46b089fb54098613d10ca82e30ff2 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 24 Jun 2022 03:09:37 -0500 Subject: [PATCH] Account for RTL on Android Tablet (#15417) * Account for RTL on Android Tablet * - additional RTL fixes * - added comments * Update build-osx.yml Co-authored-by: Gerald Versluis --- .../AppCompat/FlyoutPageContainer.cs | 19 ++++- .../AppCompat/FlyoutPageRenderer.cs | 71 ++++++++++++++----- .../AppCompat/NavigationPageRenderer.cs | 11 ++- .../Renderers/ViewGroupExtensions.cs | 22 ++++++ build/steps/build-osx.yml | 3 + 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageContainer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageContainer.cs index acbca626798..0a9f2ed36bb 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageContainer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageContainer.cs @@ -274,15 +274,32 @@ Rectangle GetBounds(bool isFlyoutPage, int left, int top, int right, int bottom) { //to keep some behavior we have on iPad where you can toggle and it won't do anything bool isDefaultNoToggle = _parent.FlyoutLayoutBehavior == FlyoutLayoutBehavior.Default; - xPos = isFlyoutPage ? 0 : (_parent.IsPresented || isDefaultNoToggle ? DefaultWidthFlyout : 0); + + if (_parent.FlowDirection == FlowDirection.RightToLeft) + { + double rightDp2 = Context.FromPixels(right); + xPos = isFlyoutPage ? rightDp2 - DefaultWidthFlyout : (_parent.IsPresented || isDefaultNoToggle ? 0 : rightDp2 - DefaultWidthFlyout); + } + else + { + xPos = isFlyoutPage ? 0 : (_parent.IsPresented || isDefaultNoToggle ? DefaultWidthFlyout : 0); + } + width = isFlyoutPage ? DefaultWidthFlyout : _parent.IsPresented || isDefaultNoToggle ? width - DefaultWidthFlyout : width; } else { //if we are showing the normal popover master doesn't have padding supressPadding = isFlyoutPage; + + if (_parent.FlowDirection == FlowDirection.RightToLeft && isFlyoutPage) + { + xPos = width - DefaultWidthFlyout; + } + //popover make the master smaller width = isFlyoutPage && (Device.Info.CurrentOrientation.IsLandscape() || Device.Idiom == TargetIdiom.Tablet) ? DefaultWidthFlyout : width; + } double padding = supressPadding ? 0 : Context.FromPixels(TopPadding); diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs index 6353f353457..c17f63d5cb6 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs @@ -308,31 +308,59 @@ protected override void OnLayout(bool changed, int l, int t, int r, int b) base.OnLayout(changed, l, t, r, b); //hack to make the split layout handle touches the full width if (FlyoutPageController.ShouldShowSplitMode && _flyoutLayout != null) - _flyoutLayout.Right = r; + { + if (Element.FlowDirection == FlowDirection.RightToLeft) + _flyoutLayout.Left = l; + else + _flyoutLayout.Right = r; + } } - async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e) + async void UpdateFlyoutLayoutBehavior(bool requestLayout = false) { - if (nameof(Device.Info.CurrentOrientation) == e.PropertyName) + if (!FlyoutPageController.ShouldShowSplitMode && Presented) { - if (!FlyoutPageController.ShouldShowSplitMode && Presented) + FlyoutPageController.CanChangeIsPresented = true; + //hack : when the orientation changes and we try to close the Flyout on Android + //sometimes Android picks the width of the screen previous to the rotation + //this leaves a little of the flyout visible, the hack is to delay for 100ms closing the drawer + await Task.Delay(100); + + //Renderer may have been disposed during the delay + if (_disposed) { - FlyoutPageController.CanChangeIsPresented = true; - //hack : when the orientation changes and we try to close the Flyout on Android - //sometimes Android picks the width of the screen previous to the rotation - //this leaves a little of the flyout visible, the hack is to delay for 100ms closing the drawer - await Task.Delay(100); - - //Renderer may have been disposed during the delay - if (_disposed) - { - return; - } + return; + } - CloseDrawer(_flyoutLayout); + CloseDrawer(_flyoutLayout); + } + else if (FlyoutPageController.ShouldShowSplitMode) + { + OpenDrawer(_flyoutLayout, false); + } + + UpdateSplitViewLayout(); + + if (requestLayout) + { + _flyoutLayout?.MaybeRequestLayout(); + _detailLayout?.MaybeRequestLayout(); + + if (Device.Idiom == TargetIdiom.Tablet && _flyoutLayout != null) + { + // This is required to add/remove the drawer button + // This basically runs the same code that runs when + // a device changes between landscape/portrait + _detailLayout.GetFirstChildOfType()?.ResetToolbar(); } + } + } - UpdateSplitViewLayout(); + void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (nameof(Device.Info.CurrentOrientation) == e.PropertyName) + { + UpdateFlyoutLayoutBehavior(); } } @@ -373,7 +401,16 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) UpdateBackgroundColor(Element); else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName) + { UpdateFlowDirection(); + + // This will move the drawer layout button to the proper side of the toolbar + _detailLayout.GetFirstChildOfType()?.UpdateToolbar(); + } + else if (e.Is(FlyoutPage.FlyoutLayoutBehaviorProperty)) + { + UpdateFlyoutLayoutBehavior(true); + } } void FlyoutPageAppearing(object sender, EventArgs e) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs index 99c1ae3cd19..6a856e9aa05 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs @@ -736,7 +736,7 @@ void RemovePage(Page page) }); } - void ResetToolbar() + internal void ResetToolbar() { AToolbar oldToolbar = _toolbar; @@ -933,7 +933,7 @@ protected virtual void UpdateMenuItemIcon(Context context, IMenuItem menuItem, T ToolbarExtensions.UpdateMenuItemIcon(context, menuItem, toolBarItem, null); } - void UpdateToolbar() + internal void UpdateToolbar() { if (_disposed) return; @@ -982,6 +982,13 @@ void UpdateToolbar() { toggle.DrawerIndicatorEnabled = _flyoutPage.ShouldShowToolbarButton(); toggle.SyncState(); + + // When pivoting between split mode and flyout mode + // The DrawerArrowDrawable Progress will get out of sync and show a back button + // this forces it back to a hamburger + + if (toggle.DrawerArrowDrawable != null) + toggle.DrawerArrowDrawable.Progress = 0; } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/ViewGroupExtensions.cs b/Xamarin.Forms.Platform.Android/Renderers/ViewGroupExtensions.cs index dfcb9ca2998..7bdddc0db15 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ViewGroupExtensions.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ViewGroupExtensions.cs @@ -23,5 +23,27 @@ internal static IEnumerable GetChildrenOfType(this AViewGroup self) where } } } + + public static T GetFirstChildOfType(this AViewGroup viewGroup) where T : AView + { + for (var i = 0; i < viewGroup.ChildCount; i++) + { + AView child = viewGroup.GetChildAt(i); + + if (child is T typedChild) + return typedChild; + + if (child is AViewGroup vg) + { + var descendant = vg.GetFirstChildOfType(); + if (descendant != null) + { + return descendant; + } + } + } + + return null; + } } } \ No newline at end of file diff --git a/build/steps/build-osx.yml b/build/steps/build-osx.yml index 08528023434..145a957caa3 100644 --- a/build/steps/build-osx.yml +++ b/build/steps/build-osx.yml @@ -61,6 +61,9 @@ steps: inputs: provProfileSecureFile: 'Xamarin Forms iOS Provisioning.mobileprovision' + - bash: rm -rf ~/.config/NuGet/NuGet.Config + displayName: 'Workaround to make build work' + - task: Bash@3 displayName: 'Build Control Gallery IPA' inputs: