Browse Source

Merge branch 'master' into bindingoperations-donothing

pull/2641/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
9eb80474ee
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs
  2. 2
      src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs
  3. 4
      src/Avalonia.Controls/ShutdownMode.cs
  4. 87
      src/Avalonia.ReactiveUI/AutoSuspendHelper.cs
  5. 98
      tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
  6. 4
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

2
src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs

@ -6,7 +6,7 @@ using System;
namespace Avalonia.Controls.ApplicationLifetimes
{
/// <summary>
/// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Exit"/> event.
/// Contains the arguments for the <see cref="IControlledApplicationLifetime.Exit"/> event.
/// </summary>
public class ControlledApplicationLifetimeExitEventArgs : EventArgs
{

2
src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs

@ -8,7 +8,7 @@ using System.Linq;
namespace Avalonia.Controls.ApplicationLifetimes
{
/// <summary>
/// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Startup"/> event.
/// Contains the arguments for the <see cref="IControlledApplicationLifetime.Startup"/> event.
/// </summary>
public class ControlledApplicationLifetimeStartupEventArgs : EventArgs
{

4
src/Avalonia.Controls/ShutdownMode.cs

@ -1,10 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Controls
{
/// <summary>
/// Describes the possible values for <see cref="Application.ShutdownMode"/>.
/// Describes the possible values for <see cref="IClassicDesktopStyleApplicationLifetime.ShutdownMode"/>.
/// </summary>
public enum ShutdownMode
{

87
src/Avalonia.ReactiveUI/AutoSuspendHelper.cs

@ -0,0 +1,87 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using System.Threading;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive;
using ReactiveUI;
using System;
using Avalonia.Controls.ApplicationLifetimes;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI AutoSuspendHelper which initializes suspension hooks for
/// Avalonia applications. Call its constructor in your app's composition root,
/// before calling the RxApp.SuspensionHost.SetupDefaultSuspendResume method.
/// </summary>
public sealed class AutoSuspendHelper : IEnableLogger, IDisposable
{
private readonly Subject<IDisposable> _shouldPersistState = new Subject<IDisposable>();
private readonly Subject<Unit> _isLaunchingNew = new Subject<Unit>();
/// <summary>
/// Initializes a new instance of the <see cref="AutoSuspendHelper"/> class.
/// </summary>
/// <param name="lifetime">Pass in the Application.ApplicationLifetime property.</param>
public AutoSuspendHelper(IApplicationLifetime lifetime)
{
RxApp.SuspensionHost.IsResuming = Observable.Never<Unit>();
RxApp.SuspensionHost.IsLaunchingNew = _isLaunchingNew;
if (lifetime is IControlledApplicationLifetime controlled)
{
this.Log().Debug("Using IControlledApplicationLifetime events to handle app exit.");
controlled.Exit += (sender, args) => OnControlledApplicationLifetimeExit();
RxApp.SuspensionHost.ShouldPersistState = _shouldPersistState;
}
else if (lifetime != null)
{
var type = lifetime.GetType().FullName;
var message = $"Don't know how to detect app exit event for {type}.";
throw new NotSupportedException(message);
}
else
{
var message = "ApplicationLifetime is null. "
+ "Ensure you are initializing AutoSuspendHelper "
+ "when Avalonia application initialization is completed.";
throw new ArgumentNullException(message);
}
var errored = new Subject<Unit>();
AppDomain.CurrentDomain.UnhandledException += (o, e) => errored.OnNext(Unit.Default);
RxApp.SuspensionHost.ShouldInvalidateState = errored;
}
/// <summary>
/// Call this method in your App.OnFrameworkInitializationCompleted method.
/// </summary>
public void OnFrameworkInitializationCompleted() => _isLaunchingNew.OnNext(Unit.Default);
/// <summary>
/// Disposes internally stored observers.
/// </summary>
public void Dispose()
{
_shouldPersistState.Dispose();
_isLaunchingNew.Dispose();
}
private void OnControlledApplicationLifetimeExit()
{
this.Log().Debug("Received IControlledApplicationLifetime exit event.");
var manual = new ManualResetEvent(false);
_shouldPersistState.OnNext(Disposable.Create(() => manual.Set()));
manual.WaitOne();
this.Log().Debug("Completed actions on IControlledApplicationLifetime exit event.");
}
}
}

98
tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs

@ -0,0 +1,98 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Threading;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia;
using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AutoSuspendHelperTest
{
[DataContract]
public class AppState
{
[DataMember]
public string Example { get; set; }
}
public class ExoticApplicationLifetimeWithoutLifecycleEvents : IDisposable, IApplicationLifetime
{
public void Dispose() { }
}
[Fact]
public void AutoSuspendHelper_Should_Immediately_Fire_IsLaunchingNew()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
var isLaunchingReceived = false;
var application = AvaloniaLocator.Current.GetService<Application>();
application.ApplicationLifetime = lifetime;
// Initialize ReactiveUI Suspension as in real-world scenario.
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.IsLaunchingNew.Subscribe(_ => isLaunchingReceived = true);
suspension.OnFrameworkInitializationCompleted();
Assert.True(isLaunchingReceived);
}
}
[Fact]
public void ShouldPersistState_Should_Fire_On_App_Exit_When_SuspensionDriver_Is_Initialized()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
var shouldPersistReceived = false;
var application = AvaloniaLocator.Current.GetService<Application>();
application.ApplicationLifetime = lifetime;
// Initialize ReactiveUI Suspension as in real-world scenario.
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new AppState { Example = "Foo" };
RxApp.SuspensionHost.ShouldPersistState.Subscribe(_ => shouldPersistReceived = true);
RxApp.SuspensionHost.SetupDefaultSuspendResume(new DummySuspensionDriver());
suspension.OnFrameworkInitializationCompleted();
lifetime.Shutdown();
Assert.True(shouldPersistReceived);
Assert.Equal("Foo", RxApp.SuspensionHost.GetAppState<AppState>().Example);
}
}
[Fact]
public void AutoSuspendHelper_Should_Throw_For_Not_Supported_Lifetimes()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ExoticApplicationLifetimeWithoutLifecycleEvents())
{
var application = AvaloniaLocator.Current.GetService<Application>();
application.ApplicationLifetime = lifetime;
Assert.Throws<NotSupportedException>(() => new AutoSuspendHelper(application.ApplicationLifetime));
}
}
}
}

4
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -171,7 +171,7 @@ namespace Avalonia.ReactiveUI.UnitTests
[Fact]
public void Activation_For_View_Fetcher_Should_Support_Windows()
{
using (var application = UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var window = new TestWindowWithWhenActivated();
Assert.False(window.Active);
@ -187,7 +187,7 @@ namespace Avalonia.ReactiveUI.UnitTests
[Fact]
public void Activatable_Window_View_Model_Is_Activated_And_Deactivated()
{
using (var application = UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var viewModel = new ActivatableViewModel();
var window = new ActivatableWindow { ViewModel = viewModel };

Loading…
Cancel
Save