Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [55.6.4]
- Fixed multiple memory leaks across components: unsubscribed events in `BottomSheetHandler`, `BaseDatePickerHandler`, `BaseNullableDatePicker`, `TabView`, `Shell`, `SkeletonView`, `SegmentedControl`, `StateView`, `ItemPicker`, `SearchPage`, `ScrollPickerHandler`, `FloatingNavigationButton`, `GalleryBottomSheet`, and `SystemMessage`

## [55.6.3]
- [ScrollView][iOS] ShouldBounce now sets correct native property.

Expand Down
2 changes: 1 addition & 1 deletion build/bootstrapper/bootstrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fi


#azure-CLI
if az > /dev/null ; then
if az > /dev/null 2>&1 ; then
echo "✅ Azure CLI was found"
else
echo "❌ Azure CLI not found, installing it..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,7 @@ private void GoToEditState()

m_currentlyRotatedImageDisplayed.TranslationY -= m_topToolbar.HeightRequest;

m_currentlyRotatedImageDisplayed.SizeChanged += delegate
{
if (m_startingImageWidth is not null)
return;

m_startingImageWidth = m_currentlyRotatedImageDisplayed.Width;
m_startingImageHeight = m_currentlyRotatedImageDisplayed.Height;

m_carouselView.Opacity = 0;
m_navigatePreviousImageButton.IsVisible = false;
m_navigateNextImageButton.IsVisible = false;
};
m_currentlyRotatedImageDisplayed.SizeChanged += OnRotatedImageSizeChanged;

m_rotatingImageTcs = null;

Expand All @@ -169,11 +158,25 @@ private void GoToDefaultState()

m_positionBeforeEdit = m_carouselView!.Position;

m_currentlyRotatedImageDisplayed.SizeChanged -= OnRotatedImageSizeChanged;
m_grid.Remove(m_currentlyRotatedImageDisplayed);
UpdateNavigationButtonsVisibility(m_carouselView.Position);
OnImagesChanged();
}

private void OnRotatedImageSizeChanged(object? sender, EventArgs e)
{
if (m_startingImageWidth is not null)
return;

m_startingImageWidth = m_currentlyRotatedImageDisplayed.Width;
m_startingImageHeight = m_currentlyRotatedImageDisplayed.Height;

m_carouselView!.Opacity = 0;
m_navigatePreviousImageButton.IsVisible = false;
m_navigateNextImageButton.IsVisible = false;
}

async void IGalleryDefaultStateObserver.RemoveImage()
{
if(Images.Count == 0)
Expand Down Expand Up @@ -276,6 +279,7 @@ private void OnImagesChanged()

if (m_carouselView is not null)
{
m_carouselView.SizeChanged -= OnCarouselViewSizeChanged;
m_carouselView.PositionChanged -= CarouselViewOnPositionChanged;
try
{
Expand Down Expand Up @@ -310,30 +314,32 @@ private void OnImagesChanged()
};
m_carouselViewWrapperView.Content = m_carouselView;

m_carouselView.SizeChanged += delegate
{
if (m_hasSetToolbarHeights)
{
return;
}

var actualImageHeight = m_carouselView.Width / CameraPreview.ThreeFourRatio;
var letterBoxHeight = m_carouselView.Height - actualImageHeight;

m_topToolbar.HeightRequest = letterBoxHeight * CameraPreview.TopToolbarPercentHeightOfLetterBox;
m_bottomToolbar.HeightRequest = letterBoxHeight * CameraPreview.BottomToolbarPercentHeightOfLetterBox;

m_carouselViewWrapperView.TranslationY -= m_topToolbar.HeightRequest;
m_navigatePreviousImageButton.TranslationY -= m_topToolbar.HeightRequest;
m_navigateNextImageButton.TranslationY -= m_topToolbar.HeightRequest;

m_hasSetToolbarHeights = true;
};
m_carouselView.SizeChanged += OnCarouselViewSizeChanged;

m_carouselView.PositionChanged += CarouselViewOnPositionChanged;
m_grid.Insert(0, m_carouselViewWrapperView);
}

private void OnCarouselViewSizeChanged(object? sender, EventArgs e)
{
if (m_hasSetToolbarHeights)
{
return;
}

var actualImageHeight = m_carouselView!.Width / CameraPreview.ThreeFourRatio;
var letterBoxHeight = m_carouselView.Height - actualImageHeight;

m_topToolbar.HeightRequest = letterBoxHeight * CameraPreview.TopToolbarPercentHeightOfLetterBox;
m_bottomToolbar.HeightRequest = letterBoxHeight * CameraPreview.BottomToolbarPercentHeightOfLetterBox;

m_carouselViewWrapperView.TranslationY -= m_topToolbar.HeightRequest;
m_navigatePreviousImageButton.TranslationY -= m_topToolbar.HeightRequest;
m_navigateNextImageButton.TranslationY -= m_topToolbar.HeightRequest;

m_hasSetToolbarHeights = true;
}

private void OnStartingIndexChanged()
{
if(m_carouselView is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ await Task.Run(() =>
if (PreviewView is not null)
{
PreviewView.OnZoomChanged -= PreviewViewOnZoomChanged;
PreviewView.OnTapToFocus -= PreviewViewOnOnTapToFocus;
PreviewView?.Dispose();
PreviewView = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,6 @@ public void Dispose()
{
m_timer.Stop();
m_timer.Elapsed -= OnTimerEnded;
m_timer.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ public partial class BottomSheetHandler : ContentViewHandler
private BottomSheetHeader m_bottomSheetHeader;
private List<WeakReference<SearchBar>> m_weakSearchBars = [];
private WeakReference<AView>? m_weakCurrentFocusedSearchBar;
private BottomSheetCallback? m_bottomSheetCallback;
private KeyListener? m_keyListener;

public AView OnBeforeOpening(IMauiContext mauiContext, Context context, AView bottomSheetAndroidView,
RelativeLayout rootLayout, LinearLayout bottomSheetLayout)
{
if (VirtualView is not BottomSheet bottomSheet) return new AView(Context);

m_bottomSheet = bottomSheet;
bottomSheet.BottomSheetDialog.Behavior.AddBottomSheetCallback(
new BottomSheetCallback(this));
m_bottomSheetCallback = new BottomSheetCallback(this);
m_keyListener = new KeyListener(this);
bottomSheet.BottomSheetDialog.Behavior.AddBottomSheetCallback(m_bottomSheetCallback);
bottomSheet.BottomSheetDialog.SetOnShowListener(new DialogInterfaceOnShowListener(this));
bottomSheet.BottomSheetDialog.SetOnKeyListener(new KeyListener(this));
bottomSheet.BottomSheetDialog.SetOnKeyListener(m_keyListener);

//Add a handle, with a innerGrid that works as a big hit box for the user to hit
//Inspired by com.google.android.material.bottomsheet.BottomSheetDragHandleView , which will be added in Xamarin Android Material Design v1.7.0. https://github.com/material-components/material-components-android/commit/ac7b761294808748df167b50b223b591ca9dac06
Expand Down Expand Up @@ -234,6 +237,16 @@ protected override void DisconnectHandler(ContentViewGroup platformView)
{
base.DisconnectHandler(platformView);

if (m_bottomSheetCallback is not null)
{
m_bottomSheet.BottomSheetDialog.Behavior.RemoveBottomSheetCallback(m_bottomSheetCallback);
m_bottomSheetCallback = null;
}

m_bottomSheet.BottomSheetDialog.SetOnShowListener(null);
m_bottomSheet.BottomSheetDialog.SetOnKeyListener(null);
m_keyListener = null;

s_mEmptyNonFitToContentView?.RemoveFromParent();
m_bottomSheetHeader.DisconnectHandlers();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ private BoxView CreateBox(SkeletonShape shape)
return box;
}

protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);

if (args.NewHandler is not null)
return;

StopAnimation();
}

private void StartAnimation()
{
StopAnimation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,20 @@ private async Task FadeIn(IView? view)
}
}

private StateViewModel? m_previousStateViewModel;

private void OnStateViewModelChanged()
{
if(StateViewModel is null)
return;

if (m_previousStateViewModel is not null)
{
m_previousStateViewModel.OnStateChanged -= OnStateChanged;
}

m_previousStateViewModel = StateViewModel;

ErrorView ??= new ErrorView { BindingContext = StateViewModel.Error };
LoadingView ??= new LoadingView { BindingContext = StateViewModel.Loading };
EmptyView ??= new EmptyView { BindingContext = StateViewModel.Empty };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@ private static partial void MapIsClickable(FloatingNavigationButtonHandler handl
if (handler.PlatformView is not global::Android.Views.View aView) return;
if (handler.VirtualView is not FloatingNavigationButton fab) return;

aView.Click -= handler.OnNativeViewClick;

if (floatingNavigationButton.IsClickable)
{
aView.Clickable = true;
aView.Click += (_, _) =>
{
_ = fab.Close();
};
aView.Click += handler.OnNativeViewClick;
}
else
{
aView.Clickable = false;
}
}

private void OnNativeViewClick(object? sender, EventArgs e)
{
if (VirtualView is FloatingNavigationButton fab)
{
_ = fab.Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public static void Close()
public static void Remove()
{
PlatformRemove();
FloatingNavigationButton = null;
}

private static partial void PlatformRemove();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ protected override void DisconnectHandler(UIDatePicker platformView)
{
base.DisconnectHandler(platformView);

platformView.ValueChanged -= OnValueChanged;
platformView.EditingDidBegin -= OnOpen;
platformView.EditingDidEnd -= OnClose;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ private static void ItemsSourceChanged(BindableObject bindable, object oldValue,
picker.AddContextMenuItems();
}

if (oldValue is INotifyCollectionChanged oldNotifyCollectionChanged)
{
oldNotifyCollectionChanged.CollectionChanged -= picker.OnCollectionChanged;
}

if (newValue is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += picker.OnCollectionChanged;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ protected override void OnHandlerChanging(HandlerChangingEventArgs args)
base.OnHandlerChanging(args);

if(args.NewHandler is null)
{
DateEnabledSwitch.Toggled -= OnSwitchToggled;
return;
}

m_dateOrTimePicker = CreateDateOrTimePicker();
m_dateOrTimePicker.IsVisible = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ protected override void DisconnectHandler(Chip platformView)

platformView.Click -= PlatformViewOnClick;
m_scrollPickerViewModel.OnAnyComponentsDataInvalidated -= SetChipTitle;
m_scrollPickerViewModel.Dispose();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -95,32 +95,34 @@ private View CreateSegment()
horizontalStackLayout.Add(checkedImage);
horizontalStackLayout.Add(label);
border.Content = horizontalStackLayout;
border.SizeChanged += ((sender, _) =>
{
if (sender is not View view) return;
border.SizeChanged += OnBorderSizeChanged;

return border;
}

// Sometimes on Android, the different does not have equal heights, so this is a workaround to ensure all borders have same height.
private void OnBorderSizeChanged(object? sender, EventArgs _)
{
if (sender is not Border border) return;

// Sometimes on Android, the different does not have equal heights, so this is a workaround to ensure all borders have same height.
#if __ANDROID__
border.HeightRequest = this.Height;
#endif

if (view.BindingContext is not SelectableItemViewModel selectableListItem) return;

var radius = Sizes.GetSize(SizeName.radius_xlarge);
var roundRectangle = new RoundRectangle() { StrokeThickness = 0};
if (m_allSelectableItems.Last() == selectableListItem)
{
roundRectangle.CornerRadius = new CornerRadius(0, radius, 0, radius);
}
else if (m_allSelectableItems.First() == selectableListItem)
{
roundRectangle.CornerRadius = new CornerRadius(radius, 0, radius, 0);
}
if (border.BindingContext is not SelectableItemViewModel selectableListItem) return;

border.StrokeShape = roundRectangle;
});
var radius = Sizes.GetSize(SizeName.radius_xlarge);
var roundRectangle = new RoundRectangle() { StrokeThickness = 0};
if (m_allSelectableItems.Last() == selectableListItem)
{
roundRectangle.CornerRadius = new CornerRadius(0, radius, 0, radius);
}
else if (m_allSelectableItems.First() == selectableListItem)
{
roundRectangle.CornerRadius = new CornerRadius(radius, 0, radius, 0);
}

return border;
border.StrokeShape = roundRectangle;
}

private void OnItemTouched(SelectableItemViewModel selectableItemViewModel)
Expand Down Expand Up @@ -158,9 +160,23 @@ private void SendDidSelect(object item)
SelectedItemCommand?.Execute(item);
}

private void UnsubscribeBorderEvents()
{
foreach (var child in m_horizontalStackLayout.Children)
{
if (child is Border border)
{
border.SizeChanged -= OnBorderSizeChanged;
}
}
}

private void ItemsSourceChanged()
{
if (ItemsSource == null) return;

UnsubscribeBorderEvents();

var listOfSelectableItems = new List<SelectableItemViewModel>();

foreach (var item in ItemsSource.Cast<object>().ToList())
Expand All @@ -183,4 +199,14 @@ private void ItemsSourceChanged()
m_allSelectableItems = listOfSelectableItems;
BindableLayout.SetItemsSource(m_horizontalStackLayout, m_allSelectableItems);
}

protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);

if (args.NewHandler is null)
{
UnsubscribeBorderEvents();
}
}
}
Loading