diff --git a/maui/src/BottomSheet/SfBottomSheet.cs b/maui/src/BottomSheet/SfBottomSheet.cs index 789f3267..a1ef3b35 100644 --- a/maui/src/BottomSheet/SfBottomSheet.cs +++ b/maui/src/BottomSheet/SfBottomSheet.cs @@ -1,4 +1,4 @@ -using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Controls.Shapes; using Syncfusion.Maui.Toolkit.Internals; using Syncfusion.Maui.Toolkit.Themes; using Syncfusion.Maui.Toolkit.Helper; @@ -74,11 +74,16 @@ public partial class SfBottomSheet : SfView, IParentThemeElement /// bool _isPointerPressed; - // Touch tracking - /// - /// The initial Y-coordinate of a touch event on the bottom sheet. - /// - double _initialTouchY; + /// + /// Indicates whether the overlay grid is currently added to the view hierarchy. + /// + bool _isOverlayAdded; + + // Touch tracking + /// + /// The initial Y-coordinate of a touch event on the bottom sheet. + /// + double _initialTouchY; /// /// The starting Y-coordinate of a swipe gesture on the bottom sheet. @@ -1242,7 +1247,7 @@ internal Color OverlayBackgroundColor /// public void Show() { - if (_bottomSheet is null || _overlayGrid is null) + if (_bottomSheet is null) { return; } @@ -1267,7 +1272,7 @@ public void Show() /// public void Close() { - if(_bottomSheet is null || _overlayGrid is null) + if(_bottomSheet is null) { return; } @@ -1275,7 +1280,7 @@ public void Close() AnimateBottomSheet(Height, onFinish: () => { _bottomSheet.IsVisible = false; - _overlayGrid.IsVisible = false; + RemoveOverlayFromView(); }); if (_isSheetOpen) @@ -1356,9 +1361,8 @@ void InitializeLayout() InitializeBottomSheetBorder(); InitializeContentBorder(); - if (_bottomSheet is not null && _overlayGrid is not null) + if (_bottomSheet is not null) { - Children.Add(_overlayGrid); Children.Add(_bottomSheet); _bottomSheet.IsVisible = false; } @@ -1370,6 +1374,7 @@ void InitializeLayout() void UpdateContentView() { Children.Clear(); + _isOverlayAdded = false; // Reset overlay state UpdateAllChild(); } @@ -1379,7 +1384,6 @@ void UpdateContentView() void UpdateAllChild() { AddChild(Content); - AddChild(_overlayGrid); AddChild(_bottomSheet); } @@ -1404,7 +1408,7 @@ void InitializeOverlayGrid() { BackgroundColor = OverlayBackgroundColor, Opacity = DefaultOverlayOpacity, - IsVisible = false + IsVisible = true }; var tapGestureRecognizer = new TapGestureRecognizer(); @@ -1485,6 +1489,36 @@ void InitializeBottomSheetBorder() }; } + /// + /// Adds the overlay grid to the view hierarchy if it's not already added and modal is enabled. + /// + void AddOverlayToView() + { + if (_overlayGrid is not null && IsModal && !_isOverlayAdded) + { + if (!Children.Contains(_overlayGrid)) + { + Children.Insert(Children.Count - 1, _overlayGrid); // Insert before bottom sheet + } + _isOverlayAdded = true; + } + } + + /// + /// Removes the overlay grid from the view hierarchy. + /// + void RemoveOverlayFromView() + { + if (_overlayGrid is not null && _isOverlayAdded) + { + if (Children.Contains(_overlayGrid)) + { + Children.Remove(_overlayGrid); + } + _isOverlayAdded = false; + } + } + /// /// Calculates the initial height for the half-expanded state. /// @@ -2040,7 +2074,7 @@ void RegisterSizeChangedEvent() /// void SetupBottomSheetForShow() { - if (_isSheetOpen || _bottomSheet is null || _overlayGrid is null) + if (_isSheetOpen || _bottomSheet is null) { return; } @@ -2048,8 +2082,12 @@ void SetupBottomSheetForShow() // Position the bottom sheet just below the visible area _bottomSheet.TranslationY = Height; _bottomSheet.IsVisible = true; - _overlayGrid.IsVisible = IsModal; - _overlayGrid.Opacity = 0; + + // Add overlay to view if modal + if (IsModal) + { + AddOverlayToView(); + } } @@ -2133,11 +2171,6 @@ void AnimateBottomSheet(double targetPosition, Action? onFinish = null) _bottomSheet.AbortAnimation("bottomSheetAnimation"); } - if (_overlayGrid.AnimationIsRunning("overlayGridAnimation")) - { - _overlayGrid.AbortAnimation("overlayGridAnimation"); - } - int animationDuration = this.GetClampedAnimationDuration(); const int topPadding = 2; _isSheetOpen = true; @@ -2159,46 +2192,45 @@ void AnimateBottomSheet(double targetPosition, Action? onFinish = null) /// void AnimateOverlay(int animationDuration) { - if (_overlayGrid is not null) + if (_overlayGrid is null || !IsModal) { - double startValue = 0; - double endValue = 0; - _overlayGrid.IsVisible = IsModal; + return; + } - if (IsModal) + // Ensure overlay is added to view when needed + bool shouldShowOverlay = State is not (BottomSheetState.Collapsed or BottomSheetState.Hidden); + + if (shouldShowOverlay) + { + AddOverlayToView(); + } + + if (_overlayGrid.AnimationIsRunning("overlayGridAnimation")) + { + _overlayGrid.AbortAnimation("overlayGridAnimation"); + } + + double startValue = _overlayGrid.Opacity; + double endValue = shouldShowOverlay ? DefaultOverlayOpacity : 0; + + var overlayGridAnimation = new Animation(d => + { + if (!double.IsNaN(d)) { - if (State is BottomSheetState.Collapsed || State is BottomSheetState.Hidden) - { - startValue = _overlayGrid.Opacity; - endValue = 0; - } - else - { - startValue = _overlayGrid.Opacity; - endValue = DefaultOverlayOpacity; - } + _overlayGrid.Opacity = d; + } + }, startValue, endValue); - var overlayGridAnimation = new Animation(d => + _overlayGrid.Animate("overlayGridAnimation", overlayGridAnimation, + length: (uint)animationDuration, + easing: Easing.Linear, + finished: (v, c) => + { + if (!shouldShowOverlay) { - // Ensure the opacity is only updated with valid numeric values to avoid rendering issues. - if (!double.IsNaN(d)) - { - _overlayGrid.Opacity = d; - } + RemoveOverlayFromView(); } - , startValue, endValue); - _overlayGrid.Animate("overlayGridAnimation", overlayGridAnimation, - length: (uint)animationDuration, - easing: Easing.Linear, - finished: (e, v) => - { - if (State is BottomSheetState.Collapsed || State is BottomSheetState.Hidden) - { - _overlayGrid.IsVisible = false; - } - }); - } - } + }); } /// @@ -2342,7 +2374,7 @@ AllowedState is BottomSheetAllowedState.HalfExpanded && /// The current Y coordinate of the touch point. void UpdateBottomSheetPosition(double newTranslationY, double touchY) { - if (_bottomSheet is null || _overlayGrid is null) + if (_bottomSheet is null) { return; } @@ -2350,8 +2382,21 @@ void UpdateBottomSheetPosition(double newTranslationY, double touchY) _bottomSheet.TranslationY = newTranslationY; _initialTouchY = touchY; _bottomSheet.HeightRequest = Height - newTranslationY; - _overlayGrid.IsVisible = IsModal && (_bottomSheet.HeightRequest > CollapsedHeight); - _overlayGrid.Opacity = CalculateOverlayOpacity(_bottomSheet.HeightRequest); + // Manage overlay visibility during touch + bool shouldShowOverlay = IsModal && (_bottomSheet.HeightRequest > CollapsedHeight); + + if (shouldShowOverlay) + { + AddOverlayToView(); + if (_overlayGrid is not null) + { + _overlayGrid.Opacity = CalculateOverlayOpacity(_bottomSheet.HeightRequest); + } + } + else + { + RemoveOverlayFromView(); + } } /// @@ -2517,12 +2562,18 @@ static void OnIsModalPropertyChanged(BindableObject bindable, object oldValue, o { if (bindable is SfBottomSheet sheet) { - if (sheet._overlayGrid is not null && (sheet.State is BottomSheetState.FullExpanded || sheet.State is BottomSheetState.HalfExpanded)) - { - sheet._overlayGrid.IsVisible = sheet.IsModal; + bool isModal = (bool)newValue; + + if (isModal && (sheet.State is BottomSheetState.FullExpanded or BottomSheetState.HalfExpanded)) + { + sheet.AddOverlayToView(); sheet.AnimateOverlay(150); - } - } + } + else if (!isModal) + { + sheet.RemoveOverlayFromView(); + } + } } diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs index 28c58c3c..d2c224a1 100644 --- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs +++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs @@ -38,7 +38,7 @@ public void Constructor_InitializesDefaultsCorrectly() Assert.Equal(4d, _bottomSheet.GrabberHeight); Assert.Equal(32d, _bottomSheet.GrabberWidth); Assert.Equal(12d, _bottomSheet.GrabberCornerRadius); - Assert.Equal(150d, _bottomSheet.AnimationDuration); + Assert.Equal(150d,_bottomSheet.AnimationDuration); if (_bottomSheet.GrabberBackground is SolidColorBrush grabberBrush) { var grabberColor = grabberBrush.Color; @@ -564,7 +564,7 @@ public void InitializeLayout() var bottomSheet = GetPrivateField(_bottomSheet, "_bottomSheet"); var overlayGrid = GetPrivateField(_bottomSheet, "_overlayGrid"); Assert.True(_bottomSheet.Children?.Contains(bottomSheet)); - Assert.True(_bottomSheet.Children?.Contains(overlayGrid)); + Assert.False(_bottomSheet.Children?.Contains(overlayGrid)); } [Fact] @@ -592,7 +592,7 @@ public void InitializeOverlayGrid() Grid? overlayGrid = (Grid?)GetPrivateField(_bottomSheet, "_overlayGrid"); Assert.Equal(overlayGrid?.BackgroundColor, Color.FromArgb("#80000000")); Assert.Equal(overlayGrid?.Opacity, 0.5); - Assert.Equal(overlayGrid?.IsVisible, false); + Assert.Equal(overlayGrid?.IsVisible, true); } [Fact] @@ -852,7 +852,7 @@ public void UpdateStateChanged(BottomSheetState oldState, BottomSheetState newSt [Theory] [InlineData(true, true)] - [InlineData(false, false)] + [InlineData(false, true)] public void SetupBottomSheetForShow(bool input, bool expected) { _bottomSheet.IsModal = input; @@ -860,7 +860,7 @@ public void SetupBottomSheetForShow(bool input, bool expected) SfGrid? overlay = (SfGrid?)GetPrivateField(_bottomSheet, "_overlayGrid"); SfBorder? bottomsheet = (SfBorder?)GetPrivateField(_bottomSheet, "_bottomSheet"); Assert.True(bottomsheet?.IsVisible); - Assert.Equal(0, overlay?.Opacity); + Assert.Equal(0.5, overlay?.Opacity); Assert.Equal(expected, overlay?.IsVisible); } @@ -868,8 +868,8 @@ public void SetupBottomSheetForShow(bool input, bool expected) public void GetCollapsedPosition() { InvokePrivateMethod(_bottomSheet, "GetCollapsedPosition"); - SfGrid? overlay = (SfGrid?)GetPrivateField(_bottomSheet, "_overlayGrid"); - Assert.False(overlay?.IsVisible); + var overlay = GetPrivateField(_bottomSheet, "_overlayGrid"); + Assert.False(_bottomSheet.Children?.Contains(overlay)); } [Fact]