using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using Tango.Core; using Tango.Core.Commands; using Tango.Core.DI; using Tango.PPC.Common; using Tango.PPC.Common.Modules; using Tango.PPC.Common.Navigation; using Tango.PPC.Common.Notifications; using Tango.PPC.Common.Threading; using Tango.PPC.UI.Views; using Tango.PPC.UI.ViewsContracts; using Tango.SharedUI.Controls; namespace Tango.PPC.UI.Navigation { /// /// Represents the default PPC navigation manager. /// /// public class EurekaNavigationManager : ExtendedObject, INavigationManager { //private event Action NavigationCycleCompleted; //private event Action BeforeNavigationCycleCompleted; private class AwaitingVMResult { public PPCViewModel FromVM { get; set; } public PPCViewModel ToVM { get; set; } public Action Action { get; set; } } private List _awaitingVMResults; private IDispatcherProvider _dispatcherProvider; private IPPCModuleLoader _moduleLoader; private INotificationProvider _notificationProvider; private String _lastFullPath; private bool _preventHistory; private bool _navigating_back; public event EventHandler CurrentVMChanged; private Stack _navigationHistory; private Object _currentVM; /// /// Gets the current view model. /// public PPCViewModel CurrentVM { set { var previous = _currentVM; _currentVM = value; var vm = _currentVM as PPCViewModel; if (_currentVM != previous && vm != null) { CurrentVMChanged?.Invoke(this, vm); } } get { return _currentVM as PPCViewModel; } } private IPPCModule _currentModule; /// /// Gets or sets the current module. /// public IPPCModule CurrentModule { get { return _currentModule; } private set { _currentModule = value; RaisePropertyChangedAuto(); } } /// /// Navigates to the previous view. /// public RelayCommand NavigateBackCommand { get; private set; } /// /// Navigates to the specified full path in command parameter. /// public RelayCommand NavigateToCommand { get; private set; } /// /// Initializes a new instance of the class. /// /// The module loader. public EurekaNavigationManager(IPPCModuleLoader moduleLoader, IDispatcherProvider dispatcherProvider, INotificationProvider notificationProvider) { IsBackEnabled = true; _awaitingVMResults = new List(); _navigationHistory = new Stack(); _moduleLoader = moduleLoader; _notificationProvider = notificationProvider; NavigateToCommand = new RelayCommand(async (x) => await NavigateTo(x)); NavigateBackCommand = new RelayCommand(async () => await NavigateBack()); _dispatcherProvider = dispatcherProvider; } /// /// Navigates to the specified PPC view. /// /// The view. public Task NavigateTo(NavigationView view, bool pushToHistory = true) { pushToHistory = false; if (view == NavigationView.HomeModule) { _navigationHistory.Clear(); _lastFullPath = null; var firstModule = _moduleLoader.UserModules.FirstOrDefault(); if (firstModule != null) { var moduleAtt = firstModule.GetType().GetCustomAttribute(); if (moduleAtt != null) { return NavigateTo(firstModule.GetType(), pushToHistory, moduleAtt.HomeViewName); } else { return NavigateTo(firstModule.GetType(), pushToHistory); } } else { return NavigateTo(NavigationView.NoPermissionsView); } } else { LogManager.Log($"Navigating to: {view.ToString()}..."); var fromView = GetMainNavigationControl().SelectedElement; FrameworkElement toView = null; toView = GetMainNavigationControl().NavigateTo(view.ToString(), (Action)(() => { CurrentVM = toView.DataContext as PPCViewModel; NotifyOnNavigated(fromView.DataContext, toView.DataContext); })); NotifyOnBeforeNavigated(fromView.DataContext, toView.DataContext); return Task.FromResult(true); } } /// /// Navigates to the specified PPC view with the specified receive object. /// /// The view. /// /// /// public Task NavigateWithObject(NavigationView view, TPass obj, bool pushToHistory = true) { LogManager.Log($"Navigating to: {view.ToString()}, with object {typeof(TPass).Name}..."); GetMainNavigationControl().NavigateTo(view.ToString()); INavigationObjectReceiver receiver = GetMainNavigationControl().Elements.FirstOrDefault(x => (x.GetType().Name == view.ToString() || NavigationControl.GetNavigationName(x) == view.ToString()) && x.DataContext is INavigationObjectReceiver).DataContext as INavigationObjectReceiver; if (receiver != null) { receiver.OnNavigatedToWithObject(obj); } return Task.FromResult(true); } /// /// Navigates to the specified module. /// /// public Task NavigateTo(bool pushToHistory = true) where T : IPPCModule { return NavigateTo(typeof(T)); } /// /// Navigates to the specified module using the view path (e.g MainView.JobsView). /// /// /// The view path. public Task NavigateTo(string viewPath, bool pushToHistory = true) where T : IPPCModule { return NavigateTo(pushToHistory, viewPath.Split('.')); } /// /// Navigates to the specified module using the view path (e.g MainView,JobsView). /// This method makes it easy to do stuff like NavigateTo(nameof(MainView),nameof(JobsView)); /// /// /// The view path. public Task NavigateTo(bool pushToHistory = true, params String[] viewPath) where T : IPPCModule { return NavigateTo(typeof(T), pushToHistory, viewPath); } /// /// Navigates to the specified module and view by full path (e.g Jobs.JobsView). /// /// The full path. public async Task NavigateTo(String fullPath, bool pushToHistory = true, Action onNavigating = null, Action onNavigated = null) { if (_lastFullPath != null && !_lastFullPath.Contains("JobsV2Module")) { pushToHistory = false; } try { IsNavigating = true; String[] path = fullPath.Split('.'); var module = _moduleLoader.UserModules.SingleOrDefault(x => x.GetType().Name == path[0] || x.Name == path[0]); if (module == null) { await _notificationProvider.ShowError("The specified module was not loaded."); IsNavigating = false; return false; } if (path.Length == 1 && path[0] == CurrentModule.Name) { IsNavigating = false; return true; } LogManager.Log($"Navigating to: {fullPath}..."); var fromVM = CurrentVM; if (CurrentVM != null && CurrentVM is INavigationBlocker) { if (_navigating_back) { if (!await (CurrentVM as INavigationBlocker).OnNavigateBackRequest()) { IsNavigating = false; return false; } } else { if (!await (CurrentVM as INavigationBlocker).OnNavigateOutRequest()) { IsNavigating = false; return false; } } } if (pushToHistory && _lastFullPath != null && !_preventHistory) { _navigationHistory.Push(_lastFullPath); RaisePropertyChanged(nameof(CanNavigateBack)); } _lastFullPath = fullPath; GetMainNavigationControl().NavigateTo(NavigationView.LayoutView.ToString()); var navigationControl = GetLayoutNavigationControl(); CurrentModule = module; var moduleView = navigationControl.NavigateTo(module.Name); CurrentVM = moduleView.DataContext as PPCViewModel; if (path.Length > 1) { var moduleNavigation = moduleView.FindChildOffline(); if (moduleNavigation != null) { moduleNavigation.RegisterForLoadedOrNow(async (x, e) => { var lastView = moduleNavigation.GetElement(path.Last()); if (lastView != null) { onNavigating?.Invoke(fromVM as PPCViewModel, lastView.DataContext as PPCViewModel); } foreach (var view in path.Skip(1)) { await Task.Delay(100); FrameworkElement v = null; v = moduleNavigation.NavigateTo(view, () => { if (v != null) { NotifyOnNavigated(fromVM, v.DataContext); onNavigated?.Invoke(fromVM as PPCViewModel, v.DataContext as PPCViewModel); NotifyAwaitingVMResults(fromVM as PPCViewModel, v.DataContext as PPCViewModel); } }); NotifyOnBeforeNavigated(fromVM, v.DataContext); if (v != null) { CurrentVM = v.DataContext as PPCViewModel; if (view != path.Last()) { moduleNavigation = v.FindChildOffline(); } } else { throw LogManager.Log(new ArgumentNullException("Could not navigate to " + fullPath)); } } }); } else { onNavigating?.Invoke(fromVM as PPCViewModel, CurrentVM as PPCViewModel); NotifyOnBeforeNavigated(fromVM, CurrentVM); await Task.Delay(navigationControl.TransitionDuration.TimeSpan); NotifyOnNavigated(fromVM, CurrentVM); onNavigated?.Invoke(fromVM as PPCViewModel, CurrentVM as PPCViewModel); NotifyAwaitingVMResults(fromVM as PPCViewModel, CurrentVM as PPCViewModel); } } else { NotifyOnBeforeNavigated(fromVM, CurrentVM); onNavigating?.Invoke(fromVM as PPCViewModel, CurrentVM as PPCViewModel); await Task.Delay(navigationControl.TransitionDuration.TimeSpan); NotifyOnNavigated(fromVM, CurrentVM); onNavigated?.Invoke(fromVM as PPCViewModel, CurrentVM as PPCViewModel); NotifyAwaitingVMResults(fromVM as PPCViewModel, CurrentVM as PPCViewModel); } return true; } catch (Exception ex) { IsNavigating = false; LogManager.Log(ex, $"Error navigating to '{fullPath}'."); await _notificationProvider.ShowError($"Error navigating to '{fullPath}'."); return false; } } /// /// Navigates for result. /// /// The type of the module. /// The type of the view. /// The type of the result. /// The type of the object. /// The object. /// if set to true [push to history]. /// public async Task NavigateForResult(TObject obj, bool pushToHistory = true) where TModule : IPPCModule { TaskCompletionSource source = new TaskCompletionSource(); var fromVM = CurrentVM; await NavigateTo(typeof(TModule).Name + "." + typeof(TView).Name, pushToHistory, (from, to) => { _awaitingVMResults.Add(new AwaitingVMResult() { FromVM = fromVM as PPCViewModel, ToVM = to as PPCViewModel, Action = () => { if (to is INavigationResultProvider) { source.SetResult((to as INavigationResultProvider).GetNavigationResult()); } } }); if (to is INavigationResultProvider) { (to as INavigationResultProvider).OnNavigationObjectReceived(obj); } }); return await source.Task; } /// /// Navigates to the specified module and view with the specified object. /// /// The type of the module. /// The type of the view. /// The type of the pass. /// The object. /// if set to true [push to history]. /// public Task NavigateWithObject(TPass obj, bool pushToHistory = true) where TModule : IPPCModule { return NavigateTo(typeof(TModule).Name + "." + typeof(TView).Name, pushToHistory, (fromVM, toVM) => { if (toVM is INavigationObjectReceiver) { (toVM as INavigationObjectReceiver).OnNavigatedToWithObject(obj); } }); } private Task NavigateTo(Type moduleType, bool pushToHistory = true, params String[] viewPath) { if (viewPath != null && viewPath.Length > 0) { return NavigateTo(moduleType.Name + "." + String.Join(".", viewPath), pushToHistory); } else { return NavigateTo(moduleType.Name, pushToHistory); } } /// /// Gets a value indicating whether the navigation system is able to navigate to the previous view. /// public bool CanNavigateBack { get { return _navigationHistory.Count > 0; } } private bool _isBackEnabled; /// /// Gets a value indicating whether the back should be enabled. /// public bool IsBackEnabled { get { return _isBackEnabled; } set { _isBackEnabled = value; RaisePropertyChangedAuto(); } } private bool _isNavigating; /// /// Gets or sets a value indicating whether the navigation system is currently navigating. /// public bool IsNavigating { get { return _isNavigating; } set { _isNavigating = value; RaisePropertyChangedAuto(); } } /// /// Navigates to the previous view if is true. /// public async Task NavigateBack() { LogManager.Log("Navigating back..."); _navigating_back = true; if (_navigationHistory.Count > 0) { String first = _navigationHistory.Pop(); _preventHistory = true; if (await NavigateTo(first)) { RaisePropertyChanged(nameof(CanNavigateBack)); _preventHistory = false; _navigating_back = false; return true; } else { _navigationHistory.Push(first); _preventHistory = false; _navigating_back = false; RaisePropertyChanged(nameof(CanNavigateBack)); return false; } } else { await NavigateTo(NavigationView.HomeModule); RaisePropertyChanged(nameof(CanNavigateBack)); _preventHistory = false; _navigating_back = false; return true; } } /// /// Clears the navigation back history. /// public void ClearHistory() { LogManager.Log("Navigation history cleared."); _navigationHistory.Clear(); RaisePropertyChanged(nameof(CanNavigateBack)); } /// /// Clears the navigation back history except the specified view type. /// /// public void ClearHistoryExcept() { LogManager.Log($"Navigation history cleared except for {typeof(T).Name}."); var history_list = _navigationHistory.ToList(); history_list = history_list.Where(x => x.Contains(typeof(T).Name)).Distinct().ToList(); _navigationHistory.Clear(); foreach (var item in history_list) { _navigationHistory.Push(item); } RaisePropertyChanged(nameof(CanNavigateBack)); } private void NotifyOnBeforeNavigated(object fromVM, object toVM) { if (fromVM == toVM) return; if (fromVM is PPCViewModel) { (fromVM as PPCViewModel)?.OnBeforeNavigatedFrom(); } if (toVM is PPCViewModel) { (toVM as PPCViewModel)?.OnBeforeNavigatedTo(); } } private void NotifyOnNavigated(object fromVM, object toVM) { IsNavigating = false; if (fromVM == toVM) return; if (fromVM is PPCViewModel) { (fromVM as PPCViewModel)?.OnNavigatedFrom(); } if (toVM is PPCViewModel) { (toVM as PPCViewModel)?.OnNavigatedTo(); (toVM as PPCViewModel)?.OnNavigatedTo(fromVM as PPCViewModel); } } private void NotifyAwaitingVMResults(PPCViewModel fromVM, PPCViewModel toVM) { var awaiter = _awaitingVMResults.SingleOrDefault(x => x.FromVM == toVM && x.ToVM == fromVM); if (awaiter != null) { _awaitingVMResults.Remove(awaiter); awaiter.Action(); } } private NavigationControl GetLayoutNavigationControl() { return TangoIOC.Default.GetInstance().GetNavigationControl(); } private NavigationControl GetMainNavigationControl() { return TangoIOC.Default.GetInstance().GetNavigationControl(); } } }