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

Implementation of LazyView + Sample + Unit Tests #1102

Merged
merged 9 commits into from
Mar 29, 2023
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public partial class AppShell : Shell
CreateViewModelMapping<AvatarViewSizesPage, AvatarViewSizesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<DrawingViewPage, DrawingViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<ExpanderPage, ExpanderViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<LazyViewPage, LazyViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MediaElementPage, MediaElementViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MultiplePopupPage, MultiplePopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupAnchorPage, PopupAnchorViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
Expand Down
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
// Add Views Pages + ViewModels
services.AddTransientWithShellRoute<DrawingViewPage, DrawingViewViewModel>();
services.AddTransientWithShellRoute<ExpanderPage, ExpanderViewModel>();
services.AddTransientWithShellRoute<LazyViewPage, LazyViewViewModel>();
services.AddTransientWithShellRoute<MediaElementPage, MediaElementViewModel>();
services.AddTransientWithShellRoute<MultiplePopupPage, MultiplePopupViewModel>();
services.AddTransientWithShellRoute<PopupAnchorPage, PopupAnchorViewModel>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using CommunityToolkit.Maui.Markup;

namespace CommunityToolkit.Maui.Sample.Pages.Views.LazyView;

public class CustomLazyView<TView> : Maui.Views.LazyView where TView : View, new()
{
public override async ValueTask LoadViewAsync()
jfversluis marked this conversation as resolved.
Show resolved Hide resolved
{
// display a loading indicator
Content = new ActivityIndicator { IsRunning = true }.Center();

// simulate a long running task
await Task.Delay(3000);

// load the view
Content = new TView { BindingContext = BindingContext };

SetHasLazyViewLoaded(true);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
xmlns:local="clr-namespace:CommunityToolkit.Maui.Sample.Pages.Views.LazyView"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.LazyViewPage"
Title="LazyView Page"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
Padding="0"
x:TypeArguments="viewModels:LazyViewViewModel"
x:DataType="viewModels:LazyViewViewModel">


<ScrollView VerticalScrollBarVisibility="Always" Padding="12">
<VerticalStackLayout x:Name="LayoutContainer" Spacing="12" >
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Heading" TargetType="Label">
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
<Setter Property="FontSize" Value="Medium" />
<Setter Property="HorizontalOptions" Value="Center"/>
<Setter Property="LineBreakMode" Value="WordWrap" />
<Setter Property="Margin" Value="10" />
</Style>
<Style x:Key="MyViewStyle" TargetType="local:MyView">
<Setter Property="HorizontalOptions" Value="Center"/>
<Setter Property="WidthRequest" Value="200"/>
<Setter Property="HeightRequest" Value="200"/>
</Style>
<Style x:Key="HR" TargetType="Line">
<Setter Property="Stroke" Value="{AppThemeBinding Light=Black, Dark=White}" />
<Setter Property="X2" Value="300" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>

<Label Text="This page demonstrates the LazyView. It instantiates 'MyView' in 3 different ways."
Style="{StaticResource Heading}"/>

<Label Text="With Page Initialization" Style="{StaticResource Heading}"/>
<local:MyView Style="{StaticResource MyViewStyle}"/>

<Line Style="{StaticResource HR}" />

<Label Text="Lazy Loading After Page Activation" Style="{StaticResource Heading}"/>
<Label Text="{Binding Source={x:Reference LazyActiviation}, Path=HasLazyViewLoaded, StringFormat='HasLazyViewLoaded = {0}'}" Style="{StaticResource Heading}"/>
<local:CustomLazyView x:Name="LazyActiviation" x:TypeArguments="local:MyView" Style="{StaticResource MyViewStyle}"/>

<Line Style="{StaticResource HR}" />

<Label Text="Lazy Loading Based On User Action" Style="{StaticResource Heading}"/>
<Label Text="{Binding Source={x:Reference LazyUserAction}, Path=HasLazyViewLoaded, StringFormat='HasLazyViewLoaded = {0}'}" Style="{StaticResource Heading}"/>
<Button Text="Load View Now" Clicked="LoadLazyView_Clicked" WidthRequest="200" IsVisible="{Binding Source={x:Reference LazyUserAction}, Path=HasLazyViewLoaded, Converter={mct:InvertedBoolConverter}}" />
<local:MyLazyView x:Name="LazyUserAction" Style="{StaticResource MyViewStyle}"/>
</VerticalStackLayout>
</ScrollView>
</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class LazyViewPage : BasePage<LazyViewViewModel>
{
public LazyViewPage(LazyViewViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}

protected override async void OnAppearing()
{
base.OnAppearing();
await LazyActiviation.LoadViewAsync();
}

async void LoadLazyView_Clicked(object sender, EventArgs e)
{
await LazyUserAction.LoadViewAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using CommunityToolkit.Maui.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views.LazyView;

public class MyLazyView : LazyView<MyView>
{
public override async ValueTask LoadViewAsync()
{
await base.LoadViewAsync();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<ContentView
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.LazyView.MyView"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

<Grid>
<Image Source="dotnet_bot.png"/>
</Grid>

</ContentView>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CommunityToolkit.Maui.Sample.Pages.Views.LazyView;

public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public class LazyViewViewModel : BaseViewModel
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public ViewsGalleryViewModel()
SectionModel.Create<AvatarViewSizesViewModel>("AvatarView Sizes Page", Colors.Red, "A page demonstrating AvatarViews with various size options."),
SectionModel.Create<DrawingViewViewModel>("DrawingView", Colors.Red, "DrawingView provides a canvas for users to \"paint\" on the screen. The drawing can also be captured and displayed as an Image."),
SectionModel.Create<ExpanderViewModel>("Expander Page", Colors.Red, "Expander allows collapse and expand content."),
SectionModel.Create<LazyViewViewModel>("LazyView", Colors.Red, "LazyView is a view that allows you to load its children in a delayed manner."),
SectionModel.Create<MediaElementViewModel>("MediaElement", Colors.Red, "MediaElement is a view for playing video and audio"),
SectionModel.Create<MultiplePopupViewModel>("Mutiple Popups Page", Colors.Red, "A page demonstrating multiple different Popups"),
SectionModel.Create<PopupPositionViewModel>("Custom Positioning Popup", Colors.Red, "Displays a basic popup anywhere on the screen using VerticalOptions and HorizontalOptions"),
Expand Down
1 change: 0 additions & 1 deletion src/CommunityToolkit.Maui.UnitTests/BaseTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Globalization;
using CommunityToolkit.Maui.UnitTests.Mocks;
using Microsoft.Maui.Dispatching;

namespace CommunityToolkit.Maui.UnitTests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using CommunityToolkit.Maui.Converters;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests;
namespace CommunityToolkit.Maui.UnitTests.Converters;

public abstract class BaseOneWayConverterTest<TConverter> : ConverterTest<TConverter> where TConverter : ICommunityToolkitValueConverter, new()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using CommunityToolkit.Maui.Converters;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests;
namespace CommunityToolkit.Maui.UnitTests.Converters;

public class ColorToBlackOrWhiteConverterTests : BaseOneWayConverterTest<ColorToBlackOrWhiteConverter>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using CommunityToolkit.Maui.Converters;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests;
namespace CommunityToolkit.Maui.UnitTests.Converters;

public class ColorToColorForTextConverterTests : BaseOneWayConverterTest<ColorToColorForTextConverter>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using CommunityToolkit.Maui.Converters;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests;
namespace CommunityToolkit.Maui.UnitTests.Converters;

public class ColorToGrayScaleColorConverterTests : BaseOneWayConverterTest<ColorToGrayScaleColorConverter>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CommunityToolkit.Maui.Views;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views;

public class LazyViewTests : BaseHandlerTest
{
[Fact]
public void CheckHasLoadedFalseAfterConstruction()
{
var lazyView = new LazyView<Button>();

Assert.False(lazyView.HasLazyViewLoaded);
}

[Fact]
public async Task CheckHasLoadedTrueAfterCallLoadView()
{
var lazyView = new LazyView<Button>();

await lazyView.LoadViewAsync();

Assert.True(lazyView.HasLazyViewLoaded);
}

[Fact]
public async Task CheckContentSetAfterLoadView()
{
var lazyView = new LazyView<Button>();

await lazyView.LoadViewAsync();

Assert.True(lazyView.HasLazyViewLoaded);
Assert.IsAssignableFrom<Button>(lazyView.Content);
}

[Fact]
public async Task CheckBindingContextIsPassedToCreatedView()
{
var lazyView = new LazyView<Button>();
var bindingContext = new object();
lazyView.BindingContext = bindingContext;

await lazyView.LoadViewAsync();

Assert.True(lazyView.HasLazyViewLoaded);
Assert.IsAssignableFrom<Button>(lazyView.Content);
Assert.Equal(bindingContext, lazyView.Content.BindingContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using FluentAssertions;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views.MediaElement;
namespace CommunityToolkit.Maui.UnitTests.Views;

public class MediaElementTests : BaseHandlerTest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Globalization;
using CommunityToolkit.Maui.Converters;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
using FluentAssertions;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views.MediaElement;
namespace CommunityToolkit.Maui.UnitTests.Views;

public class MediaSourceConverterTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using FluentAssertions;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views.MediaElement;
namespace CommunityToolkit.Maui.UnitTests.Views;

public class MediaSourceTests
{
Expand Down
60 changes: 60 additions & 0 deletions src/CommunityToolkit.Maui/Views/LazyView/LazyView.shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace CommunityToolkit.Maui.Views;

/// <summary>
/// This a basic implementation implementing <see cref="LazyView"/>
/// </summary>
/// <typeparam name="TView">Any <see cref="View"/></typeparam>
public class LazyView<TView> : LazyView where TView : View, new()
{
/// <summary>
/// This method initializes <see cref="LazyView{TView}"/>.
/// </summary>
/// <returns><see cref="ValueTask"/></returns>
public override ValueTask LoadViewAsync()
{
Content = new TView { BindingContext = BindingContext };

SetHasLazyViewLoaded(true);

return ValueTask.CompletedTask;
}
}

/// <summary>
/// Abstract base class for <see cref="LazyView{TView}"/>
/// </summary>
public abstract class LazyView : ContentView
{
internal static readonly BindablePropertyKey HasLazyViewLoadedPropertyKey = BindableProperty.CreateReadOnly(nameof(HasLazyViewLoaded), typeof(bool), typeof(LazyView), false);

/// <summary>
/// This is a read-only <see cref="BindableProperty"/> that indicates when the view is loaded.
/// </summary>
public static readonly BindableProperty HasLazyViewLoadedProperty = HasLazyViewLoadedPropertyKey.BindableProperty;

/// <summary>
/// This is a read-only property that indicates when the view is loaded.
/// </summary>
public bool HasLazyViewLoaded => (bool)GetValue(HasLazyViewLoadedProperty);

/// <summary>
/// Use this method to do the initialization of the <see cref="View"/> and change the status HasViewLoaded value here.
/// </summary>
/// <returns><see cref="ValueTask"/></returns>
public abstract ValueTask LoadViewAsync();

/// <inheritdoc/>
protected override void OnBindingContextChanged()
{
if (Content is not null && Content is not ActivityIndicator)
{
Content.BindingContext = BindingContext;
}
}

/// <summary>
/// This method change the value of the <see cref="HasLazyViewLoaded"/> property.
/// </summary>
/// <param name="hasLoaded"></param>
protected void SetHasLazyViewLoaded(bool hasLoaded) => SetValue(HasLazyViewLoadedPropertyKey, hasLoaded);
}