Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wire RefreshView up to our xplat layout workflow #23169

Merged
merged 8 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading