diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 5a1ddaa59c..510e3cbe95 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -42,6 +42,7 @@ body: label: Version with bug description: In what version do you see this issue? Run `dotnet workload list` to find your version. options: + - 9.0.0-rc.1.24453.9 - 9.0.0-preview.7.24407.4 - 9.0.0-preview.6.24327.7 - 9.0.0-preview.5.24307.10 @@ -49,6 +50,7 @@ body: - 9.0.0-preview.3.10457 - 9.0.0-preview.2.10293 - 9.0.0-preview.1.9973 + - 8.0.90 SR9 - 8.0.82 SR8.2 - 8.0.80 SR8 - 8.0.71 SR7.1 @@ -123,12 +125,14 @@ body: - 8.0.71 SR7.1 - 8.0.80 SR8 - 8.0.82 SR8.2 + - 8.0.90 SR9 - 9.0.0-preview.1.9973 - 9.0.0-preview.2.10293 - 9.0.0-preview.3.10457 - 9.0.0-preview.4.10690 - 9.0.0-preview.5.24307.10 - 9.0.0-preview.6.24327.7 + - 9.0.0-preview.7.24407.4 validations: required: true - type: dropdown diff --git a/eng/pipelines/device-tests.yml b/eng/pipelines/device-tests.yml index faf2e578d3..473e915337 100644 --- a/eng/pipelines/device-tests.yml +++ b/eng/pipelines/device-tests.yml @@ -72,14 +72,20 @@ parameters: - name: iosPool type: object default: - name: $(macosTestsVmPool) - vmImage: macOS-14 + name: $(iosTestsVmPool) + vmImage: $(iosTestsVmImage) + demands: + - macOS.Name -equals Sonoma + - macOS.Architecture -equals x64 - name: catalystPool type: object default: - name: $(macosTestsVmPool) - vmImage: macOS-14 + name: $(iosTestsVmPool) + vmImage: $(iosTestsVmImage) + demands: + - macOS.Name -equals Sonoma + - macOS.Architecture -equals x64 - name: windowsPool type: object diff --git a/eng/pipelines/ui-tests.yml b/eng/pipelines/ui-tests.yml index b8f0be6778..d6ea2bd6b5 100644 --- a/eng/pipelines/ui-tests.yml +++ b/eng/pipelines/ui-tests.yml @@ -88,7 +88,7 @@ parameters: - name: macosPool type: object default: - name: $(macosTestsVmPool) + name: Azure Pipelines vmImage: macOS-14 - name: androidCompatibilityPool diff --git a/src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs index 4c0bd874b5..2b7802aadf 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs @@ -132,7 +132,6 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility { var child = GetChildAt(0); child?.RemoveFromParent(); - child?.Dispose(); } Element?.Handler?.DisconnectHandler(); diff --git a/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs index 366edbf80a..01cbc54376 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs @@ -959,10 +959,8 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility { if (_prototype != null) { - var element = _prototype.VirtualView; - element?.Handler?.DisconnectHandler(); - //_prototype?.Dispose(); - //_prototype = null; + _prototype?.DisconnectHandler(); + _prototype = null; } } } diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellItemRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellItemRenderer.cs index 528eaa84a4..537a91a9a2 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellItemRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellItemRenderer.cs @@ -345,7 +345,7 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility UITableViewCell[] GetMoreNavigationCells() { - if (MoreNavigationController.TopViewController.View is UITableView uITableView) + if (MoreNavigationController.TopViewController.View is UITableView uITableView && uITableView.Window is not null) return uITableView.VisibleCells; return EmptyUITableViewCellArray; diff --git a/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs b/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs index 7e00f252fc..4d79bddb25 100644 --- a/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs +++ b/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs @@ -38,7 +38,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items protected override void ConnectHandler(ListViewBase platformView) { ItemsView.Scrolled += CarouselScrolled; - ListViewBase.SizeChanged += OnListViewSizeChanged; + platformView.SizeChanged += OnListViewSizeChanged; UpdateScrollBarVisibilityForLoop(); @@ -50,8 +50,11 @@ namespace Microsoft.Maui.Controls.Handlers.Items if (ItemsView != null) ItemsView.Scrolled -= CarouselScrolled; - platformView.SizeChanged -= OnListViewSizeChanged; - _proxy.Unsubscribe(); + if (platformView != null) + { + platformView.SizeChanged -= OnListViewSizeChanged; + _proxy.Unsubscribe(); + } if (_scrollViewer != null) { diff --git a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs index 38bc29107c..8046dd7e7b 100644 --- a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs +++ b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs @@ -57,6 +57,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items protected override void DisconnectHandler(ListViewBase platformView) { VirtualView.ScrollToRequested -= ScrollToRequested; + CleanUpCollectionViewSource(platformView); base.DisconnectHandler(platformView); } @@ -154,6 +155,11 @@ namespace Microsoft.Maui.Controls.Handlers.Items protected abstract ListViewBase SelectListViewBase(); protected virtual void CleanUpCollectionViewSource() + { + CleanUpCollectionViewSource(ListViewBase); + } + + private void CleanUpCollectionViewSource(ListViewBase platformView) { if (CollectionViewSource is not null) { @@ -174,7 +180,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items // Remove all children inside the ItemsSource if (VirtualView is not null) { - foreach (var item in ListViewBase.GetChildren()) + foreach (var item in platformView.GetChildren()) { var element = item.GetVisualElement(); VirtualView.RemoveLogicalChild(element); @@ -183,7 +189,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items if (VirtualView?.ItemsSource is null) { - ListViewBase.ItemsSource = null; + platformView.ItemsSource = null; return; } } diff --git a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs index 756aa3f6bf..c67911fb66 100644 --- a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs +++ b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs @@ -17,6 +17,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items protected override void DisconnectHandler(UIView platformView) { ItemsView.ScrollToRequested -= ScrollToRequested; + Controller?.DisposeItemsSource(); base.DisconnectHandler(platformView); } diff --git a/src/Controls/src/Core/Handlers/Items/iOS/GridViewLayout.cs b/src/Controls/src/Core/Handlers/Items/iOS/GridViewLayout.cs index 3330227909..bda227c7cc 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/GridViewLayout.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/GridViewLayout.cs @@ -243,7 +243,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items return false; } - if (layoutAttributesForRectElements[0].Frame.Top != CollectionView.Frame.Top + CollectionView.ContentInset.Bottom) + // We need to determine whether this 'if' statement is needed, as its relevance is currently uncertain. + if (layoutAttributesForRectElements[0].Frame.Top != CollectionView.Frame.Top) { return false; } diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs index 24b060a8da..677685022b 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs @@ -329,6 +329,15 @@ namespace Microsoft.Maui.Controls.Handlers.Items (ItemsView as IView)?.InvalidateMeasure(); } + internal void DisposeItemsSource() + { + _measurementCells?.Clear(); + ItemsViewLayout?.ClearCellSizeCache(); + ItemsSource?.Dispose(); + ItemsSource = new EmptySource(); + CollectionView.ReloadData(); + } + public virtual void UpdateFlowDirection() { CollectionView.UpdateFlowDirection(ItemsView); diff --git a/src/Controls/src/Core/Menu/MenuItem.cs b/src/Controls/src/Core/Menu/MenuItem.cs index c40dcf2467..58d81b3d4b 100644 --- a/src/Controls/src/Core/Menu/MenuItem.cs +++ b/src/Controls/src/Core/Menu/MenuItem.cs @@ -30,7 +30,11 @@ namespace Microsoft.Maui.Controls public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create(nameof(IsDestructive), typeof(bool), typeof(MenuItem), false); /// Bindable property for . - public static readonly BindableProperty IconImageSourceProperty = BindableProperty.Create(nameof(IconImageSource), typeof(ImageSource), typeof(MenuItem), default(ImageSource)); + public static readonly BindableProperty IconImageSourceProperty = BindableProperty.Create(nameof(IconImageSource), typeof(ImageSource), typeof(MenuItem), default(ImageSource), + propertyChanged: (bindable, oldValue, newValue) => { + ((MenuItem)bindable).AddRemoveLogicalChildren(oldValue, newValue); + } + ); /// Bindable property for . public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create( diff --git a/src/Controls/tests/DeviceTests/Elements/CarouselView/CarouselViewTests.cs b/src/Controls/tests/DeviceTests/Elements/CarouselView/CarouselViewTests.cs index efb175da32..17c0b297ec 100644 --- a/src/Controls/tests/DeviceTests/Elements/CarouselView/CarouselViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/CarouselView/CarouselViewTests.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Handlers.Items; @@ -112,6 +113,45 @@ namespace Microsoft.Maui.DeviceTests Assert.NotNull(handler.PlatformView); }); } + + #if !ANDROID //https://github.com/dotnet/maui/pull/24610 + [Fact] + public async void DisconnectedCarouselViewDoesNotHookCollectionViewChanged() + { + SetupBuilder(); + + CollectionChangedObservableCollection data = new CollectionChangedObservableCollection() + { + 1, + 2, + }; + + var template = new DataTemplate(() => + { + return new Grid() + { + new Label() + }; + }); + + var carouselView = new CarouselView() + { + ItemTemplate = template, + ItemsSource = data + }; + + await CreateHandlerAndAddToWindow(carouselView, async (handler) => + { + await Task.Delay(100); + Assert.NotNull(handler.PlatformView); + Assert.False(data.IsCollectionChangedEventEmpty); + }); + + carouselView.Handler?.DisconnectHandler(); + + Assert.True(data.IsCollectionChangedEventEmpty); + } + #endif } internal class CustomDataTemplateSelectorSelector : DataTemplateSelector @@ -129,4 +169,17 @@ namespace Microsoft.Maui.DeviceTests return Template2; } } -} \ No newline at end of file + + internal class CollectionChangedObservableCollection : ObservableCollection, INotifyCollectionChanged + { + NotifyCollectionChangedEventHandler collectionChanged; + + event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + { + add { collectionChanged += value; base.CollectionChanged += value; } + remove { collectionChanged -= value; base.CollectionChanged -= value; } + } + + public bool IsCollectionChangedEventEmpty => collectionChanged is null; + } +} diff --git a/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs b/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs index fea5fc2123..b4f6e8cc74 100644 --- a/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs @@ -28,10 +28,65 @@ namespace Microsoft.Maui.DeviceTests handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); + handlers.AddHandler(); }); }); } + + [Fact +#if ANDROID + (Skip = "https://github.com/dotnet/maui/issues/24701") +#endif + ] + public async Task ChangingTemplateTypeDoesNotCrash() + { + SetupBuilder(); + ObservableCollection data1 = new ObservableCollection() + { + "cat", + "dog", + }; + ObservableCollection data2 = new ObservableCollection() + { + "dog", + "cat", + }; + + var template1 = new DataTemplate(() => + { + return new ViewCell() + { + View = new Label() + }; + }); + + var template2 = new DataTemplate(() => + { + return new ViewCell() + { + View = new Entry() + }; + }); + + var listView = new ListView() + { + HasUnevenRows = true, + ItemTemplate = new FunctionalDataTemplateSelector((item, container) => + { + return item.ToString() == "cat" ? template1 : template2; + }), + IsGroupingEnabled = true, + ItemsSource = new ObservableCollection>(){data1} + }; + + await CreateHandlerAndAddToWindow(new VerticalStackLayout(){ listView }, async (handler) => + { + listView.ItemsSource = new ObservableCollection>(){data2}; + await Task.Delay(5000); + }); + } + [Fact] public async Task RemovingFirstItemOfListViewDoesntCrash() { @@ -346,5 +401,20 @@ namespace Microsoft.Maui.DeviceTests Assert.Equal("6", cells[2].Text); }); } + + class FunctionalDataTemplateSelector : DataTemplateSelector + { + public Func Selector { get; } + + public FunctionalDataTemplateSelector(Func selectTemplate) + { + Selector = selectTemplate; + } + + protected override DataTemplate OnSelectTemplate(object item, BindableObject container) + { + return Selector.Invoke(item, container); + } + } } -} \ No newline at end of file +} diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewSingleItemAlignmentWithFooter.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewSingleItemAlignmentWithFooter.png new file mode 100644 index 0000000000..13b2bb2daf Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewSingleItemAlignmentWithFooter.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationBetweenFlyoutItems.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationBetweenFlyoutItems.png new file mode 100644 index 0000000000..12d33aa6b6 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationBetweenFlyoutItems.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue15196.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue15196.xaml new file mode 100644 index 0000000000..915d6836d2 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue15196.xaml @@ -0,0 +1,13 @@ + + + + + + + +