Skip to content

Commit

Permalink
Expander (#478)
Browse files Browse the repository at this point in the history
* Expander initial commit

Add samples

Fix BindingContext is null


Android Expander


iOS Configure Header


Do not arrange content on expanded change


More samples, fix Android


InvalidateMeasure, add animation


Animation Android. fix WIndows build


Update sample


Fix PR comments


Fix Apple animation


Fix pipeline


Expander tests


Summary

* Add Command and CommandParameter

* Detect cell and force  update size

* Expander2

* Register Expander page

* Remove Expander Handler

* Fix formatting

* Remove animation

* Use StackLayout instead of Grid because it wraps content

* Add docs

* Update samples

* Fix Expander content is not wrapped

* Fix tests

* listview resize

* Fix formatting

* Remove incorrect namespace

* Update Sample, Use Static Methods

* Update ExpanderPageCS.cs

* Ensure Command Fires

* Remove Children Before Creating New Grid

* Add `InvalidEnumArgumentException` Test

* Avoid Re-initializing Grid

* Fixes Expander crash when Header or Content is null

* Add `Title`

* Disable `ListView` + `CollectionView` support

Co-authored-by: Brandon Minnick <[email protected]>
Co-authored-by: Pedro Jesus <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2022
1 parent cda2fbe commit 1ea488c
Show file tree
Hide file tree
Showing 14 changed files with 558 additions and 5 deletions.
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 @@ -102,6 +102,7 @@ public partial class AppShell : Shell
CreateViewModelMapping<AvatarViewShapesPage, AvatarViewShapesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<AvatarViewSizesPage, AvatarViewSizesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<DrawingViewPage, DrawingViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<ExpanderPage, ExpanderViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MultiplePopupPage, MultiplePopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupAnchorPage, PopupAnchorViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupPositionPage, PopupPositionViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0-ios;net6.0-android;net6.0-maccatalyst</TargetFrameworks>
Expand Down Expand Up @@ -60,8 +60,15 @@
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.Analyzers.CodeFixes\CommunityToolkit.Maui.Analyzers.CodeFixes.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))=='android'">
<RuntimeIdentifiers>android-arm;android-arm64;android-x86;android-x64</RuntimeIdentifiers>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('windows'))=='false' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))=='maccatalyst' and $(Configuration) == 'Debug'">
<RuntimeIdentifiers>maccatalyst-arm64;maccatalyst-x64</RuntimeIdentifiers>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
</PropertyGroup>
</Project>
3 changes: 2 additions & 1 deletion samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
// Add Extensions Pages + ViewModels
services.AddTransientWithShellRoute<ColorAnimationExtensionsPage, ColorAnimationExtensionsViewModel>();

// Add ImageSources pages + ViewModels
// Add ImageSources Pages + ViewModels
services.AddTransientWithShellRoute<GravatarImageSourcePage, GravatarImageSourceViewModel>();

// Add Layouts Pages + ViewModels
Expand All @@ -147,6 +147,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)

// Add Views Pages + ViewModels
services.AddTransientWithShellRoute<DrawingViewPage, DrawingViewViewModel>();
services.AddTransientWithShellRoute<ExpanderPage, ExpanderViewModel>();
services.AddTransientWithShellRoute<MultiplePopupPage, MultiplePopupViewModel>();
services.AddTransientWithShellRoute<PopupAnchorPage, PopupAnchorViewModel>();
services.AddTransientWithShellRoute<PopupPositionPage, PopupPositionViewModel>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.ExpanderPage"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:TypeArguments="viewModels:ExpanderViewModel"
x:DataType="viewModels:ExpanderViewModel"
Title="Expander">

<ScrollView>
<VerticalStackLayout Spacing="10">
<Button Text="Navigate to C# Sample" Clicked="GoToCSharpSampleClicked"/>

<Label Text="Simple expander" FontSize="24" FontAttributes="Bold"/>

<mct:Expander>
<mct:Expander.Header>
<Label Text="Simple Expander (Tap Me)" FontSize="16" FontAttributes="Bold"/>
</mct:Expander.Header>

<mct:Expander.Content BackgroundColor="LightGray">
<VerticalStackLayout>
<Label Text="Item 1"/>
<Label Text="Item 2"/>
</VerticalStackLayout>
</mct:Expander.Content>
</mct:Expander>

<Label Text="Multi-level expander" FontSize="24" FontAttributes="Bold"/>

<mct:Expander Direction="Up">
<mct:Expander.Header>
<Label Text="Multi-Level Expander (Tap Me)" FontSize="16" FontAttributes="Bold"/>
</mct:Expander.Header>
<mct:Expander.Content BackgroundColor="LightGray">
<mct:Expander Direction="Down" BackgroundColor="LightGray">
<mct:Expander.Header>
<Label Text="Nested Expander (Tap Me)" FontSize="14" FontAttributes="Bold"/>
</mct:Expander.Header>
<mct:Expander.Content>
<Label Text="Item 1" />
</mct:Expander.Content>
</mct:Expander>
</mct:Expander.Content>
</mct:Expander>
</VerticalStackLayout>
</ScrollView>
</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class ExpanderPage : BasePage<ExpanderViewModel>
{
public ExpanderPage(ExpanderViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}

async void Expander_ExpandedChanged(object sender, Core.ExpandedChangedEventArgs e)
{
var collapsedText = e.IsExpanded ? "expanded" : "collapsed";
await Toast.Make($"Expander is {collapsedText}").Show(CancellationToken.None);
}

async void GoToCSharpSampleClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ExpanderPageCS());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
using CommunityToolkit.Maui.Markup;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public class ExpanderPageCS : ContentPage
{
public ExpanderPageCS()
{
Title = "Expander Page, C# UI";

Content = new VerticalStackLayout()
{
Spacing = 12,

Children =
{
new Label()
.Text("Expander C# Sample")
.Font(bold: true, size: 24)
.CenterHorizontal(),

new Picker() { ItemsSource = Enum.GetValues<ExpandDirection>(), Title = "Direction" }
.CenterHorizontal().TextCenter()
.Assign(out Picker picker),

new Expander
{
Header = new Label()
.Text("Expander (Tap Me)")
.Font(bold: true, size: 18),

Content = new VerticalStackLayout()
{
new Image()
.Source("https://avatars.githubusercontent.com/u/9011267?v=4")
.Size(120)
.Aspect(Aspect.AspectFit),

new Label()
.Text(".NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating mobile and desktop apps with C# and XAML. Using .NET MAUI, you can develop apps that can run on Android, iOS, iPadOS, macOS, and Windows from a single shared codebase.")
.Font(italic: true)

}.Padding(10)

}.CenterHorizontal()
.Bind(Expander.DirectionProperty, nameof(Picker.SelectedIndex), source: picker, convert: (int selectedIndex) => Enum.IsDefined(typeof(ExpandDirection), selectedIndex) ? (ExpandDirection)selectedIndex : default)
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public partial class ExpanderViewModel : BaseViewModel
{
public ObservableCollection<ContentCreator> ContentCreators { get; } = new();

public ExpanderViewModel()
{
ContentCreators.Add(new ContentCreator("Brandon Minnick", "https://codetraveler.io/", "https://avatars.githubusercontent.com/u/13558917"));
ContentCreators.Add(new ContentCreator("Gerald Versluis", "https://blog.verslu.is/", "https://avatars.githubusercontent.com/u/939291"));
ContentCreators.Add(new ContentCreator("Kym Phillpotts", "https://kymphillpotts.com", "https://avatars.githubusercontent.com/u/1327346"));
ContentCreators.Add(new ContentCreator("Pedro Jesus", "https://github.com/pictos", "https://avatars.githubusercontent.com/u/20712372"));
ContentCreators.Add(new ContentCreator("Shaun Lawrence", "https://github.com/bijington", "https://avatars.githubusercontent.com/u/17139988"));
ContentCreators.Add(new ContentCreator("Vladislav Antonyuk", "https://vladislavantonyuk.azurewebsites.net", "https://avatars.githubusercontent.com/u/33021114"));
}
}

public record ContentCreator(string Name, string Resource, string Image);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Sample.ViewModels.Views.AvatarView;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
Expand All @@ -20,9 +20,10 @@ public ViewsGalleryViewModel()
SectionModel.Create<AvatarViewShapesViewModel>("AvatarView Shapes Page", Colors.Red, "A page demonstrating AvatarViews with various shape options."),
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<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"),
SectionModel.Create<PopupAnchorViewModel>("Anchor Popup", Colors.Red, "Popups can be anchored to other view's on the screen"),
SectionModel.Create<PopupAnchorViewModel>("Anchor Popup", Colors.Red, "Popups can be anchored to other view's on the screen")
})
{
}
Expand Down
27 changes: 27 additions & 0 deletions src/CommunityToolkit.Maui.Core/Interfaces/IExpander.shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Allows collapse and expand content.
/// </summary>
public interface IExpander : IContentView
{
/// <summary>
/// Expander header.
/// </summary>
public IView? Header { get; }

/// <summary>
/// Gets or sets expand direction.
/// </summary>
public ExpandDirection Direction { get; }

/// <summary>
/// Gets or sets Expander collapsible state.
/// </summary>
public bool IsExpanded { get; set; }

/// <summary>
/// Action when <see cref="IsExpanded"/> changes
/// </summary>
void ExpandedChanged(bool isExpanded);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Expander expand direction.
/// </summary>
public enum ExpandDirection
{
/// <summary>
/// Expander expands down
/// </summary>
Down,

/// <summary>
/// Expander expands up
/// </summary>
Up
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Contains Expander IsExpanded state.
/// </summary>
public class ExpandedChangedEventArgs : EventArgs
{
/// <summary>
/// Initialize a new instance of <see cref="ExpandedChangedEventArgs"/>
/// </summary>
public ExpandedChangedEventArgs(bool isExpanded)
{
IsExpanded = isExpanded;
}

/// <summary>
/// True if Is Expanded.
/// </summary>
public bool IsExpanded { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Core.Views;
using FluentAssertions;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views.Expander;

public class ExpandedChangedEventArgsTests : BaseHandlerTest
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsExpandedShouldBeEqualInExpandedChangedEventArgs(bool isExpanded)
{
var eventArgs = new ExpandedChangedEventArgs(isExpanded);
eventArgs.IsExpanded.Should().Be(isExpanded);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.ComponentModel;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.UnitTests.Mocks;
using FluentAssertions;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views.Expander;

public class ExpanderTests : BaseHandlerTest
{
readonly Maui.Views.Expander expander = new();

[Fact]
public void ExpanderShouldBeAssignedToIExpander()
{
new Maui.Views.Expander().Should().BeAssignableTo<IExpander>();
}

[Fact]
public void CheckDefaultValues()
{
Assert.Equal(ExpandDirection.Down, expander.Direction);
Assert.False(expander.IsExpanded);
Assert.Null(expander.Content);
Assert.Null(expander.Header);
Assert.Null(expander.Command);
Assert.Null(expander.CommandParameter);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ExpandedChangedIsExpandedPassedWithEvent(bool expectedIsExpanded)
{
bool? isExpanded = null;
var action = new EventHandler<ExpandedChangedEventArgs>((_, e) => isExpanded = e.IsExpanded);
expander.ExpandedChanged += action;
((IExpander)expander).ExpandedChanged(expectedIsExpanded);
expander.ExpandedChanged -= action;

isExpanded.Should().Be(expectedIsExpanded);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ExpandedChangedCommandExecutedWithParams(bool expectedIsExpanded)
{
bool? isExpanded = null;

expander.Command = new Command<bool>(parameter => isExpanded = parameter);
expander.CommandParameter = expectedIsExpanded;
((IExpander)expander).ExpandedChanged(expectedIsExpanded);

isExpanded.Should().Be(expectedIsExpanded);
}

[Theory]
[InlineData((ExpandDirection)(-1))]
[InlineData((ExpandDirection)2)]
public void ExpanderDirectionThrowsInvalidEnumArgumentException(ExpandDirection direction)
{
Assert.Throws<InvalidEnumArgumentException>(() => expander.Direction = direction);
}
}
Loading

0 comments on commit 1ea488c

Please sign in to comment.