From 2f9aff82cacaf694819f9a77c501c3741703f381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Mon, 16 Sep 2019 14:13:07 +0200 Subject: [PATCH 001/203] Added Carousel SnapBehavior Core sample (#7444) --- .../CarouselSnapGallery.cs | 141 ++++++++++++++++++ .../CarouselViewGallery.cs | 2 + 2 files changed, 143 insertions(+) create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselSnapGallery.cs diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselSnapGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselSnapGallery.cs new file mode 100644 index 00000000000..a12c126419a --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselSnapGallery.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; +using Xamarin.Forms.Internals; +using Xamarin.Forms.PlatformConfiguration; +using Xamarin.Forms.PlatformConfiguration.iOSSpecific; + +namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.CarouselViewGalleries +{ + [Preserve(AllMembers = true)] + public class CarouselSnapGallery : ContentPage + { + public CarouselSnapGallery() + { + On().SetLargeTitleDisplay(LargeTitleDisplayMode.Never); + + var viewModel = new CarouselItemsGalleryViewModel(); + + Title = $"CarouselView Snap Options"; + + var layout = new Grid + { + RowDefinitions = new RowDefinitionCollection + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star } + } + }; + + var snapPointsStack = new StackLayout + { + Margin = new Thickness(12) + }; + + var snapPointsLabel = new Label { FontSize = 10, Text = "SnapPointsType:" }; + var snapPointsTypes = Enum.GetNames(typeof(SnapPointsType)).Select(b => b).ToList(); + + var snapPointsTypePicker = new Picker + { + ItemsSource = snapPointsTypes, + SelectedItem = snapPointsTypes[1] + }; + + snapPointsStack.Children.Add(snapPointsLabel); + snapPointsStack.Children.Add(snapPointsTypePicker); + + layout.Children.Add(snapPointsStack, 0, 0); + + var snapPointsAlignmentsStack = new StackLayout + { + Margin = new Thickness(12) + }; + + var snapPointsAlignmentsLabel = new Label { FontSize = 10, Text = "SnapPointsAlignment:" }; + var snapPointsAlignments = Enum.GetNames(typeof(SnapPointsAlignment)).Select(b => b).ToList(); + + var snapPointsAlignmentPicker = new Picker + { + ItemsSource = snapPointsAlignments, + SelectedItem = snapPointsAlignments[0] + }; + + snapPointsAlignmentsStack.Children.Add(snapPointsAlignmentsLabel); + snapPointsAlignmentsStack.Children.Add(snapPointsAlignmentPicker); + + layout.Children.Add(snapPointsAlignmentsStack, 0, 1); + + var itemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal) + { + SnapPointsType = SnapPointsType.Mandatory, + SnapPointsAlignment = SnapPointsAlignment.Start + }; + + var itemTemplate = GetCarouselTemplate(); + + var carouselView = new CarouselView + { + ItemsSource = viewModel.Items, + ItemsLayout = itemsLayout, + ItemTemplate = itemTemplate, + BackgroundColor = Color.LightGray, + PeekAreaInsets = new Thickness(0, 0, 300, 0), + Margin = new Thickness(12), + AutomationId = "TheCarouselView" + }; + + layout.Children.Add(carouselView, 0, 2); + + + snapPointsTypePicker.SelectedIndexChanged += (sender, e) => + { + if (carouselView.ItemsLayout is LinearItemsLayout linearItemsLayout) + { + Enum.TryParse(snapPointsTypePicker.SelectedItem.ToString(), out SnapPointsType snapPointsType); + linearItemsLayout.SnapPointsType = snapPointsType; + } + }; + + snapPointsAlignmentPicker.SelectedIndexChanged += (sender, e) => + { + if (carouselView.ItemsLayout is LinearItemsLayout linearItemsLayout) + { + Enum.TryParse(snapPointsAlignmentPicker.SelectedItem.ToString(), out SnapPointsAlignment snapPointsAlignment); + linearItemsLayout.SnapPointsAlignment = snapPointsAlignment; + } + }; + + Content = layout; + BindingContext = viewModel; + } + + internal DataTemplate GetCarouselTemplate() + { + return new DataTemplate(() => + { + var grid = new Grid(); + + var info = new Label + { + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + Margin = new Thickness(6) + }; + + info.SetBinding(Label.TextProperty, new Binding("Name")); + + grid.Children.Add(info); + + var frame = new Frame + { + Content = grid, + HasShadow = false + }; + + frame.SetBinding(BackgroundColorProperty, new Binding("Color")); + + return frame; + }); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs index 30eec7db3fb..6ce4b1e9039 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs @@ -28,6 +28,8 @@ public CarouselViewGallery() new CarouselXamlGallery(), Navigation), GalleryBuilder.NavButton("CarouselView (Items)", () => new CarouselItemsGallery(), Navigation), + GalleryBuilder.NavButton("CarouselView Snap", () => + new CarouselSnapGallery(), Navigation) } } }; From 8b346079e298024428d4a418b3bcdf09aef300f7 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Mon, 16 Sep 2019 07:02:58 -0600 Subject: [PATCH 002/203] Implement EmptyView on UWP (#7438) --- .../CollectionView/FormsGridView.cs | 45 +++++- .../CollectionView/FormsListView.cs | 42 +++++ .../CollectionView/IEmptyView.cs | 10 ++ .../CollectionView/ItemsViewRenderer.cs | 98 +++++++++++- .../CollectionView/ItemsViewStyles.xaml | 146 +++++++++++++++++- .../StructuredItemsViewRenderer.cs | 4 +- .../Xamarin.Forms.Platform.UAP.csproj | 2 + 7 files changed, 337 insertions(+), 10 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/IEmptyView.cs diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs b/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs index 694447a7c83..68781df1916 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs @@ -1,14 +1,17 @@ -using Windows.UI.Xaml; +using System; +using System.Globalization; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Xamarin.Forms.Platform.UWP; -namespace Xamarin.Forms.Platform.UAP +namespace Xamarin.Forms.Platform.UWP { - // TODO hartez 2018/06/06 10:01:48 Consider whether this should be internal; it might be that we just want to make the ItemsPanel resources configurable in CollectionViewRenderer - internal class FormsGridView : GridView + internal class FormsGridView : GridView, IEmptyView { int _maximumRowsOrColumns; ItemsWrapGrid _wrapGrid; + ContentControl _emptyViewContentControl; + FrameworkElement _emptyView; public FormsGridView() { @@ -30,6 +33,16 @@ public int MaximumRowsOrColumns } } + public Visibility EmptyViewVisibility + { + get { return (Visibility)GetValue(EmptyViewVisibilityProperty); } + set { SetValue(EmptyViewVisibilityProperty, value); } + } + + public static readonly DependencyProperty EmptyViewVisibilityProperty = + DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), + typeof(FormsGridView), new PropertyMetadata(Visibility.Collapsed)); + // TODO hartez 2018/06/06 10:01:32 Probably should just create a local enum for this? public void UseHorizontalItemsPanel() { @@ -37,7 +50,7 @@ public void UseHorizontalItemsPanel() (ItemsPanelTemplate)Windows.UI.Xaml.Application.Current.Resources["HorizontalGridItemsPanel"]; } - public void UseVerticalalItemsPanel() + public void UseVerticalItemsPanel() { ItemsPanel = (ItemsPanelTemplate)Windows.UI.Xaml.Application.Current.Resources["VerticalGridItemsPanel"]; @@ -64,5 +77,27 @@ void OnLoaded(object sender, RoutedEventArgs e) { FindItemsWrapGrid(); } + + public void SetEmptyView(FrameworkElement emptyView) + { + _emptyView = emptyView; + + if (_emptyViewContentControl != null) + { + _emptyViewContentControl.Content = emptyView; + } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _emptyViewContentControl = GetTemplateChild("EmptyViewContentControl") as ContentControl; + + if (_emptyView != null) + { + _emptyViewContentControl.Content = _emptyView; + } + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs b/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs new file mode 100644 index 00000000000..532a135c223 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs @@ -0,0 +1,42 @@ +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Xamarin.Forms.Platform.UWP +{ + internal class FormsListView : Windows.UI.Xaml.Controls.ListView, IEmptyView + { + ContentControl _emptyViewContentControl; + FrameworkElement _emptyView; + + public Visibility EmptyViewVisibility + { + get { return (Visibility)GetValue(EmptyViewVisibilityProperty); } + set { SetValue(EmptyViewVisibilityProperty, value); } + } + + public static readonly DependencyProperty EmptyViewVisibilityProperty = + DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), typeof(FormsListView), new PropertyMetadata(Visibility.Collapsed)); + + public void SetEmptyView(FrameworkElement emptyView) + { + _emptyView = emptyView; + + if (_emptyViewContentControl != null) + { + _emptyViewContentControl.Content = emptyView; + } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _emptyViewContentControl = GetTemplateChild("EmptyViewContentControl") as ContentControl; + + if (_emptyView != null) + { + _emptyViewContentControl.Content = _emptyView; + } + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/IEmptyView.cs b/Xamarin.Forms.Platform.UAP/CollectionView/IEmptyView.cs new file mode 100644 index 00000000000..9fb4894d237 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/IEmptyView.cs @@ -0,0 +1,10 @@ +using Windows.UI.Xaml; + +namespace Xamarin.Forms.Platform.UWP +{ + internal interface IEmptyView + { + Visibility EmptyViewVisibility { get; set; } + void SetEmptyView(FrameworkElement emptyView); + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 2477e2120d4..14a6745e18e 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -14,6 +14,7 @@ using UwpScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility; using UWPApp = Windows.UI.Xaml.Application; using UWPDataTemplate = Windows.UI.Xaml.DataTemplate; +using System.Collections.Specialized; namespace Xamarin.Forms.Platform.UWP { @@ -28,6 +29,9 @@ public abstract class ItemsViewRenderer : ViewRenderer protected UWPDataTemplate ViewTemplate => (UWPDataTemplate)UWPApp.Current.Resources["View"]; protected UWPDataTemplate ItemsViewTemplate => (UWPDataTemplate)UWPApp.Current.Resources["ItemsViewDefaultTemplate"]; + FrameworkElement _emptyView; + View _formsEmptyView; + protected ItemsControl ItemsControl { get; private set; } protected override void OnElementChanged(ElementChangedEventArgs args) @@ -57,6 +61,10 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE { UpdateVerticalScrollBarVisibility(); } + else if (changedProperty.IsOneOf(ItemsView.EmptyViewProperty, ItemsView.EmptyViewTemplateProperty)) + { + UpdateEmptyView(); + } } protected abstract ListViewBase SelectListViewBase(); @@ -76,6 +84,11 @@ protected virtual void UpdateItemsSource() if (itemsSource == null) { + if (_collectionViewSource?.Source is INotifyCollectionChanged incc) + { + incc.CollectionChanged -= ItemsChanged; + } + _collectionViewSource = null; return; } @@ -97,6 +110,11 @@ protected virtual void UpdateItemsSource() Source = TemplatedItemSourceFactory.Create(itemsSource, itemTemplate, Element), IsSourceGrouped = false }; + + if (_collectionViewSource?.Source is INotifyCollectionChanged incc) + { + incc.CollectionChanged += ItemsChanged; + } } else { @@ -108,6 +126,13 @@ protected virtual void UpdateItemsSource() } ListViewBase.ItemsSource = _collectionViewSource.View; + + UpdateEmptyViewVisibility(); + } + + void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateEmptyViewVisibility(); } protected virtual void UpdateItemTemplate() @@ -148,6 +173,7 @@ protected virtual void SetUpNewElement(ItemsView newElement) UpdateItemsSource(); UpdateVerticalScrollBarVisibility(); UpdateHorizontalScrollBarVisibility(); + UpdateEmptyView(); // Listen for ScrollTo requests newElement.ScrollToRequested += ScrollToRequested; @@ -357,5 +383,75 @@ protected virtual async Task ScrollTo(ScrollToRequestEventArgs args) await JumpTo(list, targetItem, args.ScrollToPosition); } } + + protected virtual void UpdateEmptyView() + { + if (Element == null || ListViewBase == null) + { + return; + } + + var emptyView = Element.EmptyView; + + if (emptyView == null) + { + return; + } + + switch (emptyView) + { + case string text: + _emptyView = new TextBlock { Text = text }; + break; + case View view: + _emptyView = RealizeEmptyView(view); + break; + default: + _emptyView = RealizeEmptyViewTemplate(emptyView, Element.EmptyViewTemplate); + break; + } + + (ListViewBase as IEmptyView)?.SetEmptyView(_emptyView); + + UpdateEmptyViewVisibility(); + } + + FrameworkElement RealizeEmptyViewTemplate(object bindingContext, DataTemplate emptyViewTemplate) + { + if (emptyViewTemplate == null) + { + return new TextBlock { Text = bindingContext.ToString() }; + } + + var template = emptyViewTemplate.SelectDataTemplate(bindingContext, null); + var view = template.CreateContent() as View; + + view.BindingContext = bindingContext; + return RealizeEmptyView(view); + } + + FrameworkElement RealizeEmptyView(View view) + { + _formsEmptyView = view; + return view.GetOrCreateRenderer().ContainerElement; + } + + protected virtual void UpdateEmptyViewVisibility() + { + if (_emptyView != null && ListViewBase is IEmptyView emptyView) + { + emptyView.EmptyViewVisibility = (_collectionViewSource?.View?.Count ?? 0) == 0 + ? Visibility.Visible + : Visibility.Collapsed; + + if (emptyView.EmptyViewVisibility == Visibility.Visible) + { + if (ActualWidth >= 0 && ActualHeight >= 0) + { + _formsEmptyView?.Layout(new Rectangle(0, 0, ActualWidth, ActualHeight)); + } + } + } + } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml index 5e076464707..9d5f32121cd 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml @@ -2,8 +2,8 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Xamarin.Forms.Platform.UWP"> - - + + @@ -37,5 +37,147 @@ + + + + diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs index 90870be2e41..8ede34c996d 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs @@ -53,7 +53,7 @@ protected override ListViewBase SelectListViewBase() } // Default to a plain old vertical ListView - return new Windows.UI.Xaml.Controls.ListView(); + return new FormsListView(); } protected virtual void UpdateHeader() @@ -180,7 +180,7 @@ static ListViewBase CreateGridView(GridItemsLayout gridItemsLayout) } else { - gridView.UseVerticalalItemsPanel(); + gridView.UseVerticalItemsPanel(); } gridView.MaximumRowsOrColumns = gridItemsLayout.Span; diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index 4c136a06a6e..be7349f18dd 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -40,6 +40,8 @@ + + From 5c2811a41462054c74d5c7f6fd814523cabab3b3 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Mon, 16 Sep 2019 08:23:55 -0600 Subject: [PATCH 003/203] Fix horizontal layout issues introduced in 7456; fixes #7519 fixes #7524 (#7522) --- .../Issue7519.cs | 31 +++++++++++++ .../Issue7519Xaml.xaml | 37 ++++++++++++++++ .../Issue7519Xaml.xaml.cs | 43 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 13 ++++++ Xamarin.Forms.Core/Items/CarouselView.cs | 6 +-- Xamarin.Forms.Core/Items/ListItemsLayout.cs | 5 ++- .../Items/StructuredItemsView.cs | 4 +- .../CollectionView/CarouselViewRenderer.cs | 14 ++---- .../CollectionView/ItemsViewRenderer.cs | 26 +++++------ 9 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519.cs create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519.cs new file mode 100644 index 00000000000..a8bc0eba99d --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7519, "ItemSpacing not working on LinearLayout", + PlatformAffected.All)] + public class Issue7519 : TestNavigationPage + { + protected override void Init() + { +#if APP + FlagTestHelpers.SetCollectionViewTestFlag(); + PushAsync(new Issue7519Xaml()); +#endif + } + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml new file mode 100644 index 00000000000..45aa3ac3e68 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml.cs new file mode 100644 index 00000000000..e091c5138a3 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7519Xaml.xaml.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Controls.Issues +{ +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class Issue7519Xaml : ContentPage + { + public Issue7519Xaml() + { + InitializeComponent(); + + var items = new List<_7519ItemModel>() + { + new _7519ItemModel {Name = "Item 1"}, + new _7519ItemModel {Name = "Item 2"}, + new _7519ItemModel {Name = "Item 3"}, + new _7519ItemModel {Name = "Item 4"}, + new _7519ItemModel {Name = "Item 5"}, + new _7519ItemModel {Name = "Item 6"}, + new _7519ItemModel {Name = "Item 7"}, + }; + + BindingContext = new _7519Model { Items = items }; + } + } + + [Preserve(AllMembers = true)] + public class _7519Model + { + public List<_7519ItemModel> Items { get; set; } + } + + [Preserve(AllMembers = true)] + public class _7519ItemModel + { + public string Name { get; set; } + } + +#endif +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 3870cba2515..652aab78103 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -36,6 +36,10 @@ + + + Code + @@ -1355,6 +1359,9 @@ Issue6254.xaml + + Issue7519Xaml.xaml + @@ -1380,4 +1387,10 @@ MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:Compile + + \ No newline at end of file diff --git a/Xamarin.Forms.Core/Items/CarouselView.cs b/Xamarin.Forms.Core/Items/CarouselView.cs index 0c9e9c7a383..a0266a2f37a 100644 --- a/Xamarin.Forms.Core/Items/CarouselView.cs +++ b/Xamarin.Forms.Core/Items/CarouselView.cs @@ -156,12 +156,12 @@ public object PositionChangedCommandParameter public static readonly BindableProperty ItemsLayoutProperty = BindableProperty.Create(nameof(ItemsLayout), typeof(LinearItemsLayout), typeof(ItemsView), - LinearItemsLayout.Horizontal); + LinearItemsLayout.CarouselDefault); public LinearItemsLayout ItemsLayout { - get => (LinearItemsLayout)InternalItemsLayout; - set => InternalItemsLayout = value; + get => (LinearItemsLayout)GetValue(ItemsLayoutProperty); + set => SetValue(ItemsLayoutProperty, value); } public event EventHandler CurrentItemChanged; diff --git a/Xamarin.Forms.Core/Items/ListItemsLayout.cs b/Xamarin.Forms.Core/Items/ListItemsLayout.cs index 5bcfa392cfd..47ebdf46426 100644 --- a/Xamarin.Forms.Core/Items/ListItemsLayout.cs +++ b/Xamarin.Forms.Core/Items/ListItemsLayout.cs @@ -11,7 +11,10 @@ public LinearItemsLayout([Parameter("Orientation")] ItemsLayoutOrientation orien public static readonly IItemsLayout Vertical = new LinearItemsLayout(ItemsLayoutOrientation.Vertical); public static readonly IItemsLayout Horizontal = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal); - // TODO hartez 2018/08/29 20:31:54 Need something like these previous two, but as a carousel default + internal static readonly LinearItemsLayout CarouselDefault = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal) + { + SnapPointsAlignment = SnapPointsAlignment.Center, SnapPointsType = SnapPointsType.Mandatory + }; public static readonly BindableProperty ItemSpacingProperty = BindableProperty.Create(nameof(ItemSpacing), typeof(double), typeof(LinearItemsLayout), default(double), diff --git a/Xamarin.Forms.Core/Items/StructuredItemsView.cs b/Xamarin.Forms.Core/Items/StructuredItemsView.cs index 86e57a5120c..4b09ff0653b 100644 --- a/Xamarin.Forms.Core/Items/StructuredItemsView.cs +++ b/Xamarin.Forms.Core/Items/StructuredItemsView.cs @@ -38,9 +38,7 @@ public DataTemplate FooterTemplate set => SetValue(FooterTemplateProperty, value); } - public static readonly BindableProperty ItemsLayoutProperty = - BindableProperty.Create(nameof(ItemsLayout), typeof(IItemsLayout), typeof(ItemsView), - LinearItemsLayout.Vertical); + public static readonly BindableProperty ItemsLayoutProperty = InternalItemsLayoutProperty; public IItemsLayout ItemsLayout { diff --git a/Xamarin.Forms.Platform.Android/CollectionView/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.Android/CollectionView/CarouselViewRenderer.cs index 8e1c46a0d6a..3c840ccb04c 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/CarouselViewRenderer.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/CarouselViewRenderer.cs @@ -9,7 +9,6 @@ namespace Xamarin.Forms.Platform.Android public class CarouselViewRenderer : ItemsViewRenderer, IItemsViewSource> { protected CarouselView Carousel; - IItemsLayout _layout; ItemDecoration _itemDecoration; bool _isSwipeEnabled; bool _isUpdatingPositionFromForms; @@ -30,8 +29,6 @@ protected override void Dispose(bool disposing) _itemDecoration.Dispose(); _itemDecoration = null; } - - _layout = null; } base.Dispose(disposing); @@ -48,8 +45,6 @@ protected override void SetUpNewElement(ItemsView newElement) return; } - _layout = Carousel.ItemsLayout; - UpdateIsSwipeEnabled(); UpdateInitialPosition(); UpdateItemSpacing(); @@ -107,7 +102,7 @@ protected override ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayo protected override void UpdateItemSpacing() { - if (_layout == null) + if (ItemsLayout == null) { return; } @@ -117,7 +112,7 @@ protected override void UpdateItemSpacing() RemoveItemDecoration(_itemDecoration); } - _itemDecoration = CreateSpacingDecoration(_layout); + _itemDecoration = CreateSpacingDecoration(ItemsLayout); AddItemDecoration(_itemDecoration); var adapter = GetAdapter(); @@ -131,7 +126,6 @@ protected override void UpdateItemSpacing() base.UpdateItemSpacing(); } - protected override IItemsLayout GetItemsLayout() { return Carousel.ItemsLayout; @@ -141,7 +135,7 @@ int GetItemWidth() { var itemWidth = Width; - if (_layout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + if (ItemsLayout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) { var numberOfVisibleItems = Carousel.NumberOfSideItems * 2 + 1; itemWidth = (int)(Width - Carousel.PeekAreaInsets.Left - Carousel.PeekAreaInsets.Right - Context?.ToPixels(listItemsLayout.ItemSpacing)) / numberOfVisibleItems; @@ -154,7 +148,7 @@ int GetItemHeight() { var itemHeight = Height; - if (_layout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + if (ItemsLayout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) { var numberOfVisibleItems = Carousel.NumberOfSideItems * 2 + 1; itemHeight = (int)(Height - Carousel.PeekAreaInsets.Top - Carousel.PeekAreaInsets.Bottom - Context?.ToPixels(listItemsLayout.ItemSpacing)) / numberOfVisibleItems; diff --git a/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs index d4fd3d76d76..34e35e84f0b 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs @@ -25,8 +25,8 @@ public abstract class ItemsViewRenderer bool _disposed; protected TItemsView ItemsView; + protected IItemsLayout ItemsLayout { get; private set; } - IItemsLayout _layout; SnapManager _snapManager; ScrollHelper _scrollHelper; RecyclerViewScrollListener _recyclerViewScrollListener; @@ -303,8 +303,8 @@ protected virtual void SetUpNewElement(TItemsView newElement) UpdateItemsSource(); - _layout = GetItemsLayout(); - SetLayoutManager(SelectLayoutManager(_layout)); + ItemsLayout = GetItemsLayout(); + SetLayoutManager(SelectLayoutManager(ItemsLayout)); UpdateSnapBehavior(); UpdateBackgroundColor(); @@ -315,9 +315,9 @@ protected virtual void SetUpNewElement(TItemsView newElement) UpdateVerticalScrollBarVisibility(); // Keep track of the ItemsLayout's property changes - if (_layout != null) + if (ItemsLayout != null) { - _layout.PropertyChanged += LayoutPropertyChanged; + ItemsLayout.PropertyChanged += LayoutPropertyChanged; } // Listen for ScrollTo requests @@ -364,9 +364,9 @@ protected virtual void TearDownOldElement(ItemsView oldElement) } // Stop listening for layout property changes - if (_layout != null) + if (ItemsLayout != null) { - _layout.PropertyChanged -= LayoutPropertyChanged; + ItemsLayout.PropertyChanged -= LayoutPropertyChanged; } // Stop listening for property changes @@ -413,10 +413,10 @@ protected virtual void LayoutPropertyChanged(object sender, PropertyChangedEvent { if (GetLayoutManager() is GridLayoutManager gridLayoutManager) { - gridLayoutManager.SpanCount = ((GridItemsLayout)_layout).Span; + gridLayoutManager.SpanCount = ((GridItemsLayout)ItemsLayout).Span; } } - else if (propertyChanged.IsOneOf(ItemsLayout.SnapPointsTypeProperty, ItemsLayout.SnapPointsAlignmentProperty)) + else if (propertyChanged.IsOneOf(Xamarin.Forms.ItemsLayout.SnapPointsTypeProperty, Xamarin.Forms.ItemsLayout.SnapPointsAlignmentProperty)) { UpdateSnapBehavior(); } @@ -438,7 +438,7 @@ protected virtual SnapManager GetSnapManager() { if (_snapManager == null) { - _snapManager = new SnapManager(_layout, this); + _snapManager = new SnapManager(ItemsLayout, this); } return _snapManager; } @@ -555,7 +555,7 @@ protected virtual int DetermineTargetPosition(ScrollToRequestEventArgs args) protected virtual void UpdateItemSpacing() { - if (_layout == null) + if (ItemsLayout == null) { return; } @@ -565,7 +565,7 @@ protected virtual void UpdateItemSpacing() RemoveItemDecoration(_itemDecoration); } - _itemDecoration = CreateSpacingDecoration(_layout); + _itemDecoration = CreateSpacingDecoration(ItemsLayout); AddItemDecoration(_itemDecoration); } @@ -613,7 +613,7 @@ internal void UpdateEmptyViewVisibility() else if (!showEmptyView && currAdapter != ItemsViewAdapter) { SwapAdapter(ItemsViewAdapter, true); - SetLayoutManager(SelectLayoutManager(_layout)); + SetLayoutManager(SelectLayoutManager(ItemsLayout)); } } From 128cf1bbc42ef04b1c8264e2bd0885f8df73ecfa Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 16 Sep 2019 09:48:52 -0600 Subject: [PATCH 004/203] Tweak unit test so it's more reliable and fix binlog path (#7530) * tweak unit test and fix bl path * - change test to use ThrowsAsync * - fix for nunit 2 --- Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs | 4 ++-- azure-pipelines.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs b/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs index 8d4cfabcdb5..809fe7a029d 100644 --- a/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs +++ b/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs @@ -112,8 +112,8 @@ public async Task TestInvokeOnMainThreadWithAsyncActionError() Assert.True(calledFromMainThread, "Action not invoked from main thread."); Assert.False(invoked, "Action invoked early."); - var aggregateEx = Assert.Throws(() => task.Wait(100)); - Assert.IsInstanceOf(aggregateEx.InnerException); + async Task MethodThatThrows() => await task; + Assert.Throws(async () => await MethodThatThrows()); Assert.True(invoked, "Action not invoked."); } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c4b0518e164..f7b01f959b9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -63,7 +63,7 @@ jobs: displayName: Build Android [Legacy Renderers] vmImage: $(macOSVmImage) targetFolder: Xamarin.Forms.ControlGallery.Android/legacyRenderers/ - androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /bl:$(Build.ArtifactStagingDirectory)\android-legacy.binlog' + androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /bl:$(Build.ArtifactStagingDirectory)/android-legacy.binlog' buildConfiguration: $(DefaultBuildConfiguration) - template: build/steps/build-android.yml @@ -72,7 +72,7 @@ jobs: displayName: Build Android [Pre-AppCompat] vmImage: $(macOSVmImage) targetFolder: Xamarin.Forms.ControlGallery.Android/preAppCompat - androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /p:DefineConstants="TRACE DEBUG FORMS_APPLICATION_ACTIVITY APP" /bl:$(Build.ArtifactStagingDirectory)\android-preappcompact.binlog' + androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /p:DefineConstants="TRACE DEBUG FORMS_APPLICATION_ACTIVITY APP" /bl:$(Build.ArtifactStagingDirectory)/android-preappcompact.binlog' buildConfiguration: $(DefaultBuildConfiguration) - template: build/steps/build-android.yml @@ -81,7 +81,7 @@ jobs: displayName: Build Android [Fast Renderers] vmImage: $(macOSVmImage) targetFolder: Xamarin.Forms.ControlGallery.Android/newRenderers/ - androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /p:DefineConstants="TRACE DEBUG TEST_EXPERIMENTAL_RENDERERS APP" /bl:$(Build.ArtifactStagingDirectory)\android-newrenderers.binlog' + androidProjectArguments: '/t:"Rebuild;SignAndroidPackage" /p:DefineConstants="TRACE DEBUG TEST_EXPERIMENTAL_RENDERERS APP" /bl:$(Build.ArtifactStagingDirectory)/android-newrenderers.binlog' buildConfiguration: $(DefaultBuildConfiguration) - job: osx From c0709cf54a736c84b4c22269b845c6b71a5f6101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20M=C3=BCller?= <11095003+krdmllr@users.noreply.github.com> Date: Mon, 16 Sep 2019 23:06:49 +0200 Subject: [PATCH 005/203] [macOS] Only use default font size for Time/DatePicker to avoid clipping issues (#7183) * Only use default font size * Always use default font size * Remove duplicated code * Remove extra spacing --- .../Renderers/DatePickerRenderer.cs | 14 +++++++++++--- .../Renderers/TimePickerRenderer.cs | 12 ++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs index 5150ff1c5f3..e6ee6c316e8 100644 --- a/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs +++ b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs @@ -116,9 +116,17 @@ void UpdateDateFromModel() void UpdateFont() { if (Control == null || Element == null) - return; + return; + + var newFont = Element.ToNSFont(); + + // The font needs to have the default font size to avoid clipping + var originalFontSize = (NSNumber)Control.Font.FontDescriptor.FontAttributes[NSFont.SizeAttribute]; + // Recreate the font with the default size + newFont = NSFont.FromDescription(newFont.FontDescriptor, originalFontSize.FloatValue); - Control.Font = Element.ToNSFont(); + // Apply the font + Control.Font = newFont; } void UpdateMaximumDate() @@ -147,4 +155,4 @@ void UpdateTextColor() Control.TextColor = textColor.ToNSColor(); } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs index 23500268da4..cddb3bd7a74 100644 --- a/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs +++ b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs @@ -98,7 +98,15 @@ void UpdateFont() if (Control == null || Element == null) return; - Control.Font = Element.ToNSFont(); + var newFont = Element.ToNSFont(); + + // The font needs to have the default font size to avoid clipping + var originalFontSize = (NSNumber)Control.Font.FontDescriptor.FontAttributes[NSFont.SizeAttribute]; + // Recreate the font with the default size + newFont = NSFont.FromDescription(newFont.FontDescriptor, originalFontSize.FloatValue); + + // Apply the font + Control.Font = newFont; } void UpdateTime() @@ -123,4 +131,4 @@ void UpdateTextColor() Control.TextColor = textColor.ToNSColor(); } } -} \ No newline at end of file +} From 47ac727c41676b0bc82d87b621a53454cd931394 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 16 Sep 2019 16:10:15 -0600 Subject: [PATCH 006/203] await tasks on ViewUnitTests and update MSBuild Locator (#7542) --- Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs | 32 +++++++++---------- .../Xamarin.Forms.Xaml.UnitTests.csproj | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs b/Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs index b937130e444..fdfbf0c6952 100644 --- a/Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs +++ b/Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs @@ -244,35 +244,35 @@ public void TestNativeStateConsistent () } [Test] - public void TestFadeTo () + public async Task TestFadeTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.FadeTo (0.1); + await view.FadeTo (0.1); Assert.True (Math.Abs (0.1 - view.Opacity) < 0.001); } [Test] - public void TestTranslateTo () + public async Task TestTranslateTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.TranslateTo (100, 50); + await view.TranslateTo (100, 50); Assert.AreEqual (100, view.TranslationX); Assert.AreEqual (50, view.TranslationY); } [Test] - public void ScaleTo () + public async Task ScaleTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.ScaleTo (2); + await view.ScaleTo (2); Assert.AreEqual (2, view.Scale); } @@ -291,56 +291,56 @@ public void TestNativeSizeChanged () } [Test] - public void TestRotateTo () + public async Task TestRotateTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.RotateTo (25); + await view.RotateTo (25); Assert.That (view.Rotation, Is.EqualTo (25).Within (0.001)); } [Test] - public void TestRotateYTo () + public async Task TestRotateYTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.RotateYTo (25); + await view.RotateYTo (25); Assert.That (view.RotationY, Is.EqualTo (25).Within (0.001)); } [Test] - public void TestRotateXTo () + public async Task TestRotateXTo () { var view = new View {IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.RotateXTo (25); + await view.RotateXTo (25); Assert.That (view.RotationX, Is.EqualTo (25).Within (0.001)); } [Test] - public void TestRelRotateTo () + public async Task TestRelRotateTo () { var view = new View {Rotation = 30, IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.RelRotateTo (20); + await view.RelRotateTo (20); Assert.That (view.Rotation, Is.EqualTo (50).Within (0.001)); } [Test] - public void TestRelScaleTo () + public async Task TestRelScaleTo () { var view = new View {Scale = 1, IsPlatformEnabled = true}; Ticker.Default = new BlockingTicker (); - view.RelScaleTo (1); + await view.RelScaleTo (1); Assert.That (view.Scale, Is.EqualTo (2).Within (0.001)); } diff --git a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj index df79154f4b9..a7e035004f1 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj +++ b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj @@ -20,7 +20,7 @@ - + From 30b958aef5f4cb58aa0bfd16430e2f60f701ff91 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 16 Sep 2019 19:26:30 -0600 Subject: [PATCH 007/203] update unit test to nunit3 apis (#7543) --- Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs b/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs index 809fe7a029d..570669b28cd 100644 --- a/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs +++ b/Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs @@ -113,7 +113,7 @@ public async Task TestInvokeOnMainThreadWithAsyncActionError() Assert.False(invoked, "Action invoked early."); async Task MethodThatThrows() => await task; - Assert.Throws(async () => await MethodThatThrows()); + Assert.ThrowsAsync(MethodThatThrows); Assert.True(invoked, "Action not invoked."); } From 307b6457277e23f731f6201b4495e80b1a391e3a Mon Sep 17 00:00:00 2001 From: adrianknight89 Date: Tue, 17 Sep 2019 03:38:48 -0500 Subject: [PATCH 008/203] [iOS] Remove class variable from CarouselViewLayout whose value is always true (#7484) * remove unused variable * comment change --- .../CollectionView/CarouselViewLayout.cs | 14 +++++--------- .../CollectionView/ItemsViewLayout.cs | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs index 71321654729..464484c52ad 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using CoreGraphics; using UIKit; @@ -8,8 +7,8 @@ namespace Xamarin.Forms.Platform.iOS internal class CarouselViewLayout : ItemsViewLayout { readonly CarouselView _carouselView; - readonly bool _addInsets = true; readonly ItemsLayout _itemsLayout; + public CarouselViewLayout(ItemsLayout itemsLayout, ItemSizingStrategy itemSizingStrategy, CarouselView carouselView) : base(itemsLayout, itemSizingStrategy) { _carouselView = carouselView; @@ -61,29 +60,26 @@ public override nfloat GetMinimumInteritemSpacingForSection(UICollectionView col public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section) { var insets = base.GetInsetForSection(collectionView, layout, section); - if (!_addInsets) - { - return insets; - } var left = insets.Left + (float)_carouselView.PeekAreaInsets.Left; var right = insets.Right + (float)_carouselView.PeekAreaInsets.Right; var top = insets.Top + (float)_carouselView.PeekAreaInsets.Top; var bottom = insets.Bottom + (float)_carouselView.PeekAreaInsets.Bottom; - //We give some insets so the user can be able to scroll to the first and last item + // We give some insets so the user can scroll to the first and last item if (_carouselView.NumberOfSideItems > 0) { if (ScrollDirection == UICollectionViewScrollDirection.Horizontal) { left += ItemSize.Width; right += ItemSize.Width; + return new UIEdgeInsets(insets.Top, left, insets.Bottom, right); } + return new UIEdgeInsets(ItemSize.Height, insets.Left, ItemSize.Height, insets.Right); } return new UIEdgeInsets(top, left, bottom, right); - } } -} +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs index 5f1ec53c850..ca6957a0f2e 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Runtime.CompilerServices; using CoreGraphics; using Foundation; using UIKit; From ef84417c8e3e3154918d29f89a19d406d0d6732f Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 17 Sep 2019 02:41:00 -0600 Subject: [PATCH 009/203] Remove extra brace; fixes #7545 (#7546) --- Xamarin.Forms.Platform.UAP/FormsCommandBarStyle.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Platform.UAP/FormsCommandBarStyle.xaml b/Xamarin.Forms.Platform.UAP/FormsCommandBarStyle.xaml index 31b800f8cc5..b8dee48aeec 100644 --- a/Xamarin.Forms.Platform.UAP/FormsCommandBarStyle.xaml +++ b/Xamarin.Forms.Platform.UAP/FormsCommandBarStyle.xaml @@ -453,7 +453,7 @@ - + From 32614d2f3b59e696d6b60e6b55ca21c68ee721da Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 17 Sep 2019 02:56:36 -0600 Subject: [PATCH 010/203] Add null check when using tablet on MDP (#7513) fixes #7496 --- .../AppCompat/NavigationPageRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs index 5af55368c99..1f5403dd636 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs @@ -982,7 +982,7 @@ void UpdateToolbar() var prevPage = Element.Peek(1); _defaultNavigationContentDescription = bar.SetNavigationContentDescription(prevPage, _defaultNavigationContentDescription); } - else if (_masterDetailPage != null) + else if (toggle != null && _masterDetailPage != null) { toggle.DrawerIndicatorEnabled = _masterDetailPage.ShouldShowToolbarButton(); toggle.SyncState(); From 636077b9e1d3b54b68ee0eca62e88ff318d7ab0e Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 17 Sep 2019 03:32:57 -0600 Subject: [PATCH 011/203] [Android/iOS] Update CollectionView when user changes out the header/footer (#7499) * update cv when user changes out the header/footer * - fix android to convert back from pixels on scroll - fix iOS offset on scroll args to account for header size and content inset * - fix for new api * - fix redundant BP --- .../HeaderFooterGrid.xaml | 55 +++++++------- .../HeaderFooterGrid.xaml.cs | 28 ++++++- .../HeaderFooterGridHorizontal.xaml | 74 +++++++++++++------ .../HeaderFooterGridHorizontal.xaml.cs | 26 ++++++- .../RecyclerViewScrollListener.cs | 9 ++- .../SelectableItemsViewAdapter.cs | 2 +- .../StructuredItemsViewAdapter.cs | 2 + .../CollectionView/ItemsViewController.cs | 20 +++-- .../StructuredItemsViewController.cs | 51 +++++++++---- .../StructuredItemsViewRenderer.cs | 6 ++ .../CollectionView/TemplateHelpers.cs | 3 + .../UICollectionViewDelegator.cs | 16 ++-- 12 files changed, 207 insertions(+), 85 deletions(-) diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml index 083032d9a9b..368b0415916 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml @@ -6,33 +6,36 @@ mc:Ignorable="d" x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterGrid"> - - - - - - - - - - + + + + - - - - + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs index aac3ff82cbb..883b50452c2 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs @@ -1,4 +1,5 @@ -using Xamarin.Forms.Xaml; +using System; +using Xamarin.Forms.Xaml; namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries { @@ -7,6 +8,9 @@ public partial class HeaderFooterGrid : ContentPage { readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(10); + object header = null; + object footer = null; + public HeaderFooterGrid() { InitializeComponent(); @@ -15,10 +19,30 @@ public HeaderFooterGrid() CollectionView.ItemsSource = _demoFilteredItemSource.Items; } - void Handle_Clicked(object sender, System.EventArgs e) + void AddContentClicked(object sender, System.EventArgs e) { if (sender is VisualElement ve && ve.Parent is StackLayout sl) sl.Children.Add(new Label() { Text = "Grow" }); } + + void ToggleHeader(object sender, System.EventArgs e) + { + header = CollectionView.Header ?? header; + + if (CollectionView.Header == null) + CollectionView.Header = header; + else + CollectionView.Header = null; + } + + void ToggleFooter(object sender, System.EventArgs e) + { + footer = CollectionView.Footer ?? footer; + + if (CollectionView.Footer == null) + CollectionView.Footer = footer; + else + CollectionView.Footer = null; + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml index babedd22299..3e553b57fd7 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml @@ -6,37 +6,65 @@ mc:Ignorable="d" x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterGridHorizontal"> - - - - + + + + + + + + + + + + + - + - - - + - + - - - + - + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs index fbe2478ff07..378ae661501 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs @@ -13,6 +13,8 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFoot public partial class HeaderFooterGridHorizontal : ContentPage { readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(10); + object header; + object footer; public HeaderFooterGridHorizontal() { @@ -22,10 +24,32 @@ public HeaderFooterGridHorizontal() CollectionView.ItemsSource = _demoFilteredItemSource.Items; } - void Handle_Clicked(object sender, System.EventArgs e) + + + void AddContentClicked(object sender, System.EventArgs e) { if (sender is VisualElement ve && ve.Parent is StackLayout sl) sl.Children.Add(new Label() { Text = "Grow" }); } + + void ToggleHeader(object sender, System.EventArgs e) + { + header = CollectionView.Header ?? header; + + if (CollectionView.Header == null) + CollectionView.Header = header; + else + CollectionView.Header = null; + } + + void ToggleFooter(object sender, System.EventArgs e) + { + footer = CollectionView.Footer ?? footer; + + if (CollectionView.Footer == null) + CollectionView.Footer = footer; + else + CollectionView.Footer = null; + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/CollectionView/RecyclerViewScrollListener.cs b/Xamarin.Forms.Platform.Android/CollectionView/RecyclerViewScrollListener.cs index 4f4e3855c0e..c5d174e9908 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/RecyclerViewScrollListener.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/RecyclerViewScrollListener.cs @@ -42,12 +42,13 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) centerItemIndex = CalculateCenterItemIndex(firstVisibleItemIndex, lastVisibleItemIndex, linearLayoutManager); } + var context = recyclerView.Context; var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs { - HorizontalDelta = dx, - VerticalDelta = dy, - HorizontalOffset = _horizontalOffset, - VerticalOffset = _verticalOffset, + HorizontalDelta = context.FromPixels(dx), + VerticalDelta = context.FromPixels(dy), + HorizontalOffset = context.FromPixels(_horizontalOffset), + VerticalOffset = context.FromPixels(_verticalOffset), FirstVisibleItemIndex = firstVisibleItemIndex, CenterItemIndex = centerItemIndex, LastVisibleItemIndex = lastVisibleItemIndex diff --git a/Xamarin.Forms.Platform.Android/CollectionView/SelectableItemsViewAdapter.cs b/Xamarin.Forms.Platform.Android/CollectionView/SelectableItemsViewAdapter.cs index 5be0f39a6ce..68c116bff8f 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/SelectableItemsViewAdapter.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/SelectableItemsViewAdapter.cs @@ -151,6 +151,6 @@ void UpdateFormsSelection(int adapterPosition) } return; } - } + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/CollectionView/StructuredItemsViewAdapter.cs b/Xamarin.Forms.Platform.Android/CollectionView/StructuredItemsViewAdapter.cs index 5248d4cdc1a..7239c5c8e47 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/StructuredItemsViewAdapter.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/StructuredItemsViewAdapter.cs @@ -24,10 +24,12 @@ protected override void ItemsViewPropertyChanged(object sender, PropertyChangedE if (property.Is(Xamarin.Forms.StructuredItemsView.HeaderProperty)) { UpdateHasHeader(); + NotifyDataSetChanged(); } else if (property.Is(Xamarin.Forms.StructuredItemsView.FooterProperty)) { UpdateHasFooter(); + NotifyDataSetChanged(); } } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index 256592e0870..a8ffeb33edf 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -1,4 +1,7 @@ using System; +using System.Drawing; +using System.Threading.Tasks; +using CoreGraphics; using Foundation; using UIKit; using Xamarin.Forms.Internals; @@ -253,8 +256,7 @@ internal void UpdateEmptyView() protected void UpdateSubview(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement) { - if (uiView != null) - CollectionView.Subviews.Remove(uiView); + uiView?.RemoveFromSuperview(); if (formsElement != null) { @@ -265,7 +267,9 @@ protected void UpdateSubview(object view, DataTemplate viewTemplate, ref UIView UpdateView(view, viewTemplate, ref uiView, ref formsElement); if (uiView != null) + { CollectionView.AddSubview(uiView); + } if (formsElement != null) ItemsView.AddLogicalChild(formsElement); @@ -286,16 +290,16 @@ void RemeasureLayout(VisualElement formsElement) if (IsHorizontal) { var request = formsElement.Measure(double.PositiveInfinity, CollectionView.Frame.Height, MeasureFlags.IncludeMargins); - Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(-request.Request.Width, 0, request.Request.Width, CollectionView.Frame.Height)); + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(0, 0, request.Request.Width, CollectionView.Frame.Height)); } else { var request = formsElement.Measure(CollectionView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins); - Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(0, -request.Request.Height, CollectionView.Frame.Width, request.Request.Height)); + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(0, 0, CollectionView.Frame.Width, request.Request.Height)); } } - protected void OnFormsElementMeasureInvalidated(object sender, EventArgs e) + void OnFormsElementMeasureInvalidated(object sender, EventArgs e) { if (sender is VisualElement formsElement) { @@ -306,14 +310,16 @@ protected void OnFormsElementMeasureInvalidated(object sender, EventArgs e) protected virtual void HandleFormsElementMeasureInvalidated(VisualElement formsElement) { RemeasureLayout(formsElement); - } + } internal void UpdateView(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement) { // Is view set on the ItemsView? if (view == null) { - // Clear the cached Forms and native views + if (formsElement != null) + Platform.GetRenderer(formsElement)?.DisposeRendererAndChildren(); + uiView = null; formsElement = null; } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs index da4d7482f9e..5adcb38f74a 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewController.cs @@ -31,12 +31,6 @@ protected override void Dispose(bool disposing) if (disposing) { - if (_headerViewFormsElement != null) - _headerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated; - - if (_footerViewFormsElement != null) - _footerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated; - _headerUIView = null; _headerViewFormsElement = null; _footerUIView = null; @@ -74,12 +68,14 @@ internal void UpdateFooterView() { UpdateSubview(StructuredItemsView?.Footer, StructuredItemsView?.FooterTemplate, ref _footerUIView, ref _footerViewFormsElement); + UpdateHeaderFooterPosition(); } internal void UpdateHeaderView() { UpdateSubview(StructuredItemsView?.Header, StructuredItemsView?.HeaderTemplate, ref _headerUIView, ref _headerViewFormsElement); + UpdateHeaderFooterPosition(); } void UpdateHeaderFooterPosition() @@ -99,33 +95,49 @@ void UpdateHeaderFooterPosition() if (CollectionView.ContentInset.Left != headerWidth || CollectionView.ContentInset.Right != footerWidth) { + var currentOffset = CollectionView.ContentOffset; CollectionView.ContentInset = new UIEdgeInsets(0, headerWidth, 0, footerWidth); + var xOffset = currentOffset.X + (currentInset.Left - CollectionView.ContentInset.Left); + + if (CollectionView.ContentSize.Width + headerWidth <= CollectionView.Bounds.Width) + xOffset = -headerWidth; + // if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move // this changes the offset of the list by however much the header size has changed - CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X + (currentInset.Left - CollectionView.ContentInset.Left), CollectionView.ContentOffset.Y); + CollectionView.ContentOffset = new CoreGraphics.CGPoint(xOffset, CollectionView.ContentOffset.Y); } } else { var currentInset = CollectionView.ContentInset; - nfloat headerHeight = _headerUIView?.Frame.Height ?? 0f; nfloat footerHeight = _footerUIView?.Frame.Height ?? 0f; - if (_headerUIView != null && _headerUIView.Frame.Y != headerHeight) - _headerUIView.Frame = new CoreGraphics.CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight); - - if (_footerUIView != null && (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height)) - _footerUIView.Frame = new CoreGraphics.CGRect(0, ItemsViewLayout.CollectionViewContentSize.Height, CollectionView.Frame.Width, footerHeight); - if (CollectionView.ContentInset.Top != headerHeight || CollectionView.ContentInset.Bottom != footerHeight) { + var currentOffset = CollectionView.ContentOffset; CollectionView.ContentInset = new UIEdgeInsets(headerHeight, 0, footerHeight, 0); // if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move // this changes the offset of the list by however much the header size has changed - CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X, CollectionView.ContentOffset.Y + (currentInset.Top - CollectionView.ContentInset.Top)); + + var yOffset = currentOffset.Y + (currentInset.Top - CollectionView.ContentInset.Top); + + if (CollectionView.ContentSize.Height + headerHeight <= CollectionView.Bounds.Height) + yOffset = -headerHeight; + + CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X, yOffset); + } + + if (_headerUIView != null && _headerUIView.Frame.Y != headerHeight) + { + _headerUIView.Frame = new CoreGraphics.CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight); + } + + if (_footerUIView != null && (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height)) + { + _footerUIView.Frame = new CoreGraphics.CGRect(0, ItemsViewLayout.CollectionViewContentSize.Height, CollectionView.Frame.Width, footerHeight); } } } @@ -135,5 +147,14 @@ protected override void HandleFormsElementMeasureInvalidated(VisualElement forms base.HandleFormsElementMeasureInvalidated(formsElement); UpdateHeaderFooterPosition(); } + + internal void UpdateLayoutMeasurements() + { + if (_headerViewFormsElement != null) + HandleFormsElementMeasureInvalidated(_headerViewFormsElement); + + if (_footerViewFormsElement != null) + HandleFormsElementMeasureInvalidated(_footerViewFormsElement); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewRenderer.cs index 3a083668bcc..9c73506524e 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/StructuredItemsViewRenderer.cs @@ -57,5 +57,11 @@ protected override ItemsViewLayout SelectLayout() // Fall back to vertical list return new ListViewLayout(new LinearItemsLayout(ItemsLayoutOrientation.Vertical), itemSizingStrategy); } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + StructuredItemsViewController.UpdateLayoutMeasurements(); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs index e74f4c02b9b..27a884f90ca 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs @@ -13,9 +13,12 @@ public static IVisualElementRenderer CreateRenderer(View view) throw new ArgumentNullException(nameof(view)); } + Platform.GetRenderer(view)?.DisposeRendererAndChildren(); var renderer = Platform.CreateRenderer(view); Platform.SetRenderer(view, renderer); + renderer.NativeView.Bounds = view.Bounds.ToRectangleF(); + return renderer; } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/UICollectionViewDelegator.cs b/Xamarin.Forms.Platform.iOS/CollectionView/UICollectionViewDelegator.cs index a99f43c0aa4..d7df74b28d2 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/UICollectionViewDelegator.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/UICollectionViewDelegator.cs @@ -46,6 +46,10 @@ public override void Scrolled(UIScrollView scrollView) if (indexPathsForVisibleItems.Count == 0) return; + var contentInset = scrollView.ContentInset; + var contentOffsetX = scrollView.ContentOffset.X + contentInset.Left; + var contentOffsetY = scrollView.ContentOffset.Y + contentInset.Top; + var firstVisibleItemIndex = (int)indexPathsForVisibleItems.First().Item; var centerPoint = new CGPoint(ItemsViewController.CollectionView.Center.X + ItemsViewController.CollectionView.ContentOffset.X, ItemsViewController.CollectionView.Center.Y + ItemsViewController.CollectionView.ContentOffset.Y); var centerIndexPath = ItemsViewController.CollectionView.IndexPathForItemAtPoint(centerPoint); @@ -53,10 +57,10 @@ public override void Scrolled(UIScrollView scrollView) var lastVisibleItemIndex = (int)indexPathsForVisibleItems.Last().Item; var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs { - HorizontalDelta = scrollView.ContentOffset.X - _previousHorizontalOffset, - VerticalDelta = scrollView.ContentOffset.Y - _previousVerticalOffset, - HorizontalOffset = scrollView.ContentOffset.X, - VerticalOffset = scrollView.ContentOffset.Y, + HorizontalDelta = contentOffsetX - _previousHorizontalOffset, + VerticalDelta = contentOffsetY - _previousVerticalOffset, + HorizontalOffset = contentOffsetX, + VerticalOffset = contentOffsetY, FirstVisibleItemIndex = firstVisibleItemIndex, CenterItemIndex = centerItemIndex, LastVisibleItemIndex = lastVisibleItemIndex @@ -64,8 +68,8 @@ public override void Scrolled(UIScrollView scrollView) ItemsViewController.ItemsView.SendScrolled(itemsViewScrolledEventArgs); - _previousHorizontalOffset = (float)scrollView.ContentOffset.X; - _previousVerticalOffset = (float)scrollView.ContentOffset.Y; + _previousHorizontalOffset = (float)contentOffsetX; + _previousVerticalOffset = (float)contentOffsetY; switch (ItemsViewController.ItemsView.RemainingItemsThreshold) { From afb4896aa8556372d8937359c5fca298b95df8e8 Mon Sep 17 00:00:00 2001 From: adrianknight89 Date: Tue, 17 Sep 2019 07:42:36 -0500 Subject: [PATCH 012/203] [Android] Refactor the code for setting reverse layout in ItemsViewRenderer (#7468) * refactor * fix * remove the return keyword --- .../CollectionView/ItemsViewRenderer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs index 34e35e84f0b..3c9a6ba6c4b 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs @@ -530,13 +530,11 @@ protected virtual void ReconcileFlowDirectionAndLayout() var effectiveFlowDirection = ((IVisualElementController)Element).EffectiveFlowDirection; - if (effectiveFlowDirection.IsRightToLeft() && !linearLayoutManager.ReverseLayout) + if (effectiveFlowDirection.IsRightToLeft()) { linearLayoutManager.ReverseLayout = true; - return; } - - if (effectiveFlowDirection.IsLeftToRight() && linearLayoutManager.ReverseLayout) + else if (effectiveFlowDirection.IsLeftToRight()) { linearLayoutManager.ReverseLayout = false; } From ba24243160f3d14f883d87782af5c4e024ef8ff5 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 17 Sep 2019 09:46:55 -0600 Subject: [PATCH 013/203] Check to see if the BeginRefresh request is no longer needed (#7514) fixes #7313 * fix refresh to not fire twice * - add automated ui test --- .../Issue5728.cs | 9 ++- .../Issue7313.cs | 69 +++++++++++++++++++ .../Renderers/ListViewRenderer.cs | 36 ++++++++-- 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7313.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5728.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5728.cs index 1cfbdbf6373..a9337732c29 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5728.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5728.cs @@ -20,7 +20,14 @@ public Issue5728() RefreshControlColor = Color.Cyan }; _listView.Refreshing += HandleListViewRefreshing; - Content = _listView; + Content = new StackLayout() + { + Children = + { + new Label() {Text = "If the refresh circle is Cyan this test has passed"}, + _listView + } + }; } protected override void OnAppearing() diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7313.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7313.cs new file mode 100644 index 00000000000..8be505d32d9 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7313.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Xamarin.Forms; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7313, "ListView RefreshControl Not Hiding", PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(UITestCategories.ListView)] +#endif + public class Issue7313 : TestContentPage + { + ListView _listView; + Label _testLoaded; + string _testReady = "If you see the refresh circle this test has failed"; + + protected override void Init() + { + _listView = new ListView + { + BackgroundColor = Color.Transparent, + IsPullToRefreshEnabled = true, + RefreshControlColor = Color.Cyan, + ItemsSource = new []{ "ListLoaded" } + }; + + _testLoaded = new Label(); + Content = new StackLayout() + { + Children = + { + _testLoaded, + _listView + } + }; + } + + protected override async void OnAppearing() + { + base.OnAppearing(); + _listView.IsRefreshing = true; + await Task.Delay(1); + _listView.IsRefreshing = false; + await Task.Delay(1); + _testLoaded.Text = _testReady; + + } + +#if UITEST && __IOS__ + [Test] + public void RefreshControlTurnsOffSuccessfully() + { + RunningApp.WaitForElement(_testReady); + + RunningApp.WaitForNoElement("RefreshControl"); + } +#endif + + } +} diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs index cbbb800add1..c9321fe01fb 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs @@ -1457,6 +1457,7 @@ internal class FormsUITableViewController : UITableViewController bool _disposed; internal bool _usingLargeTitles; bool _isRefreshing; + bool _isStartRefreshingPending; public FormsUITableViewController(ListView element, bool usingLargeTitles) : base(element.OnThisPlatform().GetGroupHeaderStyle() == GroupHeaderStyle.Plain @@ -1485,15 +1486,19 @@ public void UpdateIsRefreshing(bool refreshing) if (!_refresh.Refreshing) { + _isStartRefreshingPending = true; //hack: On iOS11 with large titles we need to adjust the scroll offset manually //since our UITableView is not the first child of the UINavigationController //This also forces the spinner color to be correct if we started refreshing immediately after changing it. UpdateContentOffset(TableView.ContentOffset.Y - _refresh.Frame.Height, () => { - if (_refresh == null) + if (_refresh == null || _disposed) return; - _refresh.BeginRefreshing(); + if( _isStartRefreshingPending) + StartRefreshing(); + + //hack: when we don't have cells in our UITableView the spinner fails to appear CheckContentSize(); TableView.ScrollRectToVisible(new RectangleF(0, 0, _refresh.Bounds.Width, _refresh.Bounds.Height), true); @@ -1505,7 +1510,7 @@ public void UpdateIsRefreshing(bool refreshing) if (RefreshControl == null) return; - _refresh.EndRefreshing(); + EndRefreshing(); UpdateContentOffset(-1); @@ -1515,6 +1520,24 @@ public void UpdateIsRefreshing(bool refreshing) } } + void StartRefreshing() + { + _isStartRefreshingPending = false; + if (_refresh?.Refreshing == true) + return; + + _refresh.BeginRefreshing(); + } + + void EndRefreshing() + { + _isStartRefreshingPending = false; + if (_refresh?.Refreshing == false) + return; + + _refresh.EndRefreshing(); + } + public void UpdatePullToRefreshEnabled(bool pullToRefreshEnabled) { if (pullToRefreshEnabled) @@ -1544,7 +1567,7 @@ public void ForceRefreshing() if (!_refresh.Refreshing && !_isRefreshing) { _isRefreshing = true; - UpdateContentOffset(TableView.ContentOffset.Y - _refresh.Frame.Height, _refresh.BeginRefreshing); + UpdateContentOffset(TableView.ContentOffset.Y - _refresh.Frame.Height, StartRefreshing); _list.SendRefreshing(); } } @@ -1592,7 +1615,7 @@ protected override void Dispose(bool disposing) if (_refresh != null) { _refresh.ValueChanged -= OnRefreshingChanged; - _refresh.EndRefreshing(); + EndRefreshing(); _refresh.Dispose(); _refresh = null; } @@ -1627,7 +1650,7 @@ void RemoveRefresh() return; if (_refresh.Refreshing || _isRefreshing) - _refresh.EndRefreshing(); + EndRefreshing(); RefreshControl = null; _refreshAdded = false; @@ -1647,6 +1670,7 @@ public class FormsRefreshControl : UIRefreshControl public FormsRefreshControl(bool usingLargeTitles) { _usingLargeTitles = usingLargeTitles; + AccessibilityIdentifier = "RefreshControl"; } public override bool Hidden From d8e3e5c5442bba3fb25d89b799a511c557bbdb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Tue, 17 Sep 2019 22:33:55 +0200 Subject: [PATCH 014/203] Implement CarouselView on UWP (#7503) * Implemented CarouselView on UWP * Implemented CurrentItem on Carousel UWP * Fixed ArgumentOutOfRangeException with the Carousel position * Removed code to update the CurrentItem on UWP (review it to improve) --- .../CollectionView/CarouselViewRenderer.cs | 360 ++++++++++++++++++ .../CollectionView/ItemContentControl.cs | 55 ++- .../CollectionView/ItemTemplateContext.cs | 14 +- .../CollectionView/ItemTemplateEnumerator.cs | 17 +- .../CollectionView/ItemsViewRenderer.cs | 17 +- .../CollectionView/ItemsViewStyles.xaml | 219 +++++++++-- .../ObservableItemTemplateCollection.cs | 23 +- .../SelectableItemsViewRenderer.cs | 4 +- .../StructuredItemsViewRenderer.cs | 4 +- .../TemplatedItemSourceFactory.cs | 6 +- .../Properties/AssemblyInfo.cs | 1 + .../Xamarin.Forms.Platform.UAP.csproj | 1 + 12 files changed, 660 insertions(+), 61 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs new file mode 100644 index 00000000000..5b6eab58acc --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs @@ -0,0 +1,360 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; +using UWPApp = Windows.UI.Xaml.Application; +using UWPDataTemplate = Windows.UI.Xaml.DataTemplate; +using WScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility; +using WSnapPointsType = Windows.UI.Xaml.Controls.SnapPointsType; +using WSnapPointsAlignment = Windows.UI.Xaml.Controls.Primitives.SnapPointsAlignment; +using System; + +namespace Xamarin.Forms.Platform.UWP +{ + public class CarouselViewRenderer : ItemsViewRenderer + { + CollectionViewSource _collectionViewSource; + ScrollViewer _scrollViewer; + double _carouselHeight; + double _carouselWidth; + + public CarouselViewRenderer() + { + CollectionView.VerifyCollectionViewFlagEnabled(nameof(CarouselView)); + } + + CarouselView CarouselView => (CarouselView)Element; + protected override IItemsLayout Layout => CarouselView?.ItemsLayout; + UWPDataTemplate CarouselItemsViewTemplate => (UWPDataTemplate)UWPApp.Current.Resources["CarouselItemsViewDefaultTemplate"]; + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs changedProperty) + { + base.OnElementPropertyChanged(sender, changedProperty); + + if (changedProperty.IsOneOf(ItemsView.ItemsSourceProperty, CarouselView.NumberOfSideItemsProperty, LinearItemsLayout.ItemSpacingProperty)) + UpdateItemsSource(); + else if (changedProperty.Is(ItemsView.ItemTemplateProperty)) + UpdateItemTemplate(); + else if (changedProperty.Is(CarouselView.PeekAreaInsetsProperty)) + UpdatePeekAreaInsets(); + else if (changedProperty.Is(CarouselView.IsSwipeEnabledProperty)) + UpdateIsSwipeEnabled(); + else if (changedProperty.Is(CarouselView.IsBounceEnabledProperty)) + UpdateIsBounceEnabled(); + } + + protected override void HandleLayoutPropertyChange(PropertyChangedEventArgs property) + { + if (property.IsOneOf(LinearItemsLayout.ItemSpacingProperty, GridItemsLayout.HorizontalItemSpacingProperty, GridItemsLayout.VerticalItemSpacingProperty)) + UpdateItemSpacing(); + else if (property.Is(ItemsLayout.SnapPointsTypeProperty)) + UpdateSnapPointsType(); + else if (property.Is(ItemsLayout.SnapPointsAlignmentProperty)) + UpdateSnapPointsAlignment(); + } + + protected override void SetUpNewElement(ItemsView newElement, bool setUpProperties) + { + base.SetUpNewElement(newElement, false); + + if (newElement != null) + { + ListViewBase.SizeChanged += OnListSizeChanged; + } + } + + protected override void TearDownOldElement(ItemsView oldElement) + { + base.TearDownOldElement(oldElement); + + if (oldElement == null) + return; + + if (ListViewBase != null) + { + ListViewBase.SizeChanged -= OnListSizeChanged; + } + + if (_scrollViewer != null) + { + _scrollViewer.ViewChanging -= OnScrollViewChanging; + _scrollViewer.ViewChanged -= OnScrollViewChanged; + } + } + + protected override void UpdateItemsSource() + { + var itemsSource = Element.ItemsSource; + + if (itemsSource == null) + return; + + var itemTemplate = Element.ItemTemplate; + + if (itemTemplate == null) + return; + + _collectionViewSource = new CollectionViewSource + { + Source = TemplatedItemSourceFactory.Create(itemsSource, itemTemplate, Element, GetItemHeight(), GetItemWidth(), GetItemSpacing()), + IsSourceGrouped = false + }; + + ListViewBase.ItemsSource = _collectionViewSource.View; + } + + protected override ListViewBase SelectListViewBase() + { + switch (Layout) + { + case LinearItemsLayout listItemsLayout: + return CreateCarouselListLayout(listItemsLayout.Orientation); + } + + return new Windows.UI.Xaml.Controls.ListView(); + } + + protected override void UpdateItemTemplate() + { + if (Element == null || ListViewBase == null) + return; + + ListViewBase.ItemTemplate = CarouselItemsViewTemplate; + } + + protected override Task ScrollTo(ScrollToRequestEventArgs args) + { + var targetItem = FindCarouselItem(args); + + // TODO: jsuarezruiz Include support to animated scroll. + ListViewBase.ScrollIntoView(targetItem); + + return Task.FromResult(null); + } + + void OnListSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e) + { + var newSize = e.NewSize; + + _carouselHeight = newSize.Height; + _carouselWidth = newSize.Width; + + _scrollViewer = ListViewBase.GetFirstDescendant(); + + if (_scrollViewer != null) + { + // TODO: jsuarezruiz This breaks the ScrollTo override. Review it. + _scrollViewer.ViewChanging += OnScrollViewChanging; + _scrollViewer.ViewChanged += OnScrollViewChanged; + } + + UpdateItemsSource(); + UpdateItemTemplate(); + UpdateIsSwipeEnabled(); + UpdateIsBounceEnabled(); + } + + void OnScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e) + { + CarouselView.SetIsDragging(true); + } + + void OnScrollViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + CarouselView.SetIsDragging(e.IsIntermediate); + } + + void UpdatePeekAreaInsets() + { + UpdateItemsSource(); + } + + void UpdateIsSwipeEnabled() + { + if (CarouselView == null) + return; + + ListViewBase.IsSwipeEnabled = CarouselView.IsSwipeEnabled; + + switch (Layout) + { + case LinearItemsLayout listItemsLayout when listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal: + ScrollViewer.SetHorizontalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? ScrollMode.Auto : ScrollMode.Disabled); + ScrollViewer.SetHorizontalScrollBarVisibility(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollBarVisibility.Auto : WScrollBarVisibility.Disabled); + break; + case LinearItemsLayout listItemsLayout when listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical: + ScrollViewer.SetVerticalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? ScrollMode.Auto : ScrollMode.Disabled); + ScrollViewer.SetVerticalScrollBarVisibility(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollBarVisibility.Auto : WScrollBarVisibility.Disabled); + break; + } + } + + void UpdateIsBounceEnabled() + { + if (_scrollViewer != null) + _scrollViewer.IsScrollInertiaEnabled = CarouselView.IsBounceEnabled; + } + + void UpdateItemSpacing() + { + UpdateItemsSource(); + + if (Layout is LinearItemsLayout listItemsLayout) + { + var itemSpacing = listItemsLayout.ItemSpacing; + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + _scrollViewer.Padding = new Windows.UI.Xaml.Thickness(0, 0, itemSpacing, 0); + + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + _scrollViewer.Padding = new Windows.UI.Xaml.Thickness(0, 0, 0, itemSpacing); + } + } + + void UpdateSnapPointsType() + { + if (_scrollViewer == null) + return; + + if (Layout is LinearItemsLayout listItemsLayout) + { + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + _scrollViewer.HorizontalSnapPointsType = GetWindowsSnapPointsType(listItemsLayout.SnapPointsType); + + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + _scrollViewer.VerticalSnapPointsType = GetWindowsSnapPointsType(listItemsLayout.SnapPointsType); + } + } + + void UpdateSnapPointsAlignment() + { + if (_scrollViewer == null) + return; + + if (Layout is LinearItemsLayout listItemsLayout) + { + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + _scrollViewer.HorizontalSnapPointsAlignment = GetWindowsSnapPointsAlignment(listItemsLayout.SnapPointsAlignment); + + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + _scrollViewer.VerticalSnapPointsAlignment = GetWindowsSnapPointsAlignment(listItemsLayout.SnapPointsAlignment); + } + } + + ListViewBase CreateCarouselListLayout(ItemsLayoutOrientation layoutOrientation) + { + Windows.UI.Xaml.Controls.ListView listView; + + if (layoutOrientation == ItemsLayoutOrientation.Horizontal) + { + listView = new Windows.UI.Xaml.Controls.ListView() + { + Style = (Windows.UI.Xaml.Style)UWPApp.Current.Resources["HorizontalCarouselListStyle"], + ItemsPanel = (ItemsPanelTemplate)UWPApp.Current.Resources["HorizontalListItemsPanel"] + }; + } + else + { + listView = new Windows.UI.Xaml.Controls.ListView() + { + Style = (Windows.UI.Xaml.Style)UWPApp.Current.Resources["VerticalCarouselListStyle"] + }; + } + + return listView; + } + + double GetItemWidth() + { + var itemWidth = _carouselWidth; + + if (Layout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + { + var numberOfVisibleItems = CarouselView.NumberOfSideItems * 2 + 1; + itemWidth = (_carouselWidth - CarouselView.PeekAreaInsets.Left - CarouselView.PeekAreaInsets.Right - listItemsLayout.ItemSpacing) / numberOfVisibleItems; + } + + return itemWidth; + } + + double GetItemHeight() + { + var itemHeight = _carouselHeight; + + if (Layout is LinearItemsLayout listItemsLayout && listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + { + var numberOfVisibleItems = CarouselView.NumberOfSideItems * 2 + 1; + itemHeight = (_carouselHeight - CarouselView.PeekAreaInsets.Top - CarouselView.PeekAreaInsets.Bottom - listItemsLayout.ItemSpacing) / numberOfVisibleItems; + } + + return itemHeight; + } + + Thickness GetItemSpacing() + { + if (Layout is LinearItemsLayout listItemsLayout) + { + var itemSpacing = listItemsLayout.ItemSpacing; + + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + return new Thickness(itemSpacing, 0, 0, 0); + + if (listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical) + return new Thickness(0, itemSpacing, 0, 0); + } + + return new Thickness(0); + } + + object FindCarouselItem(ScrollToRequestEventArgs args) + { + if (args.Mode == ScrollToMode.Position) + return _collectionViewSource.View[args.Index]; + + if (Element.ItemTemplate == null) + return args.Item; + + for (int n = 0; n < _collectionViewSource?.View.Count; n++) + { + if (_collectionViewSource.View[n] is ItemTemplateContext pair) + { + if (pair.Item == args.Item) + { + return _collectionViewSource.View[n]; + } + } + } + + return null; + } + + WSnapPointsType GetWindowsSnapPointsType(SnapPointsType snapPointsType) + { + switch (snapPointsType) + { + case SnapPointsType.Mandatory: + return WSnapPointsType.Mandatory; + case SnapPointsType.MandatorySingle: + return WSnapPointsType.MandatorySingle; + case SnapPointsType.None: + return WSnapPointsType.None; + } + + return WSnapPointsType.None; + } + + WSnapPointsAlignment GetWindowsSnapPointsAlignment(SnapPointsAlignment snapPointsAlignment) + { + switch (snapPointsAlignment) + { + case SnapPointsAlignment.Center: + return WSnapPointsAlignment.Center; + case SnapPointsAlignment.End: + return WSnapPointsAlignment.Far; + case SnapPointsAlignment.Start: + return WSnapPointsAlignment.Near; + } + + return WSnapPointsAlignment.Center; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 4f6a4eb3483..00f20cc055e 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -1,6 +1,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Xamarin.Forms.Internals; +using WThickness = Windows.UI.Xaml.Thickness; namespace Xamarin.Forms.Platform.UWP { @@ -67,6 +68,36 @@ public BindableObject FormsContainer set => SetValue(FormsContainerProperty, value); } + public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register( + nameof(ItemHeight), typeof(double), typeof(ItemContentControl), + new PropertyMetadata(default(double))); + + public double ItemHeight + { + get => (double)GetValue(ItemHeightProperty); + set => SetValue(ItemHeightProperty, value); + } + + public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register( + nameof(ItemWidth), typeof(double), typeof(ItemContentControl), + new PropertyMetadata(default(double))); + + public double ItemWidth + { + get => (double)GetValue(ItemWidthProperty); + set => SetValue(ItemWidthProperty, value); + } + + public static readonly DependencyProperty ItemSpacingProperty = DependencyProperty.Register( + nameof(ItemSpacing), typeof(Thickness), typeof(ItemContentControl), + new PropertyMetadata(default(Thickness))); + + public Thickness ItemSpacing + { + get => (Thickness)GetValue(ItemSpacingProperty); + set => SetValue(ItemSpacingProperty, value); + } + protected override void OnContentChanged(object oldContent, object newContent) { base.OnContentChanged(oldContent, newContent); @@ -118,17 +149,31 @@ protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Si } var formsElement = _renderer.Element; + if (ItemHeight != default || ItemWidth != default) + { + formsElement.Layout(new Rectangle(0, 0, ItemWidth, ItemHeight)); + + var wsize = new Windows.Foundation.Size(ItemWidth, ItemHeight); + + (Content as FrameworkElement).Margin = new WThickness(ItemSpacing.Left, ItemSpacing.Top, ItemSpacing.Right, ItemSpacing.Bottom); - Size request = formsElement.Measure(availableSize.Width, availableSize.Height, + (Content as FrameworkElement).Measure(wsize); + + return base.MeasureOverride(wsize); + } + else + { + Size request = formsElement.Measure(availableSize.Width, availableSize.Height, MeasureFlags.IncludeMargins).Request; - formsElement.Layout(new Rectangle(0, 0, request.Width, request.Height)); + formsElement.Layout(new Rectangle(0, 0, request.Width, request.Height)); - var wsize = new Windows.Foundation.Size(request.Width, request.Height); + var wsize = new Windows.Foundation.Size(request.Width, request.Height); - (Content as FrameworkElement).Measure(wsize); + (Content as FrameworkElement).Measure(wsize); - return base.MeasureOverride(wsize); + return base.MeasureOverride(wsize); + } } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs index 43bcd148b0c..3f4b6fa90ad 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs @@ -2,15 +2,27 @@ { internal class ItemTemplateContext { - public ItemTemplateContext(DataTemplate formsDataTemplate, object item, BindableObject container) + public ItemTemplateContext(DataTemplate formsDataTemplate, object item, BindableObject container, double? height = null, double? width = null, Thickness? itemSpacing = null) { FormsDataTemplate = formsDataTemplate; Item = item; Container = container; + + if (height.HasValue) + ItemHeight = height.Value; + + if (width.HasValue) + ItemWidth = width.Value; + + if (itemSpacing.HasValue) + ItemSpacing = itemSpacing.Value; } public DataTemplate FormsDataTemplate { get; } public object Item { get; } public BindableObject Container { get; } + public double ItemHeight { get; } + public double ItemWidth { get; } + public Thickness ItemSpacing { get; } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateEnumerator.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateEnumerator.cs index e45fdda1c22..62cced9a7cd 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateEnumerator.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateEnumerator.cs @@ -7,13 +7,26 @@ internal class ItemTemplateEnumerator : IEnumerable, IEnumerator readonly DataTemplate _formsDataTemplate; readonly IEnumerator _innerEnumerator; readonly BindableObject _container; + readonly double _itemHeight; + readonly double _itemWidth; + readonly Thickness _itemSpacing; - public ItemTemplateEnumerator(IEnumerable itemsSource, DataTemplate formsDataTemplate, BindableObject container) + public ItemTemplateEnumerator(IEnumerable itemsSource, DataTemplate formsDataTemplate, BindableObject container, double? itemHeight = null, double? itemWidth = null, Thickness? itemSpacing = null) { _formsDataTemplate = formsDataTemplate; _container = container; _innerEnumerator = itemsSource.GetEnumerator(); + + if (itemHeight.HasValue) + _itemHeight = itemHeight.Value; + + if (itemWidth.HasValue) + _itemWidth = itemWidth.Value; + + if (itemSpacing.HasValue) + _itemSpacing = itemSpacing.Value; } + public IEnumerator GetEnumerator() { return this; @@ -25,7 +38,7 @@ public bool MoveNext() if (moveNext) { - Current = new ItemTemplateContext(_formsDataTemplate, _innerEnumerator.Current, _container); + Current = new ItemTemplateContext(_formsDataTemplate, _innerEnumerator.Current, _container, _itemHeight, _itemWidth, _itemSpacing); } return moveNext; diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 14a6745e18e..1857f6b6cc7 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -38,7 +38,7 @@ protected override void OnElementChanged(ElementChangedEventArgs args { base.OnElementChanged(args); TearDownOldElement(args.OldElement); - SetUpNewElement(args.NewElement); + SetUpNewElement(args.NewElement, true); } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs changedProperty) @@ -152,7 +152,7 @@ void LayoutPropertyChanged(object sender, PropertyChangedEventArgs property) HandleLayoutPropertyChange(property); } - protected virtual void SetUpNewElement(ItemsView newElement) + protected virtual void SetUpNewElement(ItemsView newElement, bool setUpProperties) { if (newElement == null) { @@ -169,11 +169,14 @@ protected virtual void SetUpNewElement(ItemsView newElement) SetNativeControl(ListViewBase); } - UpdateItemTemplate(); - UpdateItemsSource(); - UpdateVerticalScrollBarVisibility(); - UpdateHorizontalScrollBarVisibility(); - UpdateEmptyView(); + if (setUpProperties) + { + UpdateItemTemplate(); + UpdateItemsSource(); + UpdateVerticalScrollBarVisibility(); + UpdateHorizontalScrollBarVisibility(); + UpdateEmptyView(); + } // Listen for ScrollTo requests newElement.ScrollToRequested += ScrollToRequested; diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml index 9d5f32121cd..c74b732e32e 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml @@ -4,38 +4,50 @@ xmlns:local="using:Xamarin.Forms.Platform.UWP"> - - + + - - - - + + - - - - + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ObservableItemTemplateCollection.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ObservableItemTemplateCollection.cs index 1736635715c..006b3ab152f 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ObservableItemTemplateCollection.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ObservableItemTemplateCollection.cs @@ -10,9 +10,12 @@ internal class ObservableItemTemplateCollection : ObservableCollection _structuredItemsView.ItemsLayout; } - protected override void SetUpNewElement(ItemsView newElement) + protected override void SetUpNewElement(ItemsView newElement, bool setUpProperties) { _structuredItemsView = newElement as StructuredItemsView; - base.SetUpNewElement(newElement); + base.SetUpNewElement(newElement, setUpProperties); if (newElement == null) { diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs b/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs index bcf568a49d7..f5535bd57a4 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs @@ -5,15 +5,15 @@ namespace Xamarin.Forms.Platform.UWP { internal static class TemplatedItemSourceFactory { - internal static object Create(IEnumerable itemsSource, DataTemplate itemTemplate, BindableObject container) + internal static object Create(IEnumerable itemsSource, DataTemplate itemTemplate, BindableObject container, double? itemHeight = null, double? itemWidth = null, Thickness? itemSpacing = null) { switch (itemsSource) { case IList list when itemsSource is INotifyCollectionChanged: - return new ObservableItemTemplateCollection(list, itemTemplate, container); + return new ObservableItemTemplateCollection(list, itemTemplate, container, itemHeight, itemWidth, itemSpacing); } - return new ItemTemplateEnumerator(itemsSource, itemTemplate, container); + return new ItemTemplateEnumerator(itemsSource, itemTemplate, container, itemHeight, itemWidth, itemSpacing); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs index e068a186f5f..e48e034047a 100644 --- a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs @@ -12,6 +12,7 @@ [assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))] [assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))] [assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))] +[assembly: ExportRenderer(typeof(CarouselView), typeof(CarouselViewRenderer))] [assembly: ExportRenderer(typeof(CollectionView), typeof(CollectionViewRenderer))] [assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))] [assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))] diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index be7349f18dd..a1afd3db506 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -40,6 +40,7 @@ + From dce4a884bcda4e742726441c1ea40374fbfc0e3e Mon Sep 17 00:00:00 2001 From: Stuart Lang Date: Tue, 17 Sep 2019 21:37:14 +0100 Subject: [PATCH 015/203] [UWP] Check for null Platform in OnDestroy fixes #7331 (#7363) --- .../AppCompat/FormsAppCompatActivity.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs index fc49aee358f..b0c3d85be24 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs @@ -278,9 +278,11 @@ protected override void OnDestroy() PopupManager.Unsubscribe(this); - _layout.RemoveView(Platform); - - Platform?.Dispose(); + if (Platform != null) + { + _layout.RemoveView(Platform); + Platform.Dispose(); + } PreviousActivityDestroying.Set(); From 86042a2dd45f2512ad7d42fc77121cbbc1062c1d Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 18 Sep 2019 00:40:21 +0300 Subject: [PATCH 016/203] [macOS] PushModalAsync a 2nd time causes a crash (#7250) fixes #6866 * fixes https://github.com/xamarin/Xamarin.Forms/issues/6866 * fixed index boundaries to include 0 index --- Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs index 07e29027c95..5c966655af2 100644 --- a/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs +++ b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs @@ -122,10 +122,15 @@ Task HideModalAsync(Page modal, bool animated) NSViewControllerTransitionOptions option = animated ? NSViewControllerTransitionOptions.SlideDown : NSViewControllerTransitionOptions.None; - var task = _renderer.HandleAsyncAnimation(controller, toViewController, option, - () => modal.DisposeModalAndChildRenderers() , modal); + () => + { + modal.DisposeModalAndChildRenderers(); + var removingIndex = Array.IndexOf(_renderer.ChildViewControllers, controller); + if(removingIndex >= 0) + _renderer.RemoveChildViewController(removingIndex); + }, modal); return task; } } -} \ No newline at end of file +} From e44bfb8d01dd846d2907a58225928c93b2615baf Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 17 Sep 2019 16:12:16 -0600 Subject: [PATCH 017/203] Additional proguard classes (#7527) fixes #5742 --- .nuspec/proguard.cfg | 5 ++++- .../Xamarin.Forms.ControlGallery.Android.csproj | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.nuspec/proguard.cfg b/.nuspec/proguard.cfg index 7ec96902f61..86aaef0683d 100644 --- a/.nuspec/proguard.cfg +++ b/.nuspec/proguard.cfg @@ -1,4 +1,7 @@ -keep class android.support.v7.widget.FitWindowsFrameLayout { *; } -dontwarn android.support.v7.widget.FitWindowsFrameLayout -keep class android.support.design.** { *; } --keep class android.support.multidex.MultiDexApplication { *; } \ No newline at end of file +-keep class android.support.multidex.MultiDexApplication { *; } +-keep class android.support.design.internal.BaselineLayout { *; } +-dontwarn android.support.design.internal.BaselineLayout +-keep class com.google.firebase.provider.FirebaseInitProvider { *; } \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj index 8293faf7caa..5a134a35d8e 100644 --- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj +++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj @@ -379,6 +379,11 @@ MSBuild:UpdateGeneratedFiles + + + proguard.cfg + + From aeafec9ff672edd32e0968c0a39ec2c835765c3f Mon Sep 17 00:00:00 2001 From: Morten Nielsen <1378165+dotMorten@users.noreply.github.com> Date: Tue, 17 Sep 2019 15:26:56 -0700 Subject: [PATCH 018/203] Adds UWP support to Shell (#6015) * Some basics to get started * more renderers * Fix titlebar color * More shell stuff working * Hacked some more UI in * Fix null ref issue * Move renderer registration outside common code (for now) * Re-write of the renderers to better use a cleaner UWP approach * Moved functionality around, bug fixesetc * Added null check * Added null-check on appearance and use default colors as fallback * Handle change in flyout behavior to correctly turn the flyout on/off * Handle the TabBarIsVisible property * code formatting * Ensure FlyoutHeader isn't show if the app starts up with a minimal pane * Throw if used on versions lower than Windows 10 1809 * Added null-check for when ShellContent isn't set * Support tabs in FlyoutItems with Display AsMultipleItems by using the generated FlyoutGroupings instead * Improve pane behavior and styling * Undo Android change used during testing * Fix platform support check * Use FileImageSourcePathConverter on NavigationViewItem instead of a custom control (so I deleted ShellNavigationViewItemRenderer which is no longer needed). Ensure `FileImageSourcePathConverter` won't throw if it didn't get a FileImageSource. Move the flyout data templates into a resource so they can be overridden and compiled. * Delete renamed file * Use a resource instead of parsing a string template * Handle search box property changes * Update page title on property change * Update bottombar when shellitems change * Guard against API usage not present * Platform check comments * Fix problem running in release mode (use Bindable to generate XamlMetadata * Trigger rebind of menu items source when collection changes * Added support for Toolbar * Fix searchbox behavior (still lacks expand/collapse feature) * Add overload for defining the navigation transition * Use different navigation transitions based on navigatin direction * Hides header with show / hide nav command * collapses header area on hide nav * Move to use WinUI * Fix runtime issues after merge. * - rebase fixes * - rebase fixes * - fix spaces/tabs * - flags, hide apis, delete assembly info * - set flag on UWP CG * - expose renderer creations and make them all public * - formatting fixes * - address PR comments * - fix header so it's full width and swappable --- .nuspec/Xamarin.Forms.nuspec | 1 + .../App.xaml.cs | 1 + Xamarin.Forms.Core/ExperimentalFlags.cs | 1 + Xamarin.Forms.Core/Shell/Shell.cs | 20 +- .../ImageSourceIconElementConverter.cs | 2 +- Xamarin.Forms.Platform.UAP/PageExtensions.cs | 14 +- .../Properties/AssemblyInfo.cs | 1 + Xamarin.Forms.Platform.UAP/Resources.xaml | 3 +- .../Shell/ShellFlyoutTemplateSelector.cs | 27 ++ .../Shell/ShellHeaderRenderer.cs | 77 ++++ .../Shell/ShellItemRenderer.cs | 345 ++++++++++++++++++ .../Shell/ShellRenderer.cs | 255 +++++++++++++ .../Shell/ShellSectionRenderer.cs | 277 ++++++++++++++ .../Shell/ShellStyles.xaml | 30 ++ .../Shell/ShellToolbarItemRenderer.cs | 34 ++ .../ViewToRendererConverter.cs | 15 +- .../Xamarin.Forms.Platform.UAP.csproj | 10 + 17 files changed, 1103 insertions(+), 10 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellFlyoutTemplateSelector.cs create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellHeaderRenderer.cs create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellItemRenderer.cs create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellRenderer.cs create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellSectionRenderer.cs create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml create mode 100644 Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec index 6c35e8022fe..44a90efc3ef 100644 --- a/.nuspec/Xamarin.Forms.nuspec +++ b/.nuspec/Xamarin.Forms.nuspec @@ -225,6 +225,7 @@ + diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/App.xaml.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/App.xaml.cs index f852f100532..615c37b2860 100644 --- a/Xamarin.Forms.ControlGallery.WindowsUniversal/App.xaml.cs +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/App.xaml.cs @@ -70,6 +70,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) rootFrame.NavigationFailed += OnNavigationFailed; + Forms.SetFlags("Shell_UWP_Experimental"); Forms.Init (e); //FormsMaps.Init (Controls.App.Config["UWPMapsAuthKey"]); diff --git a/Xamarin.Forms.Core/ExperimentalFlags.cs b/Xamarin.Forms.Core/ExperimentalFlags.cs index 33dd6df14ea..4e5500c2586 100644 --- a/Xamarin.Forms.Core/ExperimentalFlags.cs +++ b/Xamarin.Forms.Core/ExperimentalFlags.cs @@ -10,6 +10,7 @@ namespace Xamarin.Forms internal static class ExperimentalFlags { internal const string CollectionViewExperimental = "CollectionView_Experimental"; + internal const string ShellUWPExperimental = "Shell_UWP_Experimental"; [EditorBrowsable(EditorBrowsableState.Never)] public static void VerifyFlagEnabled( diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index 2c71e5664c8..4b4426499fa 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Xamarin.Forms.Internals; @@ -551,11 +553,11 @@ ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellS public static readonly BindableProperty FlyoutHeaderProperty = BindableProperty.Create(nameof(FlyoutHeader), typeof(object), typeof(Shell), null, BindingMode.OneTime, - propertyChanged: OnFlyoutHeaderChanged); + propertyChanging: OnFlyoutHeaderChanging); public static readonly BindableProperty FlyoutHeaderTemplateProperty = BindableProperty.Create(nameof(FlyoutHeaderTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime, - propertyChanged: OnFlyoutHeaderTemplateChanged); + propertyChanging: OnFlyoutHeaderTemplateChanging); public static readonly BindableProperty FlyoutIsPresentedProperty = BindableProperty.Create(nameof(FlyoutIsPresented), typeof(bool), typeof(Shell), false, BindingMode.TwoWay); @@ -909,13 +911,13 @@ static void UpdateChecked(Element root, bool isChecked = true) } } - static void OnFlyoutHeaderChanged(BindableObject bindable, object oldValue, object newValue) + static void OnFlyoutHeaderChanging(BindableObject bindable, object oldValue, object newValue) { var shell = (Shell)bindable; shell.OnFlyoutHeaderChanged(oldValue, newValue); } - static void OnFlyoutHeaderTemplateChanged(BindableObject bindable, object oldValue, object newValue) + static void OnFlyoutHeaderTemplateChanging(BindableObject bindable, object oldValue, object newValue) { var shell = (Shell)bindable; shell.OnFlyoutHeaderTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue); @@ -1100,6 +1102,16 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { FlyoutHeaderView }); } + + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void VerifyShellUWPFlagEnabled( + string constructorHint = null, + [CallerMemberName] string memberName = "") + { + ExperimentalFlags.VerifyFlagEnabled(nameof(Shell), ExperimentalFlags.ShellUWPExperimental); + } + class NavigationImpl : NavigationProxy { readonly Shell _shell; diff --git a/Xamarin.Forms.Platform.UAP/ImageSourceIconElementConverter.cs b/Xamarin.Forms.Platform.UAP/ImageSourceIconElementConverter.cs index 76fa87243e4..1df6a44d72f 100644 --- a/Xamarin.Forms.Platform.UAP/ImageSourceIconElementConverter.cs +++ b/Xamarin.Forms.Platform.UAP/ImageSourceIconElementConverter.cs @@ -20,4 +20,4 @@ public object ConvertBack(object value, Type targetType, object parameter, strin throw new NotImplementedException(); } } -} +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/PageExtensions.cs b/Xamarin.Forms.Platform.UAP/PageExtensions.cs index 3a44df91ab6..c00009c06ed 100644 --- a/Xamarin.Forms.Platform.UAP/PageExtensions.cs +++ b/Xamarin.Forms.Platform.UAP/PageExtensions.cs @@ -19,14 +19,14 @@ protected override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEvent if (e.Parameter == null) { - throw new InvalidOperationException($"Cannot navigate to {nameof(FormsEmbeddedPageWrapper)} without " + throw new InvalidOperationException($"Cannot navigate to {nameof(FormsEmbeddedPageWrapper)} without " + $"providing a {nameof(Xamarin.Forms.Page)} identifier."); } // Find the page instance in the dictionary and then discard it so we don't prevent it from being collected var key = (Guid)e.Parameter; var page = Pages[key]; - Pages.Remove(key); + Pages.Remove(key); // Convert that page into a FrameWorkElement we can display in the ContentPresenter FrameworkElement frameworkElement = page.CreateFrameworkElement(); @@ -78,13 +78,23 @@ internal static FrameworkElement ToFrameworkElement(this VisualElement visualEle public static bool Navigate(this Windows.UI.Xaml.Controls.Frame frame, ContentPage page) { + return Navigate(frame, page, null); + } + + internal static bool Navigate(this Windows.UI.Xaml.Controls.Frame frame, ContentPage page, Windows.UI.Xaml.Media.Animation.NavigationTransitionInfo infoOverride) + { + if (page == null) { throw new ArgumentNullException(nameof(page)); } Guid id = Guid.NewGuid(); + FormsEmbeddedPageWrapper.Pages.Add(id, page); + if (infoOverride != null) + return frame.Navigate(typeof(FormsEmbeddedPageWrapper), id, infoOverride); + return frame.Navigate(typeof(FormsEmbeddedPageWrapper), id); } } diff --git a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs index e48e034047a..132c99465ea 100644 --- a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs @@ -31,6 +31,7 @@ [assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))] [assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))] [assembly: ExportRenderer(typeof(RefreshView), typeof(RefreshViewRenderer))] +[assembly: ExportRenderer(typeof(Shell), typeof(ShellRenderer))] //ImageSources diff --git a/Xamarin.Forms.Platform.UAP/Resources.xaml b/Xamarin.Forms.Platform.UAP/Resources.xaml index 50bc0d6a3a5..90b0a8dd7ae 100644 --- a/Xamarin.Forms.Platform.UAP/Resources.xaml +++ b/Xamarin.Forms.Platform.UAP/Resources.xaml @@ -15,7 +15,8 @@ - + + diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellFlyoutTemplateSelector.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellFlyoutTemplateSelector.cs new file mode 100644 index 00000000000..19b104e97c3 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellFlyoutTemplateSelector.cs @@ -0,0 +1,27 @@ +namespace Xamarin.Forms.Platform.UWP +{ + public class ShellFlyoutTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector + { + Windows.UI.Xaml.DataTemplate BaseShellItemTemplate { get; } + Windows.UI.Xaml.DataTemplate MenuItemTemplate { get; } + Windows.UI.Xaml.DataTemplate SeperatorTemplate { get; } + + public ShellFlyoutTemplateSelector() + { + BaseShellItemTemplate = (Windows.UI.Xaml.DataTemplate)Windows.UI.Xaml.Application.Current.Resources["ShellFlyoutBaseShellItemTemplate"]; + MenuItemTemplate = (Windows.UI.Xaml.DataTemplate)Windows.UI.Xaml.Application.Current.Resources["ShellFlyoutMenuItemTemplate"]; + SeperatorTemplate = (Windows.UI.Xaml.DataTemplate)Windows.UI.Xaml.Application.Current.Resources["ShellFlyoutSeperatorTemplate"]; + } + + protected override Windows.UI.Xaml.DataTemplate SelectTemplateCore(object item) + { + if (item is BaseShellItem) + return BaseShellItemTemplate; + if (item is MenuItem) + return MenuItemTemplate; + if (item == null) + return SeperatorTemplate; + return base.SelectTemplateCore(item); + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellHeaderRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellHeaderRenderer.cs new file mode 100644 index 00000000000..91362c75a6f --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellHeaderRenderer.cs @@ -0,0 +1,77 @@ +using System; +using Windows.Foundation; +using Windows.UI.Xaml; + +namespace Xamarin.Forms.Platform.UWP +{ + public class ShellHeaderRenderer : Windows.UI.Xaml.Controls.ContentControl + { + Shell _shell; + + public ShellHeaderRenderer(Shell element) + { + Shell.VerifyShellUWPFlagEnabled(nameof(ShellHeaderRenderer)); + + SetElement(element); + SizeChanged += OnShellHeaderRendererSizeChanged; + HorizontalContentAlignment = HorizontalAlignment.Stretch; + VerticalContentAlignment = VerticalAlignment.Stretch; + } + + void OnShellHeaderRendererSizeChanged(object sender, SizeChangedEventArgs e) + { + if (Element is Layout layout) + layout.ForceLayout(); + } + + internal VisualElement Element { get; set; } + + public void SetElement(Shell shell) + { + if(_shell != null) + _shell.PropertyChanged += OnShellPropertyChanged; + + if(shell != null) + { + _shell = shell; + _shell.PropertyChanged += OnShellPropertyChanged; + UpdateHeader(); + } + } + + void OnShellPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if(e.IsOneOf(Shell.FlyoutHeaderProperty, Shell.FlyoutHeaderTemplateProperty)) + UpdateHeader(); + } + + void UpdateHeader() + { + if (Element != null) + { + if(Content is ViewToRendererConverter.WrapperControl wrapperControl) + { + wrapperControl.CleanUp(); + Content = null; + } + + Element = null; + } + + object header = null; + + if (_shell is IShellController controller) + header = controller.FlyoutHeader; + + if (header is View visualElement) + { + Element = visualElement; + Content = new ViewToRendererConverter.WrapperControl(visualElement); + } + else + { + Content = null; + } + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellItemRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellItemRenderer.cs new file mode 100644 index 00000000000..2652598dd39 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellItemRenderer.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using Windows.UI.Xaml.Controls; +using Xamarin.Forms.Internals; +using Windows.UI.Xaml; + +using UwpGrid = Windows.UI.Xaml.Controls.Grid; +using UwpColumnDefinition = Windows.UI.Xaml.Controls.ColumnDefinition; +using UwpRowDefinition = Windows.UI.Xaml.Controls.RowDefinition; +using UwpGridLength = Windows.UI.Xaml.GridLength; +using UwpGridUnitType = Windows.UI.Xaml.GridUnitType; +using UwpDataTemplate = Windows.UI.Xaml.DataTemplate; +using UwpThickness = Windows.UI.Xaml.Thickness; +using UwpStyle = Windows.UI.Xaml.Style; +using Windows.UI.Xaml.Media; +using UwpApplication = Windows.UI.Xaml.Application; + +namespace Xamarin.Forms.Platform.UWP +{ + // Responsible for rendering the content title, as well as the bottom bar list of shell sections + public class ShellItemRenderer : UwpGrid, IAppearanceObserver, IFlyoutBehaviorObserver + { + ShellSectionRenderer SectionRenderer { get; } + TextBlock _Title; + Border _BottomBarArea; + UwpGrid _BottomBar; + UwpGrid _HeaderArea; + ItemsControl _Toolbar; + + internal ShellItem ShellItem { get; set; } + + internal ShellRenderer ShellContext { get; set; } + + public ShellItemRenderer(ShellRenderer shellContext) + { + Xamarin.Forms.Shell.VerifyShellUWPFlagEnabled(nameof(ShellItemRenderer)); + _ = shellContext ?? throw new ArgumentNullException(nameof(shellContext)); + + ShellContext = shellContext; + RowDefinitions.Add(new UwpRowDefinition() { Height = new UwpGridLength(1, UwpGridUnitType.Auto) }); + RowDefinitions.Add(new UwpRowDefinition() { Height = new UwpGridLength(1, UwpGridUnitType.Star) }); + RowDefinitions.Add(new UwpRowDefinition() { Height = new UwpGridLength(1, UwpGridUnitType.Auto) }); + + _Title = new TextBlock() + { + Style = Resources["SubtitleTextBlockStyle"] as UwpStyle, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + TextWrapping = TextWrapping.NoWrap + }; + _HeaderArea = new UwpGrid() { Height = 40, Padding = new UwpThickness(10, 0, 10, 0) }; + _HeaderArea.ColumnDefinitions.Add(new UwpColumnDefinition() { Width = new UwpGridLength(1, UwpGridUnitType.Star) }); + _HeaderArea.ColumnDefinitions.Add(new UwpColumnDefinition() { Width = new UwpGridLength(1, UwpGridUnitType.Auto) }); + _HeaderArea.Children.Add(_Title); + Children.Add(_HeaderArea); + + _Toolbar = new ItemsControl() + { + ItemTemplate = UwpApplication.Current.Resources["ShellToolbarItemTemplate"] as UwpDataTemplate, + ItemsPanel = UwpApplication.Current.Resources["ShellToolbarItemsPanelTemplate"] as ItemsPanelTemplate, + }; + SetColumn(_Toolbar, 1); + _HeaderArea.Children.Add(_Toolbar); + + SectionRenderer = shellContext.CreateShellSectionRenderer(); + SetRow(SectionRenderer, 1); + + Children.Add(SectionRenderer); + + _BottomBar = new UwpGrid() { HorizontalAlignment = HorizontalAlignment.Center }; + _BottomBarArea = new Border() { Child = _BottomBar }; + SetRow(_BottomBarArea, 2); + Children.Add(_BottomBarArea); + } + + internal void SetShellContext(ShellRenderer context) + { + if (ShellContext != null) + { + ((IShellController)ShellContext.Shell).RemoveAppearanceObserver(this); + ((IShellController)ShellContext.Shell).RemoveFlyoutBehaviorObserver(this); + } + ShellContext = context; + if (ShellContext != null) + { + ((IShellController)ShellContext.Shell).AddFlyoutBehaviorObserver(this); + ((IShellController)ShellContext.Shell).AddAppearanceObserver(this, ShellContext.Shell); + UpdateHeaderInsets(); + } + } + + internal void NavigateToShellItem(ShellItem newItem, bool animate) + { + UnhookEvents(ShellItem); + ShellItem = newItem; + ShellSection = newItem.CurrentItem; + HookEvents(newItem); + } + + internal void UpdateHeaderInsets() + { + double inset = 10; + if (ShellContext.IsPaneToggleButtonVisible) + inset += 45; + if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Controls.NavigationView", "IsBackButtonVisible")) + { + if (ShellContext.IsBackButtonVisible != Microsoft.UI.Xaml.Controls.NavigationViewBackButtonVisible.Collapsed) + inset += 45; + } + _HeaderArea.Padding = new UwpThickness(inset, 0, 0, 0); + } + + void UpdateBottomBar() + { + _BottomBar.Children.Clear(); + _BottomBar.ColumnDefinitions.Clear(); + if (ShellItem?.Items.Count > 1) + { + for (int i = 0; i < ShellItem.Items.Count; i++) + { + var section = ShellItem.Items[i]; + var btn = new AppBarButton() + { + Label = section.Title, + Width = double.NaN, + MinWidth = 68, + MaxWidth = 200 + }; + if (section.Icon is FileImageSource fis) + btn.Icon = new BitmapIcon() { UriSource = new Uri("ms-appx:///" + fis.File) }; + btn.Click += (s, e) => OnShellSectionClicked(section); + _BottomBar.ColumnDefinitions.Add(new UwpColumnDefinition() { Width = new UwpGridLength(1, UwpGridUnitType.Star) }); + SetColumn(btn, i); + _BottomBar.Children.Add(btn); + } + } + } + + void OnShellSectionClicked(ShellSection shellSection) + { + if (shellSection != null) + ((IShellItemController)ShellItem).ProposeSection(shellSection); + } + + protected virtual bool ChangeSection(ShellSection shellSection) + { + return ((IShellItemController)ShellItem).ProposeSection(shellSection); + } + + #region IAppearanceObserver + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) => UpdateAppearance(appearance); + void UpdateAppearance(ShellAppearance appearance) + { + var tabBarBackgroundColor = ShellRenderer.DefaultBackgroundColor; + var tabBarForegroundColor = ShellRenderer.DefaultForegroundColor; + var titleColor = ShellRenderer.DefaultTitleColor; + if (appearance != null) + { + var a = (IShellAppearanceElement)appearance; + tabBarBackgroundColor = a.EffectiveTabBarBackgroundColor.ToWindowsColor(); + tabBarForegroundColor = a.EffectiveTabBarForegroundColor.ToWindowsColor(); + if (!appearance.TitleColor.IsDefault) + titleColor = appearance.TitleColor.ToWindowsColor(); + } + _BottomBarArea.Background = _HeaderArea.Background = + new SolidColorBrush(tabBarBackgroundColor); + _Title.Foreground = new SolidColorBrush(titleColor); + var tabbarForeground = new SolidColorBrush(tabBarForegroundColor); + foreach (var button in _BottomBar.Children.OfType()) + button.Foreground = tabbarForeground; + if (SectionRenderer is IAppearanceObserver iao) + iao.OnAppearanceChanged(appearance); + } + + #endregion + + ShellSection _shellSection; + + protected ShellSection ShellSection + { + get => _shellSection; + set + { + if (_shellSection == value) + return; + var oldValue = _shellSection; + if (_shellSection != null) + { + ((IShellSectionController)_shellSection).RemoveDisplayedPageObserver(this); + } + + _shellSection = value; + if (value != null) + { + OnShellSectionChanged(oldValue, value); + ((IShellSectionController)ShellSection).AddDisplayedPageObserver(this, UpdateDisplayedPage); + } + UpdateBottomBar(); + } + } + + void HookEvents(ShellItem shellItem) + { + shellItem.PropertyChanged += OnShellItemPropertyChanged; + ((INotifyCollectionChanged)shellItem.Items).CollectionChanged += OnShellItemsChanged; + foreach (var shellSection in shellItem.Items) + { + HookChildEvents(shellSection); + } + } + + protected virtual void UnhookEvents(ShellItem shellItem) + { + if (shellItem != null) + { + foreach (var shellSection in shellItem.Items) + { + UnhookChildEvents(shellSection); + } + ((INotifyCollectionChanged)shellItem.Items).CollectionChanged -= OnShellItemsChanged; + ShellItem.PropertyChanged -= OnShellItemPropertyChanged; + ShellSection = null; + ShellItem = null; + } + } + + void HookChildEvents(ShellSection shellSection) + { + ((IShellSectionController)shellSection).NavigationRequested += OnNavigationRequested; + } + + protected virtual void UnhookChildEvents(ShellSection shellSection) + { + ((IShellSectionController)shellSection).NavigationRequested -= OnNavigationRequested; + } + + protected virtual void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == ShellItem.CurrentItemProperty.PropertyName) + ShellSection = ShellItem.CurrentItem; + } + + protected virtual void OnShellItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (ShellSection shellSection in e.OldItems) + UnhookChildEvents(shellSection); + } + + if (e.NewItems != null) + { + foreach (ShellSection shellSection in e.NewItems) + HookChildEvents(shellSection); + } + UpdateBottomBar(); + } + + protected virtual void OnShellSectionChanged(ShellSection oldSection, ShellSection newSection) + { + SwitchSection(ShellNavigationSource.ShellSectionChanged, newSection, null, oldSection != null); + } + + void SwitchSection(ShellNavigationSource source, ShellSection section, Page page, bool animate = true) + { + SectionRenderer.NavigateToShellSection(source, section, animate); + } + + Page DisplayedPage { get; set; } + + void UpdateDisplayedPage(Page page) + { + if (DisplayedPage != null) + { + DisplayedPage.PropertyChanged -= OnPagePropertyChanged; + } + DisplayedPage = page; + if (DisplayedPage != null) + { + DisplayedPage.PropertyChanged += OnPagePropertyChanged; + } + UpdateBottomBarVisibility(); + UpdatePageTitle(); + UpdateToolbar(); + } + + void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Shell.TabBarIsVisibleProperty.PropertyName) + { + UpdateBottomBarVisibility(); + } + else if (e.PropertyName == Page.TitleProperty.PropertyName) + { + UpdatePageTitle(); + } + else if (e.PropertyName == Shell.NavBarIsVisibleProperty.PropertyName) + { + UpdateNavBarVisibility(); + } + } + + void UpdateNavBarVisibility() + { + if (DisplayedPage == null || Shell.GetNavBarIsVisible(DisplayedPage)) + { + _HeaderArea.Visibility = Visibility.Visible; + Shell.SetFlyoutBehavior(Shell.Current, Xamarin.Forms.FlyoutBehavior.Flyout); + } + else + { + _HeaderArea.Visibility = Visibility.Collapsed; + Shell.SetFlyoutBehavior(Shell.Current, Xamarin.Forms.FlyoutBehavior.Disabled); + } + } + + void UpdatePageTitle() + { + _Title.Text = DisplayedPage?.Title ?? ShellSection?.Title ?? ""; + } + + void UpdateBottomBarVisibility() + { + _BottomBar.Visibility = DisplayedPage == null || Shell.GetTabBarIsVisible(DisplayedPage) ? Visibility.Visible : Visibility.Collapsed; + } + + void UpdateToolbar() + { + _Toolbar.ItemsSource = DisplayedPage?.ToolbarItems; + } + + void OnNavigationRequested(object sender, NavigationRequestedEventArgs e) + { + SwitchSection((ShellNavigationSource)e.RequestType, (ShellSection)sender, e.Page, e.Animated); + } + + void IFlyoutBehaviorObserver.OnFlyoutBehaviorChanged(FlyoutBehavior behavior) + { + UpdateHeaderInsets(); + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellRenderer.cs new file mode 100644 index 00000000000..b5dd7848093 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellRenderer.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Windows.Foundation.Metadata; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; + +namespace Xamarin.Forms.Platform.UWP +{ + [Windows.UI.Xaml.Data.Bindable] + public class ShellRenderer : Microsoft.UI.Xaml.Controls.NavigationView, IVisualElementRenderer, IAppearanceObserver, IFlyoutBehaviorObserver + { + internal static readonly Windows.UI.Color DefaultBackgroundColor = Windows.UI.Color.FromArgb(255, 3, 169, 244); + internal static readonly Windows.UI.Color DefaultForegroundColor = Windows.UI.Colors.White; + internal static readonly Windows.UI.Color DefaultTitleColor = Windows.UI.Colors.White; + internal static readonly Windows.UI.Color DefaultUnselectedColor = Windows.UI.Color.FromArgb(180, 255, 255, 255); + const string TogglePaneButton = "TogglePaneButton"; + const string NavigationViewBackButton = "NavigationViewBackButton"; + + ShellItemRenderer ItemRenderer { get; } + + public ShellRenderer() + { + Xamarin.Forms.Shell.VerifyShellUWPFlagEnabled(nameof(ShellRenderer)); + IsBackEnabled = false; + IsBackButtonVisible = Microsoft.UI.Xaml.Controls.NavigationViewBackButtonVisible.Collapsed; + IsSettingsVisible = false; + PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.LeftMinimal; + IsPaneOpen = false; + Content = ItemRenderer = CreateShellItemRenderer(); + MenuItemTemplateSelector = CreateShellFlyoutTemplateSelector(); + if (ApiInformation.IsEventPresent("Windows.UI.Xaml.Controls.NavigationView", "PaneClosing")) + PaneClosing += (s, e) => OnPaneClosed(); + if (ApiInformation.IsEventPresent("Windows.UI.Xaml.Controls.NavigationView", "PaneOpening")) + PaneOpening += (s, e) => OnPaneOpening(); + ItemInvoked += OnMenuItemInvoked; + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + UpdatePaneButtonColor(TogglePaneButton, !IsPaneOpen); + UpdatePaneButtonColor(NavigationViewBackButton, !IsPaneOpen); + } + + void OnPaneOpening() + { + if (Shell != null) + Shell.FlyoutIsPresented = true; + UpdatePaneButtonColor(TogglePaneButton, false); + UpdatePaneButtonColor(NavigationViewBackButton, false); + } + + void OnPaneClosed() + { + if (Shell != null) + Shell.FlyoutIsPresented = false; + UpdatePaneButtonColor(TogglePaneButton, true); + UpdatePaneButtonColor(NavigationViewBackButton, true); + } + + void OnMenuItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var item = args.InvokedItemContainer?.DataContext as Element; + if (item != null) + ((IShellController)Element).OnFlyoutItemSelected(item); + } + + #region IVisualElementRenderer + + event EventHandler _elementChanged; + + event EventHandler IVisualElementRenderer.ElementChanged + { + add { _elementChanged += value; } + remove { _elementChanged -= value; } + } + + FrameworkElement IVisualElementRenderer.ContainerElement => this; + + VisualElement IVisualElementRenderer.Element => Element; + + SizeRequest IVisualElementRenderer.GetDesiredSize(double widthConstraint, double heightConstraint) + { + var constraint = new Windows.Foundation.Size(widthConstraint, heightConstraint); + + double oldWidth = Width; + double oldHeight = Height; + + Height = double.NaN; + Width = double.NaN; + + Measure(constraint); + var result = new Size(Math.Ceiling(DesiredSize.Width), Math.Ceiling(DesiredSize.Height)); + + Width = oldWidth; + Height = oldHeight; + + return new SizeRequest(result); + } + + public UIElement GetNativeElement() => null; + + public void Dispose() + { + SetElement(null); + } + + public void SetElement(VisualElement element) + { + if (Element != null && element != null) + throw new NotSupportedException("Reuse of the Shell Renderer is not supported"); + + if (element != null) + { + Element = (Shell)element; + Element.SizeChanged += OnElementSizeChanged; + OnElementSet(Element); + Element.PropertyChanged += OnElementPropertyChanged; + ItemRenderer.SetShellContext(this); + _elementChanged?.Invoke(this, new VisualElementChangedEventArgs(null, Element)); + } + else if(Element != null) + { + Element.SizeChanged -= OnElementSizeChanged; + Element.PropertyChanged -= OnElementPropertyChanged; + } + } + + #endregion IVisualElementRenderer + + protected internal Shell Element { get; set; } + + internal Shell Shell => Element; + + void OnElementSizeChanged(object sender, EventArgs e) + { + InvalidateMeasure(); + } + + protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Shell.CurrentItemProperty.PropertyName) + { + SwitchShellItem(Element.CurrentItem); + } + else if (e.PropertyName == Shell.FlyoutIsPresentedProperty.PropertyName) + { + IsPaneOpen = Shell.FlyoutIsPresented; + } + } + + protected virtual void OnElementSet(Shell shell) + { + var shr = CreateShellHeaderRenderer(shell); + PaneCustomContent = shr; + MenuItemsSource = IterateItems(); + SwitchShellItem(shell.CurrentItem, false); + IsPaneOpen = Shell.FlyoutIsPresented; + ((IShellController)Element).AddFlyoutBehaviorObserver(this); + ((IShellController)shell).AddAppearanceObserver(this, shell); + } + + IEnumerable IterateItems() + { + var groups = ((IShellController)Shell).GenerateFlyoutGrouping(); + foreach (var group in groups) + { + if (group.Count > 0 && group != groups[0]) + { + yield return null; // Creates a separator + } + foreach (var item in group) + { + yield return item; + } + } + } + + void SwitchShellItem(ShellItem newItem, bool animate = true) + { + SelectedItem = newItem; + ItemRenderer.NavigateToShellItem(newItem, animate); + } + + void UpdatePaneButtonColor(string name, bool overrideColor) + { + var toggleButton = GetTemplateChild(name) as Control; + if (toggleButton != null) + { + var titleBar = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TitleBar; + if (overrideColor) + toggleButton.Foreground = new SolidColorBrush(titleBar.ButtonForegroundColor.Value); + else + toggleButton.ClearValue(Control.ForegroundProperty); + } + } + + #region IAppearanceObserver + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) + { + Windows.UI.Color backgroundColor = DefaultBackgroundColor; + Windows.UI.Color titleColor = DefaultTitleColor; + if (appearance != null) + { + if (!appearance.BackgroundColor.IsDefault) + backgroundColor = appearance.BackgroundColor.ToWindowsColor(); + if (!appearance.TitleColor.IsDefault) + titleColor = appearance.TitleColor.ToWindowsColor(); + } + + var titleBar = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TitleBar; + titleBar.BackgroundColor = titleBar.ButtonBackgroundColor = backgroundColor; + titleBar.ForegroundColor = titleBar.ButtonForegroundColor = titleColor; + UpdatePaneButtonColor(TogglePaneButton, !IsPaneOpen); + UpdatePaneButtonColor(NavigationViewBackButton, !IsPaneOpen); + } + + #endregion IAppearanceObserver + + void IFlyoutBehaviorObserver.OnFlyoutBehaviorChanged(FlyoutBehavior behavior) + { + switch (behavior) + { + case FlyoutBehavior.Disabled: + IsPaneToggleButtonVisible = false; + IsPaneVisible = false; + PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.LeftMinimal; + IsPaneOpen = false; + break; + + case FlyoutBehavior.Flyout: + IsPaneVisible = true; + IsPaneToggleButtonVisible = true; + bool shouldOpen = Shell.FlyoutIsPresented; + PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.LeftMinimal; //This will trigger opening the flyout + IsPaneOpen = shouldOpen; + break; + + case FlyoutBehavior.Locked: + IsPaneVisible = true; + IsPaneToggleButtonVisible = false; + PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.Left; + break; + } + } + + public virtual ShellFlyoutTemplateSelector CreateShellFlyoutTemplateSelector() => new ShellFlyoutTemplateSelector(); + public virtual ShellHeaderRenderer CreateShellHeaderRenderer(Shell shell) => new ShellHeaderRenderer(shell); + public virtual ShellItemRenderer CreateShellItemRenderer() => new ShellItemRenderer(this); + public virtual ShellSectionRenderer CreateShellSectionRenderer() => new ShellSectionRenderer(); + } +} diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellSectionRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellSectionRenderer.cs new file mode 100644 index 00000000000..eb938423734 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellSectionRenderer.cs @@ -0,0 +1,277 @@ +using System; +using System.ComponentModel; +using Windows.Foundation.Metadata; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; + +namespace Xamarin.Forms.Platform.UWP +{ + // Renders the actual page area where the contents gets rendered, as well as set of optional top-bar menu items and search box. + [Windows.UI.Xaml.Data.Bindable] + public class ShellSectionRenderer : Microsoft.UI.Xaml.Controls.NavigationView, IAppearanceObserver + { + Windows.UI.Xaml.Controls.Frame Frame { get; } + Page Page; + ShellContent CurrentContent; + ShellSection ShellSection; + + public ShellSectionRenderer() + { + Xamarin.Forms.Shell.VerifyShellUWPFlagEnabled(nameof(ShellSectionRenderer)); + MenuItemTemplate = (Windows.UI.Xaml.DataTemplate)Windows.UI.Xaml.Application.Current.Resources["ShellSectionMenuItemTemplate"]; + IsBackButtonVisible = Microsoft.UI.Xaml.Controls.NavigationViewBackButtonVisible.Collapsed; + IsSettingsVisible = false; + AlwaysShowHeader = false; + PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.Top; + ItemInvoked += MenuItemInvoked; + + AutoSuggestBox = new Windows.UI.Xaml.Controls.AutoSuggestBox() { Width = 300 }; + AutoSuggestBox.TextChanged += SearchBox_TextChanged; + AutoSuggestBox.QuerySubmitted += SearchBox_QuerySubmitted; + AutoSuggestBox.SuggestionChosen += SearchBox_SuggestionChosen; + + Frame = new Windows.UI.Xaml.Controls.Frame(); + Content = Frame; + this.SizeChanged += ShellSectionRenderer_SizeChanged; + Resources["NavigationViewTopPaneBackground"] = new Windows.UI.Xaml.Media.SolidColorBrush(ShellRenderer.DefaultBackgroundColor); + Resources["TopNavigationViewItemForeground"] = new Windows.UI.Xaml.Media.SolidColorBrush(ShellRenderer.DefaultForegroundColor); + Resources["TopNavigationViewItemForegroundSelected"] = new Windows.UI.Xaml.Media.SolidColorBrush(ShellRenderer.DefaultForegroundColor); + Resources["NavigationViewSelectionIndicatorForeground"] = new Windows.UI.Xaml.Media.SolidColorBrush(ShellRenderer.DefaultForegroundColor); + } + + void ShellSectionRenderer_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e) + { + Page.ContainerArea = new Rectangle(0, 0, e.NewSize.Width, e.NewSize.Height); + } + + void MenuItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var shellContent = args.InvokedItemContainer?.DataContext as ShellContent; + var shellItem = ShellSection.RealParent as ShellItem; + + if(shellItem.RealParent is IShellController controller) + { + var result = controller.ProposeNavigation(ShellNavigationSource.Pop, shellItem, ShellSection, shellContent, null, true); + if (result) + { + ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent); + } + } + } + + internal void NavigateToShellSection(ShellNavigationSource source, ShellSection section, bool animate = true) + { + _ = section ?? throw new ArgumentNullException(nameof(section)); + + if (ShellSection != null) + { + ShellSection.PropertyChanged -= OnShellSectionPropertyChanged; + ((System.Collections.Specialized.INotifyCollectionChanged)section.Items).CollectionChanged -= OnShellSectionRendererCollectionChanged; + ShellSection = null; + MenuItemsSource = null; + } + + ShellSection = section; + ShellSection.PropertyChanged += OnShellSectionPropertyChanged; + SelectedItem = null; + IsPaneVisible = section.Items.Count > 1; + MenuItemsSource = section.Items; + ((System.Collections.Specialized.INotifyCollectionChanged)section.Items).CollectionChanged += OnShellSectionRendererCollectionChanged; + SelectedItem = section.CurrentItem; + NavigateToContent(source, section.CurrentItem, animate); + } + + void OnShellSectionRendererCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + // This shouldn't be necessary, but MenuItemsSource doesn't appear to be listening for INCC + // Revisit once using WinUI instead. + MenuItemsSource = null; + MenuItemsSource = ShellSection?.Items; + } + + void OnShellSectionPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == ShellSection.CurrentItemProperty.PropertyName) + { + NavigateToContent(ShellNavigationSource.ShellSectionChanged, ShellSection.CurrentItem); + } + } + + internal void NavigateToContent(ShellNavigationSource source, ShellContent shellContent, bool animate = true) + { + if (CurrentContent != null && Page != null) + { + Page.PropertyChanged -= OnPagePropertyChanged; + ((IShellContentController)CurrentContent).RecyclePage(Page); + } + CurrentContent = shellContent; + if (shellContent != null) + { + Page = ((IShellContentController)shellContent).GetOrCreateContent(); + Page.PropertyChanged += OnPagePropertyChanged; + + Frame.Navigate((ContentPage)Page, GetTransitionInfo(source)); + UpdateSearchHandler(Shell.GetSearchHandler(Page)); + } + } + + NavigationTransitionInfo GetTransitionInfo(ShellNavigationSource navSource) + { + switch (navSource) + { + case ShellNavigationSource.Push: + return new SlideNavigationTransitionInfo(); // { Effect = SlideNavigationTransitionEffect.FromRight }; Requires SDK 17763 + case ShellNavigationSource.Pop: + case ShellNavigationSource.PopToRoot: + return new SlideNavigationTransitionInfo(); // { Effect = SlideNavigationTransitionEffect.FromLeft }; Requires SDK 17763 + case ShellNavigationSource.ShellSectionChanged: + return null; + } + return null; + } + + void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Shell.SearchHandlerProperty.PropertyName) + { + UpdateSearchHandler(Shell.GetSearchHandler(Page)); + } + } + + #region Search + + SearchHandler _currentSearchHandler; + + void UpdateSearchHandler(SearchHandler searchHandler) + { + if (_currentSearchHandler != null) + { + _currentSearchHandler.PropertyChanged -= SearchHandler_PropertyChanged; + } + _currentSearchHandler = searchHandler; + if (AutoSuggestBox == null) + return; + if (searchHandler != null) + { + searchHandler.PropertyChanged += SearchHandler_PropertyChanged; + AutoSuggestBox.Visibility = searchHandler.SearchBoxVisibility == SearchBoxVisibility.Hidden ? Windows.UI.Xaml.Visibility.Collapsed : Windows.UI.Xaml.Visibility.Visible; + AutoSuggestBox.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Stretch; + AutoSuggestBox.PlaceholderText = searchHandler.Placeholder; + AutoSuggestBox.IsEnabled = searchHandler.IsSearchEnabled; + AutoSuggestBox.ItemsSource = _currentSearchHandler.ItemsSource; + ToggleSearchBoxVisibility(); + UpdateQueryIcon(); + IsPaneVisible = true; + } + else + { + IsPaneVisible = ShellSection.Items.Count > 1; + AutoSuggestBox.Visibility = Windows.UI.Xaml.Visibility.Collapsed; + } + } + + void SearchHandler_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (AutoSuggestBox == null) + return; + if (e.PropertyName == SearchHandler.PlaceholderProperty.PropertyName) + { + AutoSuggestBox.PlaceholderText = _currentSearchHandler.Placeholder; + } + else if (e.PropertyName == SearchHandler.IsSearchEnabledProperty.PropertyName) + { + AutoSuggestBox.IsEnabled = _currentSearchHandler.IsSearchEnabled; + } + else if (e.PropertyName == SearchHandler.ItemsSourceProperty.PropertyName) + { + AutoSuggestBox.ItemsSource = _currentSearchHandler.ItemsSource; + } + else if (e.PropertyName == SearchHandler.QueryProperty.PropertyName) + { + AutoSuggestBox.Text = _currentSearchHandler.Query; + } + else if (e.PropertyName == SearchHandler.SearchBoxVisibilityProperty.PropertyName) + { + ToggleSearchBoxVisibility(); + } + else if (e.PropertyName == SearchHandler.QueryIconProperty.PropertyName) + { + UpdateQueryIcon(); + } + } + + void ToggleSearchBoxVisibility() + { + AutoSuggestBox.Visibility = _currentSearchHandler == null || _currentSearchHandler.SearchBoxVisibility == SearchBoxVisibility.Hidden ? Windows.UI.Xaml.Visibility.Collapsed : Windows.UI.Xaml.Visibility.Visible; + if (_currentSearchHandler != null && _currentSearchHandler.SearchBoxVisibility != SearchBoxVisibility.Hidden) + { + if (_currentSearchHandler.SearchBoxVisibility == SearchBoxVisibility.Expanded) + { + // TODO: Expand search + } + else + { + // TODO: Collapse search + } + } + } + + void UpdateQueryIcon() + { + if (_currentSearchHandler != null) + { + if (_currentSearchHandler.QueryIcon is FileImageSource fis) + AutoSuggestBox.QueryIcon = new BitmapIcon() { UriSource = new Uri("ms-appx:///" + fis.File) }; + else + AutoSuggestBox.QueryIcon = new SymbolIcon(Symbol.Find); + } + } + + void SearchBox_TextChanged(Windows.UI.Xaml.Controls.AutoSuggestBox sender, Windows.UI.Xaml.Controls.AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason != Windows.UI.Xaml.Controls.AutoSuggestionBoxTextChangeReason.ProgrammaticChange) + _currentSearchHandler.Query = sender.Text; + } + + void SearchBox_SuggestionChosen(Windows.UI.Xaml.Controls.AutoSuggestBox sender, Windows.UI.Xaml.Controls.AutoSuggestBoxSuggestionChosenEventArgs args) + { + ((ISearchHandlerController)_currentSearchHandler).ItemSelected(args.SelectedItem); + } + + void SearchBox_QuerySubmitted(Windows.UI.Xaml.Controls.AutoSuggestBox sender, Windows.UI.Xaml.Controls.AutoSuggestBoxQuerySubmittedEventArgs args) + { + ((ISearchHandlerController)_currentSearchHandler).QueryConfirmed(); + } + + #endregion Search + + #region IAppearanceObserver + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) => UpdateAppearance(appearance); + + void UpdateAppearance(ShellAppearance appearance) + { + var tabBarBackgroundColor = ShellRenderer.DefaultBackgroundColor; + var tabBarForegroundColor = ShellRenderer.DefaultForegroundColor; + if (appearance != null) + { + var a = (IShellAppearanceElement)appearance; + tabBarBackgroundColor = a.EffectiveTabBarBackgroundColor.ToWindowsColor(); + tabBarForegroundColor = a.EffectiveTabBarForegroundColor.ToWindowsColor(); + } + + UpdateBrushColor("NavigationViewTopPaneBackground", tabBarBackgroundColor); + UpdateBrushColor("TopNavigationViewItemForeground", tabBarForegroundColor); + UpdateBrushColor("TopNavigationViewItemForegroundSelected", tabBarForegroundColor); + UpdateBrushColor("NavigationViewSelectionIndicatorForeground", tabBarForegroundColor); + } + + void UpdateBrushColor(string resourceKey, Windows.UI.Color color) + { + if (Resources[resourceKey] is Windows.UI.Xaml.Media.SolidColorBrush sb) + sb.Color = color; + } + + #endregion + } +} diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml b/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml new file mode 100644 index 00000000000..658a7c623d5 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs new file mode 100644 index 00000000000..7f96fcbdb35 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; + +namespace Xamarin.Forms.Platform.UWP +{ + public class ShellToolbarItemRenderer : Windows.UI.Xaml.Controls.Button + { + public ShellToolbarItemRenderer() + { + Xamarin.Forms.Shell.VerifyShellUWPFlagEnabled(nameof(ShellToolbarItemRenderer)); + Click += OnClick; + } + + void OnClick(object sender, RoutedEventArgs e) + { + if (ToolbarItem is IMenuItemController controller) + controller?.Activate(); + } + + public ToolbarItem ToolbarItem + { + get { return (ToolbarItem)GetValue(ToolbarItemProperty); } + set { SetValue(ToolbarItemProperty, value); } + } + + public static readonly DependencyProperty ToolbarItemProperty = + DependencyProperty.Register("ToolbarItem", typeof(ToolbarItem), typeof(ShellToolbarItemRenderer), new PropertyMetadata(null)); + } +} diff --git a/Xamarin.Forms.Platform.UAP/ViewToRendererConverter.cs b/Xamarin.Forms.Platform.UAP/ViewToRendererConverter.cs index d450d2c4a9b..cc8ca43bf95 100644 --- a/Xamarin.Forms.Platform.UAP/ViewToRendererConverter.cs +++ b/Xamarin.Forms.Platform.UAP/ViewToRendererConverter.cs @@ -37,12 +37,18 @@ internal class WrapperControl : Panel FrameworkElement FrameworkElement { get; } - internal void CleanUp() => _view?.Cleanup(); + internal void CleanUp() + { + _view?.Cleanup(); + + if(_view != null) + _view.MeasureInvalidated -= OnMeasureInvalidated; + } public WrapperControl(View view) { _view = view; - _view.MeasureInvalidated += (sender, args) => { InvalidateMeasure(); }; + _view.MeasureInvalidated += OnMeasureInvalidated; IVisualElementRenderer renderer = Platform.CreateRenderer(view); Platform.SetRenderer(view, renderer); @@ -64,6 +70,11 @@ public WrapperControl(View view) } } + void OnMeasureInvalidated(object sender, EventArgs e) + { + InvalidateMeasure(); + } + protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize) { _view.IsInNativeLayout = true; diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index a1afd3db506..961dafa16be 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -93,6 +93,12 @@ + + + + + + StepperControl.xaml @@ -241,6 +247,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + Designer MSBuild:Compile From 4f075d658e1f7ef94d9ef60e18c8328ec64f11bd Mon Sep 17 00:00:00 2001 From: Pavel Yakovlev Date: Wed, 18 Sep 2019 01:35:37 +0300 Subject: [PATCH 019/203] [C] added intellisense for Keyboard property (#7553) fixes azdo 835646 --- .../AttributeTableBuilder.cs | 8 +++ .../KeyboardDesignTypeConverter.cs | 55 +++++++++++++++++++ .../Xamarin.Forms.Core.Design.csproj | 1 + 3 files changed, 64 insertions(+) create mode 100644 Xamarin.Forms.Core.Design/KeyboardDesignTypeConverter.cs diff --git a/Xamarin.Forms.Core.Design/AttributeTableBuilder.cs b/Xamarin.Forms.Core.Design/AttributeTableBuilder.cs index f98b86e7598..e6cefb86a1e 100644 --- a/Xamarin.Forms.Core.Design/AttributeTableBuilder.cs +++ b/Xamarin.Forms.Core.Design/AttributeTableBuilder.cs @@ -43,6 +43,14 @@ public AttributeTableBuilder () "ItemsLayout", new System.ComponentModel.TypeConverterAttribute(typeof(ItemsLayoutDesignTypeConverter)))); + AddCallback(typeof(InputView), builder => builder.AddCustomAttributes( + nameof(Keyboard), + new System.ComponentModel.TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)))); + + AddCallback(typeof(EntryCell), builder => builder.AddCustomAttributes( + nameof(Keyboard), + new System.ComponentModel.TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)))); + // TODO: OnPlatform/OnIdiom // These two should be proper markup extensions, to follow WPF syntax for those. // That would allow us to turn on XAML validation, which otherwise fails. diff --git a/Xamarin.Forms.Core.Design/KeyboardDesignTypeConverter.cs b/Xamarin.Forms.Core.Design/KeyboardDesignTypeConverter.cs new file mode 100644 index 00000000000..aed4c929b10 --- /dev/null +++ b/Xamarin.Forms.Core.Design/KeyboardDesignTypeConverter.cs @@ -0,0 +1,55 @@ +namespace Xamarin.Forms.Core.Design +{ + using System.Linq; + using System.ComponentModel; + using System; + using System.Reflection; + + public class KeyboardDesignTypeConverter : TypeConverter + { + public KeyboardDesignTypeConverter() + { + } + + protected StandardValuesCollection Values + { + get; + set; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + // This tells XAML this converter can be used to process strings + // Without this the values won't show up as hints + if (sourceType == typeof(string)) + return true; + + return base.CanConvertFrom(context, sourceType); + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + if (Values == null) + { + var props = typeof(Keyboard) + .GetProperties(BindingFlags.Static | BindingFlags.Public) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToArray(); + Values = new StandardValuesCollection(props); + } + + return Values; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return false; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Core.Design/Xamarin.Forms.Core.Design.csproj b/Xamarin.Forms.Core.Design/Xamarin.Forms.Core.Design.csproj index 18e9fe52b5b..ba1e5c41057 100644 --- a/Xamarin.Forms.Core.Design/Xamarin.Forms.Core.Design.csproj +++ b/Xamarin.Forms.Core.Design/Xamarin.Forms.Core.Design.csproj @@ -73,6 +73,7 @@ + From f417a8743e97187e4f3eead279285fedc652af6b Mon Sep 17 00:00:00 2001 From: Pavel Yakovlev Date: Wed, 18 Sep 2019 01:50:33 +0300 Subject: [PATCH 020/203] removed MatchParent value from Property Editor (#7551) --- Xamarin.Forms.Core/Visuals/VisualMarker.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Xamarin.Forms.Core/Visuals/VisualMarker.cs b/Xamarin.Forms.Core/Visuals/VisualMarker.cs index 8d220c5c6e0..c7802520b07 100644 --- a/Xamarin.Forms.Core/Visuals/VisualMarker.cs +++ b/Xamarin.Forms.Core/Visuals/VisualMarker.cs @@ -1,10 +1,13 @@ -namespace Xamarin.Forms +using System.ComponentModel; + +namespace Xamarin.Forms { public static class VisualMarker { static bool _isMaterialRegistered = false; static bool _warnedAboutMaterial = false; + [EditorBrowsable(EditorBrowsableState.Never)] public static IVisual MatchParent { get; } = new MatchParentVisual(); public static IVisual Default { get; } = new DefaultVisual(); public static IVisual Material { get; } = new MaterialVisual(); From 66b32f6f04d4e9b09894c9534f3f3487cc5df2d1 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Wed, 18 Sep 2019 16:41:18 +0200 Subject: [PATCH 021/203] [X] avoid throwing in VisualDiagnostics (#7571) this fixes a regression introduced in #7474 - fixes #7570 --- Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs | 18 ++++++++++++------ .../Diagnostics/DebuggerHelper.cs | 4 ++++ .../Diagnostics/VisualDiagnostics.cs | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs index 099ad3535ae..087409d8cd2 100644 --- a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs +++ b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs @@ -346,22 +346,28 @@ public static void SetPropertyValue(object xamlelement, XmlName propertyName, ob //If it's a BindableProberty, SetValue if (xpe == null && TrySetValue(xamlelement, property, attached, value, lineInfo, serviceProvider, out xpe)) { - VisualDiagnostics.RegisterSourceInfo(value, null, ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); - VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) { + VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + } return; } //If we can assign that value to a normal property, let's do it if (xpe == null && TrySetProperty(xamlelement, localName, value, lineInfo, serviceProvider, context, out xpe)) { - VisualDiagnostics.RegisterSourceInfo(value, null, ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); - VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) { + VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + } return; } //If it's an already initialized property, add to it if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, xKey, lineInfo, serviceProvider, context, out xpe)) { - VisualDiagnostics.RegisterSourceInfo(value, null, ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); - VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) { + VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.SendVisualTreeChanged(xamlelement, value); + } return; } diff --git a/Xamarin.Forms.Xaml/Diagnostics/DebuggerHelper.cs b/Xamarin.Forms.Xaml/Diagnostics/DebuggerHelper.cs index f25bfb0d8e6..c83585efc18 100644 --- a/Xamarin.Forms.Xaml/Diagnostics/DebuggerHelper.cs +++ b/Xamarin.Forms.Xaml/Diagnostics/DebuggerHelper.cs @@ -7,6 +7,10 @@ namespace Xamarin.Forms.Xaml.Diagnostics { static class DebuggerHelper { +#if DEBUG + static DebuggerHelper() => _mockDebuggerIsAttached = true; +#endif + internal static bool _mockDebuggerIsAttached; public static bool DebuggerIsAttached => _mockDebuggerIsAttached || Debugger.IsAttached; } diff --git a/Xamarin.Forms.Xaml/Diagnostics/VisualDiagnostics.cs b/Xamarin.Forms.Xaml/Diagnostics/VisualDiagnostics.cs index 42c6d689887..6fdeec11b3d 100644 --- a/Xamarin.Forms.Xaml/Diagnostics/VisualDiagnostics.cs +++ b/Xamarin.Forms.Xaml/Diagnostics/VisualDiagnostics.cs @@ -12,7 +12,7 @@ class VisualDiagnostics static ConditionalWeakTable sourceInfos = new ConditionalWeakTable(); internal static void RegisterSourceInfo(object target, Uri uri, int lineNumber, int linePosition) { - if (DebuggerHelper.DebuggerIsAttached) + if (DebuggerHelper.DebuggerIsAttached && !sourceInfos.TryGetValue(target, out _)) sourceInfos.Add(target, new XamlSourceInfo(uri, lineNumber, linePosition)); } From 23a6afdac3e0ff3fde1a0769fd95d40b569df374 Mon Sep 17 00:00:00 2001 From: Chris King Date: Wed, 18 Sep 2019 09:29:51 -1000 Subject: [PATCH 022/203] Update dot file --- .create-stubs.bat | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.create-stubs.bat b/.create-stubs.bat index dfb826aea6c..4555ef14c3d 100644 --- a/.create-stubs.bat +++ b/.create-stubs.bat @@ -146,6 +146,10 @@ echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\FormsEmbeddedPageWrapper.xbf echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\StepperControl.xbf echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\FormsCheckBoxStyle.xbf +mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml +mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml\DensityStyles +echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml\DensityStyles\Compact.xbf + mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\CollectionView\ echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\CollectionView\ItemsViewStyles.xbf From 62b8a554260976d7e2f7c10d8071194148b7620b Mon Sep 17 00:00:00 2001 From: Chris King Date: Wed, 18 Sep 2019 09:45:53 -1000 Subject: [PATCH 023/203] Update dot file --- .create-stubs.bat | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.create-stubs.bat b/.create-stubs.bat index 4555ef14c3d..9b31164cb9f 100644 --- a/.create-stubs.bat +++ b/.create-stubs.bat @@ -150,6 +150,9 @@ mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml\DensityStyles echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Microsoft.UI.Xaml\DensityStyles\Compact.xbf +mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Shell +echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\Shell\ShellStyles.xbf + mkdir Xamarin.Forms.Platform.UAP\bin\%CONFIG%\CollectionView\ echo foo > Xamarin.Forms.Platform.UAP\bin\%CONFIG%\CollectionView\ItemsViewStyles.xbf From 9c5e6f256e101f8662bcd95871472fae697522ce Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Thu, 19 Sep 2019 12:33:32 +0200 Subject: [PATCH 024/203] [C] Fix TemplateBinding for non-logicalchildren (#7506) Spans, e.g., are no LogicalChildren of FormattedString, which is not a LogicalChildren of Label. The newly introduced way of propagating the TemplatedParent was top-down, using LogicalChildren, was not assigning the correct TemplatedParent on Spans. This reverts to a bottom-up lookup, hooking on ParentSet, like it used to be. As a side effect, the TemplateBindings are no longer updated on reparenting, but this was the original behavior, and I can't think of a case where this would be needed. The regression to `{TemplateBinding}` was introduced by #4375. - fixes #7494 --- .../RelativeSourceBindingTests.cs | 34 ----------- Xamarin.Forms.Core/Binding.cs | 12 ++-- Xamarin.Forms.Core/BindingExpression.cs | 58 +++---------------- Xamarin.Forms.Core/Element.cs | 52 ++--------------- Xamarin.Forms.Core/FormattedString.cs | 56 ++++-------------- .../Issues/Gh7494.xaml | 24 ++++++++ .../Issues/Gh7494.xaml.cs | 49 ++++++++++++++++ 7 files changed, 104 insertions(+), 181 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs diff --git a/Xamarin.Forms.Core.UnitTests/RelativeSourceBindingTests.cs b/Xamarin.Forms.Core.UnitTests/RelativeSourceBindingTests.cs index 976139a9ab7..babb21431c5 100644 --- a/Xamarin.Forms.Core.UnitTests/RelativeSourceBindingTests.cs +++ b/Xamarin.Forms.Core.UnitTests/RelativeSourceBindingTests.cs @@ -130,40 +130,6 @@ public void RelativeSourceAncestorTypeBinding() Assert.AreEqual(label1.Text, stack2.StyleId); Assert.AreEqual(label1.BindingContext, "foobar"); } - - [Test] - public void RelativeSourceTemplatedParentBinding() - { - var cc1 = new CustomControl - { - CustomText = "RelativeSource Binding 1!", - ControlTemplate = new ControlTemplate(typeof(MyCustomControlTemplate)) - }; - var cc2 = new CustomControl - { - CustomText = "RelativeSource Binding 2!", - ControlTemplate = new ControlTemplate(typeof(MyCustomControlTemplate)) - }; - - var realLabel = cc1.LogicalChildren[0].LogicalChildren[0] as Label; - Assert.AreEqual(cc1.CustomText, realLabel.Text); - Assert.AreEqual(realLabel.TemplatedParent, cc1); - - // Test reparenting - int templatedParentChangeCount = 0; - realLabel.PropertyChanged += (sender, e) => - { - if (e.PropertyName == nameof(Element.TemplatedParent)) - templatedParentChangeCount++; - }; - ((StackLayout)realLabel.Parent).Children.Remove(realLabel); - Assert.IsNull(realLabel.TemplatedParent); - Assert.IsNull(realLabel.Text); - ((StackLayout)cc2.LogicalChildren[0]).Children.Add(realLabel); - Assert.AreEqual(realLabel.TemplatedParent, cc2); - Assert.AreEqual(cc2.CustomText, realLabel.Text); - Assert.AreEqual(2, templatedParentChangeCount); - } } public class CustomControl : ContentView diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs index 86f2e8e36b4..f1a1170d898 100644 --- a/Xamarin.Forms.Core/Binding.cs +++ b/Xamarin.Forms.Core/Binding.cs @@ -83,7 +83,7 @@ public object Source ThrowIfApplied(); _source = value; if ((value as RelativeBindingSource)?.Mode == RelativeBindingSourceMode.TemplatedParent) - this.AllowChaining = true; + AllowChaining = true; } } @@ -137,17 +137,18 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro } } - void ApplyRelativeSourceBinding( +#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void + async void ApplyRelativeSourceBinding( BindableObject targetObject, BindableProperty targetProperty) +#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void { if (!(targetObject is Element elem)) throw new InvalidOperationException(); if (!(Source is RelativeBindingSource relativeSource)) return; - object resolvedSource = null; - + object resolvedSource; switch (relativeSource.Mode) { case RelativeBindingSourceMode.Self: @@ -155,8 +156,7 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro break; case RelativeBindingSourceMode.TemplatedParent: - _expression.SubscribeToTemplatedParentChanges(elem, targetProperty); - resolvedSource = elem.TemplatedParent; + resolvedSource = await TemplateUtilities.FindTemplatedParentAsync(elem); break; case RelativeBindingSourceMode.FindAncestor: diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs index e1a8093c1a0..5793dc0bccf 100644 --- a/Xamarin.Forms.Core/BindingExpression.cs +++ b/Xamarin.Forms.Core/BindingExpression.cs @@ -16,7 +16,6 @@ internal class BindingExpression readonly List _parts = new List(); - bool _trackingTemplatedParent; BindableProperty _targetProperty; WeakReference _weakSource; WeakReference _weakTarget; @@ -24,13 +23,8 @@ internal class BindingExpression internal BindingExpression(BindingBase binding, string path) { - if (binding == null) - throw new ArgumentNullException(nameof(binding)); - if (path == null) - throw new ArgumentNullException(nameof(path)); - - Binding = binding; - Path = path; + Binding = binding ?? throw new ArgumentNullException(nameof(binding)); + Path = path ?? throw new ArgumentNullException(nameof(path)); ParsePath(); } @@ -47,15 +41,13 @@ internal void Apply(bool fromTarget = false) if (_weakSource == null || _weakTarget == null) return; - BindableObject target; - if (!_weakTarget.TryGetTarget(out target)) + if (!_weakTarget.TryGetTarget(out BindableObject target)) { Unapply(); return; } - object source; - if (_weakSource.TryGetTarget(out source) && _targetProperty != null) + if (_weakSource.TryGetTarget(out var source) && _targetProperty != null) ApplyCore(source, target, _targetProperty, fromTarget); } @@ -66,12 +58,10 @@ internal void Apply(object sourceObject, BindableObject target, BindableProperty { _targetProperty = property; - BindableObject prevTarget; - if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target)) + if (_weakTarget != null && _weakTarget.TryGetTarget(out BindableObject prevTarget) && !ReferenceEquals(prevTarget, target)) throw new InvalidOperationException("Binding instances can not be reused"); - object previousSource; - if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject)) + if (_weakSource != null && _weakSource.TryGetTarget(out var previousSource) && !ReferenceEquals(previousSource, sourceObject)) throw new InvalidOperationException("Binding instances can not be reused"); _weakSource = new WeakReference(sourceObject); @@ -82,29 +72,19 @@ internal void Apply(object sourceObject, BindableObject target, BindableProperty internal void Unapply() { - object sourceObject; - if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject)) + if (_weakSource != null && _weakSource.TryGetTarget(out var sourceObject)) { for (var i = 0; i < _parts.Count - 1; i++) { BindingExpressionPart part = _parts[i]; if (!part.IsSelf) - { part.TryGetValue(sourceObject, out sourceObject); - } part.Unsubscribe(); } } - if (_trackingTemplatedParent) - { - BindableObject target = null; - if (_weakTarget?.TryGetTarget(out target) == true && target is Element elem) - elem.TemplatedParentChanged -= OnTargetTemplatedParentChanged; - } - _weakSource = null; _weakTarget = null; @@ -124,7 +104,6 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource); object current = sourceObject; - object previous = null; BindingExpressionPart part = null; for (var i = 0; i < _parts.Count; i++) @@ -154,8 +133,6 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop if (part.NextPart != null && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) && current is INotifyPropertyChanged inpc) part.Subscribe(inpc); - - previous = current; } Debug.Assert(part != null, "There should always be at least the self part in the expression."); @@ -407,7 +384,6 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) } } #if !NETSTANDARD1_0 - TupleElementNamesAttribute tupleEltNames; if ( property != null && part.NextPart != null && property.PropertyType.IsGenericType @@ -419,7 +395,7 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>) || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>) || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>)) - && (tupleEltNames = property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) as TupleElementNamesAttribute) != null) + && property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) is TupleElementNamesAttribute tupleEltNames) { //modify the nextPart to access the tuple item via the ITuple indexer var nextPart = part.NextPart; @@ -435,7 +411,7 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) } } - static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) }; + static readonly Type[] DecimalTypes = { typeof(float), typeof(decimal), typeof(double) }; internal static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget) { @@ -471,22 +447,6 @@ internal static bool TryConvert(ref object value, BindableProperty targetPropert } } - internal void SubscribeToTemplatedParentChanges(Element target, BindableProperty targetProperty) - { - _targetProperty = targetProperty; - target.TemplatedParentChanged += OnTargetTemplatedParentChanged; - _trackingTemplatedParent = true; - } - - void OnTargetTemplatedParentChanged(object sender, EventArgs e) - { - if (!(sender is Element elem) || - !(this.Binding is Binding binding)) - return; - binding.Unapply(); - binding.Apply(null, elem, _targetProperty); - } - // SubscribeToAncestryChanges, ClearAncestryChangeSubscriptions, FindAncestryIndex, and // OnElementParentSet are used with RelativeSource ancestor-type bindings, to detect when // there has been an ancestry change requiring re-applying the binding, and to minimize diff --git a/Xamarin.Forms.Core/Element.cs b/Xamarin.Forms.Core/Element.cs index ca812b92b36..b96b0a5fe12 100644 --- a/Xamarin.Forms.Core/Element.cs +++ b/Xamarin.Forms.Core/Element.cs @@ -12,15 +12,8 @@ public abstract partial class Element : BindableObject, IElement, INameScope, IE { public static readonly BindableProperty MenuProperty = BindableProperty.CreateAttached(nameof(Menu), typeof(Menu), typeof(Element), null); - public static Menu GetMenu(BindableObject bindable) - { - return (Menu)bindable.GetValue(MenuProperty); - } - - public static void SetMenu(BindableObject bindable, Menu menu) - { - bindable.SetValue(MenuProperty, menu); - } + public static Menu GetMenu(BindableObject bindable) => (Menu)bindable.GetValue(MenuProperty); + public static void SetMenu(BindableObject bindable, Menu menu) => bindable.SetValue(MenuProperty, menu); internal static readonly ReadOnlyCollection EmptyChildren = new ReadOnlyCollection(new Element[0]); @@ -59,8 +52,8 @@ public string AutomationId public string ClassId { - get { return (string)GetValue(ClassIdProperty); } - set { SetValue(ClassIdProperty, value); } + get => (string)GetValue(ClassIdProperty); + set => SetValue(ClassIdProperty, value); } public IList Effects @@ -164,10 +157,7 @@ internal Element ParentOverride [EditorBrowsable(EditorBrowsableState.Never)] public Element RealParent { get; private set; } - Dictionary DynamicResources - { - get { return _dynamicResources ?? (_dynamicResources = new Dictionary()); } - } + Dictionary DynamicResources => _dynamicResources ?? (_dynamicResources = new Dictionary()); void IElement.AddResourcesChangedListener(Action onchanged) { @@ -213,38 +203,6 @@ public Element Parent OnParentSet(); OnPropertyChanged(); - - RefreshTemplatedParent(); - } - } - - internal event EventHandler TemplatedParentChanged; - - BindableObject _templatedParent; - public BindableObject TemplatedParent - { - get => _templatedParent; - private set - { - _templatedParent = value; - TemplatedParentChanged?.Invoke(this, null); - OnPropertyChanged(); - } - } - - void RefreshTemplatedParent() - { - var templatedParent = this.IsTemplateRoot - ? this.Parent - : this.Parent?.TemplatedParent; - if (ReferenceEquals(templatedParent, this.TemplatedParent)) - return; - - this.TemplatedParent = templatedParent; - foreach (var element in this.LogicalChildren) - { - if (!element.IsTemplateRoot) - element.RefreshTemplatedParent(); } } diff --git a/Xamarin.Forms.Core/FormattedString.cs b/Xamarin.Forms.Core/FormattedString.cs index 801ac21fedc..657b0239eac 100644 --- a/Xamarin.Forms.Core/FormattedString.cs +++ b/Xamarin.Forms.Core/FormattedString.cs @@ -13,10 +13,7 @@ public class FormattedString : Element readonly SpanCollection _spans = new SpanCollection(); internal event NotifyCollectionChangedEventHandler SpansCollectionChanged; - public FormattedString() - { - _spans.CollectionChanged += OnCollectionChanged; - } + public FormattedString() => _spans.CollectionChanged += OnCollectionChanged; protected override void OnBindingContextChanged() { @@ -25,25 +22,13 @@ protected override void OnBindingContextChanged() SetInheritedBindingContext(Spans[i], BindingContext); } - public IList Spans - { - get { return _spans; } - } - - public static explicit operator string(FormattedString formatted) - { - return formatted.ToString(); - } + public IList Spans => _spans; - public static implicit operator FormattedString(string text) - { - return new FormattedString { Spans = { new Span { Text = text } } }; - } + public static explicit operator string(FormattedString formatted) => formatted.ToString(); - public override string ToString() - { - return string.Concat(Spans.Select(span => span.Text)); - } + public static implicit operator FormattedString(string text) => new FormattedString { Spans = { new Span { Text = text } } }; + + public override string ToString() => string.Concat(Spans.Select(span => span.Text)); void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { @@ -81,37 +66,18 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) SpansCollectionChanged?.Invoke(sender, e); } - void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) - { - OnPropertyChanged(nameof(Spans)); - } + void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) => OnPropertyChanged(nameof(Spans)); - void OnItemPropertyChanging(object sender, PropertyChangingEventArgs e) - { - OnPropertyChanging(nameof(Spans)); - } + void OnItemPropertyChanging(object sender, PropertyChangingEventArgs e) => OnPropertyChanging(nameof(Spans)); class SpanCollection : ObservableCollection { - protected override void InsertItem(int index, Span item) - { - if (item == null) - throw new ArgumentNullException("item"); - - base.InsertItem(index, item); - } - - protected override void SetItem(int index, Span item) - { - if (item == null) - throw new ArgumentNullException("item"); - - base.SetItem(index, item); - } + protected override void InsertItem(int index, Span item) => base.InsertItem(index, item ?? throw new ArgumentNullException(nameof(item))); + protected override void SetItem(int index, Span item) => base.SetItem(index, item ?? throw new ArgumentNullException(nameof(item))); protected override void ClearItems() { - List removed = new List(this); + var removed = new List(this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml new file mode 100644 index 00000000000..f50587e2642 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs new file mode 100644 index 00000000000..46ca4d5b5a8 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.Forms; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public partial class Gh7494 : ContentPage + { + public Gh7494() + { + InitializeComponent(); + } + public Gh7494(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; + + [Test] + public void TemplateBindingInSpans([Values(false, true)]bool useCompiledXaml) + { + var layout = new Gh7494(useCompiledXaml); + var view = layout.Content as Gh7494Content; + var templatedLabel = ((StackLayout)view.Children[0]).Children[0] as Label; + + Assert.That(templatedLabel.FormattedText.Spans[0].Text, Is.EqualTo(view.Title)); + } + } + } + + public class Gh7494Content : ContentView + { + public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Gh7494Content), default(string)); + + public string Title { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + } +} From 1f5f9348c51e85bc2ee6e263da46457e81c917f7 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Thu, 19 Sep 2019 15:58:29 +0200 Subject: [PATCH 025/203] [X] do not fail on generic BP (#7588) While looking for a potential TypeConverter attribute on the static getter of a generic attached BP, resolve the generic return type. - fixes #7559 --- .../BindablePropertyReferenceExtensions.cs | 7 +-- .../Issues/Gh7494.xaml.cs | 5 +- .../Issues/Gh7559.xaml | 8 +++ .../Issues/Gh7559.xaml.cs | 60 +++++++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml.cs diff --git a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs index 641232b958a..082e5940fdb 100644 --- a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs @@ -34,10 +34,9 @@ public static TypeReference GetBindablePropertyType(this FieldReference bpRef, I public static TypeReference GetBindablePropertyTypeConverter(this FieldReference bpRef, ModuleDefinition module) { - TypeReference propertyDeclaringType; var owner = bpRef.DeclaringType; var bpName = bpRef.Name.EndsWith("Property", StringComparison.Ordinal) ? bpRef.Name.Substring(0, bpRef.Name.Length - 8) : bpRef.Name; - var property = owner.GetProperty(pd => pd.Name == bpName, out propertyDeclaringType); + var property = owner.GetProperty(pd => pd.Name == bpName, out TypeReference propertyDeclaringType); var propertyType = property?.PropertyType?.ResolveGenericParameters(propertyDeclaringType); var staticGetter = owner.GetMethods(md => md.Name == $"Get{bpName}" && md.IsStatic && @@ -52,8 +51,8 @@ public static TypeReference GetBindablePropertyTypeConverter(this FieldReference attributes.AddRange(propertyType.ResolveCached().CustomAttributes); if (staticGetter != null && staticGetter.HasCustomAttributes) attributes.AddRange(staticGetter.CustomAttributes); - if (staticGetter != null && staticGetter.ReturnType.ResolveCached().HasCustomAttributes) - attributes.AddRange(staticGetter.ReturnType.ResolveCached().CustomAttributes); + if (staticGetter != null && staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached().HasCustomAttributes) + attributes.AddRange(staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached().CustomAttributes); return attributes.FirstOrDefault(cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName))?.ConstructorArguments [0].Value as TypeReference; } diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs index 46ca4d5b5a8..1f07ba80a1f 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7494.xaml.cs @@ -10,10 +10,7 @@ namespace Xamarin.Forms.Xaml.UnitTests { public partial class Gh7494 : ContentPage { - public Gh7494() - { - InitializeComponent(); - } + public Gh7494() => InitializeComponent(); public Gh7494(bool useCompiledXaml) { //this stub will be replaced at compile time diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml new file mode 100644 index 00000000000..e5b3ed97f63 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml @@ -0,0 +1,8 @@ + + + diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml.cs new file mode 100644 index 00000000000..28b609a992f --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7559.xaml.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.Forms; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public partial class Gh7559 : ContentPage + { + public Gh7559() => InitializeComponent(); + public Gh7559(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; + + [Test] + public void GenericBPCompiles([Values(false, true)]bool useCompiledXaml) + { + if (useCompiledXaml) + MockCompiler.Compile(typeof(Gh7559)); + var layout = new Gh7559(useCompiledXaml); + var value = Gh7559Generic.GetIcon(layout); + Assert.That(value, Is.EqualTo(Gh7559Enum.LetterA)); + } + } + } + + public abstract class Gh7559Generic + { + public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(T), typeof(Gh7559Generic), default(T)); + + public static T GetIcon(BindableObject bindable) + { + return (T)bindable.GetValue(IconProperty); + } + + public static void SetIcon(BindableObject bindable, T value) + { + bindable.SetValue(IconProperty, value); + } + } + + public enum Gh7559Enum + { + LetterX = 'X', + LetterA = 'A', + } + + public class Gh7559A : Gh7559Generic + { } +} From 69e0685fdebb8f9964a74e34de9a3a810aa95d00 Mon Sep 17 00:00:00 2001 From: adrianknight89 Date: Fri, 20 Sep 2019 11:57:05 -0500 Subject: [PATCH 026/203] [Android] Fix ItemSpacing for last item in CollectionView (#7516) * fix item spacing * Update Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems Co-Authored-By: Gerald Versluis --- .../Issue7357.xaml | 35 ++++++ .../Issue7357.xaml.cs | 118 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 12 ++ .../CollectionView/SpacingItemDecoration.cs | 8 +- 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml new file mode 100644 index 00000000000..89f33052cc9 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml.cs new file mode 100644 index 00000000000..59d421c9944 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7357.xaml.cs @@ -0,0 +1,118 @@ +using System.Collections.ObjectModel; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using System; +using System.Security.Cryptography; +using Xamarin.Forms.Xaml; +using System.Threading; +using System.Collections.Generic; +using System.Threading.Tasks; + +#if UITEST +using Xamarin.UITest; +using Xamarin.UITest.Queries; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +using System.Linq; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7357, "[Android] Setting ItemSpacing creates spacing for the last item in CollectionView", PlatformAffected.Android)] + public partial class Issue7357 : TestContentPage + { +#if APP + SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + bool _isUpdatingSource; + + public Issue7357() + { + Device.SetFlags(new List { CollectionView.CollectionViewExperimental }); + + InitializeComponent(); + + (CollectionView7357.ItemsLayout as LinearItemsLayout).ItemSpacing = 5; + + BindingContext = new ViewModel7357(); + } + + async void CollectionView_RemainingItemsThresholdReached(object sender, System.EventArgs e) + { + if (_isUpdatingSource) + return; + + await _semaphoreSlim.WaitAsync(); + + try + { + _isUpdatingSource = true; + + var itemsSource = ((sender as CollectionView).ItemsSource as ObservableCollection); + var count = itemsSource.Count; + + if (count == 100) + return; + + for (var i = count; i < count + 50; i++) + { + var model = new Model7357 { Text = "Item " + i }; + + if (i == 99) + model.BackgroundColor = Color.Pink; + + itemsSource.Add(model); + } + } + finally + { + _semaphoreSlim.Release(); + _isUpdatingSource = false; + } + } +#endif + + protected override void Init() + { + + } + } + + [Preserve(AllMembers = true)] + public class ViewModel7357 + { + public ObservableCollection ItemsSource { get; set; } = new ObservableCollection(); + + public ViewModel7357() + { + for (var i = 0; i < 50; i++) + { + var model = new Model7357 { Text = "Item " + i }; + + if (i == 49) + model.BackgroundColor = Color.Pink; + + ItemsSource.Add(model); + } + } + } + + [Preserve(AllMembers = true)] + public class Model7357 + { + public string Text { get; set; } + + public Color BackgroundColor { get; set; } = Color.Beige; + + public Model7357() + { + + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 652aab78103..03acc40b347 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -36,6 +36,9 @@ + + Code + Code @@ -1359,6 +1362,9 @@ Issue6254.xaml + + Issue7357.xaml + Issue7519Xaml.xaml @@ -1387,6 +1393,12 @@ MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:Compile + + Designer diff --git a/Xamarin.Forms.Platform.Android/CollectionView/SpacingItemDecoration.cs b/Xamarin.Forms.Platform.Android/CollectionView/SpacingItemDecoration.cs index 6a8644ffa82..919ce83e4ae 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/SpacingItemDecoration.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/SpacingItemDecoration.cs @@ -74,13 +74,17 @@ public override void GetItemOffsets(Rect outRect, AView view, RecyclerView paren if (_orientation == ItemsLayoutOrientation.Vertical) { outRect.Left = spanIndex == 0 ? 0 : (int)_adjustedHorizontalSpacing; - outRect.Bottom = (int)_adjustedVerticalSpacing; + + if (parent.GetChildAdapterPosition(view) != parent.GetAdapter().ItemCount - 1) + outRect.Bottom = (int)_adjustedVerticalSpacing; } if (_orientation == ItemsLayoutOrientation.Horizontal) { outRect.Top = spanIndex == 0 ? 0 : (int)_adjustedVerticalSpacing; - outRect.Right = (int)_adjustedHorizontalSpacing; + + if (parent.GetChildAdapterPosition(view) != parent.GetAdapter().ItemCount - 1) + outRect.Right = (int)_adjustedHorizontalSpacing; } } } From 025e1694d8b461ac6253bd891fba0a2ca44cdac9 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Fri, 20 Sep 2019 11:37:15 -0600 Subject: [PATCH 027/203] Make sure transition from empty CollectionView measures cell size; fixes #7472 (#7580) fixes #7472 fixes #7548 --- .../CollectionViewGallery.cs | 41 ++++++++++--------- .../CollectionView/ItemsViewController.cs | 8 ++++ .../CollectionView/ObservableItemsSource.cs | 22 ++++++---- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGallery.cs index 6e2e5d8a40e..be9b989579f 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGallery.cs @@ -14,26 +14,29 @@ public class CollectionViewGallery : ContentPage { public CollectionViewGallery() { - Content = new StackLayout + Content = new ScrollView { - Children = - { - new Button { Text ="Enable CollectionView", AutomationId = "EnableCollectionView", Command = new Command(() => Device.SetFlags(new[] { ExperimentalFlags.CollectionViewExperimental })) }, - GalleryBuilder.NavButton("Default Text Galleries", () => new DefaultTextGallery(), Navigation), - GalleryBuilder.NavButton("DataTemplate Galleries", () => new DataTemplateGallery(), Navigation), - GalleryBuilder.NavButton("Observable Collection Galleries", () => new ObservableCollectionGallery(), Navigation), - GalleryBuilder.NavButton("Snap Points Galleries", () => new SnapPointsGallery(), Navigation), - GalleryBuilder.NavButton("ScrollTo Galleries", () => new ScrollToGallery(), Navigation), - GalleryBuilder.NavButton("CarouselView Galleries", () => new CarouselViewGallery(), Navigation), - GalleryBuilder.NavButton("EmptyView Galleries", () => new EmptyViewGallery(), Navigation), - GalleryBuilder.NavButton("Selection Galleries", () => new SelectionGallery(), Navigation), - GalleryBuilder.NavButton("Propagation Galleries", () => new PropagationGallery(), Navigation), - GalleryBuilder.NavButton("Grouping Galleries", () => new GroupingGallery(), Navigation), - GalleryBuilder.NavButton("Item Size Galleries", () => new ItemsSizeGallery(), Navigation), - GalleryBuilder.NavButton("Scroll Mode Galleries", () => new ScrollModeGallery(), Navigation), - GalleryBuilder.NavButton("Alternate Layout Galleries", () => new AlternateLayoutGallery(), Navigation), - GalleryBuilder.NavButton("Header/Footer Galleries", () => new HeaderFooterGallery(), Navigation), - GalleryBuilder.NavButton("Nested CollectionViews", () => new NestedGalleries.NestedCollectionViewGallery(), Navigation), + Content = new StackLayout + { + Children = + { + new Button { Text ="Enable CollectionView", AutomationId = "EnableCollectionView", Command = new Command(() => Device.SetFlags(new[] { ExperimentalFlags.CollectionViewExperimental })) }, + GalleryBuilder.NavButton("Default Text Galleries", () => new DefaultTextGallery(), Navigation), + GalleryBuilder.NavButton("DataTemplate Galleries", () => new DataTemplateGallery(), Navigation), + GalleryBuilder.NavButton("Observable Collection Galleries", () => new ObservableCollectionGallery(), Navigation), + GalleryBuilder.NavButton("Snap Points Galleries", () => new SnapPointsGallery(), Navigation), + GalleryBuilder.NavButton("ScrollTo Galleries", () => new ScrollToGallery(), Navigation), + GalleryBuilder.NavButton("CarouselView Galleries", () => new CarouselViewGallery(), Navigation), + GalleryBuilder.NavButton("EmptyView Galleries", () => new EmptyViewGallery(), Navigation), + GalleryBuilder.NavButton("Selection Galleries", () => new SelectionGallery(), Navigation), + GalleryBuilder.NavButton("Propagation Galleries", () => new PropagationGallery(), Navigation), + GalleryBuilder.NavButton("Grouping Galleries", () => new GroupingGallery(), Navigation), + GalleryBuilder.NavButton("Item Size Galleries", () => new ItemsSizeGallery(), Navigation), + GalleryBuilder.NavButton("Scroll Mode Galleries", () => new ScrollModeGallery(), Navigation), + GalleryBuilder.NavButton("Alternate Layout Galleries", () => new AlternateLayoutGallery(), Navigation), + GalleryBuilder.NavButton("Header/Footer Galleries", () => new HeaderFooterGallery(), Navigation), + GalleryBuilder.NavButton("Nested CollectionViews", () => new NestedGalleries.NestedCollectionViewGallery(), Navigation), + } } }; } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index a8ffeb33edf..e1bed541742 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -112,6 +112,14 @@ void CheckForEmptySource() { UpdateEmptyViewVisibility(_isEmpty); } + + if (wasEmpty && !_isEmpty) + { + // If we're going from empty to having stuff, it's possible that we've never actually measured + // a prototype cell and our itemSize or estimatedItemSize are wrong/unset + // So trigger a constraint update; if we need a measurement, that will make it happen + ItemsViewLayout.ConstrainTo(CollectionView.Bounds.Size); + } } public override void ViewDidLoad() diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ObservableItemsSource.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ObservableItemsSource.cs index cb644982217..6b881cdc194 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ObservableItemsSource.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ObservableItemsSource.cs @@ -135,17 +135,21 @@ void Add(NotifyCollectionChangedEventArgs args) var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _itemsSource.IndexOf(args.NewItems[0]); var count = args.NewItems.Count; - _collectionView.PerformBatchUpdates(() => + if (!_grouped && _collectionView.NumberOfSections() != GroupCount && count > 0) { - if (!_grouped && _collectionView.NumberOfSections() != GroupCount) + // Okay, we're going from completely empty to more than 0 items; this means we don't even + // have a section 0 yet. Inserting a section 0 manually results in an unexplained crash, so instead + // we'll just reload the data so the UICollectionView can get its internal state sorted out. + _collectionView.ReloadData(); + } + else + { + _collectionView.PerformBatchUpdates(() => { - // We had an empty non-grouped list, and now we're trying to add an item; - // we need to give it a section as well - _collectionView.InsertSections(new NSIndexSet(0)); - } - - _collectionView.InsertItems(CreateIndexesFrom(startIndex, count)); - }, null); + var indexes = CreateIndexesFrom(startIndex, count); + _collectionView.InsertItems(indexes); + }, null); + } } void Remove(NotifyCollectionChangedEventArgs args) From d2beb36c573beb8fb4c5b65ba061c81860d240b9 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Fri, 20 Sep 2019 16:03:55 -0600 Subject: [PATCH 028/203] Finish ScrollTo implementations for CollectionView on UWP (#7509) partially implements #3172 * Finish ScrollTo implementations for CollectionView on UWP * Fix NRE when attempting to scroll to index greater than source length --- .../CollectionView/ItemContentControl.cs | 9 +- .../CollectionView/ItemsViewRenderer.cs | 179 +++------- .../CollectionView/ItemsViewStyles.xaml | 8 +- .../CollectionView/ScrollHelpers.cs | 338 ++++++++++++++++++ .../Xamarin.Forms.Platform.UAP.csproj | 1 + 5 files changed, 396 insertions(+), 139 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 00f20cc055e..6533dd7d020 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -119,6 +119,13 @@ internal void Realize() var formsTemplate = FormsDataTemplate; var container = FormsContainer; + var itemsView = container as ItemsView; + + if (itemsView != null && _renderer?.Element != null) + { + itemsView.RemoveLogicalChild(_renderer.Element); + } + if (dataContext == null || formsTemplate == null || container == null) { return; @@ -131,7 +138,7 @@ internal void Realize() Content = _renderer.ContainerElement; - // TODO ezhart Add View as a logical child of the ItemsView + itemsView?.AddLogicalChild(view); BindableObject.SetInheritedBindingContext(_renderer.Element, dataContext); } diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 1857f6b6cc7..f1333b057f6 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -199,137 +199,6 @@ protected virtual void TearDownOldElement(ItemsView oldElement) oldElement.ScrollToRequested -= ScrollToRequested; } - async void ScrollToRequested(object sender, ScrollToRequestEventArgs args) - { - await ScrollTo(args); - } - - object FindBoundItem(ScrollToRequestEventArgs args) - { - if (args.Mode == ScrollToMode.Position) - { - return _collectionViewSource.View[args.Index]; - } - - if (Element.ItemTemplate == null) - { - return args.Item; - } - - for (int n = 0; n < _collectionViewSource.View.Count; n++) - { - if (_collectionViewSource.View[n] is ItemTemplateContext pair) - { - if (pair.Item == args.Item) - { - return _collectionViewSource.View[n]; - } - } - } - - return null; - } - - async Task JumpTo(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition) - { - var tcs = new TaskCompletionSource(); - void ViewChanged(object s, ScrollViewerViewChangedEventArgs e) => tcs.TrySetResult(null); - var scrollViewer = list.GetFirstDescendant(); - - try - { - scrollViewer.ViewChanged += ViewChanged; - - if (scrollToPosition == ScrollToPosition.Start) - { - list.ScrollIntoView(targetItem, ScrollIntoViewAlignment.Leading); - } - else if (scrollToPosition == ScrollToPosition.MakeVisible) - { - list.ScrollIntoView(targetItem, ScrollIntoViewAlignment.Default); - } - else - { - // Center and End are going to be more complicated. - } - - await tcs.Task; - } - finally - { - scrollViewer.ViewChanged -= ViewChanged; - } - - } - - async Task ChangeViewAsync(ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, bool disableAnimation) - { - var tcs = new TaskCompletionSource(); - void ViewChanged(object s, ScrollViewerViewChangedEventArgs e) => tcs.TrySetResult(null); - - try - { - scrollViewer.ViewChanged += ViewChanged; - scrollViewer.ChangeView(horizontalOffset, verticalOffset, null, disableAnimation); - await tcs.Task; - } - finally - { - scrollViewer.ViewChanged -= ViewChanged; - } - } - - async Task AnimateTo(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition) - { - var scrollViewer = list.GetFirstDescendant(); - - var targetContainer = list.ContainerFromItem(targetItem) as UIElement; - - if (targetContainer == null) - { - var horizontalOffset = scrollViewer.HorizontalOffset; - var verticalOffset = scrollViewer.VerticalOffset; - - await JumpTo(list, targetItem, scrollToPosition); - targetContainer = list.ContainerFromItem(targetItem) as UIElement; - await ChangeViewAsync(scrollViewer, horizontalOffset, verticalOffset, true); - } - - if (targetContainer == null) - { - // Did not find the target item anywhere - return; - } - - // TODO hartez 2018/10/04 16:37:35 Okay, this sort of works for vertical lists but fails totally on horizontal lists. - var transform = targetContainer.TransformToVisual(scrollViewer.Content as UIElement); - var position = transform?.TransformPoint(new Windows.Foundation.Point(0, 0)); - - if (!position.HasValue) - { - return; - } - - // TODO hartez 2018/10/05 17:23:23 The animated scroll works fine vertically if we are scrolling to a greater Y offset. - // If we're scrolling back up to a lower Y offset, it just gives up and sends us to 0 (first item) - // Works fine if we disable animation, but that's not very helpful - - scrollViewer.ChangeView(position.Value.X, position.Value.Y, null, false); - - //if (scrollToPosition == ScrollToPosition.End) - //{ - // // Modify position - //} - //else if (scrollToPosition == ScrollToPosition.Center) - //{ - // // Modify position - //} - //else - //{ - - //} - } - void UpdateVerticalScrollBarVisibility() { if (_defaultVerticalScrollVisibility == null) @@ -375,18 +244,60 @@ protected virtual async Task ScrollTo(ScrollToRequestEventArgs args) return; } - var targetItem = FindBoundItem(args); + var item = FindBoundItem(args); + + if (item == null) + { + // Item wasn't found in the list, so there's nothing to scroll to + return; + } if (args.IsAnimated) { - await AnimateTo(list, targetItem, args.ScrollToPosition); + await ScrollHelpers.AnimateToItemAsync(list, item, args.ScrollToPosition); } else { - await JumpTo(list, targetItem, args.ScrollToPosition); + await ScrollHelpers.JumpToItemAsync(list, item, args.ScrollToPosition); } } + async void ScrollToRequested(object sender, ScrollToRequestEventArgs args) + { + await ScrollTo(args); + } + + object FindBoundItem(ScrollToRequestEventArgs args) + { + if (args.Mode == ScrollToMode.Position) + { + if (args.Index >= _collectionViewSource.View.Count) + { + return null; + } + + return _collectionViewSource.View[args.Index]; + } + + if (Element.ItemTemplate == null) + { + return args.Item; + } + + for (int n = 0; n < _collectionViewSource.View.Count; n++) + { + if (_collectionViewSource.View[n] is ItemTemplateContext pair) + { + if (pair.Item == args.Item) + { + return _collectionViewSource.View[n]; + } + } + } + + return null; + } + protected virtual void UpdateEmptyView() { if (Element == null || ListViewBase == null) diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml index c74b732e32e..69f851059c0 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml @@ -2,10 +2,10 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Xamarin.Forms.Platform.UWP"> - - - - + + + + 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%0A!#6bE1npH=d5vzo~5q- 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 + + + PreserveNewest + + 14.0 diff --git a/Xamarin.Forms.ControlGallery.iOS/Info.plist b/Xamarin.Forms.ControlGallery.iOS/Info.plist index 1e0ae33da46..84f952da0b9 100644 --- a/Xamarin.Forms.ControlGallery.iOS/Info.plist +++ b/Xamarin.Forms.ControlGallery.iOS/Info.plist @@ -142,6 +142,7 @@ Fonts/Roboto-Bold.ttf Fonts/Roboto-Regular.ttf Fonts/Roboto-BoldItalic.ttf + Fonts/fa-solid-900.ttf diff --git a/Xamarin.Forms.ControlGallery.iOS/Resources/Fonts/fa-solid-900.ttf b/Xamarin.Forms.ControlGallery.iOS/Resources/Fonts/fa-solid-900.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dde916237a518ddb9692e352fe2a722df2826431 GIT binary patch literal 192472 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%0A!#6bE1npH=d5vzo~5q- zbYFxwJ>E1H@jbL!VsAk{hL9fU@#3k0?LC2BJkz8A`xrS(-TQr! zF+7v2XF4T}2M3%yhI~37fn0=aJ?MA?w(WogrGE&-%1}l^+Y(41gU(kI%$KFBmIqht-ndGiXk}OHUhU5p~dV>VokBCVCV$Wzy zpmYXyo#TX-P5!jObQE!7iMk80crx07)Jo*21wz5rX6V0)`u7FG?9_TT<=ORU{ZR^8 z?5KZ-tZ#68MY{k^>{N|Ze|^k-$gpk*FC=7inQ#8N$EOpqHohI6N5MO6NVlv2eB6_J63;3 zq^<2^p5~IGN6nt6Y1$jtC=$i(G<2u+-?tVw{8xQnf-Qrw06nk&OTG?iqw0?W5kB(d zU+35?mCW9*I3R46;Bz#olz|L-6M~rHkbY=L;Z8%W^E$BzxV}zgqJU8vX~3o;o_qNpE)6#o*;--_Z0*Jkvx;O6_ z8QHP9d(&R5c=pId;+yO4(PB3yD5^hkl3zB_(%aiIaT&kKmIF?y^>Wx%MI-oZS_`dP z)BWL@qLi7o&4oIo0@xDZ6r+ zXbnV!Gxm18+=*De*Y%L=5!Yj`FS@?(`jzXn(%84dI8Hkj*f>m556L!bvv~INwZLY_ zK;cLnfARfiyl?ExbA99YJmLD8N!ueE+yB|`cX(yvd!LZ>{mk$AGv{Z%Z|v6u@qlY* zN#Yr$)2=7Iyu5l<-FL>}z*B3K_{mEvPktFfMB_#4 zTAsZ~ndTdf+45TXY^Ae5VS26Ux(4fN*RDIUEe?*-O38EKGo)z$=R5&J#`>Nd;+_Dp z@r~`D0C8tPaO3_yS`$NHN|Ckez-nrB(oU_GmRC!SAzSO)6H4{_R2BTDPq?(e|-DUg6Q@ z4bt!7E&JUb*2H|5x;;EL7>HtThemt{l+f_d81&9VZrfc(tM;l|)h`+rL!p%?z9|`B}!n_$?vjZp_1!=PqrMWooXc?`%RFwFx{)I)PIe5(<~Pu_Xs!k#(XbOj3k#NDk@M^r&Bpf{T*>bZK8YA44Cw|A$*`Re#7g| z`hAnx5#s_ObgjGlyug6Qx)6TL&NJV7!W3t0nEZLhy%Onv1_I}GcVnYjae)yO=83nG zt)AlDY&j%^l?X#vdq=yBTm!n$__nPK5)nnMk3kCuxF}24s4^TmWhK^~r3^N=p>r|E z=%I}dLcVF{(nVBwOF)cJIP&>vx;;U-OS-^gDo=3)YoBau4#`~Wa3YRyO~;fsNEqyZ z1dRst(zV9e%V-iYB}a%f*o3P6puxu7$aCP)yJ|h*~rvi+(E#dMso<7 zL$b2Xhhcd_FW1cz0us+ScQ|ZA4{fAhY{+Iv1bj9q_%(gr)an6!bKuh!(&%)B4yRpD z7!e@js&In&OHeZ$R@RE>bfdO09*qhA|EG@##{>eN2?>8`AM4ZqcYR!Gbh0t&L_=(R zd?$M|G?Q_Ae}=Q;;~6qJY+lM3;_%f@CTzY6nE{9lZ5ob9#6CEZ!V~e4g=jve=|DCj zB?!qQ`=M)7Z2X`vr7|OfzIY0U_5-9g0#U@SGBRhHuB9Z)|@ANr0n4 z4S6^B4TQUI!2Kqo9nacVtXEx+l(wKS3a++B8kG|LSM+s$jp&Tn6UH|GHGO5T0^w;) z5lvZrc@0kg;?CM@!N^}|?-g^%bs^$7T!6p^mc}WBgG?952i7-8hCi77u&b^59Xu;} zwL>~UU$rMVLdY?sru1-PR2beM$M!Nq6tFdfy_5ppuN|^!t$B?puqBVd4spXz+XS6E z9A}@?w8ufLOPZF1`(yF}tP5UI3*5GJ3mFX?eE}{KTR%MlZ}Bu8p8a(!Lom!un@ogG z@vpnRZvP$Ue+IWcu4yIwBsFc<1DZzs(UpLWVhTP(+EHk|MHnmwcJeWm0$e(-jYd{l z_G_~H)=%Pi;z}&_^aif1-=i`{uxXWXf=%H9{p}5J4+gh~_g-04^(R#$TSmfrmyx$^ z9AgeU63LomZKod$dqDOM3o?r+)216K5(HgiKfmyk7Y?2ijs%B-k?=Y1)v+hj&I8rE zs|R)lXwwOVoOn8Wc1|81oXDIz*(9<5@hjr-pCKeM@Nuje5_;F6@vU3O4|PFCLM%7e zBTY#09%<>uW+fO4T4!?#-}NleZCH!?`h<0YTv%A_Sr!$ zm#Yx+k0~n>8$$KIs}8Eb1jbdI_)nCfh+gFCU{{gnP5sw~SUS7RU|qk{DbudoZ*$7R zm2mxsQ-a zZDZeX%3jwsyu&H`T*LegPC4Wn;J~N@D7p%&<~(EvwXn9Zi2R3^%XP3=S}Nz}vem>;dUN7} zVxgKiZ_%n07jW%Q-IM?_(j&`wfQ8aAJS>lwW^l{82PDQ57nCh4ajcln;nKQRPR(Ec z{3PBy40p#4b^OzBOO6lk*f9*Z$Y1}8KmAY_{61q?0W=Iq*bSREjqaa*{&5UV8RMK& zBfkf&*MItEJZx-ac;{}c$lWkz$E|WDS1jyF*gZhI#w#{X-1zGFC)bZGJO#ao73v66S!`v7AZha23M-6r^%j2 zn<_@ab!FB{ELt;zmBd1^QcaYKrCL5!PUO zPBzXa*d#Vb*v}l*KHqBCOhNW4CS!|Zgu`J86o7p_e zvjtXQMOI?BurjN#Dyy;MY>}N{OYBy58+$r?2D_a-6Fl%)?AgeQ^&EC5doH_+-OZlI zp3h#u?qM%vFJkwy``C-wOV~@<{p@Az(^z%S$%@r(H- z{4l?iKZRe$FXu=275pea#;@d0=U?DoO@qgmq=HKDp<=^Aq=co7&_&@U>@*nXZ^MBz#;Xma+<3H!W z;J@Vm%Kwf3ivODbhW|VN5B{J0xBPefzxeO@fAjz0f56TZf8=$3TDSzmvK0X*zJ`3u zy6^}rh!;L&d#3Gof+8fEL>L?AMzJI;CgP$+w2C&-E;>Y~=n~x`0e5(>=o6bnzZejM zVn}QjTf|neO(X?GTQMSbh*2>nc8YN^AtuExv0LmB=ZL*xpV%+XMe_9X!~xg)kxt=| zIA2^KE)*Aui^V14u(;Iq67dvqnYdgW5m$(#;+VKnJXKsJt`^sbYsGcqdU1oeQ9MoD zB&J15%!ste2usY0Igu4PakH2gd9ff0q9{t@7Eu-zQ57|DTr7$cVoBU8ZWB)z&k(nZ zXNqOt3hs7Jk--tJfH;cE3w~Dukw~Kd(zZH*&N5wnEyTrT2 zd&J*~_loz4_lpmR4~oAR9}@o{J}e#+E8-*KqvB)Y*5>Yo8nvIpTxJtcf@za_r&+bDe(jG&*F#TN8-ogU&K$u zPsPu~&&4mqFU7x#e-pnFzZSm{|1SPR{HOS>_?`GK@q6*#;(x>+#Q%yvin=&0U6M&I z;RBZ%66We~g&Wc}<9O}5Jp*(tkZw@k<$*(>|x zCfP3sIV`u!5xGN-$}zc9j>`!-DR;@;a*sSm?v?xGe%E*9xpGRL z=L*XM@}N8<&zBd-3*|-fVtI)?EH9N$k(bHK&>JSvaLE9Fz=Rq|?ijl5P~C$EPy z;70j0d6S%$DLEt4G9xWHE9YcZ=H$(CUgqV3EXblP$y;PuR%BJyfI%}opwQ6p`s`%`ClcjvEVmjr8T%lGmO2tL1oGi}Hx+}T4LMkuPMT`mr zb}gMRrsrk3n96uE#l=D%1ua*YDV~sJD^rw}r9#?6?@6ZeRjJy}=2LTCd|sxMEf%bb z_joa1Td?TiKBq_zGMsX)q2Vac!=U%v-Zn(=Mj1Le(n!>=UJ< z`0et|wMsQNyCkVEda+i`prJcgpdamcE@Kt7+(K&3@-C!GG$Hd=*`3PJO%iicl~yKK z^;fD^DLIo$&o8FR8UJhwqtZC@GzzkmPc3Ps6ebO`S1jqX#WFQFRrAJ)dXjUZSvOnh zs)>0#UM||5@i$IX4}3JA$#kxq&ReFM&&G+G^~Q+;1m`4pOU*)m@i_Z7U#>7)bjsdz zxmc-WQ@L_Q%%lq5RJmMSRAcC~i|VBwyId0W#C`30(j3x*SD{hbE(2y-qJ8^1U^}DigH1 z%Xmw1LC1R*XtFRp1*__93{9=HI%M>1dfBQjS{5cLTP&4wg}G!J2;`mx3;`bOpJcIQ z6^t}GK@(TT+X&c_xU*ao^VX6CR#d28^uCHg$d}Bf^0P)YTU(f^V&dIsS;{WCYbAv! zd269q&=ek+>X@wDQllZN0g=-4zBF(sSEVn>h;pSOvYCu$rk2lVi{*kZZ2|3PbLmvo z3T4sNWT^;9ORCWiuB#yE_OCmymDXRSzBk!(Wly!5$?%36`;i-}YNd@gsp*}|7iTQ@ zV%aKy098|!`HCB42l!;nlylZ>I#sc3XsFQqXhfGPK&ZkzQIT3k17)bpi*&7ufh;W< zrBulR%;rk6k~(g=?V*tv_=>J5N-b~y7W2TYa&F$LX3NFeT-K-oEz5YWg^!p4Y+&Yd zX{}nWrRTjEaBC?!lgsD57*?yAq%Zf_MF6W^pds_wMb&q}5NK4PA}hsm)u4mAO;@E_ z$zv6cTX_J7#G8xS9KJSB)TgRfa#afy!Gq~9Tlstnhz-;=Ky|WJOyrDCV~||P_)uHT z7Axqt<*C$iRT?WfgInBb5Jt=L;9`krnUPw+jj2M~axYjJXt=&vdKjK^vjvi8S!GYw zfg&64?O|(DSc0#vVL+TICcJ6XI8(g7il%t|ISrK8u2JJ-7uAg(yG+RL+yR8jSe1DY zFn2yxq8~LCz``QN-tyj3D^~6A@YzLsoYBWZ0i@FgMXj=cw%RP{OF=AHS$z%&R|2O2 zuFVvI6anHTNkzCUpo#~>0Q#271Aa1=QMO9?B|us}0A#Ysi-KVZ0#@=-ljIC&L^@08 z1}Kz7i?31x1Xj}JTnV#Yo3Tr7;5=r{EY)tk6$4nxS!s~{96gj6T4=3k(<=a% zO_Z95mL<0f>lYdfsW}1s!yXrlRT23DH_Wc%{k4 z9s5p&D!~mkwNiM)x$2x@)yl;r$rs*I5s0LUzDyBlQ?{$im<})jwD2s^2T;?p0%%pY z2zEsUl65Q(9jNa?g`}x`DVtJX;$L@OD~0T{1-#J4>A?C5pny6pKtL(y(soZa)YM8g zajzt6C7&Ym71hc;3%t);k`AI$E69anv7nP=mOE~VncCdOAtHqGW{M#7ic5PJvzCxOq1oX0->umXB4bO3^6cXE`qOu73AqE-s2@gY$G$c2t*Jc2_qRGJR0M>i0#gl zYn7~jCReUzGlW5@c?+TjNebXHDMx4kt#B%dJ9(?cIU>Oz@J6Fp&}VY?6wK6sM)_jJ z%HUxo5{}SOAE?H>g>L~o7?`B0lGO>h=shHd5dn~uY_8-}tfgW>hNyz;p&RPC;53-h zYVvr_S~QfBFk_`4!dejX0j=Pz0QGdOyyQbq3za#DQ&u^+u1q*(s^)kXA)k-#a>`!& z0@c{ncw2Ga@~b9xIeC1;S=L>!YM|R*`%QTWgivA10A)jUfMi#%Dk))A%vc$kgIvK4 zg1neoGG|g%+`g2Yg!CcNt6OB z%_T7uKzOq_Lqc|P4gy9g;GEP7>J%fIOV2MQ0i{YEDp+`=;?T(ok^W$|SS#C_8cBrJ zz}h)|kALkHV{NRS8A@a$2H~}ZH-b!Cj;o||6?|01r!M4kw}KVgjVdLyH|_Rz7o3|d zkPcow?r-oS1vi1!GezmwPSsGaohs7rfwBX221rLL4*0Y|zXXUCffD$s5_=VRDD|bG zI*=CA;3%uY^-O9mxbCC^E~saLSy1dk4kb(Flc{tX@Sx2ATQW_GVk99|D1Hola71QX zQPjc8A=FvzU_T%ZpoL1kwFqmHl_khm3za^S*C8<5YyzsVO&61DuAz$%W0o0c9e(Q8 z>UD1(cy&BSE)q2WoV8d0Q#9AE?w5gES8%Wt!?Osxp~fe{#He?fnj`BJ%sh zTKbUK6WXdzi9DfNeGC8sax6f&Fi*X0fz-IJ#u2P=!366Cy3Z+5C#=*cH58;?(v2az z%GNj47|vM>fxHC`MYW?U(>|@0=)@q!Qz^Ou!fz06m@?=>V4YlxQ?L{gX-y>SHWRzdChrtNC|Jw z3C#*bwK5s>av8Isl~OhMoUNjQ+%7=I@;O9~uK9^M!cu8}t-D0Mt7;_>>`H`;Kn;=( z`bolV{490!7;g#u(8|G}2rK|L=r(j!Ad;^VSb02`%EMNd z%2y0n)GTn?3ak~%Ca9fAmQzbGCZ15BZ#I}2MDmKiaiXkGTDn++#+WGi}Dgz5ZM==B^%#u|s0lB~< z%1ho-Zf*`Hi&SCWUCzzSz+wa`qzXHMWn~D`B~ehX(v_3IlX*z)CCjhO9Z8}CXkyxD z3&rC&@hO4ZmS?@V!AdIWB+x)M`-*c)LR%qKtJ*RckOA_DY6w(J&)WzJvzINbk+sTH zS7%}Hp-!q3M<225Ms%P32qkscMX>3cQ;x{0XoOv@hTMJ$Bm<|AvmXSLW^pbIj3Hx) zEov#lMr31^qOv;VR1#zj;?HV|gEM5aCT^Il5`)xmUR)sQJU_3fet4}29L3y_St45_ z+j<(J6^IJyx{wJoxk{Q$3X2*Y3K9ru0mbl5vyeiZ5=20csspLC3zggnuc}dO-ma@I z7NPJK%SlD&GKMOXeBrjh;PVxI7P2}%M8YI8=b1}YKwcI~x|%+#V$MO+AR<}?fQ8qe zB-QOOy2BJ+ETM@ppIgjh9! ztIw`eAmg-GWx8g79@uZuK?^KM@c1ng27}#~$p{a9H3)21Qa917a%zFJQwydTfIrBD zRtBjn2hj$d zsA!dW5N4l4ObF#PTLMx?`fxY!!AdQV7F8r^X4cB<6lwybbyq=>%2j<45QL7n@k>Mw z%o#E>Y5vewv{V&rQA?3U-yoB}(&^0^>((6R4Z6m0Sp6W_02-lA>d*n{r6BBuLJI85 zF~Zn-7Yxnm`6`6o0ODTKxF14*V=)9Rv>=ev4<;F;f{au2q;MuTM^?n7V!0U9QdZ04 zZoPFWlqsiXN&cmwBomIzlxquqIwT=GgGi+cJ_q{fL4L&)@M3WHv~xzX578S~Btg3% zP=WW`<=g_5ZLX3l94`VnBC3&d0#SslJTQ>q#`Uy6plXP|rK+I%_S0-(hO9h!vKl~; zfq@Q}K=;dP9&rhSHw(=%Yr#T6SgCNnX@kFvx?w7lbutBVl}-hm6I)5Bc%9QqE~U?v zA&jOiiQj^U4s^4j3Q(UHf~%!uWf3^&Q|F5MSL`zQJ^-vq)!StvDrLNc9!7ehQ&hF~ zJyifg%>p8$gZpsksR& z%N!7!_!m|=E7b45nf5iip#i#h1sWxx8Rk%8uvhX@Um zlr67RVRVMc1X5}?&~QW0ETyyl<1lG}*p)H&$jtm7b0|_iU zGE@oLNv*_F83~xoYBM>A7Jd+F3>Ps{_|avWjIa%<(I#y{=adX(MCwRZfMSwC*Fjid zP$;Lu8!QTdtP`M^%KeyBf>Of?Nft5xYz=-AQsL-f`Wzx4!5|@6E>u$PY-(v9D#6W= z#sW7(@E}EU5!T*(MVn8-kn5f&0W?K+;S8Y~3^^Dzc$Ma1`zsX7sVuxS01As27Xsw4 zO3kLo&6=w$XeBZvg@7IqZ!n}V0h1h#;fAM*4#}(}nD*0wrc{Z#+Aa7x*sp;r>8$h3 zLFcUDn$arYW|g1~i-34gA%ISknmbZ}z_2SM7uYz0S7%ZXV+|M=QgcO+NkBM0946Ir zE|;9cjJl8KFu18kOu+(4%=o0_65!kKh=WN*3vHngb|2`2z(RtlLIx&qij4r+duQnsGNU89lHoEFB2%1m16l#60pgmuP}jkKL2h;q3Cg+tjWGw_*1)5(^X zju&z%*lsKHpos8e0QLOvbYpJj@esm(C{>{BWxxy`1sRBFV@Sz57+1g{;V;atB{HI$ zRp1cRZ)FMB6?=#MQ_1m=(LoF|=5e4fK23?qu$31wK1D2S-3`hnjnM+IinLkiqUsc* zM_q;vtWFUirTvR>E(^ms@lbrw`1TnFKM}p{7vj|Ut z;|Nm^5fRUY0l*7$18mb^>M({c6z4)P|KScLL?nsZ1=m-=zUMg(c9&eTQW{9BGMCLP z`DaoyM4iDCFwn+Ku9!{Hl*pNU5%vPett!+2=6?U`2|@_w0Y!z3BI_37QDBv?;(7(V z%bLT)TjdCeSO916U^3a;A|C7sCA#Qu6reqnEf2^P>^=}9DIx+AKBPzJJ@5{dD^Bze zFdO!dB}bAWA6m&GsiH*2BdZcwD<&IafE(^d&_q)SDDYgsLSKNr0%ia(2GTecn9+n} z^jtazhfqb!K^iK$7jl54g1%4#W*4gl{7gATvi++i!deeHNAmH4QNdt>VOOodYKiov ztX{DVpq@%@LBWz^1p}P& zi8mgIr~o_xnh*JtXkdLM$mH<)(KiuGQoy5hkfsf1)FnrMhMU6jkt*h}bN7s*8{Qd= zARY=#1cO$^0+1aepvsl%61;s7i!=(JN1PXVu<5BG$B3sy1F9gaUbP4_I6|HLWnd-V zga9Oj?L?GAbj`s^t!=ldU1!@Bqjsw`M=Lc8udlS*HLI%i4p(YuzoSuWxAav? + diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6491.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6491.cs new file mode 100644 index 00000000000..7645c896acc --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6491.cs @@ -0,0 +1,45 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 6491, "[Bug] Some Font Image are cropped on iOS", + PlatformAffected.iOS)] + public class Issue6491 : TestContentPage + { + protected override void Init() + { + var stack = new StackLayout + { + HorizontalOptions = LayoutOptions.CenterAndExpand, + VerticalOptions = LayoutOptions.CenterAndExpand + }; + + var label = new Label + { + Text = "If you see the cloud in full, not clipped on the right, the test is passed" + }; + + var button = new Button + { + BackgroundColor = Color.Red, + WidthRequest = 200 + }; + + button.ImageSource = new FontImageSource + { + Glyph = "\uf0c2", + FontFamily = "FontAwesome5Free-Solid" + }; + + if (Device.RuntimePlatform == Device.UWP) + ((FontImageSource)button.ImageSource).FontFamily = "Assets/Fonts/fa-solid-900.ttf#Font Awesome 5 Free"; + + stack.Children.Add(label); + stack.Children.Add(button); + + Content = stack; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index d9b820af544..8bc59b023c7 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1026,6 +1026,7 @@ + diff --git a/Xamarin.Forms.Platform.UAP/FontImageSourceHandler.cs b/Xamarin.Forms.Platform.UAP/FontImageSourceHandler.cs index a92d95d8993..c44977f16d4 100644 --- a/Xamarin.Forms.Platform.UAP/FontImageSourceHandler.cs +++ b/Xamarin.Forms.Platform.UAP/FontImageSourceHandler.cs @@ -21,27 +21,34 @@ public sealed class FontImageSourceHandler : IImageSourceHandler, IIconElementHa var device = CanvasDevice.GetSharedDevice(); var dpi = Math.Max(_minimumDpi, Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi); - var canvasSize = (float)fontsource.Size + 2; - var imageSource = new CanvasImageSource(device, canvasSize, canvasSize, dpi); - using (var ds = imageSource.CreateDrawingSession(Windows.UI.Colors.Transparent)) + var textFormat = new CanvasTextFormat { - var textFormat = new CanvasTextFormat + FontFamily = fontsource.FontFamily, + FontSize = (float)fontsource.Size, + HorizontalAlignment = CanvasHorizontalAlignment.Center, + VerticalAlignment = CanvasVerticalAlignment.Center, + Options = CanvasDrawTextOptions.Default + }; + + using (var layout = new CanvasTextLayout(device, fontsource.Glyph, textFormat, (float)fontsource.Size, (float)fontsource.Size)) + { + var canvasWidth = (float)layout.LayoutBounds.Width + 2; + var canvasHeight = (float)layout.LayoutBounds.Height + 2; + + var imageSource = new CanvasImageSource(device, canvasWidth, canvasHeight, dpi); + using (var ds = imageSource.CreateDrawingSession(Windows.UI.Colors.Transparent)) { - FontFamily = fontsource.FontFamily, - FontSize = (float)fontsource.Size, - HorizontalAlignment = CanvasHorizontalAlignment.Center, - Options = CanvasDrawTextOptions.Default, - }; - var iconcolor = (fontsource.Color != Color.Default ? fontsource.Color : Color.White).ToWindowsColor(); + var iconcolor = (fontsource.Color != Color.Default ? fontsource.Color : Color.White).ToWindowsColor(); - // offset by 1 as we added a 1 inset - var x = textFormat.FontSize / 2f + 1f; - var y = -1f; - ds.DrawText(fontsource.Glyph, x, y, iconcolor, textFormat); - } + // offset by 1 as we added a 1 inset + var x = (float)layout.DrawBounds.X * -1; + + ds.DrawTextLayout(layout, x, 1f, iconcolor); + } - return Task.FromResult((Windows.UI.Xaml.Media.ImageSource)imageSource); + return Task.FromResult((Windows.UI.Xaml.Media.ImageSource)imageSource); + } } public Task LoadIconElementAsync(ImageSource imagesource, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs index efeaaf9c9e3..70cab540d81 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs @@ -202,13 +202,13 @@ public sealed class FontImageSourceHandler : IImageSourceHandler var fontsource = imagesource as FontImageSource; if (fontsource != null) { - var iconcolor = fontsource.Color.IsDefault ? _defaultColor : fontsource.Color; - var imagesize = new SizeF((float)fontsource.Size, (float)fontsource.Size); var font = UIFont.FromName(fontsource.FontFamily ?? string.Empty, (float)fontsource.Size) ?? UIFont.SystemFontOfSize((float)fontsource.Size); - - UIGraphics.BeginImageContextWithOptions(imagesize, false, 0f); + var iconcolor = fontsource.Color.IsDefault ? _defaultColor : fontsource.Color; var attString = new NSAttributedString(fontsource.Glyph, font: font, foregroundColor: iconcolor.ToUIColor()); + var imagesize = ((NSString)fontsource.Glyph).GetSizeUsingAttributes(attString.GetUIKitAttributes(0, out _)); + + UIGraphics.BeginImageContextWithOptions(imagesize, false, 0f); var ctx = new NSStringDrawingContext(); var boundingRect = attString.GetBoundingRect(imagesize, (NSStringDrawingOptions)0, ctx); attString.DrawString(new RectangleF( From ab9c224b35a621a37524d865621322c74cf223cd Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Wed, 2 Oct 2019 14:40:36 +0200 Subject: [PATCH 081/203] [HR] allow the HR test harness to clear cache (#7774) --- Xamarin.Forms.Core/ResourceDictionary.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Xamarin.Forms.Core/ResourceDictionary.cs b/Xamarin.Forms.Core/ResourceDictionary.cs index 4ce81e4c765..85409baf889 100644 --- a/Xamarin.Forms.Core/ResourceDictionary.cs +++ b/Xamarin.Forms.Core/ResourceDictionary.cs @@ -323,6 +323,9 @@ void OnValuesChanged(params KeyValuePair[] values) event EventHandler ValuesChanged; + //only used for unit testing + internal static void ClearCache() => s_instances = new ConditionalWeakTable(); + [Xaml.ProvideCompiled("Xamarin.Forms.Core.XamlC.RDSourceTypeConverter")] public class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter { From 2e6ba0ecb50969f56bbedced5a66d20f1a7d2f8a Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Wed, 2 Oct 2019 15:13:24 +0200 Subject: [PATCH 082/203] [X] send VisualTreeChanged for root (#7747) --- Xamarin.Forms.Xaml/XamlLoader.cs | 17 ++++++++++++++--- Xamarin.Forms.Xaml/XamlParser.cs | 7 +++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Xamarin.Forms.Xaml/XamlLoader.cs b/Xamarin.Forms.Xaml/XamlLoader.cs index 5a4f11e8b1f..042c0d21b75 100644 --- a/Xamarin.Forms.Xaml/XamlLoader.cs +++ b/Xamarin.Forms.Xaml/XamlLoader.cs @@ -35,6 +35,7 @@ using System.Text.RegularExpressions; using System.Xml; using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml.Diagnostics; namespace Xamarin.Forms.Xaml.Internals { @@ -89,7 +90,11 @@ public static void Load(object view, string xaml, Assembly rootAssembly, bool us continue; } - var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader); + var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader) { LineNumber = ((IXmlLineInfo)reader).LineNumber, LinePosition = ((IXmlLineInfo)reader).LinePosition }; + if (XamlFilePathAttribute.GetFilePathForObject(view) is string path) { + VisualDiagnostics.RegisterSourceInfo(view, new Uri(path, UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); + VisualDiagnostics.SendVisualTreeChanged(null, view); + } XamlParser.ParseXaml(rootnode, reader); #pragma warning disable 0618 var doNotThrow = ResourceLoader.ExceptionHandler2 != null || Internals.XamlLoader.DoNotThrowOnExceptions; @@ -127,7 +132,8 @@ public static object Create(string xaml, bool doNotThrow, bool useDesignProperti } var typeArguments = XamlParser.GetTypeArguments(reader); - var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, typeArguments), null, (IXmlNamespaceResolver)reader); + var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, typeArguments), null, (IXmlNamespaceResolver)reader) { LineNumber = ((IXmlLineInfo)reader).LineNumber, LinePosition = ((IXmlLineInfo)reader).LinePosition }; + XamlParser.ParseXaml(rootnode, reader); var visitorContext = new HydrationContext { ExceptionHandler = doNotThrow ? ehandler : (Action)null, @@ -135,6 +141,11 @@ public static object Create(string xaml, bool doNotThrow, bool useDesignProperti var cvv = new CreateValuesVisitor(visitorContext); cvv.Visit((ElementNode)rootnode, null); inflatedView = rootnode.Root = visitorContext.Values[rootnode]; + if (XamlFilePathAttribute.GetFilePathForObject(inflatedView) is string path) + { + VisualDiagnostics.RegisterSourceInfo(inflatedView, new Uri(path, UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); + VisualDiagnostics.SendVisualTreeChanged(null, inflatedView); + } visitorContext.RootElement = inflatedView as BindableObject; Visit(rootnode, visitorContext, useDesignProperties); @@ -162,7 +173,7 @@ public static IResourceDictionary LoadResources(string xaml, IResourcesProvider } //the root is set to null, and not to rootView, on purpose as we don't want to erase the current Resources of the view - RootNode rootNode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader); + RootNode rootNode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader) { LineNumber = ((IXmlLineInfo)reader).LineNumber, LinePosition = ((IXmlLineInfo)reader).LinePosition }; XamlParser.ParseXaml(rootNode, reader); var rNode = (IElementNode)rootNode; if (!rNode.Properties.TryGetValue(new XmlName(XamlParser.XFUri, "Resources"), out var resources)) diff --git a/Xamarin.Forms.Xaml/XamlParser.cs b/Xamarin.Forms.Xaml/XamlParser.cs index 19df0a115eb..66f7d122791 100644 --- a/Xamarin.Forms.Xaml/XamlParser.cs +++ b/Xamarin.Forms.Xaml/XamlParser.cs @@ -46,8 +46,7 @@ static class XamlParser public static void ParseXaml(RootNode rootNode, XmlReader reader) { - IList> xmlns; - var attributes = ParseXamlAttributes(reader, out xmlns); + var attributes = ParseXamlAttributes(reader, out IList> xmlns); var prefixes = PrefixesToIgnore(xmlns); (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes=new List())).AddRange(prefixes); rootNode.Properties.AddRange(attributes); @@ -139,13 +138,13 @@ static INode ReadNode(XmlReader reader, bool nested = false) var skipFirstRead = nested; Debug.Assert(reader.NodeType == XmlNodeType.Element); var name = reader.Name; - List nodes = new List(); - INode node = null; + var nodes = new List(); while (skipFirstRead || reader.Read()) { skipFirstRead = false; + INode node; switch (reader.NodeType) { case XmlNodeType.EndElement: From fc3a9643ff10346268bad4b371835d6fc7779522 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 2 Oct 2019 14:42:26 +0100 Subject: [PATCH 083/203] Fix merge --- .../Xamarin.Forms.Controls.Issues.Shared.projitems | 3 --- 1 file changed, 3 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index e361c73d5fa..0d14fe83d87 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1069,7 +1069,6 @@ -<<<<<<< HEAD Issue7035.xaml Code @@ -1079,12 +1078,10 @@ -======= ->>>>>>> 4.2.0 From 7f384f34fa6898e22d0faf4b588c9e213a429af8 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 2 Oct 2019 15:20:44 +0100 Subject: [PATCH 084/203] Remove duplicate items --- .../Xamarin.Forms.Controls.Issues.Shared.projitems | 2 -- 1 file changed, 2 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 0d14fe83d87..8ec2c03339d 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1079,8 +1079,6 @@ - - From 1acdb7b9349f0749134476361fe7f59f95360cac Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 2 Oct 2019 20:33:03 +0100 Subject: [PATCH 085/203] Update Build.Locator (#7783) --- .../Xamarin.Forms.Xaml.UnitTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj index df79154f4b9..a7e035004f1 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj +++ b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj @@ -20,7 +20,7 @@ - + From b86351d71dacd1c77438e39903b0757997a985cd Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 3 Oct 2019 09:50:00 +0100 Subject: [PATCH 086/203] Revert "ios: raise touch event on touchbegin. (#6989) fixes #3320" (#7787) This reverts commit dca82ed828e3fc1b3fede8d376ea8c4abf56d4ca. --- .../Issue3320.cs | 55 ------------------- ...rin.Forms.Controls.Issues.Shared.projitems | 1 - .../ContextActionCell.cs | 36 +++++------- .../Renderers/ListViewRenderer.cs | 50 ----------------- build/steps/build-windows.yml | 2 +- 5 files changed, 15 insertions(+), 129 deletions(-) delete mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue3320.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue3320.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue3320.cs deleted file mode 100644 index 587d2525ab7..00000000000 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue3320.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Diagnostics; -using Xamarin.Forms.CustomAttributes; -using Xamarin.Forms.Internals; -using System.Collections.ObjectModel; -using System; - -#if UITEST -using NUnit.Framework; -using Xamarin.UITest; -#endif - -namespace Xamarin.Forms.Controls.Issues -{ - -#if UITEST - [NUnit.Framework.Category(Core.UITests.UITestCategories.UwpIgnore)] -#endif - [Preserve(AllMembers = true)] - [Issue(IssueTracker.Github, 3320, "[iOS] Cells aren't highlighted on touch-down when the ListView's CachingStrategy is set to RecycleElement", PlatformAffected.iOS)] - public class Issue3320 : TestContentPage - { - - protected override void Init() - { - Title = "List Page"; - ObservableCollection source = new ObservableCollection(); - for (int i = 0; i < 50; i++) - { - - source.Add($"ListItem:{i}"); - } - - var listview = new ListView(ListViewCachingStrategy.RecycleElement) - { - ItemsSource = source - }; - - this.Content = listview; - } - -#if UITEST - [Test] - [Description("Verify that SelectedItem is selected on PressAndHold")] - [UiTest(typeof(ViewCell))] - [UiTest(typeof(ListView))] - [UiTest(typeof(ListView), "TapAndHoldSelectedItem")] - public void Issue3320TextTapAndHoldSelectsRow() - { - RunningApp.TouchAndHold(q => q.Marked("ListItem:5")); - RunningApp.Screenshot("TouchAndHold-ItemIsSelected"); - } -#endif - - } -} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 8ec2c03339d..5da64217c07 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -18,7 +18,6 @@ - Code diff --git a/Xamarin.Forms.Platform.iOS/ContextActionCell.cs b/Xamarin.Forms.Platform.iOS/ContextActionCell.cs index 51439591088..2db3c22e25a 100644 --- a/Xamarin.Forms.Platform.iOS/ContextActionCell.cs +++ b/Xamarin.Forms.Platform.iOS/ContextActionCell.cs @@ -662,28 +662,7 @@ class SelectGestureRecognizer : UITapGestureRecognizer { NSIndexPath _lastPath; - public override void TouchesEnded(NSSet touches, UIEvent evt) - { - if (_lastPath == null) - return; - - var table = (UITableView)View; - if (!_lastPath.Equals(table.IndexPathForSelectedRow)) - table.SelectRow(_lastPath, false, UITableViewScrollPosition.None); - table.Source.RowSelected(table, _lastPath); - } - - public override void TouchesBegan(NSSet touches, UIEvent evt) - { - if (_lastPath == null) - return; - - var table = (UITableView)View; - if (!_lastPath.Equals(table.IndexPathForSelectedRow)) - table.Source.RowHighlighted(table, _lastPath); - } - - public SelectGestureRecognizer() + public SelectGestureRecognizer() : base(Tapped) { ShouldReceiveTouch = (recognizer, touch) => { @@ -699,6 +678,19 @@ public SelectGestureRecognizer() return cell != null; }; } + + static void Tapped(UIGestureRecognizer recognizer) + { + var selector = (SelectGestureRecognizer)recognizer; + + if (selector._lastPath == null) + return; + + var table = (UITableView)recognizer.View; + if (!selector._lastPath.Equals(table.IndexPathForSelectedRow)) + table.SelectRow(selector._lastPath, false, UITableViewScrollPosition.None); + table.Source.RowSelected(table, selector._lastPath); + } } class MoreActionSheetController : UIAlertController diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs index aa3586dc12c..f350924d33e 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs @@ -952,7 +952,6 @@ internal class ListViewDataSource : UITableViewSource bool _disposed; bool _wasEmpty; bool _estimatedRowHeight; - NSIndexPath _lastTouchedCell; public UITableViewRowAnimation ReloadSectionsAnimation { get; set; } = UITableViewRowAnimation.Automatic; @@ -995,47 +994,12 @@ internal virtual void InvalidatingPrototypicalCellCache() public override void DraggingEnded(UIScrollView scrollView, bool willDecelerate) { - if (_uiTableView != null) - { - var heldSelectedRow = _uiTableView.IndexPathsForSelectedRows; - if (heldSelectedRow != null) - foreach (var row in heldSelectedRow) - { - var cell = _uiTableView.CellAt(row); - if (cell != null) - cell.Selected = true; - } - } - _isDragging = false; _uiTableViewController.UpdateShowHideRefresh(false); } public override void DraggingStarted(UIScrollView scrollView) { - if (_uiTableView != null) - { - if (_lastTouchedCell != null) - { - var lastTouchCell = _uiTableView.CellAt(_lastTouchedCell); - if (lastTouchCell != null) - { - lastTouchCell.Selected = false; - } - - _lastTouchedCell = null; - } - - var heldSelectedRow = _uiTableView.IndexPathsForSelectedRows; - if(heldSelectedRow != null) - foreach (var row in heldSelectedRow) - { - var cell = _uiTableView.CellAt(row); - if(cell != null) - cell.Selected = false; - } - } - _isDragging = true; } @@ -1052,19 +1016,6 @@ void SetupSelection(UITableViewCell nativeCell, UITableView tableView) _setupSelection = true; } - public override void RowHighlighted(UITableView tableView, NSIndexPath rowIndexPath) - { - TouchCell(rowIndexPath); - } - - void TouchCell(NSIndexPath rowIndexPath) - { - var cell = _uiTableView.CellAt(rowIndexPath); - if(cell!= null) - cell.Selected = true; - _lastTouchedCell = rowIndexPath; - } - public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { Cell cell; @@ -1221,7 +1172,6 @@ public override void RowDeselected(UITableView tableView, NSIndexPath indexPath) public override void RowSelected(UITableView tableView, NSIndexPath indexPath) { - _lastTouchedCell = null; var cell = tableView.CellAt(indexPath); if (cell == null) diff --git a/build/steps/build-windows.yml b/build/steps/build-windows.yml index 4191a5bc0c5..691d251f2bd 100644 --- a/build/steps/build-windows.yml +++ b/build/steps/build-windows.yml @@ -74,7 +74,7 @@ jobs: platform: '$(BuildPlatform)' configuration: '$(BuildConfiguration)' msbuildArguments: ${{ parameters.msbuildExtraArguments }} /p:JavaSdkDirectory="$(JAVA_HOME_8_X64)" - + - task: VSTest@2 displayName: 'Unit Tests' inputs: From 1c59f5b98e67438797adcd56e38ddfff1ef20fec Mon Sep 17 00:00:00 2001 From: shmin Date: Fri, 4 Oct 2019 06:50:52 +0900 Subject: [PATCH 087/203] [Tizen] fix picker focus issue in TV profile (#7770) --- .../Native/EditfieldEntry.cs | 13 ++++------- .../Renderers/PickerRenderer.cs | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Xamarin.Forms.Platform.Tizen/Native/EditfieldEntry.cs b/Xamarin.Forms.Platform.Tizen/Native/EditfieldEntry.cs index cce50d0b6c7..7b239e1c91d 100644 --- a/Xamarin.Forms.Platform.Tizen/Native/EditfieldEntry.cs +++ b/Xamarin.Forms.Platform.Tizen/Native/EditfieldEntry.cs @@ -9,6 +9,9 @@ public class EditfieldEntry : Native.Entry public event EventHandler TextBlockFocused; public event EventHandler TextBlockUnfocused; + public event EventHandler LayoutFocused; + public event EventHandler LayoutUnfocused; + public bool IsTextBlockFocused => _isTexstBlockFocused; ELayout _editfieldLayout; @@ -91,14 +94,6 @@ protected override IntPtr CreateHandle(EvasObject parent) return _editfieldLayout; } - protected ELayout EditfieldLayout - { - get - { - return _editfieldLayout; - } - } - protected virtual ELayout CreateEditFieldLayout(EvasObject parent) { var layout = new ELayout(parent); @@ -108,11 +103,13 @@ protected virtual ELayout CreateEditFieldLayout(EvasObject parent) { SetFocusOnTextBlock(false); layout.SignalEmit("elm,state,unfocused", ""); + LayoutUnfocused?.Invoke(this, EventArgs.Empty); }; layout.Focused += (s, e) => { AllowFocus(false); layout.SignalEmit("elm,state,focused", ""); + LayoutFocused?.Invoke(this, EventArgs.Empty); }; layout.KeyDown += (s, e) => diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs index 9f04445f4fb..abfc0c2c1b7 100644 --- a/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs +++ b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs @@ -30,6 +30,11 @@ protected override void Dispose(bool disposing) if (Control != null) { Control.TextBlockFocused -= OnTextBlockFocused; + if (Device.Idiom == TargetIdiom.TV) + { + Control.LayoutFocused -= OnLayoutFocused; + Control.LayoutUnfocused -= OnLayoutUnfocused; + } CleanView(); } } @@ -46,7 +51,15 @@ protected override void OnElementChanged(ElementChangedEventArgs e) InputPanelShowByOnDemand = true, }; entry.SetVerticalTextAlignment("elm.text", 0.5); + entry.HorizontalTextAlignment = Native.TextAlignment.Center; entry.TextBlockFocused += OnTextBlockFocused; + + if (Device.Idiom == TargetIdiom.TV) + { + entry.LayoutFocused += OnLayoutFocused; + entry.LayoutUnfocused += OnLayoutUnfocused; + } + SetNativeControl(entry); } base.OnElementChanged(e); @@ -88,6 +101,16 @@ void UpdateTitleColor() Control.PlaceholderColor = Element.TitleColor.ToNative(); } + void OnLayoutFocused(object sender, EventArgs e) + { + Control.FontSize = Control.FontSize * 1.5; + } + + void OnLayoutUnfocused(object sender, EventArgs e) + { + Control.FontSize = Control.FontSize / 1.5; + } + void OnTextBlockFocused(object sender, EventArgs e) { // For EFL Entry, the event will occur even if it is currently disabled. From 9cf4ccdc6fad528c54ebfc3fe2af4150ee9ccedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Fri, 4 Oct 2019 00:47:16 +0200 Subject: [PATCH 088/203] Fixed PeekAreaInsets not working on iOS (#7802) --- .../CollectionView/CarouselViewRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs index 56240f25eff..1b1f3557c19 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs @@ -22,7 +22,7 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE { base.OnElementPropertyChanged(sender, changedProperty); - if (changedProperty.Is(CarouselView.PeekAreaInsetsProperty)) + if (changedProperty.IsOneOf(CarouselView.PeekAreaInsetsProperty, CarouselView.NumberOfSideItemsProperty)) { (CarouselViewController.Layout as CarouselViewLayout).UpdateConstraints(Frame.Size); CarouselViewController.Layout.InvalidateLayout(); From 0d092133e2c8d9e64ccf56123778ee372d14dfcd Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 3 Oct 2019 17:20:07 -0600 Subject: [PATCH 089/203] Remove Material Frame Renderer from Frame element on dispose of renderer (#7752) fixes #7339 * [iOS] remove renderer from Frame on dispose * - add uitests --- .../Issue7339.cs | 58 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 2 + .../MaterialFrameRenderer.cs | 8 ++- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7339.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7339.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7339.cs new file mode 100644 index 00000000000..5020601ad58 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7339.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7339, "[iOS] Material frame renderer not being cleared", + PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(UITestCategories.Shell)] +#endif + public class Issue7339 : TestShell + { + protected override void Init() + { + Visual = VisualMarker.Material; + CreateContentPage("Item1").Content = + new StackLayout() + { + Children = + { + new Frame() + { + Content = new Label() + { + Text = "Navigate between flyout items a few times. If app doesn't crash then test has passed" + } + } + } + }; + + CreateContentPage("Item2").Content = + new StackLayout() { Children = { new Frame() } }; + } + +#if UITEST + [Test] + public void MaterialFrameDisposesCorrectly() + { + TapInFlyout("Item1"); + TapInFlyout("Item2"); + TapInFlyout("Item1"); + TapInFlyout("Item2"); + } +#endif + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 5da64217c07..95974a6846c 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -28,6 +28,7 @@ + @@ -58,6 +59,7 @@ + diff --git a/Xamarin.Forms.Material.iOS/MaterialFrameRenderer.cs b/Xamarin.Forms.Material.iOS/MaterialFrameRenderer.cs index 75ae3ffd63a..ba78e831f9b 100644 --- a/Xamarin.Forms.Material.iOS/MaterialFrameRenderer.cs +++ b/Xamarin.Forms.Material.iOS/MaterialFrameRenderer.cs @@ -100,13 +100,17 @@ protected override void Dispose(bool disposing) if (_packager == null) return; - SetElement(null); - _packager.Dispose(); _packager = null; _tracker.Dispose(); _tracker = null; + + if (Element != null) + { + Element.ClearValue(Platform.iOS.Platform.RendererProperty); + SetElement(null); + } } base.Dispose(disposing); From 831074f6350de1312ec181008f88f8e12a4462f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Fri, 4 Oct 2019 02:32:03 +0200 Subject: [PATCH 090/203] [Android] Fix crash changing the Application MainPage (#7776) fixes #7283 * Fixed Issue 7283 - Fix crash changing the Application MainPage * Update Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs * Fix build --- .../Issue7283.cs | 59 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../AppCompat/NavigationPageRenderer.cs | 14 ++++- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7283.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7283.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7283.cs new file mode 100644 index 00000000000..c3158cd646d --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7283.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7283, "[Android] Crash changing the Application MainPage", + PlatformAffected.Android)] +#if UITEST + [NUnit.Framework.Category(UITestCategories.ManualReview)] +#endif + public class Issue7283 : TestContentPage + { + public Issue7283() + { + Title = "Issue 7283"; + } + + protected override void Init() + { + var layout = new StackLayout + { + Padding = new Thickness(12) + }; + + var instructions = new Label + { + Text = "Press the Button below. If navigate without any errors, the test has passed." + }; + + var navigateButton = new Button + { + Text = "Navigate" + }; + + navigateButton.Clicked += async (sender, e) => + { + navigateButton.IsEnabled = false; + + await Task.Delay(2000); + var navigation = new NavigationPage(); + Application.Current.MainPage = navigation; + await Application.Current.MainPage.Navigation.PushAsync(new ContentPage { Title = "Did I crash?" }); + }; + + layout.Children.Add(instructions); + layout.Children.Add(navigateButton); + + Content = layout; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 8bc59b023c7..4f795e8f4a8 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1030,6 +1030,7 @@ + diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs index 1f5403dd636..2dadea707ba 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs @@ -643,7 +643,9 @@ void RegisterToolbar() if (_masterDetailPage == null) { - _masterDetailPage = PageController.InternalChildren[0] as MasterDetailPage; + if (PageController.InternalChildren.Count > 0) + _masterDetailPage = PageController.InternalChildren[0] as MasterDetailPage; + if (_masterDetailPage == null) return; } @@ -1025,7 +1027,7 @@ void UpdateToolbar() if (!textColor.IsDefault) bar.SetTitleTextColor(textColor.ToAndroid().ToArgb()); - bar.Title = currentPage.Title ?? ""; + bar.Title = currentPage?.Title ?? string.Empty; UpdateTitleIcon(); @@ -1035,6 +1037,10 @@ void UpdateToolbar() void UpdateTitleIcon() { Page currentPage = Element.CurrentPage; + + if (currentPage == null) + return; + ImageSource source = NavigationPage.GetTitleIconImageSource(currentPage); if (source == null || source.IsEmpty) @@ -1072,6 +1078,10 @@ void UpdateTitleView() return; Page currentPage = Element.CurrentPage; + + if (currentPage == null) + return; + VisualElement titleView = NavigationPage.GetTitleView(currentPage); if (_titleViewRenderer != null) { From 32cb21ae0d64bb5cdafbd622bd0894cff877b0b7 Mon Sep 17 00:00:00 2001 From: adrianknight89 Date: Thu, 3 Oct 2019 19:36:53 -0500 Subject: [PATCH 091/203] [iOS] Fix hard crash when horizontal GridViewLayout is refreshed (#7612) fixes #7593 * fix refreshview issue * fix path * remove extra line after rebase --- .../Issue7593.xaml | 54 ++ .../Issue7593.xaml.cs | 503 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 12 + .../CollectionView/GridViewLayout.cs | 25 +- 4 files changed, 581 insertions(+), 13 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml new file mode 100644 index 00000000000..30f7239906c --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml.cs new file mode 100644 index 00000000000..bd1c9c099b2 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7593.xaml.cs @@ -0,0 +1,503 @@ +using System.Collections.ObjectModel; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using System; +using System.Threading.Tasks; +using System.Windows.Input; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Collections.Generic; +using Xamarin.Forms.Xaml; + +#if UITEST +using Xamarin.UITest; +using Xamarin.UITest.Queries; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +using System.Linq; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [NUnit.Framework.Category(UITestCategories.CollectionView)] +#endif +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7593, "[Bug][iOS] Pull-to-refresh crash in horizontal grid CollectionView/RefreshView", PlatformAffected.iOS)] + public partial class Issue7593 : TestContentPage + { +#if APP + public Issue7593() + { + Device.SetFlags(new List { CollectionView.CollectionViewExperimental }); + + InitializeComponent(); + + BindingContext = new ViewModel7593(); + } +#endif + + protected override void Init() + { + + } + } + + [Preserve(AllMembers = true)] + public class ViewModel7593 : INotifyPropertyChanged + { + int itemCount = 10; + const int MaximumItemCount = 50; + const int PageSize = 10; + const int RefreshDuration = 2; + bool isRefreshing; + + public ObservableCollection Animals { get; private set; } = new ObservableCollection(); + + public bool IsRefreshing + { + get { return isRefreshing; } + set + { + isRefreshing = value; + OnPropertyChanged(); + } + } + + public ICommand LoadMoreDataCommand => new Command(GetNextPageOfData); + public ICommand RefreshCommand => new Command(async () => await RefreshDataAsync()); + + public ViewModel7593() + { + AddBears(); + } + + void GetNextPageOfData() + { + switch (itemCount) + { + case 10: + AddCats(); + break; + case 20: + AddDogs(); + break; + case 30: + AddElephants(); + break; + case 40: + AddMonkeys(); + break; + } + + if (itemCount < MaximumItemCount) + { + itemCount += PageSize; + } + } + + async Task RefreshDataAsync() + { + IsRefreshing = true; + await Task.Delay(TimeSpan.FromSeconds(RefreshDuration)); + GetNextPageOfData(); + IsRefreshing = false; + } + + void AddBears() + { + Animals.Add(new Model7593 + { + Name = "American Black Bear", + Location = "North America", + Details = "The American black bear is a medium-sized bear native to North America. It is the continent's smallest and most widely distributed bear species. American black bears are omnivores, with their diets varying greatly depending on season and location. They typically live in largely forested areas, but do leave forests in search of food. Sometimes they become attracted to human communities because of the immediate availability of food. The American black bear is the world's most common bear species.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Asian Black Bear", + Location = "Asia", + Details = "The Asian black bear, also known as the moon bear and the white-chested bear, is a medium-sized bear species native to Asia and largely adapted to arboreal life. It lives in the Himalayas, in the northern parts of the Indian subcontinent, Korea, northeastern China, the Russian Far East, the Honshū and Shikoku islands of Japan, and Taiwan. It is classified as vulnerable by the International Union for Conservation of Nature (IUCN), mostly because of deforestation and hunting for its body parts.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG/180px-Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Brown Bear", + Location = "Northern Eurasia & North America", + Details = "The brown bear is a bear that is found across much of northern Eurasia and North America. In North America the population of brown bears are often called grizzly bears. It is one of the largest living terrestrial members of the order Carnivora, rivaled in size only by its closest relative, the polar bear, which is much less variable in size and slightly larger on average. The brown bear's principal range includes parts of Russia, Central Asia, China, Canada, the United States, Scandinavia and the Carpathian region, especially Romania, Anatolia and the Caucasus. The brown bear is recognized as a national and state animal in several European countries.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Kamchatka_Brown_Bear_near_Dvuhyurtochnoe_on_2015-07-23.jpg/320px-Kamchatka_Brown_Bear_near_Dvuhyurtochnoe_on_2015-07-23.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Grizzly-Polar Bear Hybrid", + Location = "Canadian Artic", + Details = "A grizzly–polar bear hybrid is a rare ursid hybrid that has occurred both in captivity and in the wild. In 2006, the occurrence of this hybrid in nature was confirmed by testing the DNA of a unique-looking bear that had been shot near Sachs Harbour, Northwest Territories on Banks Island in the Canadian Arctic. The number of confirmed hybrids has since risen to eight, all of them descending from the same female polar bear.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Grolar.JPG/276px-Grolar.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Sloth Bear", + Location = "Indian Subcontinent", + Details = "The sloth bear is an insectivorous bear species native to the Indian subcontinent. It is listed as Vulnerable on the IUCN Red List, mainly because of habitat loss and degradation. It has also been called labiated bear because of its long lower lip and palate used for sucking insects. Compared to brown and black bears, the sloth bear is lankier, has a long, shaggy fur and a mane around the face, and long, sickle-shaped claws. It evolved from the ancestral brown bear during the Pleistocene and through convergent evolution shares features found in insect-eating mammals.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Sloth_Bear_Washington_DC.JPG/320px-Sloth_Bear_Washington_DC.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Sun Bear", + Location = "Southeast Asia", + Details = "The sun bear is a bear species occurring in tropical forest habitats of Southeast Asia. It is listed as Vulnerable on the IUCN Red List. The global population is thought to have declined by more than 30% over the past three bear generations. Suitable habitat has been dramatically reduced due to the large-scale deforestation that has occurred throughout Southeast Asia over the past three decades. The sun bear is also known as the honey bear, which refers to its voracious appetite for honeycombs and honey.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a6/Sitting_sun_bear.jpg/319px-Sitting_sun_bear.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Polar Bear", + Location = "Artic Circle", + Details = "The polar bear is a hypercarnivorous bear whose native range lies largely within the Arctic Circle, encompassing the Arctic Ocean, its surrounding seas and surrounding land masses. It is a large bear, approximately the same size as the omnivorous Kodiak bear. A boar (adult male) weighs around 350–700 kg (772–1,543 lb), while a sow (adult female) is about half that size. Although it is the sister species of the brown bear, it has evolved to occupy a narrower ecological niche, with many body characteristics adapted for cold temperatures, for moving across snow, ice and open water, and for hunting seals, which make up most of its diet. Although most polar bears are born on land, they spend most of their time on the sea ice. Their scientific name means maritime bear and derives from this fact. Polar bears hunt their preferred food of seals from the edge of sea ice, often living off fat reserves when no sea ice is present. Because of their dependence on the sea ice, polar bears are classified as marine mammals.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/6/66/Polar_Bear_-_Alaska_%28cropped%29.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Spectacled Bear", + Location = "South America", + Details = "The spectacled bear, also known as the Andean bear or Andean short-faced bear and locally as jukumari (Aymara), ukumari (Quechua) or ukuku, is the last remaining short-faced bear. Its closest relatives are the extinct Florida spectacled bear, and the giant short-faced bears of the Middle to Late Pleistocene age. Spectacled bears are the only surviving species of bear native to South America, and the only surviving member of the subfamily Tremarctinae. The species is classified as Vulnerable by the IUCN because of habitat loss.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Spectacled_Bear_-_Houston_Zoo.jpg/264px-Spectacled_Bear_-_Houston_Zoo.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Short-faced Bear", + Location = "Extinct", + Details = "The short-faced bears is an extinct bear genus that inhabited North America during the Pleistocene epoch from about 1.8 Mya until 11,000 years ago. It was the most common early North American bear and was most abundant in California. There are two recognized species: Arctodus pristinus and Arctodus simus, with the latter considered to be one of the largest known terrestrial mammalian carnivores that has ever existed. It has been hypothesized that their extinction coincides with the Younger Dryas period of global cooling commencing around 10,900 BC.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/ArctodusSimusSkeleton.jpg/320px-ArctodusSimusSkeleton.jpg" + }); + Animals.Add(new Model7593 + { + Name = "California Grizzly Bear", + Location = "Extinct", + Details = "The California grizzly bear is an extinct subspecies of the grizzly bear, the very large North American brown bear. Grizzly could have meant grizzled (that is, with golden and grey tips of the hair) or fear-inspiring. Nonetheless, after careful study, naturalist George Ord formally classified it in 1815 – not for its hair, but for its character – as Ursus horribilis (terrifying bear). Genetically, North American grizzlies are closely related; in size and coloring, the California grizzly bear was much like the grizzly bear of the southern coast of Alaska. In California, it was particularly admired for its beauty, size and strength. The grizzly became a symbol of the Bear Flag Republic, a moniker that was attached to the short-lived attempt by a group of American settlers to break away from Mexico in 1846. Later, this rebel flag became the basis for the state flag of California, and then California was known as the Bear State.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/d/de/Monarch_the_bear.jpg" + }); + } + + void AddCats() + { + Animals.Add(new Model7593 + { + Name = "Abyssinian", + Location = "Ethopia", + Details = "The Abyssinian is a breed of domestic short-haired cat with a distinctive tickedtabby coat, in which individual hairs are banded with different colors. The breed is named for Abyssinia (now called Ethiopia), where it is believed to have originated.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Gustav_chocolate.jpg/168px-Gustav_chocolate.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Arabian Mau", + Location = "Arabian Peninsula", + Details = "The Arabian Mau is a formal breed of domestic cat, originated from the desert cat, a short-haired landrace native to the desert of the Arabian Peninsula. It lives there in the streets and has adapted very well to the extreme climate. The Arabian Mau is recognized as a formal breed by few fancier and breeder organization and cat registry, World Cat Federation (WCF) and Emirates Feline Federation (EFF). Based on one landrace, the Arabian Mau is a natural breed.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/d/d3/Bex_Arabian_Mau.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Bengal", + Location = "Asia", + Details = "The Bengal cat is a domesticated cat breed created from hybrids of domestic cats and the Asian leopard cat – the breed name comes from the taxonomic name. Back-crossing to domestic cats is then done with the goal of creating a healthy, and docile cat with wild-looking, high-contrast coat. Bengals have a wild appearance and may show spots, rosettes, arrowhead markings, or marbling.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Paintedcats_Red_Star_standing.jpg/187px-Paintedcats_Red_Star_standing.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Burmese", + Location = "Thailand", + Details = "The Burmese cat is a breed of domestic cat, originating in Thailand, believed to have its roots near the present Thai-Burma border and developed in the United States and Britain.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/04/Blissandlucky11.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Cyprus", + Location = "Cyprus", + Details = "Cyprus cats, also known as Cypriot cats, Saint Helen cats, and Saint Nicholas cats, are a landrace of domestic cat found across the island of Cyprus. A standardized breed is being developed from them; among cat fancier and breeder organizations, it is presently fully recognized by the World Cat Federation (WCF), with breeding regulated by the World Cat Congress (WCC), under the name Aphrodite's Giant; and provisionally by The International Cat Association (TICA) as the Aphrodite. All three organizations permit shorthaired and semi-longhaired versions and no out-crossing to other breeds.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/CyprusShorthair.jpg/320px-CyprusShorthair.jpg" + }); + Animals.Add(new Model7593 + { + Name = "German Rex", + Location = "Germany", + Details = "The German Rex is a medium-sized breed with slender legs of a medium length. The head is round with well-developed cheeks and large, open ears. The eyes are of medium size in colours related to the coat colour. The coat is silky and short with a tendency to curl. The whiskers also curl, though less strongly than in the Cornish Rex. They may be nearly straight. All colours of coat, including white, are allowed. The body development is heavier than in the Cornish Rex - more like the European Shorthairs.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c7/German_rex_harry_%28cropped%29.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Highlander", + Location = "United States", + Details = "The Highlander (also known as the Highlander Shorthair, and originally as the Highland Lynx), is an experimental breed of cat. The unique appearance of the Highlander comes from the deliberate cross between the Desert Lynx and the Jungle Curl breeds, also recently developed. The latter of these has some non-domestic ancestry from two Asian small cat species, the leopard cat and jungle cat, making the Highlander nominally a feline hybrid, though its foundation stock is mostly domestic cat.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Highlander-7.jpg/293px-Highlander-7.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Manx", + Location = "Isle of Man", + Details = "The Manx cat is a breed of domestic cat originating on the Isle of Man, with a naturally occurring mutation that shortens the tail. Many Manx have a small stub of a tail, but Manx cats are best known as being entirely tailless; this is the most distinguishing characteristic of the breed, along with elongated hind legs and a rounded head. Manx cats come in all coat colours and patterns, though all-white specimens are rare, and the coat range of the original stock was more limited. Long-haired variants are sometimes considered a separate breed, the Cymric. Manx are prized as skilled hunters, and thus have often been sought by farmers with rodent problems, and been a preferred ship's cat breed. They are said to be social, tame and active. An old local term for the cats on their home island is stubbin. Manx have been exhibited in cat shows since the 1800s, with the first known breed standard published in 1903.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/en/9/9b/Manx_cat_by_Karen_Weaver.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Peterbald", + Location = "Russia", + Details = "The Peterbald is a cat breed of Russian origin. It was created in St Petersburg in 1994 from an experimental breeding by Olga S. Mironova. They resemble Oriental Shorthairs with a hair-losing gene. The breed was accepted for Championship class competition in 2009.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c7/Peterbald_male_Shango_by_Irina_Polunina.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Scottish Fold", + Location = "Scotland", + Details = "The Scottish Fold is a breed of domestic cat with a natural dominant-gene mutation that affects cartilage throughout the body, causing the ears to fold, bending forward and down towards the front of the head, which gives the cat what is often described as an owl-like appearance.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Adult_Scottish_Fold.jpg/240px-Adult_Scottish_Fold.jpg" + }); + } + + void AddDogs() + { + Animals.Add(new Model7593 + { + Name = "Afghan Hound", + Location = "Afghanistan", + Details = "The Afghan Hound is a hound that is distinguished by its thick, fine, silky coat and its tail with a ring curl at the end. The breed is selectively bred for its unique features in the cold mountains of Afghanistan. Other names for this breed are Kuchi Hound, Tāzī, Balkh Hound, Baluchi Hound, Barakzai Hound, Shalgar Hound, Kabul Hound, Galanday Hound or sometimes incorrectly African Hound.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/6/69/Afghane.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Alpine Dachsbracke", + Location = "Austria", + Details = "The Alpine Dachsbracke is a small breed of dog of the scent hound type originating in Austria. The Alpine Dachsbracke was bred to track wounded deer as well as boar, hare, and fox. It is highly efficient at following a trail even after it has gone cold. The Alpine Dachsbracke is very sturdy, and Austria is said to be the country of origin.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Alpejski_gończy_krótkonożny_g99.jpg/320px-Alpejski_gończy_krótkonożny_g99.jpg" + }); + Animals.Add(new Model7593 + { + Name = "American Bulldog", + Location = "United States", + Details = "The American Bulldog is a breed of utility dog descended from the Old English Bulldog.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/5/5e/American_Bulldog_600.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Bearded Collie", + Location = "Scotland", + Details = "The Bearded Collie, or Beardie, is a herding breed of dog once used primarily by Scottish shepherds, but now mostly a popular family companion. Bearded Collies have an average weight of 18–27 kilograms (40–60 lb). Males are around 51–56 centimetres (20–22 in) tall at the withers while females are around 51–53 centimetres (20–21 in) tall.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/9/9c/Bearded_Collie_600.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Boston Terrier", + Location = "United States", + Details = "The Boston Terrier is a breed of dog originating in the United States of America. This American Gentleman was accepted in 1893 by the American Kennel Club as a non-sporting breed. Color and markings are important when distinguishing this breed to the AKC standard. They should be either black, brindle or seal with white markings. Bostons are small and compact with a short tail and erect ears. The AKC says they are highly intelligent and very easily trained. They are friendly and can be stubborn at times. The average life span of a Boston is around 11 to 13 years, though some can live well into their teens.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Boston-terrier-carlos-de.JPG/320px-Boston-terrier-carlos-de.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Canadian Eskimo", + Location = "Canada", + Details = "The Canadian Eskimo Dog is an Arctic breed of working dog, which is often considered to be one of North America's oldest and rarest remaining purebred indigenous domestic canines. Other names include qimmiq or qimmit. They were brought from Siberia to North America by the Thule people 1,000 years ago, along with the Greenland Dog that is genetically identical.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/7/79/Spoonsced.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Eurohound", + Location = "Scandinavia", + Details = "A Eurohound (also known as a Eurodog or Scandinavian hound) is a type of dog bred for sled dog racing. The Eurohound is typically crossbred from the Alaskan husky group and any of a number of pointing breeds.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/9/98/Eurohound.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Irish Terrier", + Location = "Ireland", + Details = "The Irish Terrier is a dog breed from Ireland, one of many breeds of terrier. The Irish Terrier is considered one of the oldest terrier breeds. The Dublin dog show in 1873 was the first to provide a separate class for Irish Terriers. By the 1880s, Irish Terriers were the fourth most popular breed in Ireland and Britain.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/IrishTerrierSydenhamHillWoods.jpg/180px-IrishTerrierSydenhamHillWoods.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Kerry Beagle", + Location = "Ireland", + Details = "The Kerry Beagle is one of the oldest Irish hound breeds, believed to be descendant from the Old Southern Hound or the Celtic Hounds. It is the only extant scent hound breed native to Ireland.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/7/75/Kerry_Beagle_from_1915.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Norwegian Buhund", + Location = "Norway", + Details = "The Norwegian Buhund is a breed of dog of the spitz type. It is closely related to the Icelandic Sheepdog and the Jämthund. The Buhund is used as an all purpose farm and herding dog, as well as watch dog and a nanny dog.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/3/3b/Norwegian_Buhund_600.jpg" + }); + } + + void AddElephants() + { + Animals.Add(new Model7593 + { + Name = "African Bush Elephant", + Location = "Africa", + Details = "The African bush elephant, also known as the African savanna elephant, is the larger of the two species of African elephants, and the largest living terrestrial animal. These elephants were previously regarded as the same species, but the African forest elephant has been reclassified as L. cyclotis.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/African_Elephant_%28Loxodonta_africana%29_bull_%2831100819046%29.jpg/320px-African_Elephant_%28Loxodonta_africana%29_bull_%2831100819046%29.jpg" + }); + Animals.Add(new Model7593 + { + Name = "African Forest Elephant", + Location = "Africa", + Details = "The African forest elephant is a forest-dwelling species of elephant found in the Congo Basin. It is the smallest of the three extant species of elephant, but still one of the largest living terrestrial animals. The African forest elephant and the African bush elephan were considered to be one species until genetic studies indicated that they separated an estimated 2–7 million years ago. From an estimated population size of over 2 million prior to the colonization of Africa, the population in 2015 is estimated to be about 100,000 forest elephants, mostly living in the forests of Gabon. Due to a slower birth rate, the forest elephant takes longer to recover from poaching, which caused its population to fall by 65% from 2002 to 2014.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/6/6a/African_Forest_Elephant.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Desert Elephant", + Location = "Africa", + Details = "Desert elephants, or desert-adapted elephants are not a distinct species of elephant but are African bush elephants that have made their homes in the Namib and Sahara deserts in Africa. It was believed at one time that they were a subspecies of the African bush elephant but this is no longer thought to be the case. Desert-dwelling elephants were once more widespread in Africa than they are now and are currently found only in Namibia and Mali. They tend to migrate from one waterhole to another following traditional routes which depend on the seasonal availability of food and water. They face pressure from poaching and from changes in land use by humans.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Desert_elephants_in_the_Huab_River.jpg/320px-Desert_elephants_in_the_Huab_River.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Borneo Elephant", + Location = "Asia", + Details = "The Borneo elephant, also called the Borneo pygmy elephant, is a subspecies of Asian elephant that inhabits northeastern Borneo, in Indonesia and Malaysia. Its origin remains the subject of debate. A definitive subspecific classification as Elephas maximus borneensis awaits a detailed range-wide morphometric and genetic study. Since 1986, Elephas maximus has been listed as Endangered on the IUCN Red List as the population has declined by at least 50% over the last three generations, estimated to be 60–75 years. The species is pre-eminently threatened by habitat loss, degradation and fragmentation.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Elephant_%40_kabini.jpg/180px-Elephant_%40_kabini.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Indian Elephant", + Location = "Asia", + Details = "The Indian elephant is one of three extant recognized subspecies of the Asian elephant and native to mainland Asia. Since 1986, the Asian elephant has been listed as Endangered on the IUCN Red List as the wild population has declined by at least 50% since the 1930s to 1940s, i.e. three elephant generations. The Asian elephant is threatened by habitat loss, degradation and fragmentation.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Elephas_maximus_%28Bandipur%29.jpg/320px-Elephas_maximus_%28Bandipur%29.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Sri Lankan Elephant", + Location = "Asia", + Details = "The Sri Lankan elephant is one of three recognized subspecies of the Asian elephant, and native to Sri Lanka. Since 1986, Elephas maximus has been listed as endangered by IUCN as the population has declined by at least 50% over the last three generations, estimated to be 60–75 years. The species is primarily threatened by habitat loss, degradation and fragmentation.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Srilankan_tuskelephant.jpg/213px-Srilankan_tuskelephant.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Sumatran Elephant", + Location = "Asia", + Details = "The Sumatran elephant is one of three recognized subspecies of the Asian elephant, and native to the Indonesia island of Sumatra. In 2011, the Sumatran elephant has been classified as critically endangered by IUCN as the population has declined by at least 80% over the last three generations, estimated to be about 75 years. The subspecies is pre-eminently threatened by habitat loss, degradation and fragmentation, and poaching; over 69% of potential elephant habitat has been lost within the last 25 years. Much of the remaining forest cover is in blocks smaller than 250 km2 (97 sq mi), which are too small to contain viable elephant populations.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Borobudur-Temple-Park_Elephant-cage-01.jpg/320px-Borobudur-Temple-Park_Elephant-cage-01.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Pygmy Elephant", + Location = "Africa & Asia", + Details = "Pygmy elephants live in both Africa and Asia.The African pygmy elephant is currently considered to be a tiny morph of the African forest elephant. The Borneo elephant, a well-documented variety of elephant, is also calledmpygmy elephant. This elephant, inhabiting tropical rainforest in north Borneo (east Sabah and extreme north Kalimantan), was long thought to be identical to the Asian elephant and descended from a captive population. In 2003, DNA comparison revealed them to be probably a new subspecies.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/9/93/Borneo-elephant-PLoS_Biology.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Mammoth", + Location = "Extinct", + Details = "A mammoth is any species of the extinct genus Mammuthus, one of the many genera that make up the order of trunked mammals called proboscideans. The various species of mammoth were commonly equipped with long, curved tusks and, in northern species, a covering of long hair. They lived from the Pliocene epoch (from around 5 million years ago) into the Holocene at about 4,000 years ago, and various species existed in Africa, Europe, Asia, and North America. They were members of the family Elephantidae, which also contains the two genera of modern elephants and their ancestors.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Columbian_mammoth.JPG/320px-Columbian_mammoth.JPG" + }); + Animals.Add(new Model7593 + { + Name = "Mastodon", + Location = "Extinct", + Details = "Mastodons are any species of extinct proboscideans in the genus Mammut, distantly related to elephants, that inhabited North and Central America during the late Miocene or late Pliocene up to their extinction at the end of the Pleistocene 10,000 to 11,000 years ago. Mastodons lived in herds and were predominantly forest-dwelling animals that fed on a mixed diet obtained by browsing and grazing with a seasonal preference for browsing, similar to living elephants.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Mammut_americanum.jpg/320px-Mammut_americanum.jpg" + }); + } + + void AddMonkeys() + { + Animals.Add(new Model7593 + { + Name = "Baboon", + Location = "Africa & Asia", + Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Capuchin Monkey", + Location = "Central & South America", + Details = "The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Blue Monkey", + Location = "Central and East Africa", + Details = "The blue monkey or diademed monkey is a species of Old World monkey native to Central and East Africa, ranging from the upper Congo River basin east to the East African Rift and south to northern Angola and Zambia", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-BlueMonkey.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Squirrel Monkey", + Location = "Central & South America", + Details = "The squirrel monkeys are the New World monkeys of the genus Saimiri. They are the only genus in the subfamily Saimirinae. The name of the genus Saimiri is of Tupi origin, and was also used as an English name by early researchers.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Saimiri_sciureus-1_Luc_Viatour.jpg/220px-Saimiri_sciureus-1_Luc_Viatour.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Golden Lion Tamarin", + Location = "Brazil", + Details = "The golden lion tamarin also known as the golden marmoset, is a small New World monkey of the family Callitrichidae.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Golden_lion_tamarin_portrait3.jpg/220px-Golden_lion_tamarin_portrait3.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Japanese Macaque", + Location = "Japan", + Details = "The Japanese macaque, is a terrestrial Old World monkey species native to Japan. They are also sometimes known as the snow monkey because they live in areas where snow covers the ground for months each", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Macaca_fuscata_fuscata1.jpg/220px-Macaca_fuscata_fuscata1.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Mandrill", + Location = "Southern Cameroon, Gabon, Equatorial Guinea, and Congo", + Details = "The mandrill is a primate of the Old World monkey family, closely related to the baboons and even more closely to the drill. It is found in southern Cameroon, Gabon, Equatorial Guinea, and Congo.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Mandrill_at_san_francisco_zoo.jpg/220px-Mandrill_at_san_francisco_zoo.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Proboscis Monkey", + Location = "Borneo", + Details = "The proboscis monkey or long-nosed monkey, known as the bekantan in Malay, is a reddish-brown arboreal Old World monkey that is endemic to the south-east Asian island of Borneo.", + ImageUrl = "http://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Proboscis_Monkey_in_Borneo.jpg/250px-Proboscis_Monkey_in_Borneo.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Red-shanked Douc", + Location = "Vietnam, Laos", + Details = "The red-shanked douc is a species of Old World monkey, among the most colourful of all primates. This monkey is sometimes called the \"costumed ape\" for its extravagant appearance. From its knees to its ankles it sports maroon-red \"stockings\", and it appears to wear white forearm length gloves. Its attire is finished with black hands and feet. The golden face is framed by a white ruff, which is considerably fluffier in males. The eyelids are a soft powder blue. The tail is white with a triangle of white hair at the base. Males of all ages have a white spot on both sides of the corners of the rump patch, and red and white genitals.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Portrait_of_a_Douc.jpg/159px-Portrait_of_a_Douc.jpg" + }); + Animals.Add(new Model7593 + { + Name = "Gray-shanked Douc", + Location = "Vietnam", + Details = "The gray-shanked douc langur is a douc species native to the Vietnamese provinces of Quảng Nam, Quảng Ngãi, Bình Định, Kon Tum, and Gia Lai. The total population is estimated at 550 to 700 individuals. In 2016, Dr Benjamin Rawson, Country Director of Fauna & Flora International - Vietnam Programme, announced a discovery of an additional population of more than 500 individuals found in Central Vietnam, bringing the total population up to approximately 1000 individuals.", + ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/Cuc.Phuong.Primate.Rehab.center.jpg/320px-Cuc.Phuong.Primate.Rehab.center.jpg" + }); + } + + #region INotifyPropertyChanged + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + } + + [Preserve(AllMembers = true)] + public class Model7593 + { + public string Name { get; set; } + public string Location { get; set; } + public string Details { get; set; } + public string ImageUrl { get; set; } + + public override string ToString() + { + return Name; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 95974a6846c..864ab31471e 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -55,6 +55,9 @@ Code + + Code + @@ -1410,6 +1413,9 @@ Issue7512.xaml + + Issue7593.xaml + @@ -1465,4 +1471,10 @@ MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:Compile + + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/GridViewLayout.cs b/Xamarin.Forms.Platform.iOS/CollectionView/GridViewLayout.cs index 0f16137783d..14c95078e46 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/GridViewLayout.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/GridViewLayout.cs @@ -99,35 +99,34 @@ public override CGSize CollectionViewContentSize public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect) { + var layoutAttributesForRectElements = base.LayoutAttributesForElementsInRect(rect); + if (!NeedsPartialColumnAdjustment()) - { - return base.LayoutAttributesForElementsInRect(rect); + { + return layoutAttributesForRectElements; } // When we implement Groups, we'll have to iterate over all of them to adjust and this will // be a lot more complicated. But until then, we only have to worry about section 0 - int section = 0; - - var fullColumns = base.LayoutAttributesForElementsInRect(rect); + var section = 0; var itemCount = CollectionView.NumberOfItemsInSection(section); - if (fullColumns.Length == itemCount) + if (layoutAttributesForRectElements.Length == itemCount) { - return fullColumns; + return layoutAttributesForRectElements; } - var missingCellCount = itemCount % _itemsLayout.Span; + var layoutAttributesForAllCells = new UICollectionViewLayoutAttributes[itemCount]; - UICollectionViewLayoutAttributes[] allCells = new UICollectionViewLayoutAttributes[fullColumns.Length + missingCellCount]; - fullColumns.CopyTo(allCells, 0); + layoutAttributesForRectElements.CopyTo(layoutAttributesForAllCells, 0); - for (int n = fullColumns.Length; n < allCells.Length; n++) + for (int i = layoutAttributesForRectElements.Length; i < layoutAttributesForAllCells.Length; i++) { - allCells[n] = LayoutAttributesForItem(NSIndexPath.FromItemSection(n, section)); + layoutAttributesForAllCells[i] = LayoutAttributesForItem(NSIndexPath.FromItemSection(i, section)); } - return allCells; + return layoutAttributesForAllCells; } bool NeedsPartialColumnAdjustment(int section = 0) From 7e136aef4a38f6ee612644f589b1ce1d53d8724e Mon Sep 17 00:00:00 2001 From: melimion <33512073+melimion@users.noreply.github.com> Date: Fri, 4 Oct 2019 17:08:45 +0300 Subject: [PATCH 092/203] [macOS] Fix Image Rotation issue (#7815) fixes #5395 * AnchorPoint fix * rotation and translationY direction fix * clipping fix * test added * anchor point and position translation fixed * test update --- .../Issue5395.cs | 59 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../VisualElementTracker.cs | 24 ++++++-- 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5395.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5395.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5395.cs new file mode 100644 index 00000000000..5136a9586d6 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5395.cs @@ -0,0 +1,59 @@ +using System; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 5395, "[macOs] Image Rotation issue", PlatformAffected.macOS)] + public class Issue5395 : ContentPage + { + public Issue5395() + { + var sl = new StackLayout() { Orientation = StackOrientation.Vertical }; + + sl.Children.Add(new Label() { Text = "Image should rotate clockwise around its center" }); + sl.Children.Add(new TestImage(0.5, 0.5, true, false)); + + sl.Children.Add(new Label() { Text = "Image should rotate clockwise around its top left corner" }); + sl.Children.Add(new TestImage(0, 0, true, false)); + + sl.Children.Add(new Label() { Text = "Image should rotate clockwise around its top right corner" }); + sl.Children.Add(new TestImage(1, 0, true, false)); + + sl.Children.Add(new Label() { Text = "Image should rotate clockwise around its bottom right corner" }); + sl.Children.Add(new TestImage(1, 1, true, false)); + + sl.Children.Add(new Label() { Text = "Image should scale on its center" }); + sl.Children.Add(new TestImage(0.5, 0.5, false, true)); + + Content = sl; + } + class TestImage : Image + { + public TestImage(double anchorx, double anchory, bool rotate, bool scale) + { + VerticalOptions = HorizontalOptions = LayoutOptions.Center; + Aspect = Aspect.AspectFit; + Source = "bank.png"; + WidthRequest = 30; + HeightRequest = 30; + BackgroundColor = Color.Red; + AnchorX = anchorx; + AnchorY = anchory; + //TranslationX = -50; + //TranslationY = 25; + if (rotate) + { + this.RotateTo(3600, 10000); + } + if (scale) + { + this.ScaleTo(2, 4000); + } + Margin = 30; + } + } + } +} + diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 4f795e8f4a8..5425d211e4e 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1031,6 +1031,7 @@ + diff --git a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs index 2661476e3ca..d78b35db603 100644 --- a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs +++ b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs @@ -254,19 +254,35 @@ void update() #endif return; } -#if __MOBILE__ - caLayer.AnchorPoint = new PointF(anchorX, anchorY); -#else - caLayer.AnchorPoint = new PointF(anchorX - 0.5f, anchorY - 0.5f); +#if !__MOBILE__ + // Y-axe on macos is inverted + translationY = -translationY; + anchorY = 1 - anchorY; + + // rotation direction on macos also inverted + rotationX = -rotationX; + rotationY = -rotationY; + rotation = -rotation; + + //otherwise scaled/rotated image clipped by parent bounds + caLayer.MasksToBounds = false; #endif + caLayer.AnchorPoint = new PointF(anchorX, anchorY); caLayer.Opacity = opacity; const double epsilon = 0.001; +#if !__MOBILE__ + // fix position, position in macos is aslo relative to anchor point + // but it's (0,0) by default, so we don't need to substract 0.5 + transform = transform.Translate(anchorX * width, 0, 0); + transform = transform.Translate(0, anchorY * height, 0); +#else // position is relative to anchor point if (Math.Abs(anchorX - .5) > epsilon) transform = transform.Translate((anchorX - .5f) * width, 0, 0); if (Math.Abs(anchorY - .5) > epsilon) transform = transform.Translate(0, (anchorY - .5f) * height, 0); +#endif if (Math.Abs(translationX) > epsilon || Math.Abs(translationY) > epsilon) transform = transform.Translate(translationX, translationY, 0); From f9b09b59039c750aea2e6e42ca41b63bc3cecd4c Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Fri, 4 Oct 2019 16:09:41 +0200 Subject: [PATCH 093/203] [C] rename file (#7818) fixes #7816 - fixes #7816 --- .../Items/{ListItemsLayout.cs => LinearItemsLayout.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Xamarin.Forms.Core/Items/{ListItemsLayout.cs => LinearItemsLayout.cs} (100%) diff --git a/Xamarin.Forms.Core/Items/ListItemsLayout.cs b/Xamarin.Forms.Core/Items/LinearItemsLayout.cs similarity index 100% rename from Xamarin.Forms.Core/Items/ListItemsLayout.cs rename to Xamarin.Forms.Core/Items/LinearItemsLayout.cs From 7d77bb5043abb765701296bfd20d5087a014a7ed Mon Sep 17 00:00:00 2001 From: Stuart Lang Date: Sun, 6 Oct 2019 19:56:33 +0100 Subject: [PATCH 094/203] Fix typo in RefreshViewRenderer (#7831) --- .../Renderers/RefreshViewRenderer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs index 6ffda38edb5..0deb8b584b9 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/RefreshViewRenderer.cs @@ -10,7 +10,7 @@ public class RefreshViewRenderer : ViewRenderer, IEffectCon bool _isDisposed; bool _isRefreshing; bool _usingLargeTitles; - nfloat _origininalY; + nfloat _originalY; nfloat _refreshControlHeight; UIView _refreshControlParent; UIRefreshControl _refreshControl; @@ -112,9 +112,9 @@ bool TryOffsetRefresh(UIView view, bool refreshing) return true; if (refreshing) - scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _origininalY - _refreshControlHeight), true); + scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _originalY - _refreshControlHeight), true); else - scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _origininalY), true); + scrollView.SetContentOffset(new CoreGraphics.CGPoint(0, _originalY), true); return true; } @@ -150,7 +150,7 @@ bool TryInsertRefresh(UIView view, int index = 0) scrollView.AlwaysBounceVertical = true; - _origininalY = scrollView.ContentOffset.Y; + _originalY = scrollView.ContentOffset.Y; _refreshControlHeight = _refreshControl.Frame.Size.Height; return true; @@ -217,4 +217,4 @@ void IEffectControlProvider.RegisterEffect(Effect effect) VisualElementRenderer.RegisterEffect(effect, this, NativeView); } } -} \ No newline at end of file +} From 22e91b7744c908929efe171e1eac5faecb1def6c Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 7 Oct 2019 13:28:04 +0100 Subject: [PATCH 095/203] [Build]Share git version (#6935) --- .gitignore | 1 + Version.targets | 5 +++++ build.cake | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 468b5de37e5..9a6e30f1517 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ caketools/ *.binlog .ionide/** /.nuspec +XamarinFormsVersionFile.txt diff --git a/Version.targets b/Version.targets index 301489cbbef..6f6a576f0a0 100644 --- a/Version.targets +++ b/Version.targets @@ -55,6 +55,11 @@ + + + + + diff --git a/build.cake b/build.cake index ccc857b8609..4ed65492872 100644 --- a/build.cake +++ b/build.cake @@ -24,6 +24,7 @@ PowerShell: #addin "nuget:?package=Cake.Android.SdkManager&version=3.0.2" #addin "nuget:?package=Cake.Boots&version=1.0.0.291" +#addin "nuget:?package=Cake.FileHelpers&version=3.2.0" ////////////////////////////////////////////////////////////////////// // TOOLS ////////////////////////////////////////////////////////////////////// @@ -167,6 +168,10 @@ Task("NuGetPack") Task("_NuGetPack") .Does(() => { + var nugetVersionFile = + GetFiles("XamarinFormsVersionFile.txt"); + var nugetversion = FileReadText(nugetVersionFile.First()); + Information("Nuget Version: {0}", nugetversion); var nugetPackageDir = Directory("./artifacts"); From eff4e4090be0b620d37323cc9aeafa3b2be8b04e Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Mon, 7 Oct 2019 16:09:51 +0200 Subject: [PATCH 096/203] [X] pass the RootAssembly to DT Context (#7853) regression introduced by #7531 - fixes #7830 --- .../Issues/Gh7830.xaml | 16 +++++++++ .../Issues/Gh7830.xaml.cs | 35 +++++++++++++++++++ Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml.cs diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml new file mode 100644 index 00000000000..f102050ee64 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml.cs new file mode 100644 index 00000000000..7e397563bcc --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7830.xaml.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.Forms; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public partial class Gh7830 : ContentPage + { + public static string StaticText = "Foo"; + public Gh7830() => InitializeComponent(); + public Gh7830(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; + + [Test] + public void CanResolvexStaticWithShortName([Values(false, true)]bool useCompiledXaml) + { + var layout = new Gh7830(useCompiledXaml); + var cell = layout.listView.ItemTemplate.CreateContent() as ViewCell; + Assert.That((cell.View as Label).Text, Is.EqualTo(StaticText)); + } + } + } +} diff --git a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs index 087409d8cd2..705e3f6bc21 100644 --- a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs +++ b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs @@ -701,7 +701,7 @@ void SetTemplate(ElementTemplate dt, INode node) ((IDataTemplate)dt).LoadTemplate = () => { #pragma warning restore 0612 var cnode = node.Clone(); - var context = new HydrationContext { ParentContext = Context, RootElement = Context.RootElement, ExceptionHandler = Context.ExceptionHandler }; + var context = new HydrationContext { ParentContext = Context, RootAssembly = Context.RootAssembly, RootElement = Context.RootElement, ExceptionHandler = Context.ExceptionHandler }; cnode.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), node.Parent); //set parents for {StaticResource} cnode.Accept(new ExpandMarkupsVisitor(context), null); cnode.Accept(new NamescopingVisitor(context), null); From 5ef1deb78b7228b3e3eb63e055fddae1201fc013 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 7 Oct 2019 09:41:24 -0600 Subject: [PATCH 097/203] Ignore empty cache and don't cache invalid stream (#7764) * Ignore empty cache and don't cache invalid stream * - msbuild locator --- Xamarin.Forms.Core/UriImageSource.cs | 39 ++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Xamarin.Forms.Core/UriImageSource.cs b/Xamarin.Forms.Core/UriImageSource.cs index cb073ef8ce1..e0562805299 100644 --- a/Xamarin.Forms.Core/UriImageSource.cs +++ b/Xamarin.Forms.Core/UriImageSource.cs @@ -124,8 +124,12 @@ async Task GetStreamAsync(Uri uri, CancellationToken cancellationToken = { cancellationToken.ThrowIfCancellationRequested(); - Stream stream; - if (!CachingEnabled) + Stream stream = null; + + if(CachingEnabled) + stream = await GetStreamFromCacheAsync(uri, cancellationToken).ConfigureAwait(false); + + if (stream == null) { try { @@ -137,8 +141,7 @@ async Task GetStreamAsync(Uri uri, CancellationToken cancellationToken = stream = null; } } - else - stream = await GetStreamFromCacheAsync(uri, cancellationToken).ConfigureAwait(false); + return stream; } @@ -183,14 +186,28 @@ async Task GetStreamAsyncUnchecked(string key, Uri uri, CancellationToke return null; } - Stream writeStream = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false); - await stream.CopyToAsync(writeStream, 16384, cancellationToken).ConfigureAwait(false); - if (writeStream != null) - writeStream.Dispose(); + if (stream == null || !stream.CanRead) + { + stream?.Dispose(); + return null; + } - stream.Dispose(); + try + { + Stream writeStream = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false); + await stream.CopyToAsync(writeStream, 16384, cancellationToken).ConfigureAwait(false); + if (writeStream != null) + writeStream.Dispose(); + + stream.Dispose(); - return await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false); + return await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false); + } + catch (Exception ex) + { + Log.Warning("Image Loading", $"Error getting stream for {Uri}: {ex}"); + return null; + } } async Task GetStreamFromCacheAsync(Uri uri, CancellationToken cancellationToken) @@ -209,7 +226,7 @@ async Task GetStreamFromCacheAsync(Uri uri, CancellationToken cancellati { await sem.WaitAsync(cancellationToken); Stream stream = await GetStreamAsyncUnchecked(key, uri, cancellationToken); - if (stream == null) + if (stream == null || stream.Length == 0 || !stream.CanRead) { sem.Release(); return null; From 6ba52f79b51da48386df8f5395fee6b33cc3cff0 Mon Sep 17 00:00:00 2001 From: maexsp Date: Tue, 8 Oct 2019 13:11:44 +0200 Subject: [PATCH 098/203] Spelling fix (#7868) Minor spelling mistake --- Xamarin.Forms.Platform.UAP/MasterDetailPageRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Xamarin.Forms.Platform.UAP/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.UAP/MasterDetailPageRenderer.cs index 8e3bfe8c8c2..f38cd6ba645 100644 --- a/Xamarin.Forms.Platform.UAP/MasterDetailPageRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/MasterDetailPageRenderer.cs @@ -320,7 +320,7 @@ void UpdateDetail() IVisualElementRenderer renderer = _detail.GetOrCreateRenderer(); element = renderer.ContainerElement; - UpdateToolbarVisibilty(); + UpdateToolbarVisibility(); } Control.Detail = element; @@ -388,7 +388,7 @@ void UpdateMaster() Control.Master = element; Control.MasterTitle = _master?.Title; - UpdateToolbarVisibilty(); + UpdateToolbarVisibility(); } void UpdateMode() @@ -411,7 +411,7 @@ void UpdateToolbarDynamicOverflowEnabled() Control.ToolbarDynamicOverflowEnabled = Element.OnThisPlatform().GetToolbarDynamicOverflowEnabled(); } - void UpdateToolbarVisibilty() + void UpdateToolbarVisibility() { // Enforce consistency rules on toolbar Control.ShouldShowToolbar = _detail is NavigationPage || _master is NavigationPage; From 51fb38f30b7593bffc2cd25b221599010d42c0f9 Mon Sep 17 00:00:00 2001 From: adrianknight89 Date: Tue, 8 Oct 2019 10:44:49 -0500 Subject: [PATCH 099/203] [iOS] Alternative proposal for PR 7794 (#7811) * emptyview fix * fix * added collectionview flag * added using for List * fix formatting * fix formatting 2 * fix formatting 3 * fixes * fix test --- .../Issue7758.xaml | 52 +++++++++++++++++++ .../Issue7758.xaml.cs | 25 +++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 14 ++++- .../CollectionView/ItemsViewController.cs | 14 +++++ .../CollectionView/ItemsViewRenderer.cs | 1 - 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml new file mode 100644 index 00000000000..3546c7b5b0e --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml.cs new file mode 100644 index 00000000000..2794be78aa8 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7758.xaml.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7758, "[iOS] EmptyView is not rendered in screen center", PlatformAffected.iOS)] + public partial class Issue7758 : TestContentPage + { + public Issue7758() + { +#if APP + Device.SetFlags(new List { CollectionView.CollectionViewExperimental }); + + InitializeComponent(); +#endif + } + + protected override void Init() + { + + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 9b181147884..e11cdf5f771 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -55,6 +55,9 @@ Code + + Code + Code @@ -1418,6 +1421,9 @@ Issue7593.xaml + + Issue7758.xaml + @@ -1479,4 +1485,10 @@ MSBuild:Compile - + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index f1c9e3d0baf..206635946e1 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -140,8 +140,13 @@ public override void ViewWillLayoutSubviews() if (!_initialConstraintsSet) { ItemsViewLayout.ConstrainTo(CollectionView.Bounds.Size); + UpdateEmptyView(); _initialConstraintsSet = true; } + else + { + ResizeEmptyView(); + } } protected virtual UICollectionViewDelegator CreateDelegator() @@ -267,6 +272,15 @@ internal void UpdateEmptyView() UpdateEmptyViewVisibility(ItemsSource?.ItemCount == 0); } + void ResizeEmptyView() + { + if (_emptyUIView != null) + _emptyUIView.Frame = CollectionView.Frame; + + if (_emptyViewFormsElement != null) + _emptyViewFormsElement.Layout(CollectionView.Frame.ToRectangle()); + } + protected void UpdateSubview(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement) { uiView?.RemoveFromSuperview(); diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs index 630351e523e..837b1d4b6f0 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs @@ -97,7 +97,6 @@ protected virtual void SetUpNewElement(ItemsView newElement) SetNativeControl(ItemsViewController.View); ItemsViewController.CollectionView.BackgroundColor = UIColor.Clear; - ItemsViewController.UpdateEmptyView(); UpdateHorizontalScrollBarVisibility(); UpdateVerticalScrollBarVisibility(); From 7863614ec0c14a406ee0f3046a4b30bbe66c8dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Tue, 8 Oct 2019 20:06:07 +0200 Subject: [PATCH 100/203] [iOS] Fix issue changing the ItemTemplate on CollectionView dynamically (#7743) * Fixed Issue 7742: Change CollectionView ItemTemplate programmatically * Fixed typo * Removed invalid test --- .../Issue7742.cs | 112 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionView/ItemsViewRenderer.cs | 4 + 3 files changed, 117 insertions(+) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7742.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7742.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7742.cs new file mode 100644 index 00000000000..ffe408800ea --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7742.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +using System.Linq; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7742, "(iOS) Changing ItemTemplate does not work as expected", PlatformAffected.iOS)] + public class Issue7742 : TestContentPage + { + CollectionView _collectionView; + + public Issue7742() + { + Title = "Issue 7742"; + } + + protected override void Init() + { + var instructions = new Label + { + Text = "Click the Button. If all cells render correctly, then the test passes." + }; + + var button = new Button + { + Text = "Change ItemTemplate", + AutomationId = "TemplateBtn" + }; + + button.Clicked += OnButton1Clicked; + + _collectionView = new CollectionView + { + BackgroundColor = Color.LightGreen, + SelectionMode = SelectionMode.None, + HeightRequest = 500 + }; + + var lines = new List(); + + for (int i = 0; i < 30; i++) + { + lines.Add(new Issue7742Model() { Text = $"Item {i}" }); + } + + _collectionView.ItemsSource = lines; + + var stack = new StackLayout(); + + stack.Children.Add(instructions); + stack.Children.Add(button); + stack.Children.Add(_collectionView); + + Content = stack; + } + + void OnButton1Clicked(object sender, EventArgs e) + { + _collectionView.ItemTemplate = CreateDataGridTemplate(2); + } + + DataTemplate CreateDataGridTemplate(int columns) + { + var template = new DataTemplate(() => + { + var grid = new Grid() { Padding = new Thickness(0), Margin = 0, RowSpacing = 0, ColumnSpacing = 0 }; + + grid.RowDefinitions.Clear(); + grid.ColumnDefinitions.Clear(); + grid.Children.Clear(); + grid.RowDefinitions.Add(new RowDefinition() { Height = 40 }); + grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + Label cell; + cell = new Label() { }; + cell.SetBinding(Label.TextProperty, "Text"); + cell.FontSize = 20; + cell.FontAttributes = FontAttributes.Bold; + cell.BackgroundColor = Color.LightBlue; + grid.Children.Add(cell, 0, 0); + + for (int i = 0; i < columns; i++) + { + grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + cell = new Label() { }; + cell.Text = "Col:" + i; + cell.FontAttributes = FontAttributes.Bold; + cell.BackgroundColor = Color.Beige; + grid.Children.Add(cell, i + 1, 0); + } + return grid; + }); + return template; + } + } + + [Preserve(AllMembers = true)] + public class Issue7742Model + { + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index e11cdf5f771..078b0fe9861 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -1084,6 +1084,7 @@ + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs index 837b1d4b6f0..e77d235f90c 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs @@ -41,6 +41,10 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE { ItemsViewController.UpdateItemsSource(); } + else if (changedProperty.Is(ItemsView.ItemTemplateProperty)) + { + UpdateLayout(); + } else if (changedProperty.IsOneOf(ItemsView.EmptyViewProperty, ItemsView.EmptyViewTemplateProperty)) { ItemsViewController.UpdateEmptyView(); From 2aeeece793a2b79f6e4dd1af0533fce8b23e376b Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 8 Oct 2019 12:16:03 -0600 Subject: [PATCH 101/203] Shell android dispose (#7768) * Better dispose of Android shell renderers * - fixup a few dispose paths * - set page to null * - header container --- .../Issue6640.cs | 95 ++++++++++++ .../Issue6738.cs | 9 +- .../TestPages/TestPages.cs | 11 +- ...rin.Forms.Controls.Issues.Shared.projitems | 1 + Xamarin.Forms.Core/Shell/ShellSection.cs | 1 - .../AppCompat/ShellFragmentContainer.cs | 7 +- .../Extensions/FragmentManagerExtensions.cs | 5 + .../Renderers/ColorChangeRevealDrawable.cs | 47 +++++- .../ShellBottomNavViewAppearanceTracker.cs | 74 ++++----- .../Renderers/ShellContentFragment.cs | 55 +++++-- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 62 +++++++- .../Renderers/ShellFlyoutRenderer.cs | 24 +++ .../ShellFlyoutTemplatedContentRenderer.cs | 142 +++++++++--------- .../Renderers/ShellFragmentPagerAdapter.cs | 13 +- .../Renderers/ShellItemRenderer.cs | 70 +++++++-- .../Renderers/ShellItemRendererBase.cs | 58 ++++--- .../Renderers/ShellRenderer.cs | 51 +++++-- .../Renderers/ShellSearchView.cs | 11 +- .../Renderers/ShellSearchViewAdapter.cs | 10 +- .../Renderers/ShellSectionRenderer.cs | 30 +++- .../ShellTabLayoutAppearanceTracker.cs | 15 +- .../ShellToolbarAppearanceTracker.cs | 11 +- .../Renderers/ShellToolbarTracker.cs | 15 +- 23 files changed, 582 insertions(+), 235 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs new file mode 100644 index 00000000000..858badb8ae3 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs @@ -0,0 +1,95 @@ +using System; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 6640, "[Android] Crash when app go background", PlatformAffected.Android)] + public class Issue6640 : TestContentPage + { + protected override void Init() + { + Content = new StackLayout + { + Children = + { + new Button + { + AutomationId = "GoToShell", + Text = "Click the button", + Command = new Command(() => + { + Application.Current.MainPage = new MasterPageShell(); + }) + } + } + }; + } + + [Preserve(AllMembers = true)] + public class MasterPageShell : TestShell + { + protected override void Init() + { + FlyoutHeader = new FlyoutHeader(); + Items.Add(new FlyoutItem + { + Title = "Issue 6640", + Items = + { + new ShellSection + { + Items = + { + new ShellContent + { + Content = new ContentPage + { + Content = new StackLayout + { + Children = + { + new Button + { + AutomationId = "Logout", + Text = "Click the button", + Command = new Command(() => + { + Application.Current.MainPage = new ContentPage + { + Content = new StackLayout + { + BackgroundColor = Color.White, + Children = + { + new Label + { + Text = $"Press Back Arrow to send application to background and wait few seconds.{Environment.NewLine}Application must not crash." + } + } + } + }; + }) + } + } + } + } + } + } + } + } + }); + } + } + + [Preserve(AllMembers = true)] + public class FlyoutHeader : TestContentPage + { + protected override void Init() + { + Content = new Grid(); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs index 0af2f95e309..0f3bcabfff8 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs @@ -71,7 +71,7 @@ protected override void Init() returnButton.Clicked += OnReturnTapped; stackLayout.Children.Add(returnButton); - var tabTwoPage = new ContentPage { Content = stackLayout }; + var tabTwoPage = new ContentPage { Content = stackLayout }; tabOne.Items.Add(tabOnePage); tabTwo.Items.Add(tabTwoPage); @@ -86,7 +86,8 @@ protected override void Init() Items = { tabOne, tabTwo } } ); - Items.Add(new FlyoutItem { + Items.Add(new FlyoutItem + { Title = flyoutOtherTitle, Items = { flyoutContent } }); @@ -104,8 +105,8 @@ public void FlyoutNavigationBetweenItemsWithNavigationStacks() RunningApp.WaitForElement(insertAutomationId); RunningApp.Tap(insertAutomationId); - TapInFlyout(flyoutOtherTitle); - TapInFlyout(flyoutMainTitle); + TapInFlyout(flyoutOtherTitle, timeoutMessage: flyoutOtherTitle); + TapInFlyout(flyoutMainTitle, timeoutMessage: flyoutMainTitle); RunningApp.WaitForElement(returnAutomationId); RunningApp.Tap(returnAutomationId); diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs index 4bb24f4f5ab..708c98512e6 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs @@ -728,10 +728,10 @@ public virtual void TearDown() AppSetup.EndIsolate(); } } - public void ShowFlyout(string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, bool testForFlyoutIcon = true) + public void ShowFlyout(string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, bool testForFlyoutIcon = true, string timeoutMessage = null) { if(testForFlyoutIcon) - RunningApp.WaitForElement(flyoutIcon); + RunningApp.WaitForElement(flyoutIcon, timeoutMessage); if (usingSwipe) { @@ -745,10 +745,11 @@ public void ShowFlyout(string flyoutIcon = FlyoutIconAutomationId, bool usingSwi } - public void TapInFlyout(string text, string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false) + public void TapInFlyout(string text, string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, string timeoutMessage = null) { - ShowFlyout(flyoutIcon, usingSwipe); - RunningApp.WaitForElement(text); + timeoutMessage = timeoutMessage ?? text; + ShowFlyout(flyoutIcon, usingSwipe, timeoutMessage: timeoutMessage); + RunningApp.WaitForElement(text, timeoutMessage); RunningApp.Tap(text); } diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 5425d211e4e..e51a2a8ec08 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -13,6 +13,7 @@ + diff --git a/Xamarin.Forms.Core/Shell/ShellSection.cs b/Xamarin.Forms.Core/Shell/ShellSection.cs index d3c5027775f..ad96c0bdd92 100644 --- a/Xamarin.Forms.Core/Shell/ShellSection.cs +++ b/Xamarin.Forms.Core/Shell/ShellSection.cs @@ -352,7 +352,6 @@ protected virtual void OnInsertPageBefore(Page page, Page before) _navStack.Insert(index, page); AddPage(page); - SendAppearanceChanged(); var args = new NavigationRequestedEventArgs(page, before, false) { diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs index d77643c6e21..e5c47d449b4 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs @@ -4,6 +4,7 @@ using Android.Views; using System; using LP = Android.Views.ViewGroup.LayoutParams; +using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android.AppCompat { @@ -18,10 +19,6 @@ public ShellFragmentContainer(ShellContent shellContent) : base() ShellContentTab = shellContent; } - protected ShellFragmentContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - public override Page Page => _page; protected override PageContainer CreatePageContainer(Context context, IVisualElementRenderer child, bool inFragment) @@ -32,7 +29,7 @@ protected override PageContainer CreatePageContainer(Context context, IVisualEle }; } - public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { _page = ((IShellContentController)ShellContentTab).GetOrCreateContent(); return base.OnCreateView(inflater, container, savedInstanceState); diff --git a/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs index fc3b58534c7..a2494ed3b80 100644 --- a/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs +++ b/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs @@ -17,6 +17,11 @@ public static FragmentTransaction AddEx(this FragmentTransaction fragmentTransac return fragmentTransaction.Add(containerViewId, fragment); } + public static FragmentTransaction ReplaceEx(this FragmentTransaction fragmentTransaction, int containerViewId, Fragment fragment) + { + return fragmentTransaction.Replace(containerViewId, fragment); + } + public static FragmentTransaction HideEx(this FragmentTransaction fragmentTransaction, Fragment fragment) { return fragmentTransaction.Hide(fragment); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs b/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs index a2aa381ec55..72191ad36f8 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs @@ -12,6 +12,11 @@ public class ColorChangeRevealDrawable : AnimationDrawable readonly AColor _endColor; readonly AColor _startColor; float _progress; + bool _disposed; + ValueAnimator _animator; + + internal AColor StartColor => _startColor; + internal AColor EndColor => _endColor; public ColorChangeRevealDrawable(AColor startColor, AColor endColor, Point center) : base() { @@ -20,11 +25,11 @@ public ColorChangeRevealDrawable(AColor startColor, AColor endColor, Point cente if (_startColor != _endColor) { - ValueAnimator animator = ValueAnimator.OfFloat(0, 1); - animator.SetInterpolator(new global::Android.Views.Animations.DecelerateInterpolator()); - animator.SetDuration(500); - animator.Update += OnUpdate; - animator.Start(); + _animator = ValueAnimator.OfFloat(0, 1); + _animator.SetInterpolator(new global::Android.Views.Animations.DecelerateInterpolator()); + _animator.SetDuration(500); + _animator.Update += OnUpdate; + _animator.Start(); _center = center; } else @@ -35,6 +40,9 @@ public ColorChangeRevealDrawable(AColor startColor, AColor endColor, Point cente public override void Draw(Canvas canvas) { + if (_disposed) + return; + if (_progress == 1) { canvas.DrawColor(_endColor); @@ -54,14 +62,39 @@ public override void Draw(Canvas canvas) { Color = _endColor }; + canvas.DrawCircle(centerX, centerY, radius * _progress, paint); } void OnUpdate(object sender, ValueAnimator.AnimatorUpdateEventArgs e) { _progress = (float)e.Animation.AnimatedValue; - if (!this.IsDisposed()) - InvalidateSelf(); + + InvalidateSelf(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + if (_animator != null) + { + _animator.Update -= OnUpdate; + + _animator.Cancel(); + + _animator.Dispose(); + + _animator = null; + } + } + + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs index f74e20b2b9e..037272dd2ad 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs @@ -14,7 +14,7 @@ public class ShellBottomNavViewAppearanceTracker : IShellBottomNavViewAppearance ShellItem _shellItem; ColorStateList _defaultList; bool _disposed; - Color _lastColor = Color.Default; + ColorStateList _colorStateList; public ShellBottomNavViewAppearanceTracker(IShellContext shellContext, ShellItem shellItem) { @@ -42,7 +42,6 @@ public virtual void SetAppearance(BottomNavigationView bottomView, ShellAppearan var unselectedColor = controller.EffectiveTabBarUnselectedColor; var titleColor = controller.EffectiveTabBarTitleColor; - if (_defaultList == null) { #if __ANDROID_28__ @@ -52,41 +51,45 @@ public virtual void SetAppearance(BottomNavigationView bottomView, ShellAppearan #endif } - var colorStateList = MakeColorStateList(titleColor, disabledColor, unselectedColor); - bottomView.ItemTextColor = colorStateList; - bottomView.ItemIconTintList = colorStateList; - - colorStateList.Dispose(); + _colorStateList = MakeColorStateList(titleColor, disabledColor, unselectedColor); + bottomView.ItemTextColor = _colorStateList; + bottomView.ItemIconTintList = _colorStateList; SetBackgroundColor(bottomView, backgroundColor); } protected virtual void SetBackgroundColor(BottomNavigationView bottomView, Color color) { - if (_lastColor.IsDefault) - _lastColor = color; - - using (var menuView = bottomView.GetChildAt(0) as BottomNavigationMenuView) + var menuView = bottomView.GetChildAt(0) as BottomNavigationMenuView; + var oldBackground = bottomView.Background; + var colorDrawable = oldBackground as ColorDrawable; + var colorChangeRevealDrawable = oldBackground as ColorChangeRevealDrawable; + AColor lastColor = colorChangeRevealDrawable?.EndColor ?? colorDrawable?.Color ?? Color.Default.ToAndroid(); + var newColor = color.ToAndroid(); + + if (menuView == null) { - if (menuView == null) + if (colorDrawable != null && lastColor == newColor) + return; + + if (lastColor != color.ToAndroid() || colorDrawable == null) { bottomView.SetBackground(new ColorDrawable(color.ToAndroid())); } - else - { - var index = _shellItem.Items.IndexOf(_shellItem.CurrentItem); - using (var menu = bottomView.Menu) - index = Math.Min(index, menu.Size() - 1); - - using (var child = menuView.GetChildAt(index)) - { - var touchPoint = new Point(child.Left + (child.Right - child.Left) / 2, child.Top + (child.Bottom - child.Top) / 2); - - bottomView.Background?.Dispose(); - bottomView.SetBackground(new ColorChangeRevealDrawable(_lastColor.ToAndroid(), color.ToAndroid(), touchPoint)); - _lastColor = color; - } - } + } + else + { + if (colorChangeRevealDrawable != null && lastColor == newColor) + return; + + var index = _shellItem.Items.IndexOf(_shellItem.CurrentItem); + var menu = bottomView.Menu; + index = Math.Min(index, menu.Size() - 1); + + var child = menuView.GetChildAt(index); + var touchPoint = new Point(child.Left + (child.Right - child.Left) / 2, child.Top + (child.Bottom - child.Top) / 2); + + bottomView.SetBackground(new ColorChangeRevealDrawable(lastColor, newColor, touchPoint)); } } @@ -135,20 +138,23 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) - { - _defaultList?.Dispose(); - } + _defaultList?.Dispose(); + _colorStateList?.Dispose(); _shellItem = null; _shellContext = null; _defaultList = null; - _disposed = true; + _colorStateList = null; } } -#endregion IDisposable + #endregion IDisposable } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs index ce50e276b86..3925beb552e 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs @@ -54,6 +54,7 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) ShellContent _shellContent; Toolbar _toolbar; IShellToolbarTracker _toolbarTracker; + bool _disposed; public ShellContentFragment(IShellContext shellContext, ShellContent shellContent) { @@ -137,19 +138,9 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, return _root; } - // Use OnDestroy instead of OnDestroyView because OnDestroyView will be - // called before the animation completes. This causes tons of tiny issues. - public override void OnDestroy() + void Destroy() { - base.OnDestroy(); - - _shellPageContainer.RemoveAllViews(); - _renderer?.Dispose(); - _root?.Dispose(); - _toolbarTracker.Dispose(); - _appearanceTracker.Dispose(); - - ((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this); + ((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this); if (_shellContent != null) { @@ -158,11 +149,49 @@ public override void OnDestroy() _page = null; } + if (_shellPageContainer != null) + { + _shellPageContainer.RemoveAllViews(); + + if (_root is ViewGroup vg) + vg.RemoveView(_shellPageContainer); + } + + _renderer?.Dispose(); + _root?.Dispose(); + _toolbarTracker?.Dispose(); + _appearanceTracker?.Dispose(); + + _appearanceTracker = null; - _toolbar = null; _toolbarTracker = null; + _toolbar = null; _root = null; _renderer = null; + _shellContent = null; + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + if (disposing) + { + Destroy(); + _page = null; + } + + base.Dispose(disposing); + } + + // Use OnDestroy instead of OnDestroyView because OnDestroyView will be + // called before the animation completes. This causes tons of tiny issues. + public override void OnDestroy() + { + base.OnDestroy(); + Destroy(); } protected virtual void ResetAppearance() => _appearanceTracker.ResetAppearance(_toolbar, _toolbarTracker); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index 3d63a5c36e4..e1fa1f5af50 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -22,7 +22,12 @@ public class ShellFlyoutRecyclerAdapter : RecyclerView.Adapter List _listItems; Dictionary _templateMap = new Dictionary(); - readonly Action _selectedCallback; + + Action _selectedCallback; + + bool _disposed; + + ElementViewHolder _elementViewHolder; public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action selectedCallback) { @@ -55,7 +60,7 @@ public override int GetItemViewType(int position) else { dataTemplate = Shell.GetItemTemplate(item.Element) ?? Shell.ItemTemplate ?? DefaultItemTemplate; - } + } var template = dataTemplate.SelectDataTemplate(item.Element, Shell); var id = ((IDataTemplateController)template).Id; @@ -82,7 +87,7 @@ public LinearLayoutWithFocus(global::Android.Content.Context context) : base(con AView ITabStop.TabStop => this; -#region IVisualElementRenderer + #region IVisualElementRenderer VisualElement IVisualElementRenderer.Element => Content?.BindingContext as VisualElement; @@ -105,7 +110,7 @@ public LinearLayoutWithFocus(global::Android.Content.Context context) : base(con public event EventHandler ElementPropertyChanged; #pragma warning restore 67 -#endregion IVisualElementRenderer + #endregion IVisualElementRenderer internal View Content { get; set; } @@ -163,7 +168,9 @@ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int container.LayoutParameters = new LP(LP.MatchParent, LP.WrapContent); linearLayout.AddView(container); - return new ElementViewHolder(content, linearLayout, bar, _selectedCallback); + _elementViewHolder = new ElementViewHolder(content, linearLayout, bar, _selectedCallback); + + return _elementViewHolder; } protected virtual List GenerateItemList() @@ -198,7 +205,7 @@ View GenerateDefaultCell(string textBinding, string iconBinding) { var grid = new Grid(); var groups = new VisualStateGroupList(); - + var commonGroup = new VisualStateGroup(); commonGroup.Name = "CommonStates"; groups.Add(commonGroup); @@ -242,6 +249,27 @@ View GenerateDefaultCell(string textBinding, string iconBinding) return grid; } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + ((IShellController)Shell).StructureChanged -= OnShellStructureChanged; + + _elementViewHolder?.Dispose(); + + _listItems = null; + _selectedCallback = null; + _elementViewHolder = null; + } + + base.Dispose(disposing); + } + public class AdapterListItem { public AdapterListItem(Element element, bool drawTopLine = false) @@ -256,9 +284,10 @@ public AdapterListItem(Element element, bool drawTopLine = false) public class ElementViewHolder : RecyclerView.ViewHolder { - readonly Action _selectedCallback; + Action _selectedCallback; Element _element; AView _itemView; + bool _disposed; public ElementViewHolder(View view, AView itemView, AView bar, Action selectedCallback) : base(itemView) { @@ -321,6 +350,25 @@ void OnClicked(object sender, EventArgs e) _selectedCallback(Element); } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _itemView.Click -= OnClicked; + + Element = null; + _itemView = null; + _selectedCallback = null; + } + + base.Dispose(disposing); + } } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs index a77cd56ce3f..b8e15a85a32 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs @@ -59,6 +59,7 @@ void IFlyoutBehaviorObserver.OnFlyoutBehaviorChanged(FlyoutBehavior behavior) IShellFlyoutContentRenderer _flyoutContent; int _flyoutWidth; int _currentLockMode; + bool _disposed; public ShellFlyoutRenderer(IShellContext shellContext, Context context) : base(context) { @@ -170,5 +171,28 @@ protected virtual void UpdateDrawerLockMode(FlyoutBehavior behavior) SetScrimColor(behavior == FlyoutBehavior.Locked ? Color.Transparent.ToAndroid() : (int)DefaultScrimColor); } } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + Shell.PropertyChanged -= OnShellPropertyChanged; + + RemoveDrawerListener(this); + ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); + + RemoveView(_content); + RemoveView(_flyoutContent.AndroidView); + + _flyoutContent.Dispose(); + } + + base.Dispose(disposing); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs index 8f72d8b21cd..f162c60701b 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs @@ -1,6 +1,5 @@ using Android.Content; using Android.Graphics.Drawables; -using Android.Runtime; using Android.Support.Design.Widget; using Android.Support.V7.Widget; using Android.Util; @@ -23,18 +22,21 @@ public class ShellFlyoutTemplatedContentRenderer : Java.Lang.Object, IShellFlyou #endregion IShellFlyoutContentRenderer - IShellContext _shellContext; - bool _disposed; - HeaderContainer _headerView; - ViewGroup _rootView; - Drawable _defaultBackgroundColor; + IShellContext _shellContext; + bool _disposed; + HeaderContainer _headerView; + ViewGroup _rootView; + Drawable _defaultBackgroundColor; ImageView _bgImage; - View _flyoutHeader; - int _actionBarHeight; + AppBarLayout _appBar; + RecyclerView _recycler; + ShellFlyoutRecyclerAdapter _adapter; + View _flyoutHeader; + int _actionBarHeight; - public ShellFlyoutTemplatedContentRenderer(IShellContext shellContext) - { - _shellContext = shellContext; + public ShellFlyoutTemplatedContentRenderer(IShellContext shellContext) + { + _shellContext = shellContext; LoadView(shellContext); } @@ -51,17 +53,19 @@ protected virtual void LoadView(IShellContext shellContext) } var coordinator = LayoutInflater.FromContext(context).Inflate(Resource.Layout.FlyoutContent, null); - var recycler = coordinator.FindViewById(Resource.Id.flyoutcontent_recycler); - var appBar = coordinator.FindViewById(Resource.Id.flyoutcontent_appbar); + + _recycler = coordinator.FindViewById(Resource.Id.flyoutcontent_recycler); + + _appBar = coordinator.FindViewById(Resource.Id.flyoutcontent_appbar); _rootView = coordinator as ViewGroup; - appBar.AddOnOffsetChangedListener(this); + _appBar.AddOnOffsetChangedListener(this); _actionBarHeight = (int)context.ToPixels(56); _flyoutHeader = ((IShellController)shellContext.Shell).FlyoutHeader; - if(_flyoutHeader != null) + if (_flyoutHeader != null) _flyoutHeader.MeasureInvalidated += OnFlyoutHeaderMeasureInvalidated; _headerView = new HeaderContainer(context, _flyoutHeader) @@ -73,13 +77,12 @@ protected virtual void LoadView(IShellContext shellContext) { ScrollFlags = AppBarLayout.LayoutParams.ScrollFlagScroll }; - appBar.AddView(_headerView); + _appBar.AddView(_headerView); - var adapter = new ShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); - recycler.SetPadding(0, (int)context.ToPixels(20), 0, 0); - recycler.SetClipToPadding(false); - recycler.SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false)); - recycler.SetAdapter(adapter); + _adapter = new ShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); + _recycler.SetClipToPadding(false); + _recycler.SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false)); + _recycler.SetAdapter(_adapter); var metrics = context.Resources.DisplayMetrics; var width = Math.Min(metrics.WidthPixels, metrics.HeightPixels); @@ -102,14 +105,14 @@ protected virtual void LoadView(IShellContext shellContext) }; UpdateFlyoutHeaderBehavior(); - _shellContext.Shell.PropertyChanged += OnShellPropertyChanged; + _shellContext.Shell.PropertyChanged += OnShellPropertyChanged; - UpdateFlyoutBackground(); - } + UpdateFlyoutBackground(); + } void OnFlyoutHeaderMeasureInvalidated(object sender, EventArgs e) { - if(_headerView != null) + if (_headerView != null) UpdateFlyoutHeaderBehavior(); } @@ -118,8 +121,8 @@ protected void OnElementSelected(Element element) ((IShellController)_shellContext.Shell).OnFlyoutItemSelected(element); } - protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) - { + protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) + { if (e.PropertyName == Shell.FlyoutHeaderBehaviorProperty.PropertyName) UpdateFlyoutHeaderBehavior(); else if (e.IsOneOf( @@ -127,7 +130,7 @@ protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEven Shell.FlyoutBackgroundImageProperty, Shell.FlyoutBackgroundImageAspectProperty)) UpdateFlyoutBackground(); - } + } protected virtual void UpdateFlyoutBackground() { @@ -179,7 +182,7 @@ async void UpdateFlyoutBgImageAsync() if (_rootView.IndexOfChild(_bgImage) == -1) { - if(_bgImage.SetElevation(float.MinValue)) + if (_bgImage.SetElevation(float.MinValue)) _rootView.AddView(_bgImage); else _rootView.AddView(_bgImage, 0); @@ -191,10 +194,7 @@ protected virtual void UpdateFlyoutHeaderBehavior() { var context = _shellContext.AndroidContext; - Thickness margin = default(Thickness); - - if (_flyoutHeader != null) - margin = _flyoutHeader.Margin; + var margin = _flyoutHeader?.Margin ?? default(Thickness); var minimumHeight = Convert.ToInt32(_actionBarHeight + context.ToPixels(margin.Top) - context.ToPixels(margin.Bottom)); _headerView.SetMinimumHeight(minimumHeight); @@ -245,31 +245,49 @@ public void OnOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) _headerView.SetPadding(0, -verticalOffset, 0, 0); } - protected override void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _shellContext.Shell.PropertyChanged -= OnShellPropertyChanged; - - if (_flyoutHeader != null) - _flyoutHeader.MeasureInvalidated += OnFlyoutHeaderMeasureInvalidated; - - _headerView.Dispose(); - _rootView.Dispose(); - _defaultBackgroundColor?.Dispose(); - _bgImage?.Dispose(); + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _shellContext.Shell.PropertyChanged -= OnShellPropertyChanged; + + if (_flyoutHeader != null) + _flyoutHeader.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated; + + if (_appBar != null) + { + _appBar.RemoveOnOffsetChangedListener(this); + _appBar.RemoveView(_headerView); } - _flyoutHeader = null; - _defaultBackgroundColor = null; - _bgImage = null; + if (_recycler != null) + { + _recycler.SetLayoutManager(null); + _recycler.SetAdapter(null); + _recycler.Dispose(); + } + + _adapter?.Dispose(); + _headerView.Dispose(); + _rootView.Dispose(); + _defaultBackgroundColor?.Dispose(); + _bgImage?.Dispose(); + + _flyoutHeader = null; _rootView = null; - _headerView = null; - _shellContext = null; - _disposed = true; - } + _headerView = null; + _shellContext = null; + _appBar = null; + _recycler = null; + _adapter = null; + _defaultBackgroundColor = null; + _bgImage = null; + } base.Dispose(disposing); } @@ -281,18 +299,6 @@ public HeaderContainer(Context context, View view) : base(context, view) { } - public HeaderContainer(Context context, IAttributeSet attribs) : base(context, attribs) - { - } - - public HeaderContainer(Context context, IAttributeSet attribs, int defStyleAttr) : base(context, attribs, defStyleAttr) - { - } - - protected HeaderContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - protected override void LayoutView(double x, double y, double width, double height) { var context = Context; @@ -308,4 +314,4 @@ protected override void LayoutView(double x, double y, double width, double heig } } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs index 5c467d6ccc9..58cb8051825 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs @@ -64,14 +64,19 @@ public override void RestoreState(IParcelable state, ClassLoader loader) protected override void Dispose(bool disposing) { - base.Dispose(disposing); - if (disposing && !_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { ((INotifyCollectionChanged)_shellSection.Items).CollectionChanged -= OnItemsCollectionChanged; - _shellSection = null; - _disposed = true; + _shellSection = null; } + + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs index d5d1c64d729..22bb688f4e8 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs @@ -48,6 +48,8 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) FrameLayout _navigationArea; AView _outerLayout; IShellBottomNavViewAppearanceTracker _appearanceTracker; + BottomSheetDialog _bottomSheetDialog; + bool _disposed; public ShellItemRenderer(IShellContext shellContext) : base(shellContext) { @@ -76,29 +78,55 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, return _outerLayout; } - // Use OnDestory become OnDestroyView may fire before events are completed. - public override void OnDestroy() + + void Destroy() { - UnhookEvents(ShellItem); + if(ShellItem != null) + UnhookEvents(ShellItem); + + ((IShellController)ShellContext.Shell).RemoveAppearanceObserver(this); + + if (_bottomSheetDialog != null) + { + _bottomSheetDialog.DismissEvent -= OnMoreSheetDismissed; + _bottomSheetDialog?.Dispose(); + _bottomSheetDialog = null; + } + + _navigationArea?.Dispose(); + _appearanceTracker?.Dispose(); + _outerLayout?.Dispose(); + if (_bottomView != null) { _bottomView?.SetOnNavigationItemSelectedListener(null); _bottomView?.Background?.Dispose(); _bottomView?.Dispose(); - _bottomView = null; + } - _navigationArea?.Dispose(); - _navigationArea = null; + _bottomView = null; + _navigationArea = null; + _appearanceTracker = null; + _outerLayout = null; - _appearanceTracker?.Dispose(); - _appearanceTracker = null; + } - _outerLayout?.Dispose(); - _outerLayout = null; - } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; - ((IShellController)ShellContext.Shell).RemoveAppearanceObserver(this); + _disposed = true; + if (disposing) + Destroy(); + base.Dispose(disposing); + } + + // Use OnDestory become OnDestroyView may fire before events are completed. + public override void OnDestroy() + { + Destroy(); base.OnDestroy(); } @@ -228,9 +256,9 @@ protected virtual bool OnItemSelected(IMenuItem item) var id = item.ItemId; if (id == MoreTabId) { - var bottomSheetDialog = CreateMoreBottomSheet(OnMoreItemSelected); - bottomSheetDialog.Show(); - bottomSheetDialog.DismissEvent += OnMoreSheetDismissed; + _bottomSheetDialog = CreateMoreBottomSheet(OnMoreItemSelected); + _bottomSheetDialog.Show(); + _bottomSheetDialog.DismissEvent += OnMoreSheetDismissed; } else { @@ -256,7 +284,17 @@ protected virtual void OnMoreItemSelected(ShellSection shellSection, BottomSheet dialog.Dispose(); } - protected virtual void OnMoreSheetDismissed(object sender, EventArgs e) => OnShellSectionChanged(); + protected virtual void OnMoreSheetDismissed(object sender, EventArgs e) + { + OnShellSectionChanged(); + + if (_bottomSheetDialog != null) + { + _bottomSheetDialog.DismissEvent -= OnMoreSheetDismissed; + _bottomSheetDialog.Dispose(); + _bottomSheetDialog = null; + } + } protected override void OnShellItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs index e3fbc3b721e..348ff2c74d5 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs @@ -31,6 +31,7 @@ ShellItem IShellItemRenderer.ShellItem IShellObservableFragment _currentFragment; ShellSection _shellSection; Page _displayedPage; + bool _disposed; protected ShellItemRendererBase(IShellContext shellContext) { @@ -82,12 +83,14 @@ protected virtual IShellObservableFragment CreateFragmentForPage(Page page) return ShellContext.CreateFragmentForPage(page); } - public override void OnDestroy() + void Destroy() { - base.OnDestroy(); - foreach (var item in _fragmentMap) + { + RemoveFragment(item.Value.Fragment); item.Value.Fragment.Dispose(); + } + _fragmentMap.Clear(); ShellSection = null; @@ -96,6 +99,25 @@ public override void OnDestroy() Destroyed?.Invoke(this, EventArgs.Empty); } + public override void OnDestroy() + { + base.OnDestroy(); + Destroy(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + Destroy(); + + base.Dispose(disposing); + } + protected abstract ViewGroup GetNavigationTarget(); protected virtual IShellObservableFragment GetOrCreateFragmentForTab(ShellSection shellSection) @@ -158,7 +180,7 @@ protected virtual Task HandleFragmentUpdate(ShellNavigationSource navSourc // We need to handle this after we know what the target is // because we might accidentally remove an already added target. // Then there would be two transactions in a row, one removing and one adding - // the same fragement and things get really screwy when you do that. + // the same fragment and things get really screwy when you do that. break; default: @@ -200,11 +222,11 @@ protected virtual Task HandleFragmentUpdate(ShellNavigationSource navSourc trackFragment = target; if (_currentFragment != null) - t.Hide(_currentFragment.Fragment); + t.HideEx(_currentFragment.Fragment); if (!target.Fragment.IsAdded) - t.Add(GetNavigationTarget().Id, target.Fragment); - t.Show(target.Fragment); + t.AddEx(GetNavigationTarget().Id, target.Fragment); + t.ShowEx(target.Fragment); break; case ShellNavigationSource.Pop: @@ -213,10 +235,10 @@ protected virtual Task HandleFragmentUpdate(ShellNavigationSource navSourc trackFragment = _currentFragment; if (_currentFragment != null) - t.Remove(_currentFragment.Fragment); + t.RemoveEx(_currentFragment.Fragment); if (!target.Fragment.IsAdded) - t.Add(GetNavigationTarget().Id, target.Fragment); + t.AddEx(GetNavigationTarget().Id, target.Fragment); t.Show(target.Fragment); break; } @@ -237,7 +259,7 @@ void callback(object s, EventArgs e) result.TrySetResult(true); } - t.CommitAllowingStateLoss(); + t.CommitAllowingStateLossEx(); _currentFragment = target; @@ -346,7 +368,7 @@ void UpdateDisplayedPage(Page page) void RemoveAllButCurrent(Fragment skip) { - var trans = ChildFragmentManager.BeginTransaction(); + var trans = ChildFragmentManager.BeginTransactionEx(); foreach (var kvp in _fragmentMap) { var f = kvp.Value.Fragment; @@ -354,7 +376,7 @@ void RemoveAllButCurrent(Fragment skip) continue; trans.Remove(f); }; - trans.CommitAllowingStateLoss(); + trans.CommitAllowingStateLossEx(); } void RemoveAllPushedPages(ShellSection shellSection, bool keepCurrent) @@ -362,7 +384,7 @@ void RemoveAllPushedPages(ShellSection shellSection, bool keepCurrent) if (shellSection.Stack.Count <= 1 || (keepCurrent && shellSection.Stack.Count == 2)) return; - var t = ChildFragmentManager.BeginTransaction(); + var t = ChildFragmentManager.BeginTransactionEx(); foreach (var kvp in _fragmentMap.ToList()) { @@ -374,17 +396,17 @@ void RemoveAllPushedPages(ShellSection shellSection, bool keepCurrent) if (keepCurrent && kvp.Value.Fragment == _currentFragment) continue; - t.Remove(kvp.Value.Fragment); + t.RemoveEx(kvp.Value.Fragment); } - t.CommitAllowingStateLoss(); + t.CommitAllowingStateLossEx(); } void RemoveFragment(Fragment fragment) { - var t = ChildFragmentManager.BeginTransaction(); - t.Remove(fragment); - t.CommitAllowingStateLoss(); + var t = ChildFragmentManager.BeginTransactionEx(); + t.RemoveEx(fragment); + t.CommitAllowingStateLossEx(); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs index 1b4b0c6f41a..6fc424e6e66 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs @@ -246,10 +246,10 @@ protected virtual void SwitchFragment(FragmentManager manager, AView targetView, FragmentTransaction transaction = manager.BeginTransaction(); if (animate) - transaction.SetTransition((int)global::Android.App.FragmentTransit.EnterMask); + transaction.SetTransitionEx((int)global::Android.App.FragmentTransit.EnterMask); - transaction.Replace(_frameLayout.Id, fragment); - transaction.CommitAllowingStateLoss(); + transaction.ReplaceEx(_frameLayout.Id, fragment); + transaction.CommitAllowingStateLossEx(); void OnDestroyed (object sender, EventArgs args) { @@ -332,15 +332,17 @@ public override void Draw(Canvas canvas) { var bounds = Bounds; - var paint = new Paint(); + using (var paint = new Paint()) + { - paint.Color = _color; + paint.Color = _color; - canvas.DrawRect(new Rect(0, 0, bounds.Right, _topSize), paint); + canvas.DrawRect(new Rect(0, 0, bounds.Right, _topSize), paint); - canvas.DrawRect(new Rect(0, bounds.Bottom - _bottomSize, bounds.Right, bounds.Bottom), paint); + canvas.DrawRect(new Rect(0, bounds.Bottom - _bottomSize, bounds.Right, bounds.Bottom), paint); - paint.Dispose(); + paint.Dispose(); + } } public override void SetAlpha(int alpha) @@ -361,20 +363,37 @@ void IDisposable.Dispose() protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) + if (_currentRenderer != null && _currentRenderer.Fragment.IsAlive()) { - Element.PropertyChanged -= OnElementPropertyChanged; - Element.SizeChanged -= OnElementSizeChanged; + FragmentTransaction transaction = FragmentManager.BeginTransaction(); + transaction.RemoveEx(_currentRenderer.Fragment); + transaction.CommitAllowingStateLossEx(); + FragmentManager.ExecutePendingTransactionsEx(); } - Element = null; - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. + Element.PropertyChanged -= OnElementPropertyChanged; + Element.SizeChanged -= OnElementSizeChanged; + ((IShellController)Element).RemoveAppearanceObserver(this); - _disposed = true; + // This cast is necessary because IShellFlyoutRenderer doesn't implement IDisposable + (_flyoutRenderer as IDisposable)?.Dispose(); + + _currentRenderer.Dispose(); + _currentRenderer = null; } + + Element = null; + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + _disposed = true; } #endregion IDisposable diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs index d861819377f..fadd72f2766 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs @@ -123,20 +123,18 @@ bool TextView.IOnEditorActionListener.OnEditorAction(TextView v, ImeAction actio protected override void Dispose(bool disposing) { - base.Dispose(disposing); + if (_disposed) + return; if (disposing) { _disposed = true; - _searchHandlerAppearanceTracker?.Dispose(); SearchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged; _textBlock.ItemClick -= OnTextBlockItemClicked; _textBlock.RemoveTextChangedListener(this); _textBlock.SetOnEditorActionListener(null); - _textBlock.Adapter.Dispose(); - _textBlock.Adapter = null; _textBlock.DropDownBackground.Dispose(); _textBlock.SetDropDownBackgroundDrawable(null); @@ -144,6 +142,9 @@ protected override void Dispose(bool disposing) _clearPlaceholderButton.Click -= OnClearPlaceholderButtonClicked; _searchButton.Click -= OnSearchButtonClicked; + _textBlock.Adapter.Dispose(); + _textBlock.Adapter = null; + _searchHandlerAppearanceTracker?.Dispose(); _textBlock.Dispose(); _clearButton.Dispose(); _searchButton.Dispose(); @@ -160,6 +161,8 @@ protected override void Dispose(bool disposing) _searchHandlerAppearanceTracker = null; SearchHandler = null; + + base.Dispose(disposing); } protected virtual void LoadView(SearchHandler searchHandler) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs index 6e0e07df6a5..2e6b66a82a6 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs @@ -19,6 +19,7 @@ public class ShellSearchViewAdapter : BaseAdapter, IFilterable Filter _filter; IReadOnlyList _emptyList = new List(); IReadOnlyList ListProxy => SearchController.ListProxy ?? _emptyList; + bool _disposed; public ShellSearchViewAdapter(SearchHandler searchHandler, IShellContext shellContext) { @@ -30,19 +31,24 @@ public ShellSearchViewAdapter(SearchHandler searchHandler, IShellContext shellCo protected override void Dispose(bool disposing) { - base.Dispose(disposing); + if (_disposed) + return; + + _disposed = true; if (disposing) { SearchController.ListProxyChanged -= OnListPropxyChanged; _searchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged; - _filter.Dispose(); + _filter?.Dispose(); } _filter = null; _shellContext = null; _searchHandler = null; _defaultTemplate = null; + + base.Dispose(disposing); } public Filter Filter => _filter ?? (_filter = new CustomFilter(this)); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs index 2c1b3319d71..3f4fc60cd5b 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs @@ -113,16 +113,13 @@ void AView.IOnClickListener.OnClick(AView v) IShellToolbarAppearanceTracker _toolbarAppearanceTracker; IShellToolbarTracker _toolbarTracker; FormsViewPager _viewPager; + bool _disposed; public ShellSectionRenderer(IShellContext shellContext) { _shellContext = shellContext; } - protected ShellSectionRenderer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - public event EventHandler AnimationFinished; Fragment IShellObservableFragment.Fragment => this; @@ -171,19 +168,17 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, return _rootView = root; } - // Use OnDestroy instead of OnDestroyView because OnDestroyView will be - // called before the animation completes. This causes tons of tiny issues. - public override void OnDestroy() + void Destroy() { if (_rootView != null) { UnhookEvents(); + _viewPager.RemoveOnPageChangeListener(this); var adapter = _viewPager.Adapter; _viewPager.Adapter = null; adapter.Dispose(); - _viewPager.RemoveOnPageChangeListener(this); _toolbarAppearanceTracker.Dispose(); _tabLayoutAppearanceTracker.Dispose(); @@ -202,8 +197,27 @@ public override void OnDestroy() _viewPager = null; _rootView = null; + } + + // Use OnDestroy instead of OnDestroyView because OnDestroyView will be + // called before the animation completes. This causes tons of tiny issues. + public override void OnDestroy() + { + Destroy(); base.OnDestroy(); } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + Destroy(); + } + } protected virtual void OnAnimationFinished(EventArgs e) { diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs index 43893878a78..003cd50cb41 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs @@ -37,8 +37,7 @@ protected virtual void SetColors(TabLayout tabLayout, Color foreground, Color ba var unselectedArgb = unselected.ToAndroid(ShellRenderer.DefaultUnselectedColor).ToArgb(); tabLayout.SetTabTextColors(unselectedArgb, titleArgb); - using (var colorDrawable = new ColorDrawable(background.ToAndroid(ShellRenderer.DefaultBackgroundColor))) - tabLayout.SetBackground(colorDrawable); + tabLayout.SetBackground(new ColorDrawable(background.ToAndroid(ShellRenderer.DefaultBackgroundColor))); tabLayout.SetSelectedTabIndicatorColor(foreground.ToAndroid(ShellRenderer.DefaultForegroundColor)); } @@ -51,15 +50,11 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (!_disposed) - { - if (disposing) - { - } + if (_disposed) + return; - _shellContext = null; - _disposed = true; - } + _disposed = true; + _shellContext = null; } #endregion IDisposable diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs index ebc8bc0873e..f7f686ef0b7 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs @@ -47,13 +47,14 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) - { - } _shellContext = null; - _disposed = true; } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs index f2fb8ed772a..460e849563a 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs @@ -128,8 +128,6 @@ void AView.IOnClickListener.OnClick(AView v) else _shellContext.Shell.FlyoutIsPresented = !_shellContext.Shell.FlyoutIsPresented; } - - v.Dispose(); } protected override void Dispose(bool disposing) @@ -137,11 +135,16 @@ protected override void Dispose(bool disposing) if (_disposed) return; + _disposed = true; + if (disposing) { + if (_backButtonBehavior != null) + _backButtonBehavior.PropertyChanged -= OnBackButtonBehaviorChanged; + ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); + UpdateTitleView(_shellContext.AndroidContext, _toolbar, null); - _drawerToggle?.Dispose(); if (_searchView != null) { _searchView.View.RemoveFromParent(); @@ -150,10 +153,7 @@ protected override void Dispose(bool disposing) _searchView.Dispose(); } - ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); - - if (_backButtonBehavior != null) - _backButtonBehavior.PropertyChanged -= OnBackButtonBehaviorChanged; + _drawerToggle?.Dispose(); } _backButtonBehavior = null; @@ -164,7 +164,6 @@ protected override void Dispose(bool disposing) Page = null; _toolbar = null; _drawerLayout = null; - _disposed = true; base.Dispose(disposing); } From 724185eac8621a25e6a4514c7e02ad02199e5ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Wed, 9 Oct 2019 09:52:27 +0200 Subject: [PATCH 102/203] [iOS] Fixed crash displaying strings in CarouselView (#7858) * Added repro sample * Fixed the crash on DequeueReusableCell registering the correct cell types * Added instructions to the test * Fixed wrong paths on Controls Issues projitems --- .../Issue7789.xaml | 39 +++++++++++++++++++ .../Issue7789.xaml.cs | 38 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 14 ++++++- .../CollectionView/CarouselViewController.cs | 1 + 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml new file mode 100644 index 00000000000..8c8619e306b --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml @@ -0,0 +1,39 @@ + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml.cs new file mode 100644 index 00000000000..d9046aa126b --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7789.xaml.cs @@ -0,0 +1,38 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; +using System.Collections.Generic; + +#if UITEST +using Xamarin.UITest; +using Xamarin.UITest.Queries; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [NUnit.Framework.Category(UITestCategories.CollectionView)] +#endif +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7789, "CarouselView crash when displaying strings", PlatformAffected.iOS)] + public partial class Issue7789 : TestContentPage + { + public Issue7789() + { +#if APP + Device.SetFlags(new List { CollectionView.CollectionViewExperimental }); + InitializeComponent(); +#endif + } + + protected override void Init() + { + + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 078b0fe9861..d09034688c7 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -61,6 +61,9 @@ Code + + Code + @@ -1407,7 +1410,10 @@ Issue7357.xaml - + + Issue7789.xaml + + Issue7519Xaml.xaml @@ -1492,4 +1498,10 @@ MSBuild:Compile + + + Designer + MSBuild:Compile + + \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs index 36be38e2f0e..ced46e8b6a3 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs @@ -60,6 +60,7 @@ protected override string DetermineCellReuseId() protected override void RegisterViewTypes() { CollectionView.RegisterClassForCell(typeof(CarouselTemplatedCell), CarouselTemplatedCell.ReuseId); + base.RegisterViewTypes(); } internal void TearDown() From 6fc502b2f1a4c707183c884fce7e6e46db3e620a Mon Sep 17 00:00:00 2001 From: "Andres G. Aragoneses" Date: Wed, 9 Oct 2019 17:33:37 +0800 Subject: [PATCH 103/203] build.cake: more readable way of using an enum (#7867) * build.cake: cosmetic, less verbose GetMSBuildSettings() method * build.cake: more readable way of using an enum I had to look up what "1" meant in the documentation! (https://cakebuild.net/api/Cake.Common.Tools.MSBuild/MSBuildPlatform/) --- build.cake | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/build.cake b/build.cake index 4ed65492872..5e5e0cdb130 100644 --- a/build.cake +++ b/build.cake @@ -302,11 +302,9 @@ RunTarget(target); MSBuildSettings GetMSBuildSettings() { - var msbuildSettings = new MSBuildSettings(); - - msbuildSettings.PlatformTarget = PlatformTarget.MSIL; - msbuildSettings.MSBuildPlatform = (Cake.Common.Tools.MSBuild.MSBuildPlatform)1; - msbuildSettings.Configuration = configuration; - return msbuildSettings; - + return new MSBuildSettings { + PlatformTarget = PlatformTarget.MSIL, + MSBuildPlatform = Cake.Common.Tools.MSBuild.MSBuildPlatform.x86, + Configuration = configuration, + }; } From fd9644f3f2c40e0d738fb8573dc56e896560042b Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Wed, 9 Oct 2019 11:56:18 -0600 Subject: [PATCH 104/203] Implement grouping for UWP CollectionView (#7697) * Implement grouping for UWP CollectionView * Clean up CollectionViewSource on teardown --- .../EmptyViewStringGallery.xaml | 3 +- .../GroupingGalleries/ViewModel.cs | 12 +- .../CollectionView/CollectionViewRenderer.cs | 2 +- .../CollectionView/FormsGridView.cs | 80 +++++--- .../CollectionView/FormsListView.cs | 13 +- .../GroupFooterItemTemplateContext.cs | 23 +++ .../GroupHeaderStyleSelector.cs | 17 ++ .../CollectionView/GroupTemplateContext.cs | 50 +++++ .../GroupableItemsViewRenderer.cs | 62 +++++++ .../GroupedItemTemplateCollection.cs | 166 +++++++++++++++++ .../CollectionView/ItemContentControl.cs | 37 +++- .../CollectionView/ItemTemplateContext.cs | 14 +- .../CollectionView/ItemTemplateContextList.cs | 2 +- .../CollectionView/ItemsViewRenderer.cs | 21 +-- .../CollectionView/ItemsViewStyles.xaml | 172 ++++++++++++++---- .../StructuredItemsViewRenderer.cs | 31 +--- .../TemplatedItemSourceFactory.cs | 9 +- .../Xamarin.Forms.Platform.UAP.csproj | 15 +- 18 files changed, 600 insertions(+), 129 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/GroupFooterItemTemplateContext.cs create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/GroupHeaderStyleSelector.cs create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/GroupTemplateContext.cs create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/GroupableItemsViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/GroupedItemTemplateCollection.cs diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewStringGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewStringGallery.xaml index b851a417dce..143b6c62f69 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewStringGallery.xaml +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewStringGallery.xaml @@ -13,7 +13,8 @@ - + + No items match your filter. diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ViewModel.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ViewModel.cs index 59b21847fc2..c835e75ae9d 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ViewModel.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ViewModel.cs @@ -56,7 +56,7 @@ public SuperTeams() } )); - Add(new Team("Fantastic Four", + Add(new Team("Fantastic Four", new List { new Member("The Thing"), @@ -66,7 +66,7 @@ public SuperTeams() } )); - Add(new Team("Defenders", + Add(new Team("Defenders", new List { new Member("Doctor Strange"), @@ -78,8 +78,8 @@ public SuperTeams() new Member("Yellowjacket"), } )); - - Add(new Team("Heroes for Hire", + + Add(new Team("Heroes for Hire", new List { new Member("Luke Cage"), @@ -90,7 +90,7 @@ public SuperTeams() } )); - Add(new Team("West Coast Avengers", + Add(new Team("West Coast Avengers", new List { new Member("Hawkeye"), @@ -101,7 +101,7 @@ public SuperTeams() } )); - Add(new Team("Great Lakes Avengers", + Add(new Team("Great Lakes Avengers", new List { new Member("Squirrel Girl"), diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs index be2899bdd49..544ded39cdb 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs @@ -14,7 +14,7 @@ namespace Xamarin.Forms.Platform.UWP { - public class CollectionViewRenderer : SelectableItemsViewRenderer + public class CollectionViewRenderer : GroupableItemsViewRenderer { } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs b/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs index 762137558de..553209908e6 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/FormsGridView.cs @@ -1,60 +1,68 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using UWPApp = Windows.UI.Xaml.Application; -using UWPControlTemplate = Windows.UI.Xaml.Controls.ControlTemplate; +using UWPControls = Windows.UI.Xaml.Controls; namespace Xamarin.Forms.Platform.UWP { internal class FormsGridView : GridView, IEmptyView { - int _maximumRowsOrColumns; + int _span; ItemsWrapGrid _wrapGrid; ContentControl _emptyViewContentControl; FrameworkElement _emptyView; + Orientation _orientation; public FormsGridView() { - Template = (UWPControlTemplate)UWPApp.Current.Resources["FormsListViewTemplate"]; + // Using the full style for this control, because for some reason on 16299 we can't set the ControlTemplate + // (it just fails silently saying it can't find the resource key) + DefaultStyleKey = typeof(FormsGridView); - // TODO hartez 2018/06/06 09:52:16 Do we need to clean this up? If so, where? RegisterPropertyChangedCallback(ItemsPanelProperty, ItemsPanelChanged); Loaded += OnLoaded; } - public int MaximumRowsOrColumns + public int Span { - get => _maximumRowsOrColumns; + get => _span; set { - _maximumRowsOrColumns = value; + _span = value; if (_wrapGrid != null) { - _wrapGrid.MaximumRowsOrColumns = MaximumRowsOrColumns; + UpdateItemSize(); } } } - public Visibility EmptyViewVisibility - { - get { return (Visibility)GetValue(EmptyViewVisibilityProperty); } - set { SetValue(EmptyViewVisibilityProperty, value); } - } - public static readonly DependencyProperty EmptyViewVisibilityProperty = - DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), + DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), typeof(FormsGridView), new PropertyMetadata(Visibility.Collapsed)); - // TODO hartez 2018/06/06 10:01:32 Probably should just create a local enum for this? - public void UseHorizontalItemsPanel() + public Visibility EmptyViewVisibility { - ItemsPanel = - (ItemsPanelTemplate)UWPApp.Current.Resources["HorizontalGridItemsPanel"]; + get { return (Visibility)GetValue(EmptyViewVisibilityProperty); } + set { SetValue(EmptyViewVisibilityProperty, value); } } - public void UseVerticalItemsPanel() + public Orientation Orientation { - ItemsPanel = - (ItemsPanelTemplate)UWPApp.Current.Resources["VerticalGridItemsPanel"]; + get => _orientation; + set + { + _orientation = value; + if (_orientation == Orientation.Horizontal) + { + ItemsPanel = (ItemsPanelTemplate)UWPApp.Current.Resources["HorizontalGridItemsPanel"]; + ScrollViewer.SetHorizontalScrollMode(this, ScrollMode.Auto); + ScrollViewer.SetHorizontalScrollBarVisibility(this, UWPControls.ScrollBarVisibility.Auto); + } + else + { + ItemsPanel = (ItemsPanelTemplate)UWPApp.Current.Resources["VerticalGridItemsPanel"]; + } + } } void FindItemsWrapGrid() @@ -66,7 +74,25 @@ void FindItemsWrapGrid() return; } - _wrapGrid.MaximumRowsOrColumns = MaximumRowsOrColumns; + _wrapGrid.SizeChanged -= WrapGridSizeChanged; + _wrapGrid.SizeChanged += WrapGridSizeChanged; + } + + void WrapGridSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateItemSize(); + } + + void UpdateItemSize() + { + if (_orientation == Orientation.Horizontal) + { + _wrapGrid.ItemHeight = _wrapGrid.ActualHeight / Span; + } + else + { + _wrapGrid.ItemWidth = _wrapGrid.ActualWidth / Span; + } } void ItemsPanelChanged(DependencyObject sender, DependencyProperty dp) @@ -100,5 +126,11 @@ protected override void OnApplyTemplate() _emptyViewContentControl.Content = _emptyView; } } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + GroupFooterItemTemplateContext.EnsureSelectionDisabled(element, item); + base.PrepareContainerForItemOverride(element, item); + } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs b/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs index 95a8e97ecdb..cf36d40b049 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/FormsListView.cs @@ -15,15 +15,16 @@ public FormsListView() Template = (UWPControlTemplate)UWPApp.Current.Resources["FormsListViewTemplate"]; } + public static readonly DependencyProperty EmptyViewVisibilityProperty = + DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), + typeof(FormsListView), new PropertyMetadata(Visibility.Collapsed)); + public Visibility EmptyViewVisibility { get { return (Visibility)GetValue(EmptyViewVisibilityProperty); } set { SetValue(EmptyViewVisibilityProperty, value); } } - public static readonly DependencyProperty EmptyViewVisibilityProperty = - DependencyProperty.Register(nameof(EmptyViewVisibility), typeof(Visibility), typeof(FormsListView), new PropertyMetadata(Visibility.Collapsed)); - public void SetEmptyView(FrameworkElement emptyView) { _emptyView = emptyView; @@ -45,5 +46,11 @@ protected override void OnApplyTemplate() _emptyViewContentControl.Content = _emptyView; } } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + GroupFooterItemTemplateContext.EnsureSelectionDisabled(element, item); + base.PrepareContainerForItemOverride(element, item); + } } } diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/GroupFooterItemTemplateContext.cs b/Xamarin.Forms.Platform.UAP/CollectionView/GroupFooterItemTemplateContext.cs new file mode 100644 index 00000000000..c5b23828212 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/GroupFooterItemTemplateContext.cs @@ -0,0 +1,23 @@ +using Windows.UI.Xaml; + +namespace Xamarin.Forms.Platform.UWP +{ + internal class GroupFooterItemTemplateContext : ItemTemplateContext + { + public GroupFooterItemTemplateContext(DataTemplate formsDataTemplate, object item, + BindableObject container, double? height = null, double? width = null, Thickness? itemSpacing = null) + : base(formsDataTemplate, item, container, height, width, itemSpacing) + { + } + + public static void EnsureSelectionDisabled(DependencyObject element, object item) + { + if (item is GroupFooterItemTemplateContext) + { + // Prevent the group footer from being selectable + (element as FrameworkElement).IsHitTestVisible = false; + } + + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/GroupHeaderStyleSelector.cs b/Xamarin.Forms.Platform.UAP/CollectionView/GroupHeaderStyleSelector.cs new file mode 100644 index 00000000000..f25f01d72e9 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/GroupHeaderStyleSelector.cs @@ -0,0 +1,17 @@ +using Windows.UI.Xaml.Controls; +using UWPApp = Windows.UI.Xaml.Application; +using UWPDataTemplate = Windows.UI.Xaml.DataTemplate; + +namespace Xamarin.Forms.Platform.UWP +{ + internal class GroupHeaderStyleSelector : GroupStyleSelector + { + protected override GroupStyle SelectGroupStyleCore(object group, uint level) + { + return new GroupStyle + { + HeaderTemplate = (UWPDataTemplate)UWPApp.Current.Resources["GroupHeaderTemplate"] + }; + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/GroupTemplateContext.cs b/Xamarin.Forms.Platform.UAP/CollectionView/GroupTemplateContext.cs new file mode 100644 index 00000000000..31203143d14 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/GroupTemplateContext.cs @@ -0,0 +1,50 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.UWP +{ + internal class GroupTemplateContext + { + public ItemTemplateContext HeaderItemTemplateContext { get; } + public ItemTemplateContext FooterItemTemplateContext { get; } + public object Items { get; } + + public GroupTemplateContext(ItemTemplateContext headerItemTemplateContext, + ItemTemplateContext footerItemTemplateContext, object items) + { + HeaderItemTemplateContext = headerItemTemplateContext; + FooterItemTemplateContext = footerItemTemplateContext; + + if (footerItemTemplateContext == null) + { + Items = items; + } + else + { + // UWP ListViewBase does not support group footers. So we're going to fake the footer by adding an + // extra item to the ItemsSource so the footer shows up at the end of the group. + + if (items is IList itemsList) + { + // If it's already an IList, we want to make sure to keep it that way + itemsList.Add(footerItemTemplateContext); + Items = itemsList; + return; + } + + // If the group items are not an IList, then we'll have to append the footer the hard way + + var listPlusFooter = new List(); + + foreach (var item in (items as IEnumerable)) + { + listPlusFooter.Add(item); + } + + listPlusFooter.Add(footerItemTemplateContext); + + Items = listPlusFooter; + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/GroupableItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/GroupableItemsViewRenderer.cs new file mode 100644 index 00000000000..f44a5827b2a --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/GroupableItemsViewRenderer.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; + +namespace Xamarin.Forms.Platform.UWP +{ + public class GroupableItemsViewRenderer : SelectableItemsViewRenderer + { + GroupableItemsView _groupableItemsView; + + protected override void SetUpNewElement(ItemsView newElement) + { + _groupableItemsView = Element as GroupableItemsView; + base.SetUpNewElement(newElement); + } + + protected override void TearDownOldElement(ItemsView oldElement) + { + base.TearDownOldElement(oldElement); + _groupableItemsView = null; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs changedProperty) + { + base.OnElementPropertyChanged(sender, changedProperty); + + if (changedProperty.IsOneOf(GroupableItemsView.IsGroupedProperty, + GroupableItemsView.GroupFooterTemplateProperty, GroupableItemsView.GroupHeaderTemplateProperty)) + { + UpdateItemsSource(); + } + } + + protected override CollectionViewSource CreateCollectionViewSource() + { + if (_groupableItemsView != null && _groupableItemsView.IsGrouped) + { + var itemTemplate = Element.ItemTemplate; + var itemsSource = Element.ItemsSource; + + return new CollectionViewSource + { + Source = TemplatedItemSourceFactory.CreateGrouped(itemsSource, itemTemplate, + _groupableItemsView.GroupHeaderTemplate, _groupableItemsView.GroupFooterTemplate, Element), + IsSourceGrouped = true, + ItemsPath = new Windows.UI.Xaml.PropertyPath(nameof(GroupTemplateContext.Items)) + }; + } + else + { + return base.CreateCollectionViewSource(); + } + } + + protected override void UpdateItemTemplate() + { + base.UpdateItemTemplate(); + + ListViewBase.GroupStyleSelector = new GroupHeaderStyleSelector(); + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/GroupedItemTemplateCollection.cs b/Xamarin.Forms.Platform.UAP/CollectionView/GroupedItemTemplateCollection.cs new file mode 100644 index 00000000000..59ae0368776 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/GroupedItemTemplateCollection.cs @@ -0,0 +1,166 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace Xamarin.Forms.Platform.UWP +{ + internal class GroupedItemTemplateCollection : ObservableCollection + { + readonly IEnumerable _itemsSource; + readonly DataTemplate _itemTemplate; + readonly DataTemplate _groupHeaderTemplate; + readonly DataTemplate _groupFooterTemplate; + readonly BindableObject _container; + readonly IList _groupList; + + public GroupedItemTemplateCollection(IEnumerable itemsSource, DataTemplate itemTemplate, + DataTemplate groupHeaderTemplate, DataTemplate groupFooterTemplate, BindableObject container) + { + _itemsSource = itemsSource; + _itemTemplate = itemTemplate; + _groupHeaderTemplate = groupHeaderTemplate; + _groupFooterTemplate = groupFooterTemplate; + _container = container; + + foreach (var group in _itemsSource) + { + var groupTemplateContext = CreateGroupTemplateContext(group); + Add(groupTemplateContext); + } + + if (_itemsSource is IList groupList && _itemsSource is INotifyCollectionChanged incc) + { + _groupList = groupList; + incc.CollectionChanged += GroupsChanged; + } + } + + GroupTemplateContext CreateGroupTemplateContext(object group) + { + var groupHeaderTemplateContext = _groupHeaderTemplate != null + ? new ItemTemplateContext(_groupHeaderTemplate, group, _container) : null; + + var groupFooterTemplateContext = _groupFooterTemplate != null + ? new GroupFooterItemTemplateContext(_groupFooterTemplate, group, _container) : null; + + // This is where we'll eventually look at GroupItemPropertyName + var groupItemsList = TemplatedItemSourceFactory.Create(group as IEnumerable, _itemTemplate, _container); + + return new GroupTemplateContext(groupHeaderTemplateContext, groupFooterTemplateContext, groupItemsList); + } + + void GroupsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + Add(args); + break; + case NotifyCollectionChangedAction.Move: + Move(args); + break; + case NotifyCollectionChangedAction.Remove: + Remove(args); + break; + case NotifyCollectionChangedAction.Replace: + Replace(args); + break; + case NotifyCollectionChangedAction.Reset: + Reset(); + break; + } + } + + void Add(NotifyCollectionChangedEventArgs args) + { + var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _groupList.IndexOf(args.NewItems[0]); + + var count = args.NewItems.Count; + + for (int n = 0; n < count; n++) + { + Insert(startIndex, CreateGroupTemplateContext(args.NewItems[n])); + } + } + + void Move(NotifyCollectionChangedEventArgs args) + { + var count = args.NewItems.Count; + + if (args.OldStartingIndex > args.NewStartingIndex) + { + for (int n = 0; n < count; n++) + { + Move(args.OldStartingIndex + n, args.NewStartingIndex + n); + } + + return; + } + + for (int n = count - 1; n >= 0; n--) + { + Move(args.OldStartingIndex + n, args.NewStartingIndex + n); + } + } + + void Remove(NotifyCollectionChangedEventArgs args) + { + var startIndex = args.OldStartingIndex; + + if (startIndex < 0) + { + // INCC implementation isn't giving us enough information to know where the removed items were in the + // collection. So the best we can do is a full Reset. + Reset(); + return; + } + + var count = args.OldItems.Count; + + for (int n = startIndex + count - 1; n >= startIndex; n--) + { + RemoveAt(n); + } + } + + void Replace(NotifyCollectionChangedEventArgs args) + { + var newItemCount = args.NewItems.Count; + + if (newItemCount == args.OldItems.Count) + { + for (int n = 0; n < newItemCount; n++) + { + var index = args.OldStartingIndex + n; + var oldItem = this[index]; + var newItem = CreateGroupTemplateContext(args.NewItems[0]); + Items[index] = newItem; + var update = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, index); + OnCollectionChanged(update); + } + } + else + { + // If we're replacing one set with an equal size set, we can do a soft reset; if not, we have to completely + // rebuild the collection + Reset(); + } + } + + void Reset() + { + Items.Clear(); + _groupList.Clear(); + + foreach (var group in _itemsSource) + { + var groupTemplateContext = CreateGroupTemplateContext(group); + _groupList.Add(group); + Items.Add(groupTemplateContext); + } + + var reset = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); + OnCollectionChanged(reset); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 6533dd7d020..1a0393080b7 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -1,7 +1,9 @@ -using Windows.UI.Xaml; +using System; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Xamarin.Forms.Internals; using WThickness = Windows.UI.Xaml.Thickness; +using WSize = Windows.Foundation.Size; namespace Xamarin.Forms.Platform.UWP { @@ -148,39 +150,54 @@ void ContentLoaded(object sender, RoutedEventArgs e) InvalidateMeasure(); } - protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize) + protected override WSize MeasureOverride(WSize availableSize) { if (_renderer == null) { return base.MeasureOverride(availableSize); } + var frameworkElement = Content as FrameworkElement; + var formsElement = _renderer.Element; if (ItemHeight != default || ItemWidth != default) { formsElement.Layout(new Rectangle(0, 0, ItemWidth, ItemHeight)); - var wsize = new Windows.Foundation.Size(ItemWidth, ItemHeight); + var wsize = new WSize(ItemWidth, ItemHeight); - (Content as FrameworkElement).Margin = new WThickness(ItemSpacing.Left, ItemSpacing.Top, ItemSpacing.Right, ItemSpacing.Bottom); + frameworkElement.Margin = new WThickness(ItemSpacing.Left, ItemSpacing.Top, ItemSpacing.Right, ItemSpacing.Bottom); - (Content as FrameworkElement).Measure(wsize); + frameworkElement.Measure(wsize); return base.MeasureOverride(wsize); } else { - Size request = formsElement.Measure(availableSize.Width, availableSize.Height, - MeasureFlags.IncludeMargins).Request; + var (width, height) = formsElement.Measure(availableSize.Width, availableSize.Height, + MeasureFlags.IncludeMargins).Request; + + width = Max(width, availableSize.Width); + height = Max(height, availableSize.Height); - formsElement.Layout(new Rectangle(0, 0, request.Width, request.Height)); + formsElement.Layout(new Rectangle(0, 0, width, height)); - var wsize = new Windows.Foundation.Size(request.Width, request.Height); + var wsize = new WSize(width, height); - (Content as FrameworkElement).Measure(wsize); + frameworkElement.Measure(wsize); return base.MeasureOverride(wsize); } } + + double Max(double requested, double available) + { + return Math.Max(requested, ClampInfinity(available)); + } + + double ClampInfinity(double value) + { + return double.IsInfinity(value) ? 0 : value; + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs index 255e64b6adc..925d69c1af0 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContext.cs @@ -2,6 +2,13 @@ { internal class ItemTemplateContext { + public DataTemplate FormsDataTemplate { get; } + public object Item { get; } + public BindableObject Container { get; } + public double ItemHeight { get; } + public double ItemWidth { get; } + public Thickness ItemSpacing { get; } + public ItemTemplateContext(DataTemplate formsDataTemplate, object item, BindableObject container, double? height = null, double? width = null, Thickness? itemSpacing = null) { @@ -18,12 +25,5 @@ internal class ItemTemplateContext if (itemSpacing.HasValue) ItemSpacing = itemSpacing.Value; } - - public DataTemplate FormsDataTemplate { get; } - public object Item { get; } - public BindableObject Container { get; } - public double ItemHeight { get; } - public double ItemWidth { get; } - public Thickness ItemSpacing { get; } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContextList.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContextList.cs index 760b9c8bf7b..f745ebf535b 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContextList.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemTemplateContextList.cs @@ -67,7 +67,7 @@ IEnumerator IEnumerable.GetEnumerator() internal class ItemTemplateContextListEnumerator : IEnumerator { public ItemTemplateContext Current { get; private set; } - object IEnumerator.Current { get; } + object IEnumerator.Current => Current; int _currentIndex = -1; private ItemTemplateContextList _itemTemplateContextList; diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 91977b76cae..ff9c83fe39e 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -1,16 +1,9 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; +using System.ComponentModel; using System.Threading.Tasks; -using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Xamarin.Forms.Internals; -using Xamarin.Forms.Platform.UAP; using UwpScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility; using UWPApp = Windows.UI.Xaml.Application; using UWPDataTemplate = Windows.UI.Xaml.DataTemplate; @@ -78,7 +71,6 @@ protected virtual void UpdateItemsSource() return; } - // TODO hartez 2018-05-22 12:59 PM Handle grouping CleanUpCollectionViewSource(); @@ -109,7 +101,7 @@ protected virtual void CleanUpCollectionViewSource() } } - if (Element.ItemsSource == null) + if (Element?.ItemsSource == null) { if (CollectionViewSource?.Source is INotifyCollectionChanged incc) { @@ -213,16 +205,15 @@ protected virtual void TearDownOldElement(ItemsView oldElement) // Stop listening for ScrollTo requests oldElement.ScrollToRequested -= ScrollToRequested; - if (ListViewBase != null) + if (CollectionViewSource != null) { - ListViewBase.ItemsSource = null; + CleanUpCollectionViewSource(); } - if (CollectionViewSource != null) + if (ListViewBase != null) { - CollectionViewSource.Source = null; + ListViewBase.ItemsSource = null; } - } void UpdateVerticalScrollBarVisibility() diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml index ebfe707fc3f..7c3bd31fdef 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml @@ -2,10 +2,10 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Xamarin.Forms.Platform.UWP"> - - - - + + + + + + + + + + + diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs index 8ede34c996d..8dfed020df2 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs @@ -1,6 +1,6 @@ using System.ComponentModel; using Windows.UI.Xaml.Controls; -using Xamarin.Forms.Platform.UAP; +using UWPApp = Windows.UI.Xaml.Application; namespace Xamarin.Forms.Platform.UWP { @@ -160,42 +160,29 @@ protected override void HandleLayoutPropertyChange(PropertyChangedEventArgs prop { if (ListViewBase is FormsGridView formsGridView) { - formsGridView.MaximumRowsOrColumns = ((GridItemsLayout)Layout).Span; + formsGridView.Span = ((GridItemsLayout)Layout).Span; } } } static ListViewBase CreateGridView(GridItemsLayout gridItemsLayout) { - var gridView = new FormsGridView(); - - if (gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal) + return new FormsGridView { - gridView.UseHorizontalItemsPanel(); + Orientation = gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal + ? Orientation.Horizontal + : Orientation.Vertical, - // TODO hartez 2018/06/06 12:13:38 Should this logic just be built into FormsGridView? - ScrollViewer.SetHorizontalScrollMode(gridView, ScrollMode.Auto); - ScrollViewer.SetHorizontalScrollBarVisibility(gridView, - Windows.UI.Xaml.Controls.ScrollBarVisibility.Auto); - } - else - { - gridView.UseVerticalItemsPanel(); - } - - gridView.MaximumRowsOrColumns = gridItemsLayout.Span; - - return gridView; + Span = gridItemsLayout.Span + }; } static ListViewBase CreateHorizontalListView() { - // TODO hartez 2018/06/05 16:18:57 Is there any performance benefit to caching the ItemsPanelTemplate lookup? - // TODO hartez 2018/05/29 15:38:04 Make sure the ItemsViewStyles.xaml xbf gets into the nuspec var horizontalListView = new Windows.UI.Xaml.Controls.ListView() { ItemsPanel = - (ItemsPanelTemplate)Windows.UI.Xaml.Application.Current.Resources["HorizontalListItemsPanel"] + (ItemsPanelTemplate)UWPApp.Current.Resources["HorizontalListItemsPanel"] }; ScrollViewer.SetHorizontalScrollMode(horizontalListView, ScrollMode.Auto); diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs b/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs index 9009fc1dc5d..090bc6b9dea 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/TemplatedItemSourceFactory.cs @@ -5,7 +5,8 @@ namespace Xamarin.Forms.Platform.UWP { internal static class TemplatedItemSourceFactory { - internal static object Create(IEnumerable itemsSource, DataTemplate itemTemplate, BindableObject container, double? itemHeight = null, double? itemWidth = null, Thickness? itemSpacing = null) + internal static object Create(IEnumerable itemsSource, DataTemplate itemTemplate, BindableObject container, + double? itemHeight = null, double? itemWidth = null, Thickness? itemSpacing = null) { switch (itemsSource) { @@ -17,5 +18,11 @@ internal static object Create(IEnumerable itemsSource, DataTemplate itemTemplate return new ItemTemplateContextEnumerable(itemsSource, itemTemplate, container, itemHeight, itemWidth, itemSpacing); } + + internal static object CreateGrouped(IEnumerable itemsSource, DataTemplate itemTemplate, + DataTemplate groupHeaderTemplate, DataTemplate groupFooterTemplate, BindableObject container) + { + return new GroupedItemTemplateCollection(itemsSource, itemTemplate, groupHeaderTemplate, groupFooterTemplate, container); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index ceb5d5323c2..0e16d97d90f 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -13,7 +13,7 @@ UAP 10.0.16299.0 10.0.16299.0 - true + true 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -47,6 +47,11 @@ + + + + + @@ -213,6 +218,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -234,10 +243,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile From 2e531d726e5105f5a473441ddd41388ffe5559d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Wed, 9 Oct 2019 20:14:38 +0200 Subject: [PATCH 105/203] Fixed SnapPointsType discrepancy between CarouselView and LinearItemsLayout (#7893) --- .../Items/{ListItemsLayout.cs => LinearItemsLayout.cs} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename Xamarin.Forms.Core/Items/{ListItemsLayout.cs => LinearItemsLayout.cs} (89%) diff --git a/Xamarin.Forms.Core/Items/ListItemsLayout.cs b/Xamarin.Forms.Core/Items/LinearItemsLayout.cs similarity index 89% rename from Xamarin.Forms.Core/Items/ListItemsLayout.cs rename to Xamarin.Forms.Core/Items/LinearItemsLayout.cs index 47ebdf46426..7d46056d898 100644 --- a/Xamarin.Forms.Core/Items/ListItemsLayout.cs +++ b/Xamarin.Forms.Core/Items/LinearItemsLayout.cs @@ -13,7 +13,8 @@ public LinearItemsLayout([Parameter("Orientation")] ItemsLayoutOrientation orien internal static readonly LinearItemsLayout CarouselDefault = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal) { - SnapPointsAlignment = SnapPointsAlignment.Center, SnapPointsType = SnapPointsType.Mandatory + SnapPointsType = SnapPointsType.MandatorySingle, + SnapPointsAlignment = SnapPointsAlignment.Center }; public static readonly BindableProperty ItemSpacingProperty = From b26b3a9bb01672558561d25e4b436a047058ddf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Tuy=E1=BA=BFn?= Date: Thu, 10 Oct 2019 03:07:10 +0700 Subject: [PATCH 106/203] #7664 Use FontSizeConverter for FontImageSource.Size property (#7887) --- Xamarin.Forms.Core/FontImageSource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Xamarin.Forms.Core/FontImageSource.cs b/Xamarin.Forms.Core/FontImageSource.cs index ca19a750efb..1f723ddff50 100644 --- a/Xamarin.Forms.Core/FontImageSource.cs +++ b/Xamarin.Forms.Core/FontImageSource.cs @@ -23,6 +23,7 @@ public partial class FontImageSource : ImageSource public override bool IsEmpty => string.IsNullOrEmpty(Glyph); + [TypeConverter(typeof(FontSizeConverter))] public double Size { get => (double)GetValue(SizeProperty); set => SetValue(SizeProperty, value); } public static readonly BindableProperty SizeProperty = CreateBindableProperty(nameof(Size), 30d); From f8e6b7198ed321c48e94501cafabd3545bb6978b Mon Sep 17 00:00:00 2001 From: Pavel Yakovlev Date: Wed, 9 Oct 2019 18:33:33 -0700 Subject: [PATCH 107/203] [Shell, Android, iOS] shell menu scrolling (#5974) * [Android, iOS] add shell menu scrolling property * fix UWP build * - remove comment --- .../Issue4756.cs | 87 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + Xamarin.Forms.Core/ScrollMode.cs | 9 ++ Xamarin.Forms.Core/Shell/Shell.cs | 9 ++ .../ShellFlyoutTemplatedContentRenderer.cs | 47 +++++++++- .../CollectionView/CarouselViewRenderer.cs | 5 +- .../CollectionView/ScrollHelpers.cs | 2 +- .../StructuredItemsViewRenderer.cs | 5 +- .../Renderers/ShellTableViewController.cs | 29 +++++++ 9 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4756.cs create mode 100644 Xamarin.Forms.Core/ScrollMode.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4756.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4756.cs new file mode 100644 index 00000000000..d7c7aee0790 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4756.cs @@ -0,0 +1,87 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using System; + +#if UITEST +using Xamarin.UITest; +using Xamarin.Forms.Core.UITests; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Shell)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 4756, "Cannot prevent flyout menu list from scrolling", PlatformAffected.Default)] + public class Issue4756 : TestShell + { + protected override void Init() + { + FlowDirection = FlowDirection.RightToLeft; + FlyoutHeader = new StackLayout(); + FlyoutVerticalScrollMode = ScrollMode.Disabled; + for (int i = 0; i < 10; i++) + Items.Add(GenerateItem(i.ToString())); + } + + ShellItem GenerateItem(string title) + { + var picker = new Picker + { + ItemsSource = Enum.GetNames(typeof(ScrollMode)), + Title = "FlyoutVerticalScrollMode", + SelectedItem = FlyoutVerticalScrollMode.ToString() + }; + picker.SelectedIndexChanged += (_, e) => FlyoutVerticalScrollMode = (ScrollMode)picker.SelectedIndex; + + var section = new ShellSection + { + Items = + { + new Forms.ShellContent + { + Content = new ContentPage + { + Content = new StackLayout + { + Children = { + new Button + { + Text = "Add ShellItem", + Command = new Command(() => Items.Add(GenerateItem(Items.Count.ToString()))) + }, + new Button + { + Text = "Remove ShellItem", + Command = new Command(() => { + if (Items.Count > 1) + Items.RemoveAt(0); + }) + }, + new Label + { + Text = "FlyoutVerticalScrollMode" + }, + picker + } + } + } + } + } + }; + var item = new ShellItem + { + Title = title, + Route = title, + Items = + { + section + } + }; + item.CurrentItem = section; + return item; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 9b181147884..3e850fd0e09 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -105,6 +105,7 @@ Code + CollectionViewBindingErrors.xaml diff --git a/Xamarin.Forms.Core/ScrollMode.cs b/Xamarin.Forms.Core/ScrollMode.cs new file mode 100644 index 00000000000..8e7ef3875c0 --- /dev/null +++ b/Xamarin.Forms.Core/ScrollMode.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum ScrollMode + { + Disabled = 0, + Enabled = 1, + Auto = 2 + } +} diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index 4b4426499fa..be5037dbf7b 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -567,6 +567,9 @@ ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellS public static readonly BindableProperty FlyoutIconProperty = BindableProperty.Create(nameof(FlyoutIcon), typeof(ImageSource), typeof(Shell), null); + public static readonly BindableProperty FlyoutVerticalScrollModeProperty = + BindableProperty.Create(nameof(FlyoutVerticalScrollMode), typeof(ScrollMode), typeof(Shell), ScrollMode.Auto); + ShellNavigatedEventArgs _accumulatedEvent; bool _accumulateNavigatedEvents; View _flyoutHeaderView; @@ -578,6 +581,12 @@ public Shell() Route = Routing.GenerateImplicitRoute("shell"); } + public ScrollMode FlyoutVerticalScrollMode + { + get => (ScrollMode)GetValue(FlyoutVerticalScrollModeProperty); + set => SetValue(FlyoutVerticalScrollModeProperty, value); + } + public event EventHandler Navigated; public event EventHandler Navigating; diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs index 68f18d782f2..461d6bac4dd 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs @@ -31,6 +31,7 @@ public class ShellFlyoutTemplatedContentRenderer : Java.Lang.Object, IShellFlyou ImageView _bgImage; View _flyoutHeader; int _actionBarHeight; + ScrollLayoutManager _layoutManager; public ShellFlyoutTemplatedContentRenderer(IShellContext shellContext) { @@ -86,7 +87,7 @@ protected virtual void LoadView(IShellContext shellContext) Profile.FramePartition("Recycler.SetAdapter"); var adapter = new ShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); recycler.SetClipToPadding(false); - recycler.SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false)); + recycler.SetLayoutManager(_layoutManager = new ScrollLayoutManager(context, (int)Orientation.Vertical, false)); recycler.SetAdapter(adapter); Profile.FramePartition("Initialize BgImage"); @@ -118,6 +119,10 @@ protected virtual void LoadView(IShellContext shellContext) UpdateFlyoutBackground(); Profile.FrameEnd(); + + Profile.FramePartition(nameof(UpdateVerticalScrollMode)); + UpdateVerticalScrollMode(); + Profile.FrameEnd(); } void OnFlyoutHeaderMeasureInvalidated(object sender, EventArgs e) @@ -140,6 +145,14 @@ protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEven Shell.FlyoutBackgroundImageProperty, Shell.FlyoutBackgroundImageAspectProperty)) UpdateFlyoutBackground(); + else if (e.Is(Shell.FlyoutVerticalScrollModeProperty)) + UpdateVerticalScrollMode(); + } + + void UpdateVerticalScrollMode() + { + if (_layoutManager != null) + _layoutManager.ScrollVertically = _shellContext.Shell.FlyoutVerticalScrollMode; } protected virtual void UpdateFlyoutBackground() @@ -271,6 +284,7 @@ protected override void Dispose(bool disposing) _headerView.Dispose(); _rootView.Dispose(); + _layoutManager?.Dispose(); _defaultBackgroundColor?.Dispose(); _bgImage?.Dispose(); } @@ -281,6 +295,7 @@ protected override void Dispose(bool disposing) _rootView = null; _headerView = null; _shellContext = null; + _layoutManager = null; _disposed = true; } base.Dispose(disposing); @@ -365,4 +380,34 @@ protected override void Dispose(bool disposing) } } } + + internal class ScrollLayoutManager : LinearLayoutManager + { + public ScrollMode ScrollVertically { get; set; } = ScrollMode.Auto; + + public ScrollLayoutManager(Context context, int orientation, bool reverseLayout) : base(context, orientation, reverseLayout) + { + } + + int GetVisibleChildCount() + { + var firstVisibleIndex = FindFirstCompletelyVisibleItemPosition(); + var lastVisibleIndex = FindLastCompletelyVisibleItemPosition(); + return lastVisibleIndex - firstVisibleIndex + 1; + } + + public override bool CanScrollVertically() + { + switch (ScrollVertically) + { + case ScrollMode.Disabled: + return false; + case ScrollMode.Enabled: + return true; + default: + case ScrollMode.Auto: + return ChildCount > GetVisibleChildCount(); + } + } + } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs index e758803b294..21696481e1b 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/CarouselViewRenderer.cs @@ -8,6 +8,7 @@ using WScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility; using WSnapPointsType = Windows.UI.Xaml.Controls.SnapPointsType; using WSnapPointsAlignment = Windows.UI.Xaml.Controls.Primitives.SnapPointsAlignment; +using WScrollMode = Windows.UI.Xaml.Controls.ScrollMode; namespace Xamarin.Forms.Platform.UWP { @@ -177,11 +178,11 @@ void UpdateIsSwipeEnabled() switch (Layout) { case LinearItemsLayout listItemsLayout when listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal: - ScrollViewer.SetHorizontalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? ScrollMode.Auto : ScrollMode.Disabled); + ScrollViewer.SetHorizontalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollMode.Auto : WScrollMode.Disabled); ScrollViewer.SetHorizontalScrollBarVisibility(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollBarVisibility.Auto : WScrollBarVisibility.Disabled); break; case LinearItemsLayout listItemsLayout when listItemsLayout.Orientation == ItemsLayoutOrientation.Vertical: - ScrollViewer.SetVerticalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? ScrollMode.Auto : ScrollMode.Disabled); + ScrollViewer.SetVerticalScrollMode(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollMode.Auto : WScrollMode.Disabled); ScrollViewer.SetVerticalScrollBarVisibility(ListViewBase, CarouselView.IsSwipeEnabled ? WScrollBarVisibility.Auto : WScrollBarVisibility.Disabled); break; } diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs index 18cc60c2e9d..6c5f8d7d864 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs @@ -13,7 +13,7 @@ internal static class ScrollHelpers static bool IsVertical(ScrollViewer scrollViewer) { - return scrollViewer.HorizontalScrollMode == ScrollMode.Disabled; + return scrollViewer.HorizontalScrollMode == Windows.UI.Xaml.Controls.ScrollMode.Disabled; } static UWPPoint AdjustToMakeVisible(UWPPoint point, UWPSize itemSize, ScrollViewer scrollViewer) diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs index 8ede34c996d..d91dc7b6b94 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/StructuredItemsViewRenderer.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using Windows.UI.Xaml.Controls; using Xamarin.Forms.Platform.UAP; +using WScrollMode = Windows.UI.Xaml.Controls.ScrollMode; namespace Xamarin.Forms.Platform.UWP { @@ -174,7 +175,7 @@ static ListViewBase CreateGridView(GridItemsLayout gridItemsLayout) gridView.UseHorizontalItemsPanel(); // TODO hartez 2018/06/06 12:13:38 Should this logic just be built into FormsGridView? - ScrollViewer.SetHorizontalScrollMode(gridView, ScrollMode.Auto); + ScrollViewer.SetHorizontalScrollMode(gridView, WScrollMode.Auto); ScrollViewer.SetHorizontalScrollBarVisibility(gridView, Windows.UI.Xaml.Controls.ScrollBarVisibility.Auto); } @@ -198,7 +199,7 @@ static ListViewBase CreateHorizontalListView() (ItemsPanelTemplate)Windows.UI.Xaml.Application.Current.Resources["HorizontalListItemsPanel"] }; - ScrollViewer.SetHorizontalScrollMode(horizontalListView, ScrollMode.Auto); + ScrollViewer.SetHorizontalScrollMode(horizontalListView, WScrollMode.Auto); ScrollViewer.SetHorizontalScrollBarVisibility(horizontalListView, Windows.UI.Xaml.Controls.ScrollBarVisibility.Auto); diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs index 975755ee017..b6a65bcf476 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewController.cs @@ -36,6 +36,8 @@ void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) SetHeaderContentInset(); LayoutParallax(); } + else if (e.Is(Shell.FlyoutVerticalScrollModeProperty)) + UpdateVerticalScrollMode(); } void OnHeaderSizeChanged(object sender, EventArgs e) @@ -49,6 +51,32 @@ void OnStructureChanged(object sender, EventArgs e) { _source.ClearCache(); TableView.ReloadData(); + UpdateVerticalScrollMode(); + } + + void UpdateVerticalScrollMode() + { + switch (_context.Shell.FlyoutVerticalScrollMode) + { + case ScrollMode.Auto: + var pathToFirstRow = Foundation.NSIndexPath.FromRowSection(0, 0); + var firstCellRect = TableView.RectForRowAtIndexPath(pathToFirstRow); + var firstCellIsVisible = TableView.Bounds.Contains(firstCellRect); + + var lastRowIndex = NMath.Max(0, TableView.NumberOfRowsInSection(0) - 1); + var pathToLastRow = Foundation.NSIndexPath.FromRowSection(lastRowIndex, 0); + var cellRect = TableView.RectForRowAtIndexPath(pathToLastRow); + var lastCellIsVisible = TableView.Bounds.Contains(cellRect); + + TableView.ScrollEnabled = !firstCellIsVisible || !lastCellIsVisible; + break; + case ScrollMode.Enabled: + TableView.ScrollEnabled = true; + break; + case ScrollMode.Disabled: + TableView.ScrollEnabled = false; + break; + } } public void LayoutParallax() @@ -91,6 +119,7 @@ void SetHeaderContentInset() TableView.ContentInset = new UIEdgeInsets((nfloat)HeaderMax, 0, 0, 0); else TableView.ContentInset = new UIEdgeInsets(Platform.SafeAreaInsetsForWindow.Top, 0, 0, 0); + UpdateVerticalScrollMode(); } public override void ViewDidLoad() From c137d02ed00bfb48588f110af541073310f9a932 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Thu, 10 Oct 2019 10:19:04 +0200 Subject: [PATCH 108/203] [X] binding find the right indexer (#7896) supports having multiple indexers on a bound source, try to invoke the right one - fixes #7837 --- .../SetPropertiesVisitor.cs | 17 +++- .../TypeReferenceExtensions.cs | 6 +- Xamarin.Forms.Core/BindingExpression.cs | 93 +++++++++++-------- .../Issues/Gh7837.xaml | 16 ++++ .../Issues/Gh7837.xaml.cs | 50 ++++++++++ Xamarin.Forms.Xaml.UnitTests/MockCompiler.cs | 3 +- 6 files changed, 139 insertions(+), 46 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml.cs diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs index 80df774fb45..c33078696a6 100644 --- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -495,7 +495,22 @@ static IList<(PropertyDefinition property, TypeReference propDeclTypeRef, string if (indexArg != null) { var defaultMemberAttribute = previousPartTypeRef.GetCustomAttribute(module, ("mscorlib", "System.Reflection", "DefaultMemberAttribute")); var indexerName = defaultMemberAttribute?.ConstructorArguments?.FirstOrDefault().Value as string ?? "Item"; - var indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName && pd.GetMethod != null && pd.GetMethod.IsPublic, out var indexerDeclTypeRef); + PropertyDefinition indexer = null; + TypeReference indexerDeclTypeRef = null; + if (int.TryParse(indexArg, out _)) + indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName + && pd.GetMethod != null + && TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "Int32"))) + && pd.GetMethod.IsPublic, out indexerDeclTypeRef); + indexer = indexer ?? previousPartTypeRef.GetProperty(pd => pd.Name == indexerName + && pd.GetMethod != null + && TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "String"))) + && pd.GetMethod.IsPublic, out indexerDeclTypeRef); + indexer = indexer ?? previousPartTypeRef.GetProperty(pd => pd.Name == indexerName + && pd.GetMethod != null + && TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "String"))) + && pd.GetMethod.IsPublic, out indexerDeclTypeRef); + properties.Add((indexer, indexerDeclTypeRef, indexArg)); var indexType = indexer.GetMethod.Parameters[0].ParameterType.ResolveGenericParameters(indexerDeclTypeRef); if (!TypeRefComparer.Default.Equals(indexType, module.TypeSystem.String) && !TypeRefComparer.Default.Equals(indexType, module.TypeSystem.Int32)) diff --git a/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs index 1064e08f2e2..93e6d2f3aaa 100644 --- a/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs @@ -11,11 +11,9 @@ class TypeRefComparer : IEqualityComparer { static string GetAssembly(TypeReference typeRef) { - var md = typeRef.Scope as ModuleDefinition; - if (md != null) + if (typeRef.Scope is ModuleDefinition md) return md.Assembly.FullName; - var anr = typeRef.Scope as AssemblyNameReference; - if (anr != null) + if (typeRef.Scope is AssemblyNameReference anr) return anr.FullName; throw new ArgumentOutOfRangeException(nameof(typeRef)); } diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs index 5793dc0bccf..fdc46390eb8 100644 --- a/Xamarin.Forms.Core/BindingExpression.cs +++ b/Xamarin.Forms.Core/BindingExpression.cs @@ -243,6 +243,54 @@ void ParsePath() } } + PropertyInfo GetIndexer(TypeInfo sourceType, string indexerName, string content) + { + if (int.TryParse(content, out _)) { //try to find an indexer taking an int + foreach (var pi in sourceType.DeclaredProperties) { + if (pi.Name != indexerName) + continue; + if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(int)) + return pi; + if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(int)) + return pi; + } + } + + + //property isn't an int, or there wasn't any int indexer + foreach (var pi in sourceType.DeclaredProperties) { + if (pi.Name != indexerName) + continue; + if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(string)) + return pi; + if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(string)) + return pi; + } + + //try to fallback to an object indexer + foreach (var pi in sourceType.DeclaredProperties) + { + if (pi.Name != indexerName) + continue; + if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(object)) + return pi; + if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(object)) + return pi; + } + + //defined on an interface ? + foreach (var face in sourceType.ImplementedInterfaces) { + if (GetIndexer(face.GetTypeInfo(), indexerName, content) is PropertyInfo pi) + return pi; + } + + //defined on a base class ? + if (sourceType.BaseType is Type baseT && GetIndexer(baseT.GetTypeInfo(), indexerName, content) is PropertyInfo p) + return p; + return null; + } + + void SetupPart(TypeInfo sourceType, BindingExpressionPart part) { part.Arguments = null; @@ -254,8 +302,7 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) { if (sourceType.IsArray) { - int index; - if (!int.TryParse(part.Content, out index)) + if (!int.TryParse(part.Content, out var index)) Log.Warning("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType); else part.Arguments = new object[] { index }; @@ -265,46 +312,16 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) part.SetterType = sourceType.GetElementType(); } - DefaultMemberAttribute defaultMember = null; - foreach (var attrib in sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true)) + string indexerName = "Item"; + foreach (DefaultMemberAttribute attrib in sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true)) { - if (attrib is DefaultMemberAttribute d) - { - defaultMember = d; - break; - } + indexerName = attrib.MemberName; + break; } - string indexerName = defaultMember != null ? defaultMember.MemberName : "Item"; - part.IndexerName = indexerName; -#if NETSTANDARD2_0 - try { - property = sourceType.GetDeclaredProperty(indexerName); - } - catch (AmbiguousMatchException) { - // Get most derived instance of property - foreach (var p in sourceType.GetProperties()) { - if (p.Name == indexerName && (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType))) - property = p; - } - } -#else - property = sourceType.GetDeclaredProperty(indexerName); -#endif - - if (property == null) //is the indexer defined on the base class? - property = sourceType.BaseType.GetProperty(indexerName); - if (property == null) //is the indexer defined on implemented interface ? - { - foreach (var implementedInterface in sourceType.ImplementedInterfaces) - { - property = implementedInterface.GetProperty(indexerName); - if (property != null) - break; - } - } + property = GetIndexer(sourceType, indexerName, part.Content); if (property != null) { @@ -312,9 +329,7 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) ParameterInfo[] array = property.GetIndexParameters(); if (array.Length > 0) - { parameter = array[0]; - } if (parameter != null) { diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml new file mode 100644 index 00000000000..05e9c39b8dc --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml.cs new file mode 100644 index 00000000000..5b7eca6f53a --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7837.xaml.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.Forms; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public class Gh7837VMBase //using a base class to test #2131 + { + public string this[int index] => ""; + public string this[string index] => ""; + } + + public class Gh7837VM : Gh7837VMBase + { + public new string this[int index] => index == 42 ? "forty-two" : "dull number"; + public new string this[string index] => index.ToUpper(); + } + + public partial class Gh7837 : ContentPage + { + public Gh7837() => InitializeComponent(); + public Gh7837(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; + + [Test] + public void BindingWithMultipleIndexers([Values(false, true)]bool useCompiledXaml) + { + if (useCompiledXaml) + MockCompiler.Compile(typeof(Gh7837)); + var layout = new Gh7837(useCompiledXaml); + Assert.That(layout.label0.Text, Is.EqualTo("forty-two")); + Assert.That(layout.label1.Text, Is.EqualTo("FOO")); + Assert.That(layout.label2.Text, Is.EqualTo("forty-two")); + Assert.That(layout.label3.Text, Is.EqualTo("FOO")); + } + } + } +} diff --git a/Xamarin.Forms.Xaml.UnitTests/MockCompiler.cs b/Xamarin.Forms.Xaml.UnitTests/MockCompiler.cs index 79fd416411c..b804306a11b 100644 --- a/Xamarin.Forms.Xaml.UnitTests/MockCompiler.cs +++ b/Xamarin.Forms.Xaml.UnitTests/MockCompiler.cs @@ -35,8 +35,7 @@ public static void Compile(Type type, out MethodDefinition methdoDefinition) BuildEngine = new MSBuild.UnitTests.DummyBuildEngine() }; - IList exceptions; - if (xamlc.Execute(out exceptions) || exceptions == null || !exceptions.Any()) { + if (xamlc.Execute(out IList exceptions) || exceptions == null || !exceptions.Any()) { methdoDefinition = xamlc.InitCompForType; return; } From 88dfa6c2edbf1441334bd7d08057f6d966f5dcb0 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Thu, 10 Oct 2019 09:16:38 -0600 Subject: [PATCH 109/203] [UWP] [iOS] Verify selected items exist when updating native selections (#7902) * Add selection synchronization tests * Resync native selection after ItemsSource is updated * Update native selection on iOS when changing ItemsSource; prevent crash when selection contains items not in source; fixes #6963 * Automated test --- .../Issue6963.cs | 41 +++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../SelectionGalleries/SelectionGallery.cs | 2 + .../SelectionSynchronization.xaml | 90 +++++++++++++++++++ .../SelectionSynchronization.xaml.cs | 54 +++++++++++ .../SelectableItemsViewRenderer.cs | 3 +- .../CollectionView/ItemsViewRenderer.cs | 7 +- .../SelectableItemsViewController.cs | 6 +- .../SelectableItemsViewRenderer.cs | 22 ++++- 9 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6963.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionSynchronization.xaml create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionSynchronization.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6963.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6963.cs new file mode 100644 index 00000000000..99fba59d388 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6963.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 6963, "[Bug] CollectionView multiple pre-selection throws ArgumentOutOfRangeException when SelectedItems is bound to an ObservableCollection initialized inside the constructor.", + PlatformAffected.iOS | PlatformAffected.UWP)] + public class Issue6963 : TestNavigationPage + { + protected override void Init() + { +#if APP + FlagTestHelpers.SetCollectionViewTestFlag(); + + PushAsync(new GalleryPages.CollectionViewGalleries.SelectionGalleries.SelectionSynchronization()); +#endif + } + +#if UITEST + [Test] + public void SelectedItemsNotInSourceDoesNotCrash() + { + // If this page didn't crash, then we're good + RunningApp.WaitForElement("FirstLabel"); + } +#endif + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index d09034688c7..6dc6cacca9f 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -22,6 +22,7 @@ Code + Code diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionGallery.cs index 711cf5e4191..ad79a1b0352 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionGallery.cs @@ -30,6 +30,8 @@ public SelectionGallery() new SelectionChangedCommandParameter(), Navigation), GalleryBuilder.NavButton("Filterable Single Selection", () => new FilterSelection(), Navigation), + GalleryBuilder.NavButton("Selection Synchronization", () => + new SelectionSynchronization(), Navigation), } } }; diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionSynchronization.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionSynchronization.xaml new file mode 100644 index 00000000000..9261b634f78 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionSynchronization.xaml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + +