From b551dd96640d364dbabc72799836ad9ae22d9520 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Wed, 11 Nov 2020 11:02:50 -0700 Subject: [PATCH 01/20] Remove unnecessary UpdateConstraints calls when rotating (#12728) * Remove unnecessary extra UpdateConstraints calls when rotating Fixes #12193 * Restore correct height measurement * Scroll to current position when Carousel appears * Fix position setting * Validate scrollto position on CarouselView before attempting to scroll --- .../Issue12193.cs | 93 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionView/CarouselTemplatedCell.cs | 14 +-- .../CollectionView/CarouselViewController.cs | 40 +++----- .../CollectionView/CarouselViewLayout.cs | 11 --- .../CollectionView/CarouselViewRenderer.cs | 6 ++ .../CollectionView/ItemsViewController.cs | 1 - .../CollectionView/ItemsViewLayout.cs | 10 +- .../CollectionView/ItemsViewRenderer.cs | 2 +- 9 files changed, 126 insertions(+), 52 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12193.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12193.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12193.cs new file mode 100644 index 00000000000..8930b5d0808 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12193.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +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.CarouselView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 12193, "[Bug] CarouselView content disappears after 2 rotations if TextType=Html is used", + PlatformAffected.iOS)] + public class Issue12193 : TestContentPage + { + public const string HTML = "HTML"; + + protected override void Init() + { +#if APP + Title = "CarouselView HTML Label"; + + var instructions = new Label { Text = $"Rotate the device, then rotate it back 3 times. If the label \"{HTML}\" disappears, this test has failed." }; + + var source = new List(); + for (int n = 0; n < 10; n++) + { + source.Add($"Item: {n}"); + } + + var template = new DataTemplate(() => + { + var label = new Label + { + TextType = TextType.Html, + Text = $"

{HTML}

", + AutomationId = HTML + }; + + return label; + }); + + var cv = new CarouselView() + { + ItemsSource = source, + ItemTemplate = template, + Loop = false + }; + + var layout = new StackLayout(); + + layout.Children.Add(instructions); + layout.Children.Add(cv); + + Content = layout; +#endif + } + + +#if UITEST + [Test] + public async Task RotatingCarouselViewHTMLShouldNotDisappear() + { + int delay = 3000; + + RunningApp.SetOrientationPortrait(); + await Task.Delay(delay); + + RunningApp.SetOrientationLandscape(); + await Task.Delay(delay); + + RunningApp.SetOrientationPortrait(); + await Task.Delay(delay); + + RunningApp.SetOrientationLandscape(); + await Task.Delay(delay); + + RunningApp.SetOrientationPortrait(); + await Task.Delay(delay); + + RunningApp.WaitForElement(HTML); + } +#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 4d8ddd6434c..d033868c2a4 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 @@ Issue10897.xaml + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselTemplatedCell.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselTemplatedCell.cs index 0593cb9900a..234933c762b 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselTemplatedCell.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselTemplatedCell.cs @@ -7,26 +7,28 @@ namespace Xamarin.Forms.Platform.iOS public class CarouselTemplatedCell : TemplatedCell { public static NSString ReuseId = new NSString("Xamarin.Forms.Platform.iOS.CarouselTemplatedCell"); + CGSize _constraint; [Export("initWithFrame:")] [Internals.Preserve(Conditional = true)] protected CarouselTemplatedCell(CGRect frame) : base(frame) - { } + { + } public override void ConstrainTo(nfloat constant) { - } - CGSize _constrain; + public override void ConstrainTo(CGSize constraint) { - _constrain = constraint; - Layout(constraint); + ClearConstraints(); + + _constraint = constraint; } public override CGSize Measure() { - return new CGSize(_constrain.Width,_constrain.Height); + return new CGSize(_constraint.Width, _constraint.Height); } protected override (bool, Size) NeedsContentSizeUpdate(Size currentSize) diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs index 2cc3f7ce3dc..5bf9a73a3d4 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using System.Threading.Tasks; using CoreGraphics; using Foundation; @@ -15,7 +16,6 @@ public class CarouselViewController : ItemsViewController CarouselViewLoopManager _carouselViewLoopManager; bool _initialPositionSet; - bool _viewInitialized; bool _updatingScrollOffset; List _oldViews; int _gotoPosition = -1; @@ -64,23 +64,12 @@ public override void ViewDidLoad() public override void ViewWillLayoutSubviews() { base.ViewWillLayoutSubviews(); - if (!_viewInitialized) - { - _viewInitialized = true; - _size = CollectionView.Bounds.Size; - } - UpdateVisualStates(); } public override void ViewDidLayoutSubviews() { base.ViewDidLayoutSubviews(); - if (CollectionView.Bounds.Size != _size) - { - _size = CollectionView.Bounds.Size; - BoundsSizeChanged(); - } if (Carousel?.Loop == true && _carouselViewLoopManager != null) { @@ -88,7 +77,20 @@ public override void ViewDidLayoutSubviews() _carouselViewLoopManager.CenterIfNeeded(CollectionView, IsHorizontal); _updatingScrollOffset = false; } + UpdateInitialPosition(); + + if (CollectionView.Bounds.Size != _size) + { + _size = CollectionView.Bounds.Size; + BoundsSizeChanged(); + } + } + + void BoundsSizeChanged() + { + //if the size changed center the item + Carousel.ScrollTo(Carousel.Position, position: Xamarin.Forms.ScrollToPosition.Center, animate: false); } public override void DraggingStarted(UIScrollView scrollView) @@ -105,7 +107,7 @@ public override void UpdateItemsSource() { UnsubscribeCollectionItemsSourceChanged(ItemsSource); base.UpdateItemsSource(); - //we don't need to Subscribe becasse base calls CreateItemsViewSource + //we don't need to Subscribe because base calls CreateItemsViewSource _carouselViewLoopManager?.SetItemsSource(LoopItemsSource); _initialPositionSet = false; UpdateInitialPosition(); @@ -137,18 +139,6 @@ protected override IItemsViewSource CreateItemsViewSource() return itemsSource; } - protected void BoundsSizeChanged() - { - //we might be rotating our phone - ItemsViewLayout.ConstrainTo(CollectionView.Bounds.Size); - - //We call ReloadData so our VisibleCells also update their size - CollectionView.ReloadData(); - - //if the size changed center the item - Carousel.ScrollTo(Carousel.Position, position: Xamarin.Forms.ScrollToPosition.Center, animate: false); - } - internal void TearDown() { Carousel.PropertyChanged -= CarouselViewPropertyChanged; diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs index 764aa1a328e..afe33da5e8d 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewLayout.cs @@ -16,11 +16,6 @@ public CarouselViewLayout(ItemsLayout itemsLayout, CarouselView carouselView) : _itemsLayout = itemsLayout; } - public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds) - { - return false; - } - public override void ConstrainTo(CGSize size) { //TODO: Should we scale the items @@ -37,12 +32,6 @@ public override void ConstrainTo(CGSize size) } } - internal override void UpdateConstraints(CGSize size) - { - ConstrainTo(size); - UpdateCellConstraints(); - } - public override nfloat GetMinimumInteritemSpacingForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section) { if (_itemsLayout is LinearItemsLayout linearItemsLayout) diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs index 5f3a5444521..6cb59d63423 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewRenderer.cs @@ -24,6 +24,12 @@ protected override void ScrollToRequested(object sender, ScrollToRequestEventArg if (Carousel?.Loop == true) { var goToIndexPath = Controller.GetScrollToIndexPath(args.Index); + + if (!IsIndexPathValid(goToIndexPath)) + { + return; + } + Controller.CollectionView.ScrollToItem(goToIndexPath, args.ScrollToPosition.ToCollectionViewScrollPosition(_layout.ScrollDirection), args.IsAnimated); diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index 8409d41f87d..3716f4f32c7 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -393,6 +393,5 @@ void UpdateEmptyViewVisibility(bool isEmpty) _emptyViewDisplayed = false; } } - } } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs index 581047f1fb6..9085c91aa39 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewLayout.cs @@ -527,15 +527,9 @@ public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds) return base.ShouldInvalidateLayoutForBoundsChange(newBounds); } - return true; - } + UpdateConstraints(CollectionView.AdjustedContentInset.InsetRect(newBounds).Size); - public override void InvalidateLayout() - { - if (CollectionView != null && CollectionView.Frame != null) { - UpdateConstraints(CollectionView.Frame.Size); - } - base.InvalidateLayout(); + return true; } } } diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs index acea257b9ce..fa17941a259 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewRenderer.cs @@ -233,7 +233,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - bool IsIndexPathValid(NSIndexPath indexPath) + protected bool IsIndexPathValid(NSIndexPath indexPath) { if (indexPath.Item < 0 || indexPath.Section < 0) { From c6d03edbc37bc5369474a6b8bd414573b5ff9a77 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Wed, 11 Nov 2020 12:37:52 -0700 Subject: [PATCH 02/20] Snapshot grid structure info instead of modifying it in place (#12646) fixes #6247 fixes #10470 fixes #8512 Fixes #6247; Fixes #10470 --- Xamarin.Forms.Core/Grid.cs | 16 +- Xamarin.Forms.Core/GridCalc.cs | 1239 ++++++++++++++++---------------- 2 files changed, 636 insertions(+), 619 deletions(-) diff --git a/Xamarin.Forms.Core/Grid.cs b/Xamarin.Forms.Core/Grid.cs index 1abbd25c89b..7baac9e6f92 100644 --- a/Xamarin.Forms.Core/Grid.cs +++ b/Xamarin.Forms.Core/Grid.cs @@ -162,14 +162,14 @@ internal override void ComputeConstraintForView(View view) var result = LayoutConstraint.None; - if (_rows == null || _columns == null) - EnsureRowsColumnsInitialized(); + // grab a snapshot of this grid's structure for computing the constraints + var structure = new GridStructure(this); if (vOptions.Alignment == LayoutAlignment.Fill) { int row = GetRow(view); int rowSpan = GetRowSpan(view); - List rowDefinitions = _rows; + List rowDefinitions = structure.Rows; var canFix = true; @@ -196,7 +196,7 @@ internal override void ComputeConstraintForView(View view) { int col = GetColumn(view); int colSpan = GetColumnSpan(view); - List columnDefinitions = _columns; + List columnDefinitions = structure.Columns; var canFix = true; @@ -227,13 +227,7 @@ public void InvalidateMeasureInernalNonVirtual(InvalidationTrigger trigger) { InvalidateMeasureInternal(trigger); } - internal override void InvalidateMeasureInternal(InvalidationTrigger trigger) - { - base.InvalidateMeasureInternal(trigger); - _columns = null; - _rows = null; - } - + void OnDefinitionChanged(object sender, EventArgs args) { ComputeConstrainsForChildren(); diff --git a/Xamarin.Forms.Core/GridCalc.cs b/Xamarin.Forms.Core/GridCalc.cs index a1f46445171..800c3ce5339 100644 --- a/Xamarin.Forms.Core/GridCalc.cs +++ b/Xamarin.Forms.Core/GridCalc.cs @@ -7,19 +7,13 @@ namespace Xamarin.Forms { public partial class Grid { - List _columns; - List _rows; - protected override void LayoutChildren(double x, double y, double width, double height) { if (!InternalChildren.Any()) return; - MeasureGrid(width, height); - - // Make copies so if InvalidateMeasure is called during layout we dont crash when these get nulled - List columnsCopy = _columns; - List rowsCopy = _rows; + // grab a snapshot of this grid, fully-measured, so we can do the layout + var structure = new GridStructure(this, width, height); for (var index = 0; index < InternalChildren.Count; index++) { @@ -33,17 +27,17 @@ protected override void LayoutChildren(double x, double y, double width, double double posx = x + c * ColumnSpacing; for (var i = 0; i < c; i++) - posx += columnsCopy[i].ActualWidth; + posx += structure.Columns[i].ActualWidth; double posy = y + r * RowSpacing; for (var i = 0; i < r; i++) - posy += rowsCopy[i].ActualHeight; + posy += structure.Rows[i].ActualHeight; - double w = columnsCopy[c].ActualWidth; + double w = structure.Columns[c].ActualWidth; for (var i = 1; i < cs; i++) - w += ColumnSpacing + columnsCopy[c + i].ActualWidth; - double h = rowsCopy[r].ActualHeight; + w += ColumnSpacing + structure.Columns[c + i].ActualWidth; + double h = structure.Rows[r].ActualHeight; for (var i = 1; i < rs; i++) - h += RowSpacing + rowsCopy[r + i].ActualHeight; + h += RowSpacing + structure.Rows[r + i].ActualHeight; // in the future we can might maybe optimize by passing the already calculated size request LayoutChildIntoBoundingRegion(child, new Rectangle(posx, posy, w, h)); @@ -57,797 +51,826 @@ protected override SizeRequest OnSizeRequest(double widthConstraint, double heig if (!InternalChildren.Any()) return new SizeRequest(new Size(0, 0)); - MeasureGrid(widthConstraint, heightConstraint, true); + // grab a snapshot of this grid, fully-measured, so we can do the layout + var structure = new GridStructure(this, widthConstraint, heightConstraint, true); double columnWidthSum = 0; double nonStarColumnWidthSum = 0; - for (var index = 0; index < _columns.Count; index++) + for (var index = 0; index < structure.Columns.Count; index++) { - ColumnDefinition c = _columns[index]; + ColumnDefinition c = structure.Columns[index]; columnWidthSum += c.ActualWidth; if (!c.Width.IsStar) nonStarColumnWidthSum += c.ActualWidth; } double rowHeightSum = 0; double nonStarRowHeightSum = 0; - for (var index = 0; index < _rows.Count; index++) + for (var index = 0; index < structure.Rows.Count; index++) { - RowDefinition r = _rows[index]; + RowDefinition r = structure.Rows[index]; rowHeightSum += r.ActualHeight; if (!r.Height.IsStar) nonStarRowHeightSum += r.ActualHeight; } - var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); - var minimum = new Size(nonStarColumnWidthSum + (_columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (_rows.Count - 1) * RowSpacing); + var request = new Size(columnWidthSum + (structure.Columns.Count - 1) * ColumnSpacing, rowHeightSum + (structure.Rows.Count - 1) * RowSpacing); + var minimum = new Size(nonStarColumnWidthSum + (structure.Columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (structure.Rows.Count - 1) * RowSpacing); var result = new SizeRequest(request, minimum); return result; } - void AssignAbsoluteCells() + /// + /// Creates a snapshot of the Grid row/column structure, optionally with measurement + /// + class GridStructure { - for (var index = 0; index < _rows.Count; index++) + // These aren't strictly necessary; we could just use the auto-properties. But when we drop .NET Standard 1.0 + // support, the public accessors should be made into ReadOnlyList and we'll need these private lists + List _columns = new List(); + List _rows = new List(); + + // Technically speaking, these should really be ReadOnlyList, but AsReadOnly isn't available in .NET Standard 1.0 + // So for now we're just going to trust our internal code not to rely on modifying these + public List Columns => _columns; + public List Rows => _rows; + + public GridStructure(Grid grid) { - RowDefinition row = _rows[index]; - if (row.Height.IsAbsolute) - row.ActualHeight = row.Height.Value; + EnsureRowsColumnsInitialized(grid); } - for (var index = 0; index < _columns.Count; index++) + public GridStructure(Grid grid, double width, double height, bool requestSize = false) : this(grid) { - ColumnDefinition col = _columns[index]; - if (col.Width.IsAbsolute) - col.ActualWidth = col.Width.Value; + AssignAbsoluteCells(); + + CalculateAutoCells(grid, width, height); + + var columnSpacing = grid.ColumnSpacing; + var rowSpacing = grid.RowSpacing; + + if (!requestSize) + { + ContractAutoColumnsIfNeeded(width, columnSpacing, rowSpacing); + ContractAutoRowsIfNeeded(height, columnSpacing, rowSpacing); + } + + double totalStarsHeight = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + totalStarsHeight += row.Height.Value; + } + + double totalStarsWidth = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + totalStarsWidth += col.Width.Value; + } + + if (requestSize) + { + MeasureAndContractStarredColumns(grid, width, height, totalStarsWidth); + MeasureAndContractStarredRows(grid, width, height, totalStarsHeight); + } + else + { + CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight, columnSpacing, rowSpacing); + } + + ZeroUnassignedCells(); + + ExpandLastAutoRowIfNeeded(grid, height, requestSize); + ExpandLastAutoColumnIfNeeded(grid, width, requestSize); } - } - void CalculateAutoCells(double width, double height) - { - // this require multiple passes. First process the 1-span, then 2, 3, ... - // And this needs to be run twice, just in case a lower-span column can be determined by a larger span - for (var iteration = 0; iteration < 2; iteration++) + void AssignAbsoluteCells() { - for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + for (var index = 0; index < _rows.Count; index++) { - for (var i = 0; i < _rows.Count; i++) - { - RowDefinition row = _rows[i]; - if (!row.Height.IsAuto) - continue; - if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 - continue; + RowDefinition row = _rows[index]; + if (row.Height.IsAbsolute) + row.ActualHeight = row.Height.Value; + } - double actualHeight = row.ActualHeight; - double minimumHeight = row.MinimumHeight; - for (var index = 0; index < InternalChildren.Count; index++) + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsAbsolute) + col.ActualWidth = col.Width.Value; + } + } + + void CalculateAutoCells(Grid grid, double width, double height) + { + // this require multiple passes. First process the 1-span, then 2, 3, ... + // And this needs to be run twice, just in case a lower-span column can be determined by a larger span + for (var iteration = 0; iteration < 2; iteration++) + { + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + { + for (var i = 0; i < _rows.Count; i++) { - var child = (View)InternalChildren[index]; - if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + RowDefinition row = _rows[i]; + if (!row.Height.IsAuto) continue; - double assignedWidth = GetAssignedColumnWidth(child); - double assignedHeight = GetAssignedRowHeight(child); - double widthRequest = assignedWidth + GetUnassignedWidth(width); - double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height); - - SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); - actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); - minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 + continue; + + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + var child = (View)(grid.InternalChildren[index]); + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = assignedWidth + GetUnassignedWidth(width, grid.ColumnSpacing); + double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height, grid.RowSpacing); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - grid.RowSpacing * (GetRowSpan(child) - 1)); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - grid.RowSpacing * (GetRowSpan(child) - 1)); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; } - if (actualHeight >= 0) - row.ActualHeight = actualHeight; - if (minimumHeight >= 0) - row.MinimumHeight = minimumHeight; } - } - for (var colspan = 1; colspan <= _columns.Count; colspan++) - { - for (var i = 0; i < _columns.Count; i++) + for (var colspan = 1; colspan <= _columns.Count; colspan++) { - ColumnDefinition col = _columns[i]; - if (!col.Width.IsAuto) - continue; - if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip - continue; - - double actualWidth = col.ActualWidth; - double minimumWidth = col.MinimumWidth; - for (var index = 0; index < InternalChildren.Count; index++) + for (var i = 0; i < _columns.Count; i++) { - var child = (View)InternalChildren[index]; - if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + ColumnDefinition col = _columns[i]; + if (!col.Width.IsAuto) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip continue; - double assignedWidth = GetAssignedColumnWidth(child); - double assignedHeight = GetAssignedRowHeight(child); - double widthRequest = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); - double heightRequest = assignedHeight + GetUnassignedHeight(height); - - SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); - actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); - minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + var child = (View)(grid.InternalChildren)[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width, grid.ColumnSpacing); + double heightRequest = assignedHeight + GetUnassignedHeight(height, grid.RowSpacing); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * grid.ColumnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * grid.ColumnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + if (minimumWidth >= 0) + col.MinimumWidth = minimumWidth; } - if (actualWidth >= 0) - col.ActualWidth = actualWidth; - if (minimumWidth >= 0) - col.MinimumWidth = minimumWidth; } } } - } - - void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight) - { - double starColWidth = GetUnassignedWidth(width) / totalStarsWidth; - double starRowHeight = GetUnassignedHeight(height) / totalStarsHeight; - for (var index = 0; index < _columns.Count; index++) + void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight, double columnSpacing, double rowSpacing) { - ColumnDefinition col = _columns[index]; - if (col.Width.IsStar) - col.ActualWidth = col.Width.Value * starColWidth; + double starColWidth = GetUnassignedWidth(width, columnSpacing) / totalStarsWidth; + double starRowHeight = GetUnassignedHeight(height, rowSpacing) / totalStarsHeight; + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = col.Width.Value * starColWidth; + } + + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = row.Height.Value * starRowHeight; + } } - for (var index = 0; index < _rows.Count; index++) + double ComputeColumnWidthSum() { - RowDefinition row = _rows[index]; - if (row.Height.IsStar) - row.ActualHeight = row.Height.Value * starRowHeight; + double columnwWidthSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnwWidthSum += Math.Max(0, c.ActualWidth); + } + + return columnwWidthSum; } - } - double ComputeColumnWidthSum() - { - double columnwWidthSum = 0; - for (var index = 0; index < _columns.Count; index++) + double ComputeRowHeightSum() { - ColumnDefinition c = _columns[index]; - columnwWidthSum += Math.Max(0, c.ActualWidth); - } + double rowHeightSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowHeightSum += Math.Max(0, r.ActualHeight); + } - return columnwWidthSum; - } + return rowHeightSum; + } - double ComputeRowHeightSum() - { - double rowHeightSum = 0; - for (var index = 0; index < _rows.Count; index++) + Size ComputeCurrentSize(double columnSpacing, double rowSpacing) { - RowDefinition r = _rows[index]; - rowHeightSum += Math.Max(0, r.ActualHeight); - } + var columnWidthSum = ComputeColumnWidthSum(); + var rowHeightSum = ComputeRowHeightSum(); - return rowHeightSum; - } + return new Size(columnWidthSum + (_columns.Count - 1) * columnSpacing, rowHeightSum + (_rows.Count - 1) * rowSpacing); + } - Size ComputeCurrentSize() - { - var columnWidthSum = ComputeColumnWidthSum(); - var rowHeightSum = ComputeRowHeightSum(); + void ContractAutoColumnsIfNeeded(double targetWidth, double columnSpacing, double rowSpacing) + { + var currentSize = ComputeCurrentSize(columnSpacing, rowSpacing); - return new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); - } + if (currentSize.Width <= targetWidth) + { + return; + } - void ContractAutoColumnsIfNeeded(double targetWidth) - { - var currentSize = ComputeCurrentSize(); + double contractionSpace = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + if (c.Width.IsAuto) + contractionSpace += c.ActualWidth - c.MinimumWidth; + } + if (contractionSpace > 0) + { + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(currentSize.Width - targetWidth, 0)); + double contractFactor = contractionNeeded / contractionSpace; - if (currentSize.Width <= targetWidth) - { - return; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!col.Width.IsAuto) + continue; + double availableSpace = col.ActualWidth - Math.Max(col.MinimumWidth, 0); + double contraction = availableSpace * contractFactor; + col.ActualWidth -= contraction; + contractionNeeded -= contraction; + } + } } - double contractionSpace = 0; - for (var index = 0; index < _columns.Count; index++) - { - ColumnDefinition c = _columns[index]; - if (c.Width.IsAuto) - contractionSpace += c.ActualWidth - c.MinimumWidth; - } - if (contractionSpace > 0) + void ContractAutoRowsIfNeeded(double targetHeight, double columnSpacing, double rowSpacing) { + var currentSize = ComputeCurrentSize(columnSpacing, rowSpacing); + + if (currentSize.Height <= targetHeight) + { + return; + } + + double contractionSpace = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + if (r.Height.IsAuto) + contractionSpace += r.ActualHeight - r.MinimumHeight; + } + if (!(contractionSpace > 0)) + return; // contract as much as we can but no more - double contractionNeeded = Math.Min(contractionSpace, Math.Max(currentSize.Width - targetWidth, 0)); + double contractionNeeded = Math.Min(contractionSpace, Math.Max(currentSize.Height - targetHeight, 0)); double contractFactor = contractionNeeded / contractionSpace; - - for (var index = 0; index < _columns.Count; index++) + for (var index = 0; index < _rows.Count; index++) { - ColumnDefinition col = _columns[index]; - if (!col.Width.IsAuto) + RowDefinition row = _rows[index]; + if (!row.Height.IsAuto) continue; - double availableSpace = col.ActualWidth - Math.Max(col.MinimumWidth, 0); + double availableSpace = row.ActualHeight - row.MinimumHeight; double contraction = availableSpace * contractFactor; - col.ActualWidth -= contraction; + row.ActualHeight -= contraction; contractionNeeded -= contraction; } } - } - - void ContractAutoRowsIfNeeded(double targetHeight) - { - var currentSize = ComputeCurrentSize(); - if (currentSize.Height <= targetHeight) + void ContractStarColumnsIfNeeded(double targetWidth, double columnSpacing, double rowSpacing) { - return; - } + var request = ComputeCurrentSize(columnSpacing, rowSpacing); - double contractionSpace = 0; - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition r = _rows[index]; - if (r.Height.IsAuto) - contractionSpace += r.ActualHeight - r.MinimumHeight; - } - if (!(contractionSpace > 0)) - return; - // contract as much as we can but no more - double contractionNeeded = Math.Min(contractionSpace, Math.Max(currentSize.Height - targetHeight, 0)); - double contractFactor = contractionNeeded / contractionSpace; - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition row = _rows[index]; - if (!row.Height.IsAuto) - continue; - double availableSpace = row.ActualHeight - row.MinimumHeight; - double contraction = availableSpace * contractFactor; - row.ActualHeight -= contraction; - contractionNeeded -= contraction; - } - } + if (request.Width <= targetWidth) + { + return; + } - void ContractStarColumnsIfNeeded(double targetWidth) - { - var request = ComputeCurrentSize(); + double starColumnWidth = 0; + double starColumnMinWidth = 0; + double contractionSpace = 0; - if (request.Width <= targetWidth) - { - return; - } + for (int n = 0; n < _columns.Count; n++) + { + var column = _columns[n]; - double starColumnWidth = 0; - double starColumnMinWidth = 0; - double contractionSpace = 0; + if (!column.Width.IsStar) + { + continue; + } - for (int n = 0; n < _columns.Count; n++) - { - var column = _columns[n]; + if (starColumnWidth == 0) + { + starColumnWidth = column.ActualWidth; + } + else + { + starColumnWidth = Math.Min(column.ActualWidth, starColumnWidth); + } - if (!column.Width.IsStar) - { - continue; - } + if (column.MinimumWidth > starColumnMinWidth) + { + starColumnMinWidth = column.MinimumWidth; + } - if (starColumnWidth == 0) - { - starColumnWidth = column.ActualWidth; + contractionSpace += column.ActualWidth - column.MinimumWidth; } - else + + if (contractionSpace <= 0) { - starColumnWidth = Math.Min(column.ActualWidth, starColumnWidth); + return; } - if (column.MinimumWidth > starColumnMinWidth) + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - targetWidth, 0)); + double contractionFactor = contractionNeeded / contractionSpace; + var delta = contractionFactor >= 1 + ? starColumnWidth - starColumnMinWidth + : contractionFactor * (starColumnWidth - starColumnMinWidth); + + for (var index = 0; index < _columns.Count; index++) { - starColumnMinWidth = column.MinimumWidth; - } + ColumnDefinition column = _columns[index]; + if (!column.Width.IsStar) + { + continue; + } - contractionSpace += column.ActualWidth - column.MinimumWidth; + column.ActualWidth -= delta * column.Width.Value; + } } - if (contractionSpace <= 0) + void ContractStarRowsIfNeeded(double targetHeight, double columnSpacing, double rowSpacing) { - return; - } - - // contract as much as we can but no more - double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - targetWidth, 0)); - double contractionFactor = contractionNeeded / contractionSpace; - var delta = contractionFactor >= 1 - ? starColumnWidth - starColumnMinWidth - : contractionFactor * (starColumnWidth - starColumnMinWidth); + var request = ComputeCurrentSize(columnSpacing, rowSpacing); - for (var index = 0; index < _columns.Count; index++) - { - ColumnDefinition column = _columns[index]; - if (!column.Width.IsStar) + if (request.Height <= targetHeight) { - continue; + return; } - column.ActualWidth -= delta * column.Width.Value; - } - } + double starRowHeight = 0; + double starRowMinHeight = 0; + double contractionSpace = 0; - void ContractStarRowsIfNeeded(double targetHeight) - { - var request = ComputeCurrentSize(); + for (int n = 0; n < _rows.Count; n++) + { + var row = _rows[n]; - if (request.Height <= targetHeight) - { - return; - } + if (!row.Height.IsStar) + { + continue; + } - double starRowHeight = 0; - double starRowMinHeight = 0; - double contractionSpace = 0; + if (starRowHeight == 0) + { + starRowHeight = row.ActualHeight; + } + else + { + starRowHeight = Math.Min(row.ActualHeight, starRowHeight); + } - for (int n = 0; n < _rows.Count; n++) - { - var row = _rows[n]; + if (row.MinimumHeight > starRowMinHeight) + { + starRowMinHeight = row.MinimumHeight; + } - if (!row.Height.IsStar) - { - continue; + contractionSpace += row.ActualHeight - row.MinimumHeight; } - if (starRowHeight == 0) - { - starRowHeight = row.ActualHeight; - } - else + if (contractionSpace <= 0) { - starRowHeight = Math.Min(row.ActualHeight, starRowHeight); + return; } - if (row.MinimumHeight > starRowMinHeight) + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - targetHeight, 0)); + double contractionFactor = contractionNeeded / contractionSpace; + var delta = contractionFactor >= 1 + ? starRowHeight - starRowMinHeight + : contractionFactor * (starRowHeight - starRowMinHeight); + + for (var index = 0; index < _rows.Count; index++) { - starRowMinHeight = row.MinimumHeight; - } + RowDefinition row = _rows[index]; + if (!row.Height.IsStar) + { + continue; + } - contractionSpace += row.ActualHeight - row.MinimumHeight; + row.ActualHeight -= delta * row.Height.Value; + } } - if (contractionSpace <= 0) + void EnsureRowsColumnsInitialized(Grid grid) { - return; - } + _columns = grid.ColumnDefinitions == null ? new List() : grid.ColumnDefinitions.ToList(); + _rows = grid.RowDefinitions == null ? new List() : grid.RowDefinitions.ToList(); - double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - targetHeight, 0)); - double contractionFactor = contractionNeeded / contractionSpace; - var delta = contractionFactor >= 1 - ? starRowHeight - starRowMinHeight - : contractionFactor * (starRowHeight - starRowMinHeight); + int lastRow = -1; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + Element w = grid.InternalChildren[index]; + lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1); + } + lastRow = Math.Max(lastRow, grid.RowDefinitions.Count - 1); - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition row = _rows[index]; - if (!row.Height.IsStar) + int lastCol = -1; + for (var index = 0; index < grid.InternalChildren.Count; index++) { - continue; + Element w = grid.InternalChildren[index]; + lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1); } + lastCol = Math.Max(lastCol, grid.ColumnDefinitions.Count - 1); - row.ActualHeight -= delta * row.Height.Value; - } - } + while (_columns.Count <= lastCol) + _columns.Add(new ColumnDefinition()); + while (_rows.Count <= lastRow) + _rows.Add(new RowDefinition()); - void EnsureRowsColumnsInitialized() - { - _columns = ColumnDefinitions == null ? new List() : ColumnDefinitions.ToList(); - _rows = RowDefinitions == null ? new List() : RowDefinitions.ToList(); - - int lastRow = -1; - for (var index = 0; index < InternalChildren.Count; index++) - { - Element w = InternalChildren[index]; - lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1); + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + col.ActualWidth = -1; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + row.ActualHeight = -1; + } } - lastRow = Math.Max(lastRow, RowDefinitions.Count - 1); - int lastCol = -1; - for (var index = 0; index < InternalChildren.Count; index++) + void ExpandLastAutoColumnIfNeeded(Grid grid, double width, bool expandToRequest) { - Element w = InternalChildren[index]; - lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1); - } - lastCol = Math.Max(lastCol, ColumnDefinitions.Count - 1); + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + Element element = grid.InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; - while (_columns.Count <= lastCol) - _columns.Add(new ColumnDefinition()); - while (_rows.Count <= lastRow) - _rows.Add(new RowDefinition()); + ColumnDefinition col = GetLastAutoColumn(child); + if (col == null) + continue; - for (var index = 0; index < _columns.Count; index++) - { - ColumnDefinition col = _columns[index]; - col.ActualWidth = -1; - } - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition row = _rows[index]; - row.ActualHeight = -1; + double assignedWidth = GetAssignedColumnWidth(child); + double w = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width, grid.ColumnSpacing); + SizeRequest sizeRequest = child.Measure(w, GetAssignedRowHeight(child), MeasureFlags.IncludeMargins); + double requiredWidth = expandToRequest ? sizeRequest.Request.Width : sizeRequest.Minimum.Width; + double deltaWidth = requiredWidth - assignedWidth - (GetColumnSpan(child) - 1) * grid.ColumnSpacing; + if (deltaWidth > 0) + { + col.ActualWidth += deltaWidth; + } + } } - } - void ExpandLastAutoColumnIfNeeded(double width, bool expandToRequest) - { - for (var index = 0; index < InternalChildren.Count; index++) + void ExpandLastAutoRowIfNeeded(Grid grid, double height, bool expandToRequest) { - Element element = InternalChildren[index]; - var child = (View)element; - if (!child.IsVisible) - continue; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + Element element = grid.InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; - ColumnDefinition col = GetLastAutoColumn(child); - if (col == null) - continue; + RowDefinition row = GetLastAutoRow(child); + if (row == null) + continue; - double assignedWidth = GetAssignedColumnWidth(child); - double w = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); - SizeRequest sizeRequest = child.Measure(w, GetAssignedRowHeight(child), MeasureFlags.IncludeMargins); - double requiredWidth = expandToRequest ? sizeRequest.Request.Width : sizeRequest.Minimum.Width; - double deltaWidth = requiredWidth - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing; - if (deltaWidth > 0) - { - col.ActualWidth += deltaWidth; - } - } - } + double assignedHeight = GetAssignedRowHeight(child); + var unassignedHeight = GetUnassignedHeight(height, grid.RowSpacing); + double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + unassignedHeight; - void ExpandLastAutoRowIfNeeded(double height, bool expandToRequest) - { - for (var index = 0; index < InternalChildren.Count; index++) - { - Element element = InternalChildren[index]; - var child = (View)element; - if (!child.IsVisible) - continue; + var acw = GetAssignedColumnWidth(child); - RowDefinition row = GetLastAutoRow(child); - if (row == null) - continue; + SizeRequest sizeRequest = child.Measure(acw, h, MeasureFlags.IncludeMargins); - double assignedHeight = GetAssignedRowHeight(child); - var unassignedHeight = GetUnassignedHeight(height); - double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + unassignedHeight; + double requiredHeight = expandToRequest + ? sizeRequest.Request.Height + : sizeRequest.Request.Height <= h ? sizeRequest.Request.Height : sizeRequest.Minimum.Height; - var acw = GetAssignedColumnWidth(child); + double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * grid.RowSpacing; + if (deltaHeight > 0) + { + row.ActualHeight += deltaHeight; + } + } + } - SizeRequest sizeRequest = child.Measure(acw, h, MeasureFlags.IncludeMargins); + void MeasureAndContractStarredColumns(Grid grid, double width, double height, double totalStarsWidth) + { + double columnSpacing = grid.ColumnSpacing; + double rowSpacing = grid.RowSpacing; - double requiredHeight = expandToRequest - ? sizeRequest.Request.Height - : sizeRequest.Request.Height <= h ? sizeRequest.Request.Height : sizeRequest.Minimum.Height; + double starColWidth; + starColWidth = MeasuredStarredColumns(grid, GetUnassignedWidth(width, columnSpacing), height, totalStarsWidth); - double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing; - if (deltaHeight > 0) + if (!double.IsPositiveInfinity(width) && double.IsPositiveInfinity(height)) { - row.ActualHeight += deltaHeight; - } - } - } + // re-zero columns so GetUnassignedWidth returns correctly + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = 0; + } - void MeasureAndContractStarredColumns(double width, double height, double totalStarsWidth) - { - double starColWidth; - starColWidth = MeasuredStarredColumns(GetUnassignedWidth(width), height, totalStarsWidth); + starColWidth = Math.Max(starColWidth, GetUnassignedWidth(width, columnSpacing) / totalStarsWidth); + } - if (!double.IsPositiveInfinity(width) && double.IsPositiveInfinity(height)) - { - // re-zero columns so GetUnassignedWidth returns correctly for (var index = 0; index < _columns.Count; index++) { ColumnDefinition col = _columns[index]; if (col.Width.IsStar) - col.ActualWidth = 0; + col.ActualWidth = col.Width.Value * starColWidth; } - starColWidth = Math.Max(starColWidth, GetUnassignedWidth(width) / totalStarsWidth); + ContractStarColumnsIfNeeded(width, columnSpacing, rowSpacing); } - for (var index = 0; index < _columns.Count; index++) + void MeasureAndContractStarredRows(Grid grid, double width, double height, double totalStarsHeight) { - ColumnDefinition col = _columns[index]; - if (col.Width.IsStar) - col.ActualWidth = col.Width.Value * starColWidth; - } + double columnSpacing = grid.ColumnSpacing; + double rowSpacing = grid.RowSpacing; - ContractStarColumnsIfNeeded(width); - } + double starRowHeight; + starRowHeight = MeasureStarredRows(grid, width, GetUnassignedHeight(height, rowSpacing), totalStarsHeight); - void MeasureAndContractStarredRows(double width, double height, double totalStarsHeight) - { - double starRowHeight; - starRowHeight = MeasureStarredRows(width, GetUnassignedHeight(height), totalStarsHeight); + if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width)) + { + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = 0; + } + + starRowHeight = Math.Max(starRowHeight, GetUnassignedHeight(height, rowSpacing) / totalStarsHeight); + } - if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width)) - { for (var index = 0; index < _rows.Count; index++) { RowDefinition row = _rows[index]; if (row.Height.IsStar) - row.ActualHeight = 0; + row.ActualHeight = row.Height.Value * starRowHeight; } - starRowHeight = Math.Max(starRowHeight, GetUnassignedHeight(height) / totalStarsHeight); - } - - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition row = _rows[index]; - if (row.Height.IsStar) - row.ActualHeight = row.Height.Value * starRowHeight; - } - - ContractStarRowsIfNeeded(height); - } - - void MeasureGrid(double width, double height, bool requestSize = false) - { - EnsureRowsColumnsInitialized(); - - AssignAbsoluteCells(); - - CalculateAutoCells(width, height); - - if (!requestSize) - { - ContractAutoColumnsIfNeeded(width); - ContractAutoRowsIfNeeded(height); - } - - double totalStarsHeight = 0; - for (var index = 0; index < _rows.Count; index++) - { - RowDefinition row = _rows[index]; - if (row.Height.IsStar) - totalStarsHeight += row.Height.Value; - } - - double totalStarsWidth = 0; - for (var index = 0; index < _columns.Count; index++) - { - ColumnDefinition col = _columns[index]; - if (col.Width.IsStar) - totalStarsWidth += col.Width.Value; + ContractStarRowsIfNeeded(height, columnSpacing, rowSpacing); } - if (requestSize) + double MeasuredStarredColumns(Grid grid, double widthConstraint, double heightConstraint, double totalStarsWidth) { - MeasureAndContractStarredColumns(width, height, totalStarsWidth); - MeasureAndContractStarredRows(width, height, totalStarsHeight); - } - else - { - CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight); - } - - ZeroUnassignedCells(); - - ExpandLastAutoRowIfNeeded(height, requestSize); - ExpandLastAutoColumnIfNeeded(width, requestSize); - } - - double MeasuredStarredColumns(double widthConstraint, double heightConstraint, double totalStarsWidth) - { - double starColWidth; - for (var iteration = 0; iteration < 2; iteration++) - { - for (var colspan = 1; colspan <= _columns.Count; colspan++) + double starColWidth; + for (var iteration = 0; iteration < 2; iteration++) { - for (var i = 0; i < _columns.Count; i++) + for (var colspan = 1; colspan <= _columns.Count; colspan++) { - ColumnDefinition col = _columns[i]; - if (!col.Width.IsStar) - continue; - if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip - continue; - - double actualWidth = col.ActualWidth; - double minimumWidth = col.MinimumWidth; - for (var index = 0; index < InternalChildren.Count; index++) + for (var i = 0; i < _columns.Count; i++) { - var child = (View)InternalChildren[index]; - if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + ColumnDefinition col = _columns[i]; + if (!col.Width.IsStar) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip continue; - double assignedWidth = GetAssignedColumnWidth(child); - - // If we already have row height info, use it when measuring - double assignedHeight = GetAssignedRowHeight(child); - var hConstraint = assignedHeight > 0 ? assignedHeight : heightConstraint; - SizeRequest sizeRequest = child.Measure(widthConstraint, hConstraint, MeasureFlags.IncludeMargins); - var columnSpacing = (GetColumnSpan(child) - 1) * ColumnSpacing; - actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - columnSpacing); - minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - columnSpacing); + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + var child = (View)(grid.InternalChildren)[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + + // If we already have row height info, use it when measuring + double assignedHeight = GetAssignedRowHeight(child); + var hConstraint = assignedHeight > 0 ? assignedHeight : heightConstraint; + + SizeRequest sizeRequest = child.Measure(widthConstraint, hConstraint, MeasureFlags.IncludeMargins); + var columnSpacing = (GetColumnSpan(child) - 1) * grid.ColumnSpacing; + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - columnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - columnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + + if (minimumWidth >= 0) + col.MinimumWidth = minimumWidth; } - if (actualWidth >= 0) - col.ActualWidth = actualWidth; - - if (minimumWidth >= 0) - col.MinimumWidth = minimumWidth; } } - } - starColWidth = 0; - for (var index = 0; index < _columns.Count; index++) - { - ColumnDefinition col = _columns[index]; - if (!col.Width.IsStar || col.Width.Value == 0 || col.ActualWidth <= 0) - continue; + starColWidth = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!col.Width.IsStar || col.Width.Value == 0 || col.ActualWidth <= 0) + continue; - starColWidth += col.ActualWidth; - } + starColWidth += col.ActualWidth; + } - return Math.Max(starColWidth / totalStarsWidth, 1); - } + return Math.Max(starColWidth / totalStarsWidth, 1); + } - double MeasureStarredRows(double widthConstraint, double heightConstraint, double totalStarsHeight) - { - double starRowHeight; - for (var iteration = 0; iteration < 2; iteration++) + double MeasureStarredRows(Grid grid, double widthConstraint, double heightConstraint, double totalStarsHeight) { - for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + double starRowHeight; + for (var iteration = 0; iteration < 2; iteration++) { - for (var i = 0; i < _rows.Count; i++) + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) { - RowDefinition row = _rows[i]; - if (!row.Height.IsStar) - continue; - if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 - continue; - - double actualHeight = row.ActualHeight; - double minimumHeight = row.MinimumHeight; - for (var index = 0; index < InternalChildren.Count; index++) + for (var i = 0; i < _rows.Count; i++) { - var child = (View)InternalChildren[index]; - if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + RowDefinition row = _rows[i]; + if (!row.Height.IsStar) + continue; + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 continue; - double assignedHeight = GetAssignedRowHeight(child); - - // If we already have column width info, use it when measuring - double assignedWidth = GetAssignedColumnWidth(child); - var wConstraint = assignedWidth > 0 ? assignedWidth : widthConstraint; - SizeRequest sizeRequest = child.Measure(wConstraint, heightConstraint, MeasureFlags.IncludeMargins); - var rowSpacing = (GetRowSpan(child) - 1) * RowSpacing; - actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - rowSpacing); - minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - rowSpacing); + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < grid.InternalChildren.Count; index++) + { + var child = (View)(grid.InternalChildren)[index]; + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedHeight = GetAssignedRowHeight(child); + + // If we already have column width info, use it when measuring + double assignedWidth = GetAssignedColumnWidth(child); + var wConstraint = assignedWidth > 0 ? assignedWidth : widthConstraint; + + SizeRequest sizeRequest = child.Measure(wConstraint, heightConstraint, MeasureFlags.IncludeMargins); + var rowSpacing = (GetRowSpan(child) - 1) * grid.RowSpacing; + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - rowSpacing); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - rowSpacing); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; } - if (actualHeight >= 0) - row.ActualHeight = actualHeight; - - if (minimumHeight >= 0) - row.MinimumHeight = minimumHeight; } } + + starRowHeight = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (!row.Height.IsStar || row.Height.Value == 0 || row.ActualHeight <= 0) + continue; + + starRowHeight += row.ActualHeight; + } + + return Math.Max(starRowHeight / totalStarsHeight, 1); } - starRowHeight = 0; - for (var index = 0; index < _rows.Count; index++) + void ZeroUnassignedCells() { - RowDefinition row = _rows[index]; - if (!row.Height.IsStar || row.Height.Value == 0 || row.ActualHeight <= 0) - continue; - - starRowHeight += row.ActualHeight; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.ActualWidth < 0) + col.ActualWidth = 0; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.ActualHeight < 0) + row.ActualHeight = 0; + } } - return Math.Max(starRowHeight / totalStarsHeight, 1); - } + #region Helpers - void ZeroUnassignedCells() - { - for (var index = 0; index < _columns.Count; index++) + static bool IsInColumn(BindableObject child, int column) { - ColumnDefinition col = _columns[index]; - if (col.ActualWidth < 0) - col.ActualWidth = 0; + int childColumn = GetColumn(child); + int span = GetColumnSpan(child); + return childColumn <= column && column < childColumn + span; } - for (var index = 0; index < _rows.Count; index++) + + static bool IsInRow(BindableObject child, int row) { - RowDefinition row = _rows[index]; - if (row.ActualHeight < 0) - row.ActualHeight = 0; + int childRow = GetRow(child); + int span = GetRowSpan(child); + return childRow <= row && row < childRow + span; } - } - - #region Helpers - - static bool IsInColumn(BindableObject child, int column) - { - int childColumn = GetColumn(child); - int span = GetColumnSpan(child); - return childColumn <= column && column < childColumn + span; - } - static bool IsInRow(BindableObject child, int row) - { - int childRow = GetRow(child); - int span = GetRowSpan(child); - return childRow <= row && row < childRow + span; - } - - int NumberOfUnsetColumnWidth(BindableObject child) - { - var n = 0; - int index = GetColumn(child); - int span = GetColumnSpan(child); - for (int i = index; i < index + span; i++) - if (_columns[i].ActualWidth <= 0) - n++; - return n; - } + int NumberOfUnsetColumnWidth(BindableObject child) + { + var n = 0; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth <= 0) + n++; + return n; + } - int NumberOfUnsetRowHeight(BindableObject child) - { - var n = 0; - int index = GetRow(child); - int span = GetRowSpan(child); - for (int i = index; i < index + span; i++) - if (_rows[i].ActualHeight <= 0) - n++; - return n; - } + int NumberOfUnsetRowHeight(BindableObject child) + { + var n = 0; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight <= 0) + n++; + return n; + } - double GetAssignedColumnWidth(BindableObject child) - { - var actual = 0d; - int index = GetColumn(child); - int span = GetColumnSpan(child); - for (int i = index; i < index + span; i++) - if (_columns[i].ActualWidth >= 0) - actual += _columns[i].ActualWidth; - return actual; - } + double GetAssignedColumnWidth(BindableObject child) + { + var actual = 0d; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth >= 0) + actual += _columns[i].ActualWidth; + return actual; + } - double GetAssignedRowHeight(BindableObject child) - { - var actual = 0d; - int index = GetRow(child); - int span = GetRowSpan(child); - for (int i = index; i < index + span; i++) - if (_rows[i].ActualHeight >= 0) - actual += _rows[i].ActualHeight; - return actual; - } + double GetAssignedRowHeight(BindableObject child) + { + var actual = 0d; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight >= 0) + actual += _rows[i].ActualHeight; + return actual; + } - ColumnDefinition GetLastAutoColumn(BindableObject child) - { - int index = GetColumn(child); - int span = GetColumnSpan(child); - for (int i = index + span - 1; i >= index; i--) - if (_columns[i].Width.IsAuto) - return _columns[i]; - return null; - } + ColumnDefinition GetLastAutoColumn(BindableObject child) + { + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_columns[i].Width.IsAuto) + return _columns[i]; + return null; + } - RowDefinition GetLastAutoRow(BindableObject child) - { - int index = GetRow(child); - int span = GetRowSpan(child); - for (int i = index + span - 1; i >= index; i--) - if (_rows[i].Height.IsAuto) - return _rows[i]; - return null; - } + RowDefinition GetLastAutoRow(BindableObject child) + { + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_rows[i].Height.IsAuto) + return _rows[i]; + return null; + } - double GetUnassignedHeight(double heightRequest) - { - double assigned = (_rows.Count - 1) * RowSpacing; - for (var i = 0; i < _rows.Count; i++) + double GetUnassignedHeight(double heightRequest, double rowSpacing) { - double actual = _rows[i].ActualHeight; - if (actual >= 0) - assigned += actual; + double assigned = (_rows.Count - 1) * rowSpacing; + for (var i = 0; i < _rows.Count; i++) + { + double actual = _rows[i].ActualHeight; + if (actual >= 0) + assigned += actual; + } + return heightRequest - assigned; } - return heightRequest - assigned; - } - double GetUnassignedWidth(double widthRequest) - { - double assigned = (_columns.Count - 1) * ColumnSpacing; - for (var i = 0; i < _columns.Count; i++) + double GetUnassignedWidth(double widthRequest, double columnSpacing) { - double actual = _columns[i].ActualWidth; - if (actual >= 0) - assigned += actual; + double assigned = (_columns.Count - 1) * columnSpacing; + for (var i = 0; i < _columns.Count; i++) + { + double actual = _columns[i].ActualWidth; + if (actual >= 0) + assigned += actual; + } + return widthRequest - assigned; } - return widthRequest - assigned; - } - #endregion + #endregion + } } } \ No newline at end of file From 68f19a7912a78c86494340f650686b622e8503ec Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 12 Nov 2020 06:30:13 -0600 Subject: [PATCH 03/20] Apply internet check to additional tests (#12804) --- .../Helpers/UITestHelper.cs | 35 +++++++++++++++++++ .../TestPages/ScreenshotConditionalApp.cs | 30 ++-------------- .../BaseTestFixture.cs | 2 ++ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Helpers/UITestHelper.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Helpers/UITestHelper.cs index 8872b143be2..a8d76a3d116 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Helpers/UITestHelper.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Helpers/UITestHelper.cs @@ -6,6 +6,8 @@ using Xamarin.UITest; using NUnit.Framework; using Xamarin.UITest.Queries; +using System.Linq; +using Xamarin.Forms.Core.UITests; namespace Xamarin.Forms.Controls.Issues { @@ -35,6 +37,39 @@ public static void AssertHasText(this AppResult result, string text) Assert.Fail(); } + + public static string[] GetTestCategories(Type testType) + { + var testClassName = TestContext.CurrentContext.Test.ClassName; + + // TestContext.CurrentContext.Test.Properties["Category"] + // Only gives you the categories on the test itself + // There isn't a property I could find that gives you the Categories + // on the Test Class + return testType + .Assembly + .GetType(testClassName) + .GetCustomAttributes(typeof(NUnit.Framework.CategoryAttribute), true) + .OfType() + .Select(x => x.Name) + .Union(TestContext.CurrentContext.Test.Properties["Category"].OfType()) + .ToArray(); + } + + public static void MarkTestInconclusiveIfNoInternetConnectionIsPresent(Type testType, IApp app) + { + if (GetTestCategories(testType).Contains(UITestCategories.RequiresInternetConnection)) + { + var hasInternetAccess = $"{app.Invoke("hasInternetAccess")}"; + bool checkInternet; + + if (bool.TryParse(hasInternetAccess, out checkInternet)) + { + if (!checkInternet) + Assert.Inconclusive("Device Has No Internet Connection"); + } + } + } } } diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs index 31501fd8e1c..057f261cb67 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using NUnit.Framework.Interfaces; +using Xamarin.Forms.Controls.Issues; using Xamarin.Forms.Core.UITests; using Xamarin.UITest; using Xamarin.UITest.Queries; @@ -473,37 +474,10 @@ public void Restart() } #endif - string[] GetTestCategories() - { - var testClassName = TestContext.CurrentContext.Test.ClassName; - - // TestContext.CurrentContext.Test.Properties["Category"] - // Only gives you the categories on the test itself - // There isn't a property I could find that gives you the Categories - // on the Test Class - return GetType() - .Assembly - .GetType(testClassName) - .GetCustomAttributes(typeof(NUnit.Framework.CategoryAttribute), true) - .OfType() - .Select(x => x.Name) - .Union(TestContext.CurrentContext.Test.Properties["Category"].OfType()) - .ToArray(); - } - public void TestSetup(Type testType, bool isolate) { - if (GetTestCategories().Contains(UITestCategories.RequiresInternetConnection)) - { - var hasInternetAccess = $"{_app.Invoke("hasInternetAccess")}"; - bool checkInternet; - if (bool.TryParse(hasInternetAccess, out checkInternet)) - { - if (!checkInternet) - Assert.Inconclusive("Device Has No Internet Connection"); - } - } + UITestHelper.MarkTestInconclusiveIfNoInternetConnectionIsPresent(testType, _app); #if __WINDOWS__ RestartIfAppIsClosed(); diff --git a/Xamarin.Forms.Core.UITests.Shared/BaseTestFixture.cs b/Xamarin.Forms.Core.UITests.Shared/BaseTestFixture.cs index 0f26777a406..5b6722001bd 100644 --- a/Xamarin.Forms.Core.UITests.Shared/BaseTestFixture.cs +++ b/Xamarin.Forms.Core.UITests.Shared/BaseTestFixture.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using NUnit.Framework; using Xamarin.Forms.Controls; +using Xamarin.Forms.Controls.Issues; using Xamarin.UITest; using Xamarin.UITest.Queries; @@ -46,6 +47,7 @@ public void EnsureMemory() protected virtual void TestSetup() { //EnsureMemory(); + UITestHelper.MarkTestInconclusiveIfNoInternetConnectionIsPresent(GetType(), App); } [TearDown] From fcabac102fca331c2e118614ea3bb3c974934794 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 12 Nov 2020 06:31:17 -0600 Subject: [PATCH 04/20] Only build one UWP target when using IDE (#12808) --- UWP.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UWP.Build.props b/UWP.Build.props index a96966cf411..c8f98e097b5 100644 --- a/UWP.Build.props +++ b/UWP.Build.props @@ -3,8 +3,8 @@ $(UwpMinTargetFrameworks) - uap10.0.14393;uap10.0.16299 - + uap10.0.16299 + uap10.0.14393;uap10.0.16299 netstandard2.0 From a01da5dac48461938d58cf0d9dc0005f164cad12 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Thu, 12 Nov 2020 06:00:00 -0700 Subject: [PATCH 05/20] Prevent layout hang on iOS when CollectionView is initially invisible (#12769) fixes #12714 * Repro and automated test * Prevent iOS meltdown if CollectionView is initially invisible * Add check for invalid index path before scrolling CarouselView --- .../Issue12714.cs | 56 +++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionView/ItemsViewController.cs | 72 +++++++++++++------ 3 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs new file mode 100644 index 00000000000..b41c687425c --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Issue(IssueTracker.Github, 12714, + "[Bug] iOS application suspended at UICollectionViewFlowLayout.PrepareLayout() when using IsVisible = false", + PlatformAffected.iOS)] + public class Issue12714 : TestContentPage + { + const string Success = "Success"; + const string Show = "Show"; + + protected override void Init() + { + var items = new List() { "uno", "dos", "tres", Success }; + var cv = new CollectionView + { + ItemsSource = items, + IsVisible = false, + ItemsLayout = new GridItemsLayout(2, ItemsLayoutOrientation.Vertical) + }; + + var layout = new StackLayout() { Margin = 40 }; + + var button = new Button { Text = Show }; + button.Clicked += (sender, args) => { cv.IsVisible = !cv.IsVisible; }; + + layout.Children.Add(button); + layout.Children.Add(cv); + + Content = layout; + } + +#if UITEST + [Test] + public void InitiallyInvisbleCollectionViewSurvivesiOSLayoutNonsense() + { + RunningApp.WaitForElement(Show); + RunningApp.Tap(Show); + RunningApp.WaitForElement(Success); + } +#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 d033868c2a4..c0658ff08d9 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 @@ -46,6 +46,7 @@ + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index 3716f4f32c7..76de77345dd 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -13,7 +13,7 @@ public abstract class ItemsViewController : UICollectionViewControll public IItemsViewSource ItemsSource { get; protected set; } public TItemsView ItemsView { get; } protected ItemsViewLayout ItemsViewLayout { get; set; } - bool _initialConstraintsSet; + bool _initialized; bool _isEmpty; bool _emptyViewDisplayed; bool _disposed; @@ -36,18 +36,16 @@ public void UpdateLayout(ItemsViewLayout newLayout) return; ItemsViewLayout = newLayout; - ItemsViewLayout.GetPrototype = GetPrototype; - - Delegator = CreateDelegator(); - CollectionView.Delegate = Delegator; - // Make sure the new layout is sized properly - ItemsViewLayout.ConstrainTo(CollectionView.Bounds.Size); + _initialized = false; - CollectionView.SetCollectionViewLayout(ItemsViewLayout, false); + EnsureLayoutInitialized(); - // Reload the data so the currently visible cells get laid out according to the new layout - CollectionView.ReloadData(); + if (_initialized) + { + // Reload the data so the currently visible cells get laid out according to the new layout + CollectionView.ReloadData(); + } } protected override void Dispose(bool disposing) @@ -95,6 +93,11 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS public override nint GetItemsCount(UICollectionView collectionView, nint section) { + if (!_initialized) + { + return 0; + } + CheckForEmptySource(); return ItemsSource.ItemCountInGroup(section); @@ -125,10 +128,6 @@ public override void ViewDidLoad() base.ViewDidLoad(); ItemsSource = CreateItemsViewSource(); - ItemsViewLayout.GetPrototype = GetPrototype; - - Delegator = CreateDelegator(); - CollectionView.Delegate = Delegator; if (!Forms.IsiOS11OrNewer) AutomaticallyAdjustsScrollViewInsets = false; @@ -148,20 +147,46 @@ public override void ViewWillLayoutSubviews() { base.ViewWillLayoutSubviews(); - // We can't set this constraint up on ViewDidLoad, because Forms does other stuff that resizes the view + if (!_initialized) + { + UpdateEmptyView(); + } + + // We can't set this up during ViewDidLoad, because Forms does other stuff that resizes the view // and we end up with massive layout errors. And View[Will/Did]Appear do not fire for this controller // reliably. So until one of those options is cleared up, we set this flag so that the initial constraints // are set up the first time this method is called. - if (!_initialConstraintsSet) + EnsureLayoutInitialized(); + + if(_initialized) { - ItemsViewLayout.SetInitialConstraints(CollectionView.Bounds.Size); - UpdateEmptyView(); - _initialConstraintsSet = true; + LayoutEmptyView(); } - else + } + + void EnsureLayoutInitialized() + { + if (_initialized) { - LayoutEmptyView(); + return; } + + if (!ItemsView.IsVisible) + { + // If the CollectionView starts out invisible, we'll get a layout pass with a size of 1,1 and everything will + // go pear-shaped. So until the first time this CollectionView is visible, we do nothing. + return; + } + + _initialized = true; + + ItemsViewLayout.GetPrototype = GetPrototype; + + Delegator = CreateDelegator(); + CollectionView.Delegate = Delegator; + + ItemsViewLayout.SetInitialConstraints(CollectionView.Bounds.Size); + CollectionView.SetCollectionViewLayout(ItemsViewLayout, false); } protected virtual UICollectionViewDelegateFlowLayout CreateDelegator() @@ -183,6 +208,11 @@ public virtual void UpdateItemsSource() public override nint NumberOfSections(UICollectionView collectionView) { + if(!_initialized) + { + return 0; + } + CheckForEmptySource(); return ItemsSource.GroupCount; } From 6cd06fd655245d2fad09454c647ffda26ab7c746 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2020 13:10:48 +0000 Subject: [PATCH 06/20] Automated dotnet-format update (#12810) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../KeyboardPage.xaml.cs | 40 +++++++++---------- Xamarin.Forms.Core/Grid.cs | 2 +- Xamarin.Forms.Core/GridCalc.cs | 2 +- Xamarin.Forms.Core/RadioButton.cs | 4 +- Xamarin.Forms.Core/VisualElement.cs | 2 +- .../Renderers/TimePickerRenderer.cs | 4 +- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Xamarin.Forms.Controls/GalleryPages/DateTimePickerGalleries/KeyboardPage.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/DateTimePickerGalleries/KeyboardPage.xaml.cs index d5825b48559..67467b0424b 100644 --- a/Xamarin.Forms.Controls/GalleryPages/DateTimePickerGalleries/KeyboardPage.xaml.cs +++ b/Xamarin.Forms.Controls/GalleryPages/DateTimePickerGalleries/KeyboardPage.xaml.cs @@ -10,25 +10,25 @@ public partial class KeyboardPage : ContentPage public KeyboardPage() { InitializeComponent(); - BindingContext = this; - var dep = DependencyService.Get(); - if (dep != null) - { - keyboardphoneculture.Text = $"Device Culture: {dep.GetCurrentCultureInfo()}"; - } - else - { - var s = System.Globalization.CultureInfo.CurrentCulture.Name; - keyboardphoneculture.Text = "Device Culture: " + s; - } - } + BindingContext = this; + var dep = DependencyService.Get(); + if (dep != null) + { + keyboardphoneculture.Text = $"Device Culture: {dep.GetCurrentCultureInfo()}"; + } + else + { + var s = System.Globalization.CultureInfo.CurrentCulture.Name; + keyboardphoneculture.Text = "Device Culture: " + s; + } + } - void KeyboardType_SelectedIndexChanged(System.Object sender, System.EventArgs e) - { - string selectedValue = KeyboardType.Items[KeyboardType.SelectedIndex]; - var converter = new KeyboardTypeConverter(); - string keyboardStringValue = "Keyboard." + selectedValue; - keyboardEntry.Keyboard = (Keyboard)converter.ConvertFromInvariantString(keyboardStringValue); - } - } + void KeyboardType_SelectedIndexChanged(System.Object sender, System.EventArgs e) + { + string selectedValue = KeyboardType.Items[KeyboardType.SelectedIndex]; + var converter = new KeyboardTypeConverter(); + string keyboardStringValue = "Keyboard." + selectedValue; + keyboardEntry.Keyboard = (Keyboard)converter.ConvertFromInvariantString(keyboardStringValue); + } + } } diff --git a/Xamarin.Forms.Core/Grid.cs b/Xamarin.Forms.Core/Grid.cs index 7baac9e6f92..4f5cfa8ccd8 100644 --- a/Xamarin.Forms.Core/Grid.cs +++ b/Xamarin.Forms.Core/Grid.cs @@ -227,7 +227,7 @@ public void InvalidateMeasureInernalNonVirtual(InvalidationTrigger trigger) { InvalidateMeasureInternal(trigger); } - + void OnDefinitionChanged(object sender, EventArgs args) { ComputeConstrainsForChildren(); diff --git a/Xamarin.Forms.Core/GridCalc.cs b/Xamarin.Forms.Core/GridCalc.cs index 800c3ce5339..39377bcb54e 100644 --- a/Xamarin.Forms.Core/GridCalc.cs +++ b/Xamarin.Forms.Core/GridCalc.cs @@ -95,7 +95,7 @@ class GridStructure public List Columns => _columns; public List Rows => _rows; - public GridStructure(Grid grid) + public GridStructure(Grid grid) { EnsureRowsColumnsInitialized(grid); } diff --git a/Xamarin.Forms.Core/RadioButton.cs b/Xamarin.Forms.Core/RadioButton.cs index bd5a17cc5c0..edec79c5c92 100644 --- a/Xamarin.Forms.Core/RadioButton.cs +++ b/Xamarin.Forms.Core/RadioButton.cs @@ -435,9 +435,9 @@ static View BuildDefaultTemplate() }; BindToTemplatedParent(frame, BackgroundColorProperty, Frame.BorderColorProperty, HorizontalOptionsProperty, - MarginProperty, OpacityProperty, RotationProperty, ScaleProperty, ScaleXProperty, ScaleYProperty, + MarginProperty, OpacityProperty, RotationProperty, ScaleProperty, ScaleXProperty, ScaleYProperty, TranslationYProperty, TranslationXProperty, VerticalOptionsProperty); - + var grid = new Grid { RowSpacing = 0, diff --git a/Xamarin.Forms.Core/VisualElement.cs b/Xamarin.Forms.Core/VisualElement.cs index 07ee869e415..725dfb778da 100644 --- a/Xamarin.Forms.Core/VisualElement.cs +++ b/Xamarin.Forms.Core/VisualElement.cs @@ -986,7 +986,7 @@ void PropagateBindingContextToStateTriggers() foreach (var stateTrigger in state.StateTriggers) SetInheritedBindingContext(stateTrigger, BindingContext); } - + void OnFocused() => Focused?.Invoke(this, new FocusEventArgs(this, true)); internal void ChangeVisualStateInternal() => ChangeVisualState(); diff --git a/Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs index 6efa9a23be6..165ae940f23 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Globalization; using Android.App; using Android.Content; using Android.OS; @@ -8,7 +9,6 @@ using Android.Widget; using AColor = Android.Graphics.Color; using ATimePicker = Android.Widget.TimePicker; -using System.Globalization; namespace Xamarin.Forms.Platform.Android { @@ -17,7 +17,7 @@ public abstract class TimePickerRendererBase : ViewRenderer (DateFormat.Is24HourFormat(Context) && Element.Format == (string)TimePicker.FormatProperty.DefaultValue) || Element.Format?.Contains('H') == true; From e6224816be84bb9f09c4661b27bbcebda8ef9991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Thu, 12 Nov 2020 15:50:26 +0100 Subject: [PATCH 07/20] [iOS] Fix NRE in CarouselView without template (#12784) fixes #12777 * Fix crash in CarouselView without template * Added UITest Co-authored-by: Rui Marinho --- .../Issue12777.cs | 106 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionView/CarouselViewController.cs | 10 +- 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs new file mode 100644 index 00000000000..ca69fa521fb --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs @@ -0,0 +1,106 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using System.Collections.ObjectModel; +using System; + +#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, 12777, "[Bug] CarouselView NRE if item template is not specified", + PlatformAffected.iOS)] +#if UITEST + [Category(UITestCategories.Github10000)] + [Category(UITestCategories.CarouselView)] +#endif + public class Issue12777 : TestContentPage + { + public Issue12777() + { + BindingContext = new Issue12777ViewModel(); + } + + protected override void Init() + { + var layout = new StackLayout(); + + var instructions = new Label + { + Padding = new Thickness(12), + BackgroundColor = Color.Black, + TextColor = Color.White, + Text = "Without exceptions, the test has passed." + }; + + var carouselView = new CarouselView + { + AutomationId = "TestCarouselView" + }; + + carouselView.SetBinding(ItemsView.ItemsSourceProperty, nameof(Issue12777ViewModel.Items)); + + layout.Children.Add(instructions); + layout.Children.Add(carouselView); + + Content = layout; + } + +#if UITEST + [Test] + public void Issue12777Test() + { + RunningApp.WaitForElement("TestCarouselView"); + RunningApp.Screenshot("Test passed"); + } +#endif + } + + [Preserve(AllMembers = true)] + public class Issue12777Model + { + public Color Color { get; set; } + public string Name { get; set; } + } + + [Preserve(AllMembers = true)] + public class Issue12777ViewModel : BindableObject + { + ObservableCollection _items; + + public Issue12777ViewModel() + { + LoadItems(); + } + + public ObservableCollection Items + { + get { return _items; } + set + { + _items = value; + OnPropertyChanged(); + } + } + + void LoadItems() + { + Items = new ObservableCollection(); + + var random = new Random(); + + for (int n = 0; n < 5; n++) + { + Items.Add(new Issue12777Model + { + Color = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)), + Name = $"{n + 1}" + }); + } + } + } +} \ 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 c0658ff08d9..ca9121e4411 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 @@ -1628,6 +1628,7 @@ Issue11081.xaml + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs index 5bf9a73a3d4..e2b2c46b2be 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs @@ -35,12 +35,18 @@ public CarouselViewController(CarouselView itemsView, ItemsViewLayout layout) : public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath) { UICollectionViewCell cell; + if (Carousel?.Loop == true && _carouselViewLoopManager != null) { var cellAndCorrectedIndex = _carouselViewLoopManager.GetCellAndCorrectIndex(collectionView, indexPath, DetermineCellReuseId()); cell = cellAndCorrectedIndex.cell; var correctedIndexPath = NSIndexPath.FromRowSection(cellAndCorrectedIndex.correctedIndex, 0); - UpdateTemplatedCell(cell as TemplatedCell, correctedIndexPath); + + if (cell is DefaultCell defaultCell) + UpdateDefaultCell(defaultCell, correctedIndexPath); + + if (cell is TemplatedCell templatedCell) + UpdateTemplatedCell(templatedCell, correctedIndexPath); } else { @@ -48,8 +54,10 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS } var element = (cell as TemplatedCell)?.VisualElementRenderer?.Element; + if (element != null) VisualStateManager.GoToState(element, CarouselView.DefaultItemVisualState); + return cell; } From b2a1a823a6bb6c9d52488e33029e2b3947859e0e Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Thu, 12 Nov 2020 18:51:56 +0100 Subject: [PATCH 08/20] [X] Don't remove enclosing quotes (#12813) fixes #12763 Quotes are already parsed, and handled elsewhere - fixes #12763 --- .../Issues/Gh12763.xaml | 4 +++ .../Issues/Gh12763.xaml.cs | 33 +++++++++++++++++++ Xamarin.Forms.Xaml/MarkupExpressionParser.cs | 11 ------- 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml.cs diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml new file mode 100644 index 00000000000..4c3ca53a12b --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml @@ -0,0 +1,4 @@ + + + diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml.cs new file mode 100644 index 00000000000..c768bfc460d --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh12763.xaml.cs @@ -0,0 +1,33 @@ +// 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 Gh12763 : ContentPage + { + public Gh12763() => InitializeComponent(); + public Gh12763(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 QuotesInStringFormat([Values(false, true)] bool useCompiledXaml) + { + var layout = new Gh12763(useCompiledXaml); + Assert.That(layout.label.Text, Is.EqualTo("\"Foo\"")); + } + } + } +} diff --git a/Xamarin.Forms.Xaml/MarkupExpressionParser.cs b/Xamarin.Forms.Xaml/MarkupExpressionParser.cs index 342d8e9d0a8..78087a4e2e2 100644 --- a/Xamarin.Forms.Xaml/MarkupExpressionParser.cs +++ b/Xamarin.Forms.Xaml/MarkupExpressionParser.cs @@ -224,17 +224,6 @@ string GetNextPiece(IServiceProvider serviceProvider, ref string remaining, out while (piece.Length > 0 && char.IsWhiteSpace(piece[piece.Length - 1])) piece.Length--; - if (piece.Length >= 2) - { - char first = piece[0]; - char last = piece[piece.Length - 1]; - if ((first == '\'' && last == '\'') || (first == '"' && last == '"')) - { - piece.Remove(piece.Length - 1, 1); - piece.Remove(0, 1); - } - } - return piece.ToString(); } From 06ccda823a9781e05404ff893797fdf333011cde Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 12 Nov 2020 17:53:33 +0000 Subject: [PATCH 09/20] =?UTF-8?q?[iOS]=C2=A0Remove=20hardcoded=20value=20o?= =?UTF-8?q?n=20CarouselView=20loop=20calculations=20(#12787)=20fixes=20#12?= =?UTF-8?q?574=20fixes=20#12787?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Controls] Add repo for issue #12574 * [iOS] Handle loop calculations when ItemsSource count is small * [Controls] Update UITest with remove item test Co-authored-by: Javier Suárez --- .../Issue12574.cs | 193 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionView/CarouselViewController.cs | 8 +- 3 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs new file mode 100644 index 00000000000..33796d26ff7 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs @@ -0,0 +1,193 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using System.Diagnostics; +using System.Collections.ObjectModel; +using System; +using System.ComponentModel; +using System.Linq; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [NUnit.Framework.Category(UITestCategories.CarouselView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 12574, "CarouselView Loop=True default freezes iOS app", PlatformAffected.Default)] + public class Issue12574 : TestContentPage + { + ViewModelIssue12574 viewModel; + CarouselView _carouselView; + Button _btn; + string carouselAutomationId = "carouselView"; + string btnRemoveAutomationId = "btnRemove"; + + protected override void Init() + { + _btn = new Button + { + Text = "Remove Last", + AutomationId = btnRemoveAutomationId + }; + _btn.SetBinding(Button.CommandProperty, "RemoveItemsCommand"); + // Initialize ui here instead of ctor + _carouselView = new CarouselView + { + AutomationId = carouselAutomationId, + Margin = new Thickness(30), + BackgroundColor = Color.Yellow, + ItemTemplate = new DataTemplate(() => + { + + var stacklayout = new StackLayout(); + var labelId = new Label(); + var labelText = new Label(); + var labelDescription = new Label(); + labelId.SetBinding(Label.TextProperty, "Id"); + labelText.SetBinding(Label.TextProperty, "Text"); + labelDescription.SetBinding(Label.TextProperty, "Description"); + + stacklayout.Children.Add(labelId); + stacklayout.Children.Add(labelText); + stacklayout.Children.Add(labelDescription); + return stacklayout; + }) + }; + _carouselView.SetBinding(CarouselView.ItemsSourceProperty, "Items"); + this.SetBinding(Page.TitleProperty, "Title"); + + var layout = new Grid(); + layout.RowDefinitions.Add(new RowDefinition { Height = 100 }); + layout.RowDefinitions.Add(new RowDefinition()); + Grid.SetRow(_carouselView, 1); + layout.Children.Add(_btn); + layout.Children.Add(_carouselView); + + BindingContext = viewModel = new ViewModelIssue12574(); + Content = layout; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + viewModel.OnAppearing(); + } + +#if UITEST + [Test] + public void Issue12574Test() + { + RunningApp.WaitForElement("0 item"); + + var rect = RunningApp.Query(c => c.Marked(carouselAutomationId)).First().Rect; + var centerX = rect.CenterX; + var rightX = rect.X - 5; + RunningApp.DragCoordinates(centerX + 40, rect.CenterY, rightX, rect.CenterY); + + RunningApp.WaitForElement("1 item"); + + RunningApp.DragCoordinates(centerX + 40, rect.CenterY, rightX, rect.CenterY); + + RunningApp.WaitForElement("2 item"); + + RunningApp.Tap(btnRemoveAutomationId); + + RunningApp.WaitForElement("1 item"); + + rightX = rect.X + rect.Width - 1; + RunningApp.DragCoordinates(centerX, rect.CenterY, rightX, rect.CenterY); + + RunningApp.WaitForElement("0 item"); + } +#endif + } + + [Preserve(AllMembers = true)] + class ViewModelIssue12574 : BaseViewModel1 + { + public ObservableCollection Items { get; set; } + public Command LoadItemsCommand { get; set; } + public Command RemoveItemsCommand { get; set; } + + public ViewModelIssue12574() + { + Title = "CarouselView Looping"; + Items = new ObservableCollection(); + LoadItemsCommand = new Command(() => ExecuteLoadItemsCommand()); + RemoveItemsCommand = new Command(() => ExecuteRemoveItemsCommand()); + } + void ExecuteRemoveItemsCommand() + { + Items.Remove(Items.Last()); + } + void ExecuteLoadItemsCommand() + { + IsBusy = true; + + try + { + Items.Clear(); + for (int i = 0; i < 3; i++) + { + Items.Add(new ModelIssue12574 { Id = Guid.NewGuid().ToString(), Text = $"{i} item", Description = "This is an item description." }); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + finally + { + IsBusy = false; + } + } + + public void OnAppearing() + { + IsBusy = true; + LoadItemsCommand.Execute(null); + } + } + + [Preserve(AllMembers = true)] + class ModelIssue12574 + { + public string Id { get; set; } + public string Text { get; set; } + public string Description { get; set; } + } + + class BaseViewModel1 : INotifyPropertyChanged + { + public string Title { get; set; } + public bool IsInitialized { get; set; } + + bool _isBusy; + + /// + /// Gets or sets if VM is busy working + /// + public bool IsBusy + { + get { return _isBusy; } + set { _isBusy = value; OnPropertyChanged("IsBusy"); } + } + + //INotifyPropertyChanged Implementation + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + if (PropertyChanged == null) + return; + + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + +} \ 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 ca9121e4411..f5313e717c1 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 @@ -1628,6 +1628,7 @@ Issue11081.xaml + diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs index e2b2c46b2be..f60a2edc3a8 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/CarouselViewController.cs @@ -545,7 +545,7 @@ void CenterVerticallyIfNeeded(UICollectionView collectionView) var centerOffsetY = (LoopCount * contentHeight - boundsHeight) / 2; var distFromCenter = centerOffsetY - currentOffset.Y; - if (Math.Abs(distFromCenter) > (contentHeight / 4)) + if (Math.Abs(distFromCenter) > (contentHeight / GetMinLoopCount())) { var cellcount = distFromCenter / (cellHeight + cellPadding); var shiftCells = (int)((cellcount > 0) ? Math.Floor(cellcount) : Math.Ceiling(cellcount)); @@ -571,14 +571,14 @@ void CenterHorizontalIfNeeded(UICollectionView collectionView) var currentOffset = collectionView.ContentOffset; var contentWidth = GetTotalContentWidth(); var boundsWidth = collectionView.Bounds.Size.Width; - + if (contentWidth == 0 || cellWidth == 0) return; var centerOffsetX = (LoopCount * contentWidth - boundsWidth) / 2; var distFromCentre = centerOffsetX - currentOffset.X; - if (Math.Abs(distFromCentre) > (contentWidth / 4)) + if (Math.Abs(distFromCentre) > (contentWidth / GetMinLoopCount())) { var cellcount = distFromCentre / (cellWidth + cellPadding); var shiftCells = (int)((cellcount > 0) ? Math.Floor(cellcount) : Math.Ceiling(cellcount)); @@ -627,6 +627,8 @@ NSIndexPath GetIndexPathForCenteredItem(UICollectionView collectionView) return centerIndexPath; } + int GetMinLoopCount() => Math.Min(LoopCount, GetItemsSourceCount()); + int GetItemsSourceCount() => _itemsSource.ItemCount; nfloat GetTotalContentWidth() => GetItemsSourceCount() * _layout.ItemSize.Width; From 40b3b428e457a47b9e8e59e966433925ef9364a5 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 12 Nov 2020 11:54:17 -0600 Subject: [PATCH 10/20] Fix multiple events on toolbar items (#12797) --- .../Issue7181.cs | 12 +++++---- .../AccessibilityExtensions.cs | 11 ++++++++ .../Shell/ShellStyles.xaml | 3 +-- .../Shell/ShellToolbarItemRenderer.cs | 27 +++++++++++++++++-- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7181.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7181.cs index 017d03ac2d1..faa15126215 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7181.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7181.cs @@ -31,9 +31,11 @@ protected override void Init() { var page = CreateContentPage("Test page"); - _toolbarItem = new ToolbarItem(DefaultToolbarItemText, string.Empty, OnToolbarClicked) + _toolbarItem = new ToolbarItem() { - AutomationId = ToolbarBtn + Text = DefaultToolbarItemText, + AutomationId = ToolbarBtn, + Command = new Command(OnToolbarClicked) }; page.ToolbarItems.Add(_toolbarItem); @@ -58,20 +60,20 @@ protected override void Init() private void OnToolbarClicked() => _toolbarItem.Text = $"{AfterClickToolbarItemText} {_clicks++}"; -#if UITEST && __ANDROID__ +#if UITEST && (__ANDROID__ || __WINDOWS__) [Test] public void ShellToolbarItemTests() { var count = 0; var toolbarButton = RunningApp.WaitForElement(ToolbarBtn); - Assert.AreEqual(toolbarButton[0].Text, DefaultToolbarItemText); + Assert.AreEqual(DefaultToolbarItemText, toolbarButton[0].ReadText()); for (int i = 0; i < 5; i++) { RunningApp.Tap(ToolbarBtn); toolbarButton = RunningApp.WaitForElement(ToolbarBtn); - Assert.AreEqual($"{AfterClickToolbarItemText} {count++}", toolbarButton[0].Text); + Assert.AreEqual($"{AfterClickToolbarItemText} {count++}", toolbarButton[0].ReadText()); } RunningApp.Tap(SetToolbarIconBtn); diff --git a/Xamarin.Forms.Platform.UAP/AccessibilityExtensions.cs b/Xamarin.Forms.Platform.UAP/AccessibilityExtensions.cs index c4e78d63f27..6f43a4d6a5f 100644 --- a/Xamarin.Forms.Platform.UAP/AccessibilityExtensions.cs +++ b/Xamarin.Forms.Platform.UAP/AccessibilityExtensions.cs @@ -123,5 +123,16 @@ static string ConcatenateNameAndHint(Element Element) } + internal static void SetAutomationProperties( + this FrameworkElement frameworkElement, + Element element, + string defaultName = null) + { + frameworkElement.SetAutomationPropertiesAutomationId(element?.AutomationId); + frameworkElement.SetAutomationPropertiesName(element, defaultName); + frameworkElement.SetAutomationPropertiesHelpText(element); + frameworkElement.SetAutomationPropertiesLabeledBy(element); + frameworkElement.SetAutomationPropertiesAccessibilityView(element); + } } } diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml b/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml index 489ef4db099..457494529c8 100644 --- a/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellStyles.xaml @@ -28,8 +28,7 @@ - + diff --git a/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs b/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs index 7f96fcbdb35..7231f97edb3 100644 --- a/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/Shell/ShellToolbarItemRenderer.cs @@ -10,6 +10,16 @@ namespace Xamarin.Forms.Platform.UWP { public class ShellToolbarItemRenderer : Windows.UI.Xaml.Controls.Button { + + public static readonly DependencyProperty ToolbarItemProperty = + DependencyProperty.Register("ToolbarItem", typeof(ToolbarItem), typeof(ShellToolbarItemRenderer), new PropertyMetadata(null, OnToolbarItemChanged)); + + static void OnToolbarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((ShellToolbarItemRenderer)d) + .ToolbarItemChanged(e.OldValue as ToolbarItem, e.NewValue as ToolbarItem); + } + public ShellToolbarItemRenderer() { Xamarin.Forms.Shell.VerifyShellUWPFlagEnabled(nameof(ShellToolbarItemRenderer)); @@ -28,7 +38,20 @@ public ToolbarItem ToolbarItem set { SetValue(ToolbarItemProperty, value); } } - public static readonly DependencyProperty ToolbarItemProperty = - DependencyProperty.Register("ToolbarItem", typeof(ToolbarItem), typeof(ShellToolbarItemRenderer), new PropertyMetadata(null)); + void ToolbarItemChanged(ToolbarItem oldItem, ToolbarItem newItem) + { + if(oldItem != null) + oldItem.PropertyChanged -= ToolbarItemPropertyChanged; + + this.SetAutomationProperties(newItem, defaultName: newItem?.Text); + + if (newItem != null) + newItem.PropertyChanged += ToolbarItemPropertyChanged; + + void ToolbarItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + this.SetAutomationProperties(newItem, defaultName: newItem?.Text); + } + } } } From c803956abe604cad960051a95e29cbd54d4e55f7 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 12 Nov 2020 19:52:15 -0600 Subject: [PATCH 11/20] Remove TargetSdkVersion check for AndroidX fallback (#12825) --- .../AppCompat/FormsAppCompatActivity.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs index 5a04260954a..fef93778143 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs @@ -246,8 +246,7 @@ void OnCreate( catch (global::Android.Views.InflateException ie) { if ((ie.Cause is Java.Lang.ClassNotFoundException || ie.Cause.Cause is Java.Lang.ClassNotFoundException) && - ie.Message.Contains("Error inflating class android.support.v7.widget.Toolbar") && - this.TargetSdkVersion() >= 29) + ie.Message.Contains("Error inflating class android.support.v7.widget.Toolbar")) { Internals.Log.Warning(nameof(FormsAppCompatActivity), "Toolbar layout needs to be updated from android.support.v7.widget.Toolbar to androidx.appcompat.widget.Toolbar. " + From f50c0239eaa6b73dabae2085f251477b194b7441 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 13 Nov 2020 05:19:04 -0600 Subject: [PATCH 12/20] Build UWP version of locale tests (#12818) * Build UWP version of locale tests * - cleanup tests * - fix image permutations --- .../DateTimePickerLocalizationTests.cs | 30 ++++++-- .../Issue4597.cs | 19 ++--- .../TestPages/ScreenshotConditionalApp.cs | 10 +++ .../Utilities/AppExtensions.cs | 19 +++++ .../WinDriverApp.cs | 72 ++++++++++++++++++- 5 files changed, 135 insertions(+), 15 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs index 406b0cbd954..27fa05e0476 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs @@ -37,7 +37,7 @@ public string TimeString(String format, String time) RunningApp.ClearText("settingTime"); RunningApp.EnterText("settingTime", time); RunningApp.PressEnter(); - var text = RunningApp.WaitForElement("timeClockOptions")[0].ReadText(); + var text = RunningApp.ReadTimePicker("timeClockOptions"); return text; } @@ -49,7 +49,7 @@ public string DateString(String format, String date) RunningApp.ClearText("settingDate"); RunningApp.EnterText("settingDate", date); RunningApp.PressEnter(); - var text = RunningApp.WaitForElement("dateCalendarOptions")[0].ReadText(); + var text = RunningApp.ReadDatePicker("dateCalendarOptions"); return text; } @@ -57,12 +57,18 @@ public string DateString(String format, String date) public void TimePicker24H() { RunningApp.Tap(x => x.Marked("TimePicker")); +#if !__WINDOWS__ Assert.AreEqual("0.0.0 A", TimeString("H.m.s t", "0, 0")); Assert.AreEqual("13:05 PM", TimeString("HH:mm tt", "13, 5")); Assert.AreEqual("12 PM", TimeString("HH tt", "12, 0")); Assert.AreEqual("5.", TimeString("H.", "5, 1")); +#else + Assert.AreEqual("23:00", TimeString("HH", "23, 0")); + Assert.AreEqual("11:00:PM", TimeString("hh", "23, 0")); +#endif } +#if !__WINDOWS__ [Test] public void TimePicker12H() { @@ -83,26 +89,41 @@ public void TimePickerOther() Assert.AreEqual("QRSTUVWXYZ", TimeString("QRSTUVWXYZ", "23, 59")); Assert.AreEqual("abceijklnopqruvwx", TimeString("abceijklnopqruvwx", "23, 59")); } +#endif [Test] public void DatePickerDMY() { RunningApp.Tap(x => x.Marked("DatePicker")); +#if !__WINDOWS__ Assert.AreEqual("31/1/99", DateString("d/M/y", "1999, 1, 31")); Assert.AreEqual("02-29-00", DateString("MM-dd-yy", "2000, 2, 29")); Assert.AreEqual("2010, Apr, Thu", DateString("yyy, MMM, ddd", "2010, 4, 15")); Assert.AreEqual("August.Saturday.2015", DateString("MMMM.dddd.yyyy", "2015, 8, 1")); +#else + Assert.AreEqual("31,1,99", DateString("d/M/y", "1999, 1, 31")); + Assert.AreEqual("29,2,00", DateString("MM-dd-yy", "2000, 2, 29")); + Assert.AreEqual("Thu 15,Apr,2010", DateString("yyy, MMM, ddd", "2010, 4, 15")); + Assert.AreEqual("Saturday,August,2015", DateString("MMMM.dddd.yyyy", "2015, 8, 1")); +#endif } [Test] public void DatePickerMissing() { RunningApp.Tap(x => x.Marked("DatePicker")); +#if !__WINDOWS__ Assert.AreEqual("October 97", DateString("MMMM yy", "1997, 10, 30")); Assert.AreEqual("Monday", DateString("dddd", "2020, 7, 20")); Assert.AreEqual("2002: Dec", DateString("yyyy: MMM", "2002, 12, 31")); +#else + Assert.AreEqual("October,97", DateString("MMMM yy", "1997, 10, 30")); + Assert.AreEqual("Monday", DateString("dddd", "2020, 7, 20")); + Assert.AreEqual("Dec,2002", DateString("yyyy: MMM", "2002, 12, 31")); +#endif } +#if !__WINDOWS__ [Test] public void DatePickerLetters() { @@ -111,7 +132,8 @@ public void DatePickerLetters() Assert.AreEqual("QRSTUVWXYZ", DateString("QRSTUVWXYZ", "2002, 12, 31")); Assert.AreEqual("abceijklnopqruvwx", DateString("abceijklnopqruvwx", "2002, 12, 31")); } +#endif #endif - } -} \ No newline at end of file + } + } \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs index 4d006753161..16812d73508 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs @@ -103,7 +103,7 @@ protected override void Init() IsToggled = false, HeightRequest = 60 }; - var sourceLabel = new Label { Text = _imageFromFile }; + var sourceLabel = new Label { Text = _imageFromFile, AutomationId = "SourceLabel" }; switchToUri.Toggled += (_, e) => { @@ -176,42 +176,42 @@ protected override void Init() [Test] public void ImageFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Image), false); + RunTest(nameof(Image), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ImageFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Image), true); + RunTest(nameof(Image), false); } [Test] public void ButtonFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Button), false); + RunTest(nameof(Button), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ButtonFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Button), true); + RunTest(nameof(Button), false); } [Test] public void ImageButtonFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(ImageButton), false); + RunTest(nameof(ImageButton), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ImageButtonFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(ImageButton), true); + RunTest(nameof(ImageButton), false); } [Test] @@ -306,9 +306,10 @@ void SetupTest(string controlType, bool fileSource) RunningApp.WaitForNoElement(activeTest); } - if (fileSource && RunningApp.Query(_imageFromFile).Length == 0) + string sourceLabel = RunningApp.WaitForFirstElement("SourceLabel").ReadText(); + if (fileSource && sourceLabel != _imageFromFile) RunningApp.Tap(_switchUriId); - else if (!fileSource && RunningApp.Query(_imageFromUri).Length == 0) + else if (!fileSource && sourceLabel != _imageFromUri) RunningApp.Tap(_switchUriId); } #endif diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs index 057f261cb67..e7593cfd7b2 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs @@ -463,6 +463,16 @@ public ITestServer TestServer } #if __WINDOWS__ + public string ReadDatePicker(string marked) + { + return ((WinDriverApp)_app).ReadDatePicker(marked).ToString(); + } + + public string ReadTimePicker(string marked) + { + return ((WinDriverApp)_app).ReadTimePicker(marked).ToString(); + } + public bool RestartIfAppIsClosed() { return (_app as WinDriverApp).RestartIfAppIsClosed(); diff --git a/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs b/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs index 7bb8d03d749..a8488d26cfb 100644 --- a/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs +++ b/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs @@ -7,6 +7,7 @@ using System.Threading; using Xamarin.Forms.Controls.Issues; using Xamarin.Forms.Controls; +using Xamarin.Forms.Core.UITests; #if __IOS__ using Xamarin.UITest.iOS; #endif @@ -141,6 +142,24 @@ public static void SendAppToBackground(this IApp app, TimeSpan timeSpan) } } #endif + + public static string ReadDatePicker(this IApp app, string marked) + { +#if __WINDOWS__ + return ((ScreenshotConditionalApp)app).ReadDatePicker(marked).ToString(); +#else + return app.WaitForElement(marked)[0].ReadText(); +#endif + } + + public static string ReadTimePicker(this IApp app, string marked) + { +#if __WINDOWS__ + return ((ScreenshotConditionalApp)app).ReadTimePicker(marked).ToString(); +#else + return app.WaitForElement(marked)[0].ReadText(); +#endif + } } } diff --git a/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs b/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs index 041041e5af9..3cf5d72f52b 100644 --- a/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs +++ b/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs @@ -170,6 +170,69 @@ public void EnterText(string text) .Perform(); } + public string ReadSelectorPicker(string marked, string flyoutPresenterName, string seperator, string[] selectors) + { + WaitForAtLeastOne(() => QueryWindows(marked))[0].SendKeys(Keys.Space); + var LTRCharacter = Convert.ToChar(8206); + var RTLCharacter = Convert.ToChar(8207); + var pickerFlyout = _session.FindElementByAccessibilityId(flyoutPresenterName); + List stringSelectors = new List(); + + foreach(var selector in selectors) + ReadSelector(selector, stringSelectors); + + pickerFlyout.FindElementByAccessibilityId("AcceptButton").Click(); + return String.Join(seperator, stringSelectors); + + void ReadSelector(string marked, List data) + { + try + { + var text = RemoveExtraCharacters(pickerFlyout.FindElementByAccessibilityId(marked).Text); + data.Add(text); + } + catch + { + // when the control isn't found an exception is thrown + } + } + + string RemoveExtraCharacters(string text) + { + return + new String(text + .ToCharArray() + .Where(c => c != LTRCharacter && c != RTLCharacter) + .ToArray()); + } + } + + public string ReadDatePicker(string marked) + { + return ReadSelectorPicker( + marked, + "DatePickerFlyoutPresenter", + ",", + new[] { + "DayLoopingSelector", + "MonthLoopingSelector", + "YearLoopingSelector", + }); + } + + public string ReadTimePicker(string marked) + { + return ReadSelectorPicker( + marked, + "TimePickerFlyoutPresenter", + ":", + new[] { + "HourLoopingSelector", + "MinuteLoopingSelector", + "PeriodLoopingSelector", + }); + } + static RemoteWebElement SwapInUsefulElement(WindowsElement element) { // AutoSuggestBox on UWP has some interaction issues with WebDriver @@ -640,7 +703,9 @@ public AppResult[] WaitForElement(Func query, TimeSpan? timeout = null, TimeSpan? retryFrequency = null, TimeSpan? postTimeout = null) { Func> result = () => QueryWindows(marked); - return WaitForAtLeastOne(result, timeoutMessage, timeout, retryFrequency).Select(ToAppResult).ToArray(); + var results = WaitForAtLeastOne(result, timeoutMessage, timeout, retryFrequency).Select(ToAppResult).ToArray(); + + return results; } public AppWebResult[] WaitForElement(Func query, @@ -1116,7 +1181,10 @@ static ReadOnlyCollection WaitForAtLeastOne(Func i > 0, timeoutMessage, timeout, retryFrequency); + var results = Wait(query, i => i > 0, timeoutMessage, timeout, retryFrequency); + + + return results; } void WaitForNone(Func> query, From 1ba742540f3bb156e8b9c6035c39fda08f263c08 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2020 11:29:53 +0000 Subject: [PATCH 13/20] Automated dotnet-format update (#12829) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs | 8 ++++---- .../Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs | 4 ++-- .../Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs index 33796d26ff7..3d73d73f783 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs @@ -1,10 +1,10 @@ -using Xamarin.Forms.CustomAttributes; -using Xamarin.Forms.Internals; -using System.Diagnostics; +using System; using System.Collections.ObjectModel; -using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; #if UITEST using Xamarin.Forms.Core.UITests; diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs index b41c687425c..45bc3ac75a9 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12714.cs @@ -14,8 +14,8 @@ namespace Xamarin.Forms.Controls.Issues #if UITEST [Category(UITestCategories.CollectionView)] #endif - [Issue(IssueTracker.Github, 12714, - "[Bug] iOS application suspended at UICollectionViewFlowLayout.PrepareLayout() when using IsVisible = false", + [Issue(IssueTracker.Github, 12714, + "[Bug] iOS application suspended at UICollectionViewFlowLayout.PrepareLayout() when using IsVisible = false", PlatformAffected.iOS)] public class Issue12714 : TestContentPage { diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs index ca69fa521fb..d8b9ef0d3b3 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12777.cs @@ -1,7 +1,7 @@ -using Xamarin.Forms.CustomAttributes; -using Xamarin.Forms.Internals; +using System; using System.Collections.ObjectModel; -using System; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; #if UITEST using Xamarin.UITest; From 8b53ab0af0727c13865afb8311a865d58997efe4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Nov 2020 15:44:59 +0000 Subject: [PATCH 14/20] Automated dotnet-format update (#12846) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../DateTimePickerLocalizationTests.cs | 4 ++-- Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs index 27fa05e0476..9bbef728e01 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs @@ -135,5 +135,5 @@ public void DatePickerLetters() #endif #endif - } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs b/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs index 3cf5d72f52b..5966cf3b9d7 100644 --- a/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs +++ b/Xamarin.Forms.Core.Windows.UITests/WinDriverApp.cs @@ -178,7 +178,7 @@ public string ReadSelectorPicker(string marked, string flyoutPresenterName, stri var pickerFlyout = _session.FindElementByAccessibilityId(flyoutPresenterName); List stringSelectors = new List(); - foreach(var selector in selectors) + foreach (var selector in selectors) ReadSelector(selector, stringSelectors); pickerFlyout.FindElementByAccessibilityId("AcceptButton").Click(); @@ -210,7 +210,7 @@ string RemoveExtraCharacters(string text) public string ReadDatePicker(string marked) { return ReadSelectorPicker( - marked, + marked, "DatePickerFlyoutPresenter", ",", new[] { From 357801d1ea864daaf97f462dd7b0279cb5c080de Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Sat, 14 Nov 2020 09:46:03 -0600 Subject: [PATCH 15/20] Ignore Locale tests and one CV test on UWP (#12843) --- .../DateTimePickerLocalizationTests.cs | 6 ++++++ .../Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs index 9bbef728e01..8a8b04a7b6d 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/DateTimePickerLocalizationTests.cs @@ -54,6 +54,8 @@ public string DateString(String format, String date) } [Test] + // Tests runs locally without issues but doesn't run successfully in a hosted agent yet + [Category(UITestCategories.UwpIgnore)] public void TimePicker24H() { RunningApp.Tap(x => x.Marked("TimePicker")); @@ -92,6 +94,8 @@ public void TimePickerOther() #endif [Test] + // Tests runs locally without issues but doesn't run successfully in a hosted agent yet + [Category(UITestCategories.UwpIgnore)] public void DatePickerDMY() { RunningApp.Tap(x => x.Marked("DatePicker")); @@ -109,6 +113,8 @@ public void DatePickerDMY() } [Test] + // Tests runs locally without issues but doesn't run successfully in a hosted agent yet + [Category(UITestCategories.UwpIgnore)] public void DatePickerMissing() { RunningApp.Tap(x => x.Marked("DatePicker")); diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs index 3d73d73f783..91dd47feeed 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12574.cs @@ -16,6 +16,7 @@ namespace Xamarin.Forms.Controls.Issues { #if UITEST [NUnit.Framework.Category(UITestCategories.CarouselView)] + [NUnit.Framework.Category(UITestCategories.UwpIgnore)] #endif [Preserve(AllMembers = true)] [Issue(IssueTracker.Github, 12574, "CarouselView Loop=True default freezes iOS app", PlatformAffected.Default)] From 9090bcc44e499fcaa9959d39e574f132c802f047 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Mon, 16 Nov 2020 07:47:56 -0300 Subject: [PATCH 16/20] [GH-6184] - Make able to disable tabs inside the MoreViewController [iOS] (#9781) * Fixed the mess-up * Fix merge conflict and UITest * Update Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml.cs Co-authored-by: Shane Neuville * Added information on how to run the test * Improviments in the code and prevent crash on iOS14 or newer * make sure that Forms is into the commit * code style fix * - fix for iOS14 * - change pages * - fix delegate * - fix cast exception * - retain default text color * - add page numbers * - fix * - reduce number of more Co-authored-by: Shane Neuville --- .../Issue6184.xaml | 63 ++++++++++++++++ .../Issue6184.xaml.cs | 73 +++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 9 +++ Xamarin.Forms.Platform.iOS/Forms.cs | 9 ++- .../Renderers/ShellItemRenderer.cs | 46 ++++++++++-- 5 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml new file mode 100644 index 00000000000..b8b1f75dc2c --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml.cs new file mode 100644 index 00000000000..deb99f0690f --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6184.xaml.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +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.Shell)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 6184, "Throws exception when set isEnabled to false in ShellItem index > 5", PlatformAffected.iOS)] + public partial class Issue6184 : TestShell + { + public Issue6184() + { +#if APP + + InitializeComponent(); +#endif + } + + protected override void Init() + { + } + +#if UITEST && __IOS__ + [Test] + public void GitHubIssue6184() + { + RunningApp.WaitForElement(q => q.Marked("More")); + RunningApp.Tap(q => q.Marked("More")); + RunningApp.Tap(q => q.Marked("Issue 5")); + RunningApp.WaitForElement(q => q.Marked("Issue 5")); + } +#endif + } + + [Preserve(AllMembers = true)] + public class PageInstruction : ContentPage + { + + Label pageNumber; + public int PageNumber + { + set + { + pageNumber.Text = $"Page Number: {value}"; + } + } + + public PageInstruction() + { + pageNumber = new Label(); + var stack = new StackLayout(); + var label = new Label + { + Text = "Press the more page, and see if the Cells with Title \"Issue 5\", \"Issue 9\", \"Issue 18\" are Disabled. If don't the test fails", + FontSize = 20 + }; + stack.Children.Add(label); + stack.Children.Add(pageNumber); + Content = stack; + } + } +} + 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 f5313e717c1..33c1b9a69f1 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 @@ -68,6 +68,9 @@ Issue8308.xaml + + + Issue6184.xaml Code @@ -2402,6 +2405,12 @@ MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + Designer diff --git a/Xamarin.Forms.Platform.iOS/Forms.cs b/Xamarin.Forms.Platform.iOS/Forms.cs index c7f4f096e0d..e0ee1ee1d1f 100644 --- a/Xamarin.Forms.Platform.iOS/Forms.cs +++ b/Xamarin.Forms.Platform.iOS/Forms.cs @@ -103,6 +103,7 @@ internal static bool IsiOS14OrNewer } } + internal static bool RespondsToSetNeedsUpdateOfHomeIndicatorAutoHidden { get @@ -162,7 +163,7 @@ public static void SetFlags(params string[] flags) } s_flags = (string[])flags.Clone(); - if (s_flags.Contains ("Profile")) + if (s_flags.Contains("Profile")) Profile.Enable(); } @@ -736,9 +737,9 @@ public SizeRequest GetNativeSize(VisualElement view, double widthConstraint, dou } public OSAppTheme RequestedTheme - { - get - { + { + get + { #if __IOS__ || __TVOS__ if (!IsiOS13OrNewer) return OSAppTheme.Unspecified; diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellItemRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellItemRenderer.cs index 29da239cd5e..820affb6fe2 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellItemRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellItemRenderer.cs @@ -49,6 +49,7 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) Page _displayedPage; bool _disposed; ShellItem _shellItem; + static UIColor _defaultMoreTextLabelTextColor; IShellSectionRenderer CurrentRenderer { get; set; } @@ -76,11 +77,14 @@ public override UIViewController SelectedViewController { MoreNavigationController.WeakDelegate = this; } + + UpdateMoreCellsEnabled(); } } [Export("navigationController:didShowViewController:animated:")] - public virtual void DidShowViewController(UINavigationController navigationController, [Transient]UIViewController viewController, bool animated) + [Preserve(AllMembers = true)] + public virtual void DidShowViewController(UINavigationController navigationController, [Transient] UIViewController viewController, bool animated) { var renderer = RendererForViewController(this.SelectedViewController); if (renderer != null) @@ -88,6 +92,7 @@ public virtual void DidShowViewController(UINavigationController navigationContr ShellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, renderer.ShellSection); CurrentRenderer = renderer; } + UpdateMoreCellsEnabled(); } public override void ViewDidLayoutSubviews() @@ -271,7 +276,7 @@ void AddRenderer(IShellSectionRenderer renderer) void CreateTabRenderers() { - if(ShellItem.CurrentItem == null) + if (ShellItem.CurrentItem == null) throw new InvalidOperationException($"Content not found for active {ShellItem}. Title: {ShellItem.Title}. Route: {ShellItem.Route}."); var items = ShellItemController.GetItems(); @@ -298,18 +303,49 @@ void CreateTabRenderers() // Make sure we are at the right item GoTo(ShellItem.CurrentItem); + UpdateMoreCellsEnabled(); + } + void UpdateMoreCellsEnabled() + { + var moreNavigationCells = GetMoreNavigationCells(); + var viewControllersLength = ViewControllers.Length; // now that they are applied we can set the enabled state of the TabBar items - for (i = 0; i < ViewControllers.Length; i++) + for (int i = 4; i < viewControllersLength; i++) { + if((i - 4) >= (moreNavigationCells.Length)) + { + break; + } + var renderer = RendererForViewController(ViewControllers[i]); + var cell = moreNavigationCells[i - 4]; + if (!renderer.ShellSection.IsEnabled) { - TabBar.Items[i].Enabled = false; + cell.UserInteractionEnabled = false; + + if (_defaultMoreTextLabelTextColor == null) + _defaultMoreTextLabelTextColor = cell.TextLabel.TextColor; + + cell.TextLabel.TextColor = Color.FromRgb(213, 213, 213).ToUIColor(); + } + else if(!cell.UserInteractionEnabled) + { + cell.UserInteractionEnabled = true; + cell.TextLabel.TextColor = _defaultMoreTextLabelTextColor; } } + + UITableViewCell[] GetMoreNavigationCells() + { + if(MoreNavigationController.TopViewController.View is UITableView uITableView) + return uITableView.VisibleCells; + + return new UITableViewCell[0]; + } } - + void GoTo(ShellSection shellSection) { if (shellSection == null || _currentSection == shellSection) From db8ab0c9d772b26805cf37a65139af5e7507a756 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 16 Nov 2020 06:36:21 -0600 Subject: [PATCH 17/20] Build 14393 targets separately (#12826) * Build 14393 targets separately * Update build.cake * Update build.cake * - additional unit test fixes --- UWP.Build.props | 3 +-- .../Issue4597.cs | 23 ++++++++-------- build.cake | 27 ++++++++++++++++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/UWP.Build.props b/UWP.Build.props index c8f98e097b5..7a72440799f 100644 --- a/UWP.Build.props +++ b/UWP.Build.props @@ -3,8 +3,7 @@ $(UwpMinTargetFrameworks) - uap10.0.16299 - uap10.0.14393;uap10.0.16299 + uap10.0.16299 netstandard2.0 diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs index 45ca4cf138e..16812d73508 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4597.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Text; +using System.Threading; using Xamarin.Forms.CustomAttributes; using Xamarin.Forms.Internals; -using System.Linq; -using System.Threading; #if UITEST using Xamarin.UITest; @@ -103,7 +103,7 @@ protected override void Init() IsToggled = false, HeightRequest = 60 }; - var sourceLabel = new Label { Text = _imageFromFile }; + var sourceLabel = new Label { Text = _imageFromFile, AutomationId = "SourceLabel" }; switchToUri.Toggled += (_, e) => { @@ -176,42 +176,42 @@ protected override void Init() [Test] public void ImageFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Image), false); + RunTest(nameof(Image), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ImageFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Image), true); + RunTest(nameof(Image), false); } [Test] public void ButtonFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Button), false); + RunTest(nameof(Button), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ButtonFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(Button), true); + RunTest(nameof(Button), false); } [Test] public void ImageButtonFromFileSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(ImageButton), false); + RunTest(nameof(ImageButton), true); } [Test] [NUnit.Framework.Category(UITestCategories.RequiresInternetConnection)] public void ImageButtonFromUriSourceAppearsAndDisappearsCorrectly() { - RunTest(nameof(ImageButton), true); + RunTest(nameof(ImageButton), false); } [Test] @@ -306,9 +306,10 @@ void SetupTest(string controlType, bool fileSource) RunningApp.WaitForNoElement(activeTest); } - if (fileSource && RunningApp.Query(_imageFromFile).Length == 0) + string sourceLabel = RunningApp.WaitForFirstElement("SourceLabel").ReadText(); + if (fileSource && sourceLabel != _imageFromFile) RunningApp.Tap(_switchUriId); - else if (!fileSource && RunningApp.Query(_imageFromUri).Length == 0) + else if (!fileSource && sourceLabel != _imageFromUri) RunningApp.Tap(_switchUriId); } #endif diff --git a/build.cake b/build.cake index 1834fb838e5..0165e5047c5 100644 --- a/build.cake +++ b/build.cake @@ -800,12 +800,33 @@ Task("BuildForNuget") msbuildSettings = GetMSBuildSettings(); msbuildSettings.BinaryLogger = binaryLogger; - binaryLogger.FileName = $"{artifactStagingDirectory}/win-{configuration}-csproj.binlog"; + binaryLogger.FileName = $"{artifactStagingDirectory}/win-maps-{configuration}-csproj.binlog"; + MSBuild("./Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj", + msbuildSettings + .WithProperty("UwpMinTargetFrameworks", "uap10.0.14393") + .WithRestore()); + + msbuildSettings = GetMSBuildSettings(); + msbuildSettings.BinaryLogger = binaryLogger; + binaryLogger.FileName = $"{artifactStagingDirectory}/win-16299-{configuration}-csproj.binlog"; + MSBuild("./Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj", + msbuildSettings + .WithRestore() + .WithTarget("rebuild") + .WithProperty("DisableEmbeddedXbf", "false") + .WithProperty("EnableTypeInfoReflection", "false") + .WithProperty("UwpMinTargetFrameworks", "uap10.0.16299")); + + msbuildSettings = GetMSBuildSettings(); + msbuildSettings.BinaryLogger = binaryLogger; + binaryLogger.FileName = $"{artifactStagingDirectory}/win-14393-{configuration}-csproj.binlog"; MSBuild("./Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj", msbuildSettings + .WithRestore() .WithTarget("rebuild") .WithProperty("DisableEmbeddedXbf", "false") - .WithProperty("EnableTypeInfoReflection", "false")); + .WithProperty("EnableTypeInfoReflection", "false") + .WithProperty("UwpMinTargetFrameworks", "uap10.0.14393")); msbuildSettings = GetMSBuildSettings(); msbuildSettings.BinaryLogger = binaryLogger; @@ -1186,4 +1207,4 @@ public void SetEnvironmentVariable(string key, string value, ICakeContext contex { System.Environment.SetEnvironmentVariable(key, value); } -} \ No newline at end of file +} From 1c4163544107701d3e1d49634ac84fc1e15a604d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Mon, 16 Nov 2020 20:04:29 +0100 Subject: [PATCH 18/20] [iOS] Fix issue disposing the EmptyView (#12377) fixes #12374 fixes #12776 * Correctly dispose the native empty view in iOS * Added test instructions * Added UITest --- .../Issue12374.xaml | 87 +++++++++++ .../Issue12374.xaml.cs | 140 ++++++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 4 + .../CollectionView/ItemsViewController.cs | 16 +- 4 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12374.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12374.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12374.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12374.xaml new file mode 100644 index 00000000000..148dc08fb90 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue12374.xaml @@ -0,0 +1,87 @@ + + + +