From cf0869c6972db623a60d7a1474aa613e42dfc4f2 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 10 May 2023 14:32:42 +0300 Subject: Improved Back Button Handling. --- .../Navigation/EurekaNavigationManager.cs | 606 +++++++++++++++++++++ .../PPC/Tango.PPC.UI/Tango.PPC.UI.csproj | 3 +- .../PPC/Tango.PPC.UI/ViewModelLocator.cs | 9 +- 3 files changed, 616 insertions(+), 2 deletions(-) create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.UI/Navigation/EurekaNavigationManager.cs (limited to 'Software/Visual_Studio/PPC/Tango.PPC.UI') diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Navigation/EurekaNavigationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/Navigation/EurekaNavigationManager.cs new file mode 100644 index 000000000..63332e889 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Navigation/EurekaNavigationManager.cs @@ -0,0 +1,606 @@ +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(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj b/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj index 4e0671b57..40693b98f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj @@ -233,6 +233,7 @@ + @@ -947,7 +948,7 @@ if $(ConfigurationName) == Eureka copy /Y "$(ProjectDir)Intro.wmv" "$(TargetDir) - + diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs index c7dd0b67c..251eeb1ec 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs @@ -128,7 +128,14 @@ namespace Tango.PPC.UI TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); - TangoIOC.Default.Register(); + if (buildProvider.IsEureka) + { + TangoIOC.Default.Register(); + } + else + { + TangoIOC.Default.Register(); + } TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); -- cgit v1.3.1