From f2dcc9aa540adc4164ad97e891232a4493fddb55 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 27 Aug 2019 19:29:07 -0600 Subject: [PATCH] Implement CollectionView grouping on Android (#7199) * Move all the header/footer adjustment to IItemsViewSource fixes #7121 fixes #7102 partially implements #3172 fixes #7243 * Fix selection bugs introduced by header/footer on Android * Implement grouping for CollectionView on Android * Enable grouping tests for Android * Naming and comment cleanup * Update Xamarin.Forms.Platform.Android/CollectionView/ListSource.cs Co-Authored-By: Gerald Versluis * Update Xamarin.Forms.Platform.Android/CollectionView/ObservableGroupedSource.cs --- .../CollectionViewGrouping.cs | 4 +- .../Issue7102.cs | 50 ++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + .../CollectionModifier.cs | 6 +- .../GroupingGalleries/BasicGrouping.xaml | 2 +- .../GroupingGalleries/BasicGrouping.xaml.cs | 6 +- .../GroupingGalleries/GridGrouping.xaml | 42 ++ .../GroupingGalleries/GridGrouping.xaml.cs | 14 + .../GroupingGalleries/GroupingGallery.cs | 2 + .../GroupingGalleries/ObservableGrouping.cs | 14 +- .../FooterOnlyString.xaml | 13 + .../FooterOnlyString.xaml.cs | 25 + .../HeaderFooterGallery.cs | 2 +- .../ObservableCodeCollectionViewGallery.cs | 3 +- .../MultipleBoundSelection.xaml | 2 +- .../PreselectedItemGallery.xaml | 2 +- .../PreselectedItemsGallery.xaml | 2 +- .../SelectionChangedCommandParameter.xaml | 4 +- .../SelectionModeGallery.xaml | 2 +- .../Xamarin.Forms.Controls.csproj | 8 +- .../CollectionView/AdapterNotifier.cs | 55 +++ .../CollectionView/CarouselViewRenderer.cs | 4 +- .../CollectionView/CollectionViewRenderer.cs | 3 +- .../CollectionView/EmptySource.cs | 35 +- .../CollectionView/EmptyViewAdapter.cs | 2 +- .../GridLayoutSpanSizeLookup.cs | 3 +- .../GroupableItemsViewAdapter.cs | 67 +++ .../GroupableItemsViewRenderer.cs | 32 ++ .../ICollectionChangedNotifier.cs | 15 + .../CollectionView/IItemsViewSource.cs | 18 +- .../CollectionView/ItemContentView.cs | 2 +- .../CollectionView/ItemViewType.cs | 2 + .../CollectionView/ItemsSourceFactory.cs | 25 +- .../CollectionView/ItemsViewAdapter.cs | 100 ++-- .../CollectionView/ItemsViewRenderer.cs | 36 +- .../CollectionView/ListSource.cs | 110 ++++- .../CollectionView/ObservableGroupedSource.cs | 429 ++++++++++++++++++ .../CollectionView/ObservableItemsSource.cs | 96 ++-- .../RecyclerViewScrollListener.cs | 10 +- .../SelectableItemsViewAdapter.cs | 22 +- .../SelectableItemsViewRenderer.cs | 31 +- .../CollectionView/SelectableViewHolder.cs | 11 +- .../CollectionView/TemplatedItemViewHolder.cs | 5 +- .../CollectionView/TextViewHolder.cs | 2 +- .../CollectionView/UngroupedItemsSource.cs | 52 +++ .../Xamarin.Forms.Platform.Android.csproj | 6 + 46 files changed, 1202 insertions(+), 175 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7102.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/AdapterNotifier.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/GroupableItemsViewAdapter.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/GroupableItemsViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/ICollectionChangedNotifier.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/ObservableGroupedSource.cs create mode 100644 Xamarin.Forms.Platform.Android/CollectionView/UngroupedItemsSource.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewGrouping.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewGrouping.cs index bf25b99fc42..a56335ba107 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewGrouping.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewGrouping.cs @@ -29,7 +29,7 @@ protected override void Init() #endif } -#if UITEST && __IOS__ // Grouping is not implemented on Android yet +#if UITEST [Test] public void RemoveSelectedItem() { @@ -88,8 +88,6 @@ public void MoveGroup() RunningApp.WaitForElement("MoveGroup"); RunningApp.Tap("MoveGroup"); } - - #endif } } diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7102.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7102.cs new file mode 100644 index 00000000000..8177ae4b218 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7102.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7102, "[Bug] CollectionView Header cause delay to adding items.", + PlatformAffected.Android)] + public class Issue7102 : TestNavigationPage + { + protected override void Init() + { +#if APP + FlagTestHelpers.SetCollectionViewTestFlag(); + + PushAsync(new GalleryPages.CollectionViewGalleries.ObservableCodeCollectionViewGallery(grid: false)); +#endif + } + +#if UITEST + [Test] + public void HeaderDoesNotBreakIndexes() + { + RunningApp.WaitForElement("entryInsert"); + RunningApp.Tap("entryInsert"); + RunningApp.ClearText(); + RunningApp.EnterText("1"); + RunningApp.Tap("Insert"); + + // If the bug is still present, then there will be + // two "Item: 0" items instead of the newly inserted item + // Or the header will have disappeared + RunningApp.WaitForElement("Inserted"); + RunningApp.WaitForElement("This is the header"); + } +#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 998dbbb02aa..1711c8f2c3b 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 @@ -34,6 +34,7 @@ + diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionModifier.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionModifier.cs index fa81807bfaa..e5b05c789d8 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionModifier.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionModifier.cs @@ -17,10 +17,10 @@ protected CollectionModifier(CollectionView cv, string buttonText) HorizontalOptions = LayoutOptions.Fill }; - var button = new Button { Text = buttonText, AutomationId = $"btn{buttonText}" }; - var label = new Label { Text = LabelText, VerticalTextAlignment = TextAlignment.Center }; + var button = new Button { Text = buttonText, AutomationId = $"btn{buttonText}", HeightRequest = 20, FontSize = 10 }; + var label = new Label { Text = LabelText, VerticalTextAlignment = TextAlignment.Center, FontSize = 10 }; - Entry = new Entry { Keyboard = Keyboard.Numeric, Text = InitialEntryText, WidthRequest = 100, AutomationId = $"entry{buttonText}" }; + Entry = new Entry { Keyboard = Keyboard.Numeric, Text = InitialEntryText, WidthRequest = 100, FontSize = 10, AutomationId = $"entry{buttonText}" }; layout.Children.Add(label); layout.Children.Add(Entry); diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml index 5332e54204b..bc3910b977f 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGalleries.BasicGrouping"> - + diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml.cs index 694b66cdc35..1b03497ae69 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/BasicGrouping.xaml.cs @@ -11,12 +11,12 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGalleries { [XamlCompilation(XamlCompilationOptions.Compile)] - [Preserve (AllMembers = true)] + [Preserve(AllMembers = true)] public partial class BasicGrouping : ContentPage { - public BasicGrouping () + public BasicGrouping() { - InitializeComponent (); + InitializeComponent(); CollectionView.ItemsSource = new SuperTeams(); } diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml new file mode 100644 index 00000000000..aee06400441 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml.cs new file mode 100644 index 00000000000..05ea898a068 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GridGrouping.xaml.cs @@ -0,0 +1,14 @@ +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGalleries +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class GridGrouping : ContentPage + { + public GridGrouping() + { + InitializeComponent(); + CollectionView.ItemsSource = new SuperTeams(); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GroupingGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GroupingGallery.cs index 00b91423d46..44a34659482 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GroupingGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/GroupingGallery.cs @@ -32,6 +32,8 @@ public GroupingGallery() new SwitchGrouping(), Navigation), GalleryBuilder.NavButton("Grouping, Observable", () => new ObservableGrouping(), Navigation), + GalleryBuilder.NavButton("Grouping, Grid", () => + new GridGrouping(), Navigation), } } }; diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ObservableGrouping.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ObservableGrouping.cs index d97d508898f..3dc6047e290 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ObservableGrouping.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/GroupingGalleries/ObservableGrouping.cs @@ -10,7 +10,7 @@ public ObservableGrouping() Title = "Observable Grouped List"; var buttonStyle = new Style(typeof(Button)) { }; - buttonStyle.Setters.Add(new Setter() { Property = Button.HeightRequestProperty, Value = 20 }); + buttonStyle.Setters.Add(new Setter() { Property = Button.HeightRequestProperty, Value = 30 }); buttonStyle.Setters.Add(new Setter() { Property = Button.FontSizeProperty, Value = 10 }); var layout = new Grid @@ -34,6 +34,8 @@ public ObservableGrouping() var collectionView = new CollectionView { + Header = "This is a header", + Footer = "Hey, I'm a footer. Look at me!", ItemTemplate = ItemTemplate(), GroupFooterTemplate = GroupFooterTemplate(), GroupHeaderTemplate = GroupHeaderTemplate(), @@ -102,7 +104,15 @@ public ObservableGrouping() Style = buttonStyle }; groupRemover.Clicked += (obj, args) => { itemsSource?.Remove(itemsSource[0]); - groupRemover.Text = $"Remove {itemsSource[0].Name}"; + if (itemsSource.Count > 0) + { + groupRemover.Text = $"Remove {itemsSource[0].Name}"; + } + else + { + groupRemover.Text = ""; + groupRemover.IsEnabled = false; + } mover.Text = $"Move Selected To {itemsSource[0].Name}"; }; diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml new file mode 100644 index 00000000000..08286e4afda --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml.cs new file mode 100644 index 00000000000..c31cfcf4e3b --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/FooterOnlyString.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class FooterOnlyString : ContentPage + { + readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(20); + + public FooterOnlyString() + { + InitializeComponent(); + + CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate(); + CollectionView.ItemsSource = _demoFilteredItemSource.Items; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs index 625f6fe3bd8..ee2a11b94de 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs @@ -20,7 +20,7 @@ public HeaderFooterGallery() GalleryBuilder.NavButton("Header/Footer (Forms View)", () => new HeaderFooterView(), Navigation), GalleryBuilder.NavButton("Header/Footer (Template)", () => new HeaderFooterTemplate(), Navigation), GalleryBuilder.NavButton("Header/Footer (Grid)", () => new HeaderFooterGrid(), Navigation), - GalleryBuilder.NavButton("Header/Footer (Grid Horizontal)", () => new HeaderFooterGridHorizontal(), Navigation), + GalleryBuilder.NavButton("Footer Only (String)", () => new FooterOnlyString(), Navigation), } } }; diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs index 5fac776cfd6..00517e763a0 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs @@ -25,7 +25,8 @@ public ObservableCodeCollectionViewGallery(ItemsLayoutOrientation orientation = var itemTemplate = ExampleTemplates.PhotoTemplate(); - var collectionView = new CollectionView {ItemsLayout = itemsLayout, ItemTemplate = itemTemplate, AutomationId = "collectionview" }; + var collectionView = new CollectionView {ItemsLayout = itemsLayout, ItemTemplate = itemTemplate, + AutomationId = "collectionview", Header = "This is the header" }; var generator = new ItemsSourceGenerator(collectionView, initialItems, ItemsSourceType.ObservableCollection); diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml index d78cf9581ea..b7d8551f973 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml @@ -17,7 +17,7 @@