Skip to content
Merged
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
94 changes: 55 additions & 39 deletions src/ReactiveUI.XamForms/RoutedViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Reflection;
using Splat;
using Xamarin.Forms;

#pragma warning disable RCS1090 // Call 'ConfigureAwait(false)'.
namespace ReactiveUI.XamForms
{
/// <summary>
/// This is a <see cref="NavigationPage"/> that serves as a router.
/// </summary>
/// <seealso cref="Xamarin.Forms.NavigationPage" />
/// <seealso cref="ReactiveUI.IActivatableView" />
public class RoutedViewHost : NavigationPage, IActivatableView
[SuppressMessage("Readability", "RCS1090: Call 'ConfigureAwait(false)", Justification = "This class interacts with the UI thread.")]
public class RoutedViewHost : NavigationPage, IActivatableView, IEnableLogger
{
/// <summary>
/// The router bindable property.
Expand All @@ -38,13 +39,13 @@ public class RoutedViewHost : NavigationPage, IActivatableView
/// <exception cref="Exception">You *must* register an IScreen class representing your App's main Screen.</exception>
public RoutedViewHost()
{
this.WhenActivated(new Action<Action<IDisposable>>(d =>
this.WhenActivated(disposable =>
{
bool currentlyPopping = false;
bool popToRootPending = false;
bool userInstigated = false;

d(this.WhenAnyObservable(x => x.Router.NavigationChanged)
this.WhenAnyObservable(x => x.Router.NavigationChanged)
.Where(_ => Router.NavigationStack.Count == 0)
.Select(x =>
{
Expand All @@ -54,21 +55,25 @@ public RoutedViewHost()
popToRootPending = true;
return x;
})
.Subscribe());

var previousCount =
this.WhenAnyObservable(x => x.Router.NavigationChanged)
.CountChanged()
.ObserveOn(Router.Scheduler)
.Select(_ => Router.NavigationStack.Count)
.StartWith(Router.NavigationStack.Count);

var currentCount = previousCount.Skip(1);
.Subscribe()
.DisposeWith(disposable);

Router.NavigationChanged
.CountChanged()
.Select(_ => Router.NavigationStack.Count)
.StartWith(Router.NavigationStack.Count)
.Buffer(2, 1)
.Select(counts => new
{
Delta = counts[0] - counts[1],
Current = counts[1],

d(Observable.Zip(previousCount, currentCount, (previous, current) => new { Delta = previous - current, Current = current })
// cache current viewmodel as it might change if some other Navigation command is executed midway
CurrentViewModel = Router.GetCurrentViewModel()
})
.Where(_ => !userInstigated)
.Where(x => x.Delta > 0)
.SelectMany(
.Select(
async x =>
{
// XF doesn't provide a means of navigating back more than one screen at a time apart from navigating right back to the root page
Expand All @@ -94,14 +99,20 @@ public RoutedViewHost()
finally
{
currentlyPopping = false;
((IViewFor)CurrentPage).ViewModel = Router.GetCurrentViewModel();
if (CurrentPage is IViewFor page && x.CurrentViewModel != null)
{
page.ViewModel = x.CurrentViewModel;
}
}

return Unit.Default;
})
.Subscribe());
.Concat()
.Subscribe()
.DisposeWith(disposable);

d(this.WhenAnyObservable(x => x.Router.Navigate)
Router
.Navigate
.SelectMany(_ => PageForViewModel(Router.GetCurrentViewModel()))
.SelectMany(async page =>
{
Expand All @@ -125,40 +136,45 @@ public RoutedViewHost()
popToRootPending = false;
return page;
})
.Subscribe());
.Subscribe()
.DisposeWith(disposable);

var poppingEvent = Observable.FromEvent<EventHandler<NavigationEventArgs>, Unit>(
eventHandler =>
{
void Handler(object sender, NavigationEventArgs e) => eventHandler(Unit.Default);
return Handler;
},
x => Popped += x,
x => Popped -= x);
eventHandler =>
{
void Handler(object sender, NavigationEventArgs e) => eventHandler(Unit.Default);
return Handler;
},
x => Popped += x,
x => Popped -= x);

// NB: Catch when the user hit back as opposed to the application
// requesting Back via NavigateBack
d(poppingEvent
poppingEvent
.Where(_ => !currentlyPopping && Router != null)
.Subscribe(_ =>
{
userInstigated = true;

try
{
if (Router.NavigationStack.Count > 1)
{
Router.NavigationStack.RemoveAt(Router.NavigationStack.Count - 1);
}
Router.NavigationStack.RemoveAt(Router.NavigationStack.Count - 1);
}
finally
{
userInstigated = false;
}

((IViewFor)CurrentPage).ViewModel = Router.GetCurrentViewModel();
}));
}));
var vm = Router.GetCurrentViewModel();
if (CurrentPage is IViewFor page && vm != null)
{
// don't replace view model if vm is null
page.ViewModel = vm;
}
})
.DisposeWith(disposable);
});

var screen = Locator.Current.GetService<IScreen>();
if (screen == null)
{
Expand All @@ -169,8 +185,8 @@ public RoutedViewHost()

this.WhenAnyValue(x => x.Router)
.SelectMany(router =>
router
.NavigationStack
{
return router.NavigationStack
.ToObservable()
.Select(x => (Page)ViewLocator.Current.ResolveView(x))
.SelectMany(x => PushAsync(x).ToObservable())
Expand All @@ -184,7 +200,8 @@ public RoutedViewHost()

((IViewFor)CurrentPage).ViewModel = vm;
CurrentPage.Title = vm.UrlPathSegment;
}))
});
})
.Subscribe();
}

Expand Down Expand Up @@ -227,4 +244,3 @@ protected IObservable<Page> PageForViewModel(IRoutableViewModel vm)
}
}
}
#pragma warning restore RCS1090 // Call 'ConfigureAwait(false)'.