diff --git a/src/App/Abstractions/INavigationService.cs b/src/App/Abstractions/INavigationService.cs
new file mode 100644
index 000000000..b5741de50
--- /dev/null
+++ b/src/App/Abstractions/INavigationService.cs
@@ -0,0 +1,45 @@
+using Bit.App.Pages;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Bit.App.Abstractions
+{
+ public interface INavigationService
+ {
+ ///
+ /// Sets the viewmodel to be the main page of the application
+ ///
+ void PresentAsMainPage(BaseViewModel viewModel);
+
+ ///
+ /// Sets the viewmodel as the main page of the application, and wraps its page within a Navigation page
+ ///
+ void PresentAsNavigatableMainPage(BaseViewModel viewModel);
+
+ ///
+ /// Navigate to the given page on top of the current navigation stack
+ ///
+ Task NavigateTo(BaseViewModel viewModel);
+
+ ///
+ /// Navigate to the previous item in the navigation stack
+ ///
+ Task NavigateBack();
+
+ ///
+ /// Navigate back to the element at the root of the navigation stack
+ ///
+ Task NavigateBackToRoot();
+ }
+
+ public interface IViewLocator
+ {
+ Page CreateAndBindPageFor(TViewModel viewModel) where TViewModel : BaseViewModel;
+ }
+
+ public interface IMainPage
+ {
+ Page MainPage { get; set; }
+ }
+
+}
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index ab31a0818..28889a1f8 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -15,7 +15,7 @@ using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
{
- public partial class App : Application
+ public partial class App : Application, IMainPage
{
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService;
@@ -51,6 +51,8 @@ namespace Bit.App
_secureStorageService = ServiceContainer.Resolve("secureStorageService");
_deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ var navigator = new NavigationService(this, new ViewLocator());
+
Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
diff --git a/src/App/Services/NavigationService.cs b/src/App/Services/NavigationService.cs
new file mode 100644
index 000000000..c0c3cbb6b
--- /dev/null
+++ b/src/App/Services/NavigationService.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+using Bit.App.Abstractions;
+using Bit.App.Pages;
+
+namespace Bit.App.Services
+{
+ public class NavigationService : INavigationService
+ {
+ private readonly IMainPage _presentationRoot;
+ private readonly IViewLocator _viewLocator;
+
+ public NavigationService(IMainPage presentationRoot, IViewLocator viewLocator)
+ {
+ _presentationRoot = presentationRoot;
+ _viewLocator = viewLocator;
+ }
+
+ private Xamarin.Forms.INavigation Navigator => _presentationRoot.MainPage.Navigation;
+
+ public void PresentAsMainPage(BaseViewModel viewModel)
+ {
+ var page = _viewLocator.CreateAndBindPageFor(viewModel);
+
+ IEnumerable viewModelsToDismiss = FindViewModelsToDismiss(_presentationRoot.MainPage);
+
+ if (_presentationRoot.MainPage is NavigationPage navPage)
+ {
+ // If we're replacing a navigation page, unsub from events
+ navPage.PopRequested -= NavPagePopRequested;
+ }
+
+ // viewModel.BeforeFirstShown();
+
+ _presentationRoot.MainPage = page;
+
+ foreach (BaseViewModel toDismiss in viewModelsToDismiss)
+ {
+ // toDismiss.AfterDismissed();
+ }
+ }
+
+ public void PresentAsNavigatableMainPage(BaseViewModel viewModel)
+ {
+ var page = _viewLocator.CreateAndBindPageFor(viewModel);
+
+ NavigationPage newNavigationPage = new NavigationPage(page);
+
+ IEnumerable viewModelsToDismiss = FindViewModelsToDismiss(_presentationRoot.MainPage);
+
+ if (_presentationRoot.MainPage is NavigationPage navPage)
+ {
+ navPage.PopRequested -= NavPagePopRequested;
+ }
+
+ // viewModel.BeforeFirstShown();
+
+ // Listen for back button presses on the new navigation bar
+ newNavigationPage.PopRequested += NavPagePopRequested;
+ _presentationRoot.MainPage = newNavigationPage;
+
+ foreach (BaseViewModel toDismiss in viewModelsToDismiss)
+ {
+ // toDismiss.AfterDismissed();
+ }
+ }
+
+ private IEnumerable FindViewModelsToDismiss(Page dismissingPage)
+ {
+ var viewmodels = new List();
+
+ if (dismissingPage is NavigationPage)
+ {
+ viewmodels.AddRange(
+ Navigator
+ .NavigationStack
+ .Select(p => p.BindingContext)
+ .OfType()
+ );
+ }
+ else
+ {
+ var viewmodel = dismissingPage?.BindingContext as BaseViewModel;
+ if (viewmodel != null) viewmodels.Add(viewmodel);
+ }
+
+ return viewmodels;
+ }
+
+ private void NavPagePopRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ if (Navigator.NavigationStack.LastOrDefault()?.BindingContext is BaseViewModel poppingPage)
+ {
+ // poppingPage.AfterDismissed();
+ }
+ }
+
+ public async Task NavigateTo(BaseViewModel viewModel)
+ {
+ var page = _viewLocator.CreateAndBindPageFor(viewModel);
+
+ // await viewModel.BeforeFirstShown();
+
+ await Navigator.PushAsync(page);
+ }
+
+ public async Task NavigateBack()
+ {
+ var dismissing = Navigator.NavigationStack.Last().BindingContext as BaseViewModel;
+
+ await Navigator.PopAsync();
+
+ // dismissing?.AfterDismissed();
+ }
+
+ public async Task NavigateBackToRoot()
+ {
+ var toDismiss = Navigator
+ .NavigationStack
+ .Skip(1)
+ .Select(vw => vw.BindingContext)
+ .OfType()
+ .ToArray();
+
+ await Navigator.PopToRootAsync();
+
+ foreach (BaseViewModel viewModel in toDismiss)
+ {
+ // viewModel.AfterDismissed().FireAndForget();
+ }
+ }
+ }
+
+ public class ViewLocator : IViewLocator
+ {
+ public Page CreateAndBindPageFor(TViewModel viewModel) where TViewModel : BaseViewModel
+ {
+ var pageType = FindPageForViewModel(viewModel.GetType());
+
+ var page = (Page)Activator.CreateInstance(pageType);
+
+ page.BindingContext = viewModel;
+
+ return page;
+ }
+
+ protected virtual Type FindPageForViewModel(Type viewModelType)
+ {
+ var pageTypeName = viewModelType
+ .AssemblyQualifiedName
+ .Replace("ViewModel", "");
+
+ var pageType = Type.GetType(pageTypeName);
+ if (pageType == null)
+ throw new ArgumentException(pageTypeName + " type does not exist");
+
+ return pageType;
+ }
+ }
+}
\ No newline at end of file