Skip to content

Commit

Permalink
Wire RefreshView up to our xplat layout workflow (#23169) (#23218)
Browse files Browse the repository at this point in the history
* Use better layout/measure path with refreshview

* - fix naming

* - set RefreshView content to maui compatible container

* - add test

* - fix null operator

* Update Issue23029.xaml.cs

* - fix content panel so it removes previous content

* - add additional check
  • Loading branch information
PureWeen committed Jun 24, 2024
1 parent 2ddcf72 commit 4241bd4
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 213 deletions.
177 changes: 20 additions & 157 deletions src/Controls/tests/DeviceTests/Elements/RefreshView/RefreshViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Maui.Controls.Handlers.Items;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;
using Xunit;

namespace Microsoft.Maui.DeviceTests
Expand All @@ -22,167 +23,29 @@ void SetupBuilder()
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<RefreshView, RefreshViewHandler>();
handlers.AddHandler<Label, LabelHandler>();
handlers.AddHandler<Entry, EntryHandler>();
});
});
}
[Fact(DisplayName = "Setting the content of RefreshView removes previous platform view from visual tree")]
public async Task ChangingRefreshViewContentRemovesPreviousContentsPlatformViewFromVisualTree()
{
SetupBuilder();

var refreshView = new RefreshView();
var initialContent = new Label();
refreshView.Content = initialContent;

// [Fact(DisplayName = "IsRefreshing binding works")]
// public async Task IsRefreshingBindingWorks()
// {
// SetupBuilder();

// var vm = new RefreshPageViewModel();
// var refreshView = new RefreshView() { BindingContext = vm };

// refreshView.SetBinding(RefreshView.IsRefreshingProperty, nameof(vm.IsRefreshing), BindingMode.TwoWay);

// await CreateHandlerAndAddToWindow<RefreshViewHandler>(refreshView, async handler =>
// {
// var platformControl = handler.PlatformView;
// Assert.NotNull(platformControl);
// bool? platformIsRefreshing = null;
// #if WINDOWS
// Deferral refreshCompletionDeferral = null;
// platformControl.RefreshRequested += (s, e) =>
// {
// refreshCompletionDeferral = e.GetDeferral();
// platformIsRefreshing = true;
// };
// #endif
// vm.IsRefreshing = true;
// #if ANDROID
// platformIsRefreshing = platformControl.Refreshing;
// #elif IOS
// platformIsRefreshing = platformControl.IsRefreshing;
// #elif WINDOWS

// #endif
// Assert.NotNull(platformIsRefreshing);
// Assert.Equal(vm.IsRefreshing, platformIsRefreshing);
// await Task.Delay(300);
// vm.IsRefreshing = false;
// #if ANDROID
// platformIsRefreshing = platformControl.Refreshing;
// #elif IOS
// platformIsRefreshing = platformControl.IsRefreshing;
// #elif WINDOWS
// if(refreshCompletionDeferral != null)
// {
// refreshCompletionDeferral.Complete();
// refreshCompletionDeferral = null;
// platformIsRefreshing = false;
// }
// #endif
// Assert.Equal(vm.IsRefreshing, platformIsRefreshing);
// await Task.Delay(1000);
// });
// }

// [Fact(DisplayName = "IsRefreshing binding works when started on platform")]
// public async Task IsRefreshingBindingWorksFromPlatform()
// {
// SetupBuilder();

// var vm = new RefreshPageViewModel();
// var refreshView = new RefreshView() { BindingContext = vm };

// refreshView.SetBinding(RefreshView.IsRefreshingProperty, nameof(vm.IsRefreshing), BindingMode.TwoWay);

// await CreateHandlerAndAddToWindow<RefreshViewHandler>(refreshView, async handler =>
// {
// var platformControl = handler.PlatformView;
// Assert.NotNull(platformControl);
// bool? platformIsRefreshing = null;
// #if WINDOWS
// Deferral refreshCompletionDeferral = null;
// platformControl.RefreshRequested += (s, e) =>
// {
// refreshCompletionDeferral = e.GetDeferral();
// platformIsRefreshing = true;
// };
// platformControl.RequestRefresh();
// #endif

// #if ANDROID
// platformIsRefreshing = platformControl.Refreshing = true;
// #elif IOS
// platformIsRefreshing = platformControl.IsRefreshing = true;
// #elif WINDOWS

// #endif
// Assert.NotNull(platformIsRefreshing);
// Assert.Equal(platformIsRefreshing, vm.IsRefreshing);
// await Task.Delay(300);
// vm.IsRefreshing = false;
// #if ANDROID
// platformIsRefreshing = platformControl.Refreshing;
// #elif IOS
// platformIsRefreshing = platformControl.IsRefreshing;
// #elif WINDOWS
// if (refreshCompletionDeferral != null)
// {
// refreshCompletionDeferral.Complete();
// refreshCompletionDeferral = null;
// platformIsRefreshing = false;
// }
// #endif
// Assert.Equal(vm.IsRefreshing, platformIsRefreshing);
// await Task.Delay(1000);
// });
// }

// class RefreshPageViewModel : BaseViewModel
// {
// public RefreshPageViewModel()
// {
// Data = new ObservableCollection<string>()
// {
// "Item 1",
// "Item 2",
// "Item 3"
// };
// }
// bool _isRefreshing;
// ObservableCollection<string> _data;

// public bool IsRefreshing
// {
// get => _isRefreshing;
// set => SetProperty(ref _isRefreshing, value);
// }

// public ObservableCollection<string> Data
// {
// get => _data;
// set => SetProperty(ref _data, value);
// }
// }

// public abstract class BaseViewModel : INotifyPropertyChanged
// {
// protected bool SetProperty<T>(ref T backingStore, T value,
// [CallerMemberName] string propertyName = "",
// Action onChanged = null)
// {
// if (EqualityComparer<T>.Default.Equals(backingStore, value))
// return false;

// backingStore = value;
// onChanged?.Invoke();
// OnPropertyChanged(propertyName);
// return true;
// }

// #region INotifyPropertyChanged

// public event PropertyChangedEventHandler PropertyChanged;
// protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
// {
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
// }

// #endregion
// }
await AttachAndRun(refreshView, async (handler) =>
{
var newContent = new Entry();
Assert.NotNull(((initialContent as IView).Handler as IPlatformViewHandler).PlatformView.GetParent());
refreshView.Content = newContent;
Assert.Null(((initialContent as IView).Handler as IPlatformViewHandler).PlatformView.GetParent());
await Task.Yield();
Assert.NotNull(((newContent as IView).Handler as IPlatformViewHandler).PlatformView.GetParent());
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue23029 : _IssuesUITest
{
public override string Issue => "RefreshView doesn't use correct layout mechanisms";

public Issue23029(TestDevice device) : base(device)
{
}

[Test]
[Category(UITestCategories.RefreshView)]
public void ValidateRefreshViewContentGetsFrameSet()
{
App.WaitForElement("SizeChangedLabel");
}
}
}
45 changes: 45 additions & 0 deletions src/Controls/tests/TestCases/Issues/Issue23029.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue23029">
<!--
Remove RefreshView or comment it out, and you will notice SizeChanged is fired
and if you add RefreshView the SizeChanged will not fire.
-->
<Grid RowDefinitions="Auto,*" x:Name="grid">
<RefreshView Grid.Row="1">
<CollectionView x:Name="CView">

<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="1" />
</CollectionView.ItemsLayout>

<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0, 0, 10, 10">
<Border Padding="20" Stroke="CornflowerBlue" StrokeThickness="1.5" Background="Transparent">
<Border.StrokeShape>
<RoundRectangle CornerRadius="20"/>
</Border.StrokeShape>

<VerticalStackLayout Spacing="10">

<Label Text="{Binding ProgramName}" />

<Label Text="{Binding CourseName}" />

<Label Text="{Binding DueDate}" />

<HorizontalStackLayout Spacing="5">
<Button Text="Download" />
<Button Text="Upload" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</Grid>
</ContentPage>
54 changes: 54 additions & 0 deletions src/Controls/tests/TestCases/Issues/Issue23029.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.ObjectModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 23029, "RefreshView doesn't use correct layout mechanisms", PlatformAffected.All)]
public partial class Issue23029 : ContentPage
{
public Issue23029()
{
InitializeComponent();
ObservableCollection<AssessmentModel> list = [];
for (int i = 0; i < 100; i++)
{
list.Add(new AssessmentModel()
{
ProgramName = "International Assessment " + i.ToString(),
CourseName = "English " + i.ToString(),
DueDate = DateTime.Now.ToShortDateString()
});
}
CView.ItemsSource = list;

CView.SizeChanged += OnSizeChanged;
}

private void OnSizeChanged(object sender, EventArgs e)
{
if (CView.Frame.Height > 0 && CView.Width > 0 && grid.Children.Count == 1)
{
// Works around a bug with modifying the grid while it's being measured
this.Dispatcher.Dispatch(() =>
{
grid.Children.Insert(0, new Label() { Text = "Size Changed", AutomationId = "SizeChangedLabel" });
});
}

if (sender is CollectionView collectionView && collectionView.ItemsLayout is GridItemsLayout gridItemsLayout)
{
double maxWidthPerItem = 300;
gridItemsLayout.Span = Math.Max(1, (int)(collectionView.Width / maxWidthPerItem));
}
}

public class AssessmentModel
{
public string ProgramName { get; set; } = string.Empty;
public string CourseName { get; set; } = string.Empty;
public string DueDate { get; set; } = string.Empty;
}
}
}
32 changes: 29 additions & 3 deletions src/Core/src/Handlers/RefreshView/RefreshViewHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ protected override RefreshContainer CreatePlatformView()
{
return new RefreshContainer
{
PullDirection = RefreshPullDirection.TopToBottom
PullDirection = RefreshPullDirection.TopToBottom,
Content = new ContentPanel()
};
}

Expand All @@ -35,6 +36,12 @@ protected override void DisconnectHandler(RefreshContainer nativeView)

CompleteRefresh();

if (nativeView.Content is ContentPanel contentPanel)
{
contentPanel.Content = null;
contentPanel.CrossPlatformLayout = null;
}

base.DisconnectHandler(nativeView);
}

Expand Down Expand Up @@ -63,8 +70,27 @@ void UpdateIsRefreshing()

static void UpdateContent(IRefreshViewHandler handler)
{
handler.PlatformView.Content =
handler.VirtualView.Content?.ToPlatform(handler.MauiContext!);
IView? content;

if (handler.VirtualView is IContentView cv && cv.PresentedContent is IView view)
{
content = view;
}
else
{
content = handler.VirtualView.Content;
}

var platformContent = content?.ToPlatform(handler.MauiContext!);
if (handler.PlatformView.Content is ContentPanel contentPanel)
{
contentPanel.Content = platformContent;
contentPanel.CrossPlatformLayout = (handler.VirtualView as ICrossPlatformLayout);
}
else
{
handler.PlatformView.Content = platformContent;
}
}

static void UpdateRefreshColor(IRefreshViewHandler handler)
Expand Down
Loading

0 comments on commit 4241bd4

Please sign in to comment.