From 2f73b66dfd68be3dce478362c95c2d92bfbc81b8 Mon Sep 17 00:00:00 2001 From: cabauman Date: Wed, 5 Sep 2018 22:15:49 +0900 Subject: [PATCH] Add page routing and support various detail views --- .../Android/MasterDetail.Android.csproj | 2 +- .../MasterDetail/MasterDetail/App.xaml.cs | 4 +- .../MasterDetail/AppBootstrapper.cs | 45 +++++++++++++++++++ .../MasterDetail/MasterDetail/CustomCell.xaml | 18 -------- .../MasterDetail/CustomCell.xaml.cs | 26 ----------- .../MasterDetail/CustomCellViewModel.cs | 18 -------- .../DetailPages/LetterStreamPage.xaml | 17 +++++++ .../DetailPages/LetterStreamPage.xaml.cs | 26 +++++++++++ .../DetailPages/LetterStreamViewModel.cs | 27 +++++++++++ .../NavigablePage.xaml} | 9 ++-- .../DetailPages/NavigablePage.xaml.cs | 25 +++++++++++ .../DetailPages/NavigableViewModel.cs | 24 ++++++++++ .../DetailPages/NumberStreamPage.xaml | 17 +++++++ .../DetailPages/NumberStreamPage.xaml.cs | 29 ++++++++++++ .../DetailPages/NumberStreamViewModel.cs | 22 +++++++++ .../MasterDetail/MasterDetail/DummyPage.xaml | 23 ++++++++++ .../MasterDetail/DummyPage.xaml.cs | 28 ++++++++++++ .../MasterDetail/DummyViewModel.cs | 26 +++++++++++ .../MasterDetail/MasterDetail/MainPage.xaml | 9 ++-- .../MasterDetail/MainPage.xaml.cs | 14 +++--- .../MasterDetail/MainViewModel.cs | 37 ++++++++------- .../MasterDetail/MasterDetail/MasterCell.xaml | 18 ++++++++ .../MasterDetail/MasterCell.xaml.cs | 35 +++++++++++++++ .../MasterDetail/MasterCellViewModel.cs | 13 ++++++ .../MasterDetail/MasterDetail.csproj | 24 +--------- .../MasterDetail/MyDetailPage.xaml.cs | 36 --------------- .../MasterDetail/MyDetailViewModel.cs | 17 ------- .../MasterDetail/MasterDetail/MyModel.cs | 9 ---- .../MasterDetail/iOS/MasterDetail.iOS.csproj | 2 +- 29 files changed, 421 insertions(+), 179 deletions(-) create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/AppBootstrapper.cs delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml.cs delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/CustomCellViewModel.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamViewModel.cs rename samples/xamarin-forms/MasterDetail/MasterDetail/{MyDetailPage.xaml => DetailPages/NavigablePage.xaml} (73%) create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigableViewModel.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamViewModel.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DummyPage.xaml create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DummyPage.xaml.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/DummyViewModel.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MasterCell.xaml create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MasterCell.xaml.cs create mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MasterCellViewModel.cs delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MyDetailPage.xaml.cs delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MyDetailViewModel.cs delete mode 100644 samples/xamarin-forms/MasterDetail/MasterDetail/MyModel.cs diff --git a/samples/xamarin-forms/MasterDetail/Android/MasterDetail.Android.csproj b/samples/xamarin-forms/MasterDetail/Android/MasterDetail.Android.csproj index 5f3c5c484f..71333c98ce 100644 --- a/samples/xamarin-forms/MasterDetail/Android/MasterDetail.Android.csproj +++ b/samples/xamarin-forms/MasterDetail/Android/MasterDetail.Android.csproj @@ -49,7 +49,7 @@ - + diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/App.xaml.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/App.xaml.cs index 25e43a7755..024762c4b3 100644 --- a/samples/xamarin-forms/MasterDetail/MasterDetail/App.xaml.cs +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/App.xaml.cs @@ -13,9 +13,9 @@ public App() { InitializeComponent(); - Locator.CurrentMutable.Register(() => new CustomCell(), typeof(IViewFor)); + var bootstrapper = new AppBootstrapper(); - MainPage = new MainPage(); + MainPage = new MainPage(bootstrapper.CreateMainViewModel()); } protected override void OnStart() diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/AppBootstrapper.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/AppBootstrapper.cs new file mode 100644 index 0000000000..b0b1c81ba3 --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/AppBootstrapper.cs @@ -0,0 +1,45 @@ +using System; +using ReactiveUI; +using Splat; + +namespace MasterDetail +{ + public class AppBootstrapper + { + public AppBootstrapper() + { + RegisterViews(); + RegisterViewModels(); + } + + private void RegisterViews() + { + Locator.CurrentMutable.Register(() => new DummyPage(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new MasterCell(), typeof(IViewFor)); + + // Detail pages + Locator.CurrentMutable.Register(() => new NavigablePage(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new NumberStreamPage(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new LetterStreamPage(), typeof(IViewFor)); + } + + public MainViewModel CreateMainViewModel() + { + // In a typical routing example the IScreen implementation would be this bootstrapper class. + // However, a MasterDetailPage is designed to at the root. So, we assign the master-detail + // view model to play the part of IScreen, instead. + var viewModel = new MainViewModel(); + + return viewModel; + } + + private void RegisterViewModels() + { + // Here, we use contracts to distinguish which routable view model we want to instantiate. + // This helps us avoid a manual cast to IRoutableViewModel when calling Router.Navigate.Execute(...) + Locator.CurrentMutable.Register(() => new NavigableViewModel(), typeof(IRoutableViewModel), typeof(NavigableViewModel).FullName); + Locator.CurrentMutable.Register(() => new NumberStreamViewModel(), typeof(IRoutableViewModel), typeof(NumberStreamViewModel).FullName); + Locator.CurrentMutable.Register(() => new LetterStreamViewModel(), typeof(IRoutableViewModel), typeof(LetterStreamViewModel).FullName); + } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml b/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml deleted file mode 100644 index 1211d4d5fe..0000000000 --- a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml.cs deleted file mode 100644 index b9063db795..0000000000 --- a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCell.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reactive.Disposables; -using ReactiveUI; -using ReactiveUI.XamForms; -using Xamarin.Forms; - -namespace MasterDetail -{ - public partial class CustomCell : ReactiveViewCell - { - public CustomCell() - { - InitializeComponent(); - - this.WhenActivated( - disposables => - { - this - .OneWayBind(ViewModel, vm => vm.Title, v => v.TitleLabel.Text) - .DisposeWith(disposables); - this - .OneWayBind(ViewModel, vm => vm.IconSource, v => v.IconImage.Source, x => ImageSource.FromFile(x)) - .DisposeWith(disposables); - }); - } - } -} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCellViewModel.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCellViewModel.cs deleted file mode 100644 index 973e9d96e4..0000000000 --- a/samples/xamarin-forms/MasterDetail/MasterDetail/CustomCellViewModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using ReactiveUI; - -namespace MasterDetail -{ - public class CustomCellViewModel : ReactiveObject - { - public CustomCellViewModel(MyModel model) - { - Model = model; - } - - public MyModel Model { get; } - - public string Title => Model.Title; - - public string IconSource => Model.IconSource; - } -} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml new file mode 100644 index 0000000000..8f1bc3cf47 --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml.cs new file mode 100644 index 0000000000..ac6096ce2f --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamPage.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using ReactiveUI; +using ReactiveUI.XamForms; +using Xamarin.Forms.Xaml; + +namespace MasterDetail +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class LetterStreamPage : ReactiveContentPage + { + public LetterStreamPage() + { + InitializeComponent(); + + this.WhenActivated( + disposables => + { + this + .OneWayBind(ViewModel, vm => vm.CurrentLetter, v => v.LetterLabel.Text) + .DisposeWith(disposables); + }); + } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamViewModel.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamViewModel.cs new file mode 100644 index 0000000000..01f551bc79 --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/LetterStreamViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Reactive.Linq; +using ReactiveUI; + +namespace MasterDetail +{ + public class LetterStreamViewModel : ReactiveObject, IRoutableViewModel + { + private ObservableAsPropertyHelper _currentLetter; + + public LetterStreamViewModel() + { + _currentLetter = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Scan(64, (acc, current) => acc + 1) + .Select(x => (char)x) + .Take(26) + .ToProperty(this, x => x.CurrentLetter, scheduler: RxApp.MainThreadScheduler); + } + + public char CurrentLetter => _currentLetter.Value; + + public string UrlPathSegment => "Letter Stream Page"; + + public IScreen HostScreen { get; } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/MyDetailPage.xaml b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml similarity index 73% rename from samples/xamarin-forms/MasterDetail/MasterDetail/MyDetailPage.xaml rename to samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml index deafee092e..2993492b6b 100644 --- a/samples/xamarin-forms/MasterDetail/MasterDetail/MyDetailPage.xaml +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml @@ -3,12 +3,13 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms" xmlns:local="clr-namespace:MasterDetail" - x:Class="MasterDetail.MyDetailPage" - x:TypeArguments="local:MyDetailViewModel"> + x:Class="MasterDetail.NavigablePage" + x:TypeArguments="local:NavigableViewModel"> - diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml.cs new file mode 100644 index 0000000000..b2cc9375bd --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigablePage.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Reactive.Disposables; +using ReactiveUI; +using ReactiveUI.XamForms; +using Xamarin.Forms.Xaml; + +namespace MasterDetail +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class NavigablePage : ReactiveContentPage + { + public NavigablePage() + { + InitializeComponent(); + + this.WhenActivated( + disposables => + { + this + .BindCommand(ViewModel, vm => vm.NavigateToDummyPage, v => v.NavigateButton) + .DisposeWith(disposables); + }); + } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigableViewModel.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigableViewModel.cs new file mode 100644 index 0000000000..eb56b2397b --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NavigableViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using ReactiveUI; +using Splat; + +namespace MasterDetail +{ + public class NavigableViewModel : ReactiveObject, IRoutableViewModel + { + public NavigableViewModel(IScreen hostScreen = null) + { + HostScreen = hostScreen ?? Locator.Current.GetService(); + NavigateToDummyPage = ReactiveCommand.CreateFromObservable( + () => HostScreen.Router.Navigate.Execute(new DummyViewModel()).Select(_ => Unit.Default)); + } + + public ReactiveCommand NavigateToDummyPage { get; } + + public string UrlPathSegment => "Navigable Page"; + + public IScreen HostScreen { get; } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml new file mode 100644 index 0000000000..efef883ed2 --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml.cs new file mode 100644 index 0000000000..b279ba28be --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamPage.xaml.cs @@ -0,0 +1,29 @@ +using System; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using ReactiveUI; +using ReactiveUI.XamForms; +using Xamarin.Forms.Xaml; + +namespace MasterDetail +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class NumberStreamPage : ReactiveContentPage + { + public NumberStreamPage() + { + InitializeComponent(); + + this.WhenActivated( + disposables => + { + this + .ViewModel + .NumberStream + .ObserveOn(RxApp.MainThreadScheduler) + .BindTo(this, x => x.NumberLabel.Text) + .DisposeWith(disposables); + }); + } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamViewModel.cs b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamViewModel.cs new file mode 100644 index 0000000000..67e3f406cf --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DetailPages/NumberStreamViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Reactive.Linq; +using ReactiveUI; + +namespace MasterDetail +{ + public class NumberStreamViewModel : ReactiveObject, IRoutableViewModel + { + public NumberStreamViewModel() + { + NumberStream = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Select(x => x.ToString()); + } + + public IObservable NumberStream { get; } + + public string UrlPathSegment => "Number Stream Page"; + + public IScreen HostScreen { get; } + } +} diff --git a/samples/xamarin-forms/MasterDetail/MasterDetail/DummyPage.xaml b/samples/xamarin-forms/MasterDetail/MasterDetail/DummyPage.xaml new file mode 100644 index 0000000000..05f6528979 --- /dev/null +++ b/samples/xamarin-forms/MasterDetail/MasterDetail/DummyPage.xaml @@ -0,0 +1,23 @@ + + + + +