reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.1k stars 1.12k forks source link

Add RoutingState.NavigateForward. #2798

Closed HavenDV closed 3 years ago

HavenDV commented 3 years ago

This will allow forward navigation after using NavigateBack, much like Back/Forward in Chrome. This may be necessary for some scenarios where it is important to give the user the ability to cancel NavigateBack.

The current code I'm using for this(Unfortunately this requires modifying the original NavigateBack, so I had to add NavigateBack2):

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using DynamicData;
using DynamicData.Binding;

namespace ReactiveUI
{
    /// <summary>
    /// RoutingState manages the ViewModel Stack and allows ViewModels to
    /// navigate to other ViewModels.
    /// </summary>
    [DataContract]
    public class RoutingStateExtended : RoutingState
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RoutingState"/> class.
        /// </summary>
        public RoutingStateExtended()
            : this(RxApp.MainThreadScheduler)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RoutingState"/> class.
        /// </summary>
        /// <param name="scheduler">A scheduler for where to send navigation changes to.</param>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        public RoutingStateExtended(IScheduler scheduler)
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
            : base(scheduler)
        {
            SetupRx();
        }

        /// <summary>
        /// Gets or sets a command which will navigate back to the previous element in the stack.
        /// </summary>
        [IgnoreDataMember]
        public ReactiveCommand<Unit, IRoutableViewModel> NavigateBack2 { get; protected set; }

        /// <summary>
        /// Gets the current next navigation stack, the last element in the
        /// collection being the currently visible ViewModel.
        /// </summary>
        [DataMember]
        public ObservableCollection<IRoutableViewModel> NavigationForwardStack { get; } = new();

        /// <summary>
        /// Gets or sets a command which will navigate back to the previous element in the stack.
        /// </summary>
        [IgnoreDataMember]
        public ReactiveCommand<Unit, IRoutableViewModel> NavigateForward { get; protected set; }

        /// <summary>
        /// Gets or sets an observable which will signal when the Navigation changes.
        /// </summary>
        [IgnoreDataMember]
        public IObservable<IChangeSet<IRoutableViewModel>> NavigationForwardChanged { get; protected set; }

        [OnDeserialized]
        private void SetupRx(StreamingContext _) => SetupRx();

        private void SetupRx()
        {
            NavigationForwardChanged = NavigationForwardStack.ToObservableChangeSet();

            var countAsBehavior = Observable.Defer(() => Observable.Return(NavigationStack.Count)).Concat(NavigationChanged.CountChanged().Select(_ => NavigationStack.Count));
            var countForwardAsBehavior = Observable.Defer(() => Observable.Return(NavigationForwardStack.Count))
                .Concat(NavigationForwardChanged.CountChanged().Select(_ => NavigationForwardStack.Count));

            NavigateBack2 =
                ReactiveCommand.CreateFromObservable(
                    () =>
                    {
                        var vm = NavigationStack[NavigationStack.Count - 1];

                        NavigationStack.RemoveAt(NavigationStack.Count - 1);
                        NavigationForwardStack.Add(vm);

                        var current = NavigationStack[NavigationStack.Count - 1];
                        return Observable.Return(current);
                    },
                    countAsBehavior.Select(x => x > 1),
                    Scheduler);
            NavigateForward =
                ReactiveCommand.CreateFromObservable(
                    () =>
                    {
                        var vm = NavigationForwardStack[NavigationForwardStack.Count - 1];

                        NavigationForwardStack.RemoveAt(NavigationForwardStack.Count - 1);
                        NavigationStack.Add(vm);

                        return Observable.Return(vm);
                    },
                    countForwardAsBehavior.Select(x => x > 0),
                    Scheduler);

            Navigate
                .Subscribe(vm =>
                {
                    NavigationForwardStack.Clear();
                });
            NavigateAndReset
                .Subscribe(vm =>
                {
                    NavigationForwardStack.Clear();
                });
        }
    }
}
glennawatson commented 3 years ago

We won't be implementing this one due to it being a fairly fundamental change

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.