Skip to content

Commit

Permalink
feature for Xamarin added ReactiveShell, ReactiveShellContent, Reacti…
Browse files Browse the repository at this point in the history
…veFlyOut (#2814)

* feature for Xamarin added ReactiveShell, ShellViewModel, ReactiveFlyOut

ReactiveShell can be used when you wish to use a viewmodel on your base
NOTE: Only supports WhenActivated NOT WhenDeactivated due to the Base nature of the Shell component

ShellViewModel is a ViewModel based version of ShellContent

* Updates to resolve queries refactored tests using Xamarin.Forms.Mocks

Rename ShellViewModel to ReactiveShellContent to keep in with other extended elements

* Removed ReactiveMasterDetailPage is obsolete

* throw new NullReferenceException(nameof(Locator.Current)) if Locator.Current is null
  • Loading branch information
ChrisPulman committed Jul 6, 2021
1 parent aa85c40 commit d215b0f
Show file tree
Hide file tree
Showing 30 changed files with 1,330 additions and 2 deletions.
@@ -0,0 +1,146 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using ReactiveUI.XamForms;
using ReactiveUI.XamForms.Tests.Activation;
using ReactiveUI.XamForms.Tests.Activation.Mocks;
using Splat;
using Xamarin.Forms;
using Xunit;

namespace ReactiveUI.Tests
{
/// <summary>
/// Tests for activating views.
/// </summary>
public class ActivatingReactivePagesTests : IClassFixture<ApplicationFixture<App>>
{
private ApplicationFixture<App> _fixture;

/// <summary>
/// Initializes a new instance of the <see cref="ActivatingReactivePagesTests"/> class.
/// </summary>
/// <param name="fixture">The fixture.</param>
public ActivatingReactivePagesTests(ApplicationFixture<App> fixture)
{
_fixture = fixture;
Locator.CurrentMutable.Register(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register<IViewFor<ShellViewModel>>(() => new ShellView());
Locator.CurrentMutable.Register<IViewFor<ContentPageViewModel>>(() => new ContentPageView());
Locator.CurrentMutable.Register<IViewFor<TabbedPageViewModel>>(() => new TabbedPageView());
Locator.CurrentMutable.Register<IViewFor<CarouselPageViewModel>>(() => new CarouselPageView());
Locator.CurrentMutable.Register<IViewFor<FlyOutPageViewModel>>(() => new FlyoutPageView());
_fixture.ActivateApp(new App());
}

/// <summary>
/// Tests to make sure that views generally activate.
/// </summary>
[Fact]
public void ActivatingReactiveShellTest()
{
var main = _fixture.AppMock!.MainPage as AppShell;

Assert.Equal(1, main!.ViewModel!.IsActiveCount);
Assert.Equal(1, main.IsActiveCount);
}

/// <summary>
/// Tests to make sure that views generally activate.
/// </summary>
[Fact]
public void ActivatingReactiveContentPageTest()
{
var vm = new ContentPageViewModel();
var fixture = new ContentPageView
{
ViewModel = vm
};

// Activate
Shell.Current.Navigation.PushAsync(fixture);
Assert.Equal(1, fixture.ViewModel.IsActiveCount);
Assert.Equal(1, fixture.IsActiveCount);

// Deactivate
Shell.Current.GoToAsync("..");
fixture.ViewModel = null;
Assert.Equal(0, vm.IsActiveCount);
Assert.Equal(0, fixture.IsActiveCount);
}

/// <summary>
/// Tests to make sure that views generally activate.
/// </summary>
[Fact]
public void ActivatingReactiveTabbedPageTest()
{
var vm1 = new TabbedPageViewModel();
var fixture1 = new TabbedPageView
{
ViewModel = vm1
};

// Activate
Shell.Current.Navigation.PushAsync(fixture1);
Assert.Equal(1, fixture1.ViewModel.IsActiveCount);
Assert.Equal(1, fixture1.IsActiveCount);

// Deactivate
Shell.Current.GoToAsync("..");
fixture1.ViewModel = null;
Assert.Equal(0, vm1.IsActiveCount);
Assert.Equal(0, fixture1.IsActiveCount);
}

/// <summary>
/// Tests to make sure that views generally activate.
/// </summary>
[Fact]
public void ActivatingReactiveFlyoutPageTest()
{
var vm3 = new FlyOutPageViewModel();
var fixture3 = new FlyoutPageView
{
ViewModel = vm3
};

// Activate
Shell.Current.Navigation.PushAsync(fixture3);
Assert.Equal(1, fixture3.ViewModel!.IsActiveCount);
Assert.Equal(1, fixture3.IsActiveCount);

// Deactivate
Shell.Current.GoToAsync("..");
fixture3.ViewModel = null;
Assert.Equal(0, vm3.IsActiveCount);
Assert.Equal(0, fixture3.IsActiveCount);
}

/// <summary>
/// Tests to make sure that views generally activate.
/// </summary>
[Fact]
public void ActivatingReactiveCarouselPageTest()
{
var vm4 = new CarouselPageViewModel();
var fixture4 = new CarouselPageView
{
ViewModel = vm4
};

// Activate
Shell.Current.Navigation.PushAsync(fixture4);
Assert.Equal(1, fixture4.ViewModel!.IsActiveCount);
Assert.Equal(1, fixture4.IsActiveCount);

// Deactivate
Shell.Current.GoToAsync("..");
fixture4.ViewModel = null;
Assert.Equal(0, vm4.IsActiveCount);
Assert.Equal(0, fixture4.IsActiveCount);
}
}
}
34 changes: 34 additions & 0 deletions src/ReactiveUI.XamForms.Tests/Activation/Mocks/App.xaml
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<test:ApplicationMock
x:Class="ReactiveUI.XamForms.Tests.Activation.App"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:test="clr-namespace:ReactiveUI.Tests">
<!--
Define global resources and styles here, that apply to all pages in your app.
-->
<Application.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#2196F3</Color>
<Style TargetType="Button">
<Setter Property="TextColor" Value="White" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="#332196F3" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</test:ApplicationMock>
57 changes: 57 additions & 0 deletions src/ReactiveUI.XamForms.Tests/Activation/Mocks/App.xaml.cs
@@ -0,0 +1,57 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Xamarin.Forms;

namespace ReactiveUI.XamForms.Tests.Activation
{
/// <summary>
/// The App.
/// </summary>
/// <seealso cref="Xamarin.Forms.Application" />
public partial class App
{
/// <summary>
/// Initializes a new instance of the <see cref="App" /> class.
/// </summary>
/// <param name="page">The page.</param>
public App()
{
InitializeComponent();

MainPage = new AppShell();
}

/// <summary>
/// Application developers override this method to perform actions when the application starts.
/// </summary>
/// <remarks>
/// To be added.
/// </remarks>
protected override void OnStart()
{
}

/// <summary>
/// Application developers override this method to perform actions when the application enters the sleeping state.
/// </summary>
/// <remarks>
/// To be added.
/// </remarks>
protected override void OnSleep()
{
}

/// <summary>
/// Application developers override this method to perform actions when the application resumes from a sleeping state.
/// </summary>
/// <remarks>
/// To be added.
/// </remarks>
protected override void OnResume()
{
}
}
}
39 changes: 39 additions & 0 deletions src/ReactiveUI.XamForms.Tests/Activation/Mocks/AppShell.xaml
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rxui:ReactiveShell
x:Class="ReactiveUI.XamForms.Tests.Activation.AppShell"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:vm="clr-namespace:ReactiveUI.XamForms.Tests.Activation"
Title="Shell.XamForms.Test"
x:TypeArguments="vm:AppShellViewModel">

<!--
The overall app visual hierarchy is defined here, along with navigation.
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/
-->
<Shell.Resources>
<ResourceDictionary>
<Style x:Key="BaseStyle" TargetType="Element">
<Setter Property="Shell.BackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="Shell.ForegroundColor" Value="White" />
<Setter Property="Shell.TitleColor" Value="White" />
<Setter Property="Shell.DisabledColor" Value="#B4FFFFFF" />
<Setter Property="Shell.UnselectedColor" Value="#95FFFFFF" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="Shell.TabBarForegroundColor" Value="White" />
<Setter Property="Shell.TabBarUnselectedColor" Value="#95FFFFFF" />
<Setter Property="Shell.TabBarTitleColor" Value="White" />
</Style>
<Style BasedOn="{StaticResource BaseStyle}" TargetType="TabBar" />
<Style BasedOn="{StaticResource BaseStyle}" TargetType="FlyoutItem" />
</ResourceDictionary>
</Shell.Resources>

<rxui:ReactiveShellContent Title="Shell" x:TypeArguments="vm:ShellViewModel" />
<rxui:ReactiveShellContent Title="Content" x:TypeArguments="vm:ContentPageViewModel" />
<rxui:ReactiveShellContent Title="Tabbed" x:TypeArguments="vm:TabbedPageViewModel" />
<rxui:ReactiveShellContent Title="Carousel" x:TypeArguments="vm:CarouselPageViewModel" />
<rxui:ReactiveShellContent Title="FlyOut" x:TypeArguments="vm:FlyOutPageViewModel" />
</rxui:ReactiveShell>
35 changes: 35 additions & 0 deletions src/ReactiveUI.XamForms.Tests/Activation/Mocks/AppShell.xaml.cs
@@ -0,0 +1,35 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Reactive.Disposables;

namespace ReactiveUI.XamForms.Tests.Activation
{
/// <summary>
/// App Shell.
/// </summary>
/// <seealso cref="Xamarin.Forms.Shell" />
public partial class AppShell
{
/// <summary>
/// Initializes a new instance of the <see cref="AppShell"/> class.
/// </summary>
public AppShell()
{
InitializeComponent();
ViewModel = new();
this.WhenActivated(d =>
{
IsActiveCount++;
d(Disposable.Create(() => IsActiveCount--));
});
}

/// <summary>
/// Gets or sets the active count.
/// </summary>
public int IsActiveCount { get; set; }
}
}
@@ -0,0 +1,41 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Reactive.Disposables;

namespace ReactiveUI.XamForms.Tests.Activation
{
/// <summary>
/// Activating View Model2.
/// </summary>
/// <seealso cref="ReactiveUI.ReactiveObject" />
/// <seealso cref="ReactiveUI.IActivatableViewModel" />
public class AppShellViewModel : ReactiveObject, IActivatableViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="AppShellViewModel"/> class.
/// </summary>
public AppShellViewModel()
{
Activator = new ViewModelActivator();

this.WhenActivated(d =>
{
IsActiveCount++;
d(Disposable.Create(() => IsActiveCount--));
});
}

/// <summary>
/// Gets or sets the Activator which will be used by the View when Activation/Deactivation occurs.
/// </summary>
public ViewModelActivator Activator { get; protected set; }

/// <summary>
/// Gets or sets the active count.
/// </summary>
public int IsActiveCount { get; protected set; }
}
}

0 comments on commit d215b0f

Please sign in to comment.