diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index e3585ebcda..5fd5935ac9 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -6,7 +6,7 @@ 20.2.0 - 7.0.0 + 7.1.0 1.61.38 diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs index 572894e9c8..a18a5047ba 100644 --- a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs @@ -11,8 +11,6 @@ #else using ReactiveUI.Maui.Internal; #endif -using Splat; - #if REACTIVE_SHIM namespace ReactiveUI.Reactive; #else @@ -26,7 +24,7 @@ namespace ReactiveUI; /// [RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewLocator, which may be incompatible with trimming.")] [RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilation.")] -public partial class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger +public partial class RoutedViewHost : TransitioningContentControl, IActivatableView, IMauiRoutedViewHost { /// The router dependency property. public static readonly DependencyProperty RouterProperty = @@ -56,14 +54,7 @@ public RoutedViewHost() HorizontalContentAlignment = HorizontalAlignment.Stretch; VerticalContentAlignment = VerticalAlignment.Stretch; - MauiReactiveHelpers.InitializeRoutedViewHost( - (this, this.Log(), observable => ViewContractObservable = observable), - (nameof(Router), RouterProperty, () => Router), - (nameof(ViewContractObservable), ViewContractObservableProperty, () => ViewContractObservable), - () => ViewContract, - contract => _viewContract = contract, - ResolveViewForViewModel, - _subscriptions); + MauiReactiveHelpers.InitializeRoutedViewHost(this, RouterProperty, ViewContractObservableProperty, _subscriptions, ResolveViewForViewModel); } /// Gets or sets the of the view model stack. @@ -107,23 +98,26 @@ public string? ViewContract /// public IViewLocator? ViewLocator { get; set; } + /// + void IMauiRoutedViewHost.SetObservedViewContract(string? contract) => _viewContract = contract; + /// Resolves and hosts the view for the supplied view model/contract pair. - /// The view model and contract to resolve a view for. + /// The view model and contract to resolve a view for. [RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which may be incompatible with trimming.")] [RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, " + "or generic constraints), trimming can't validate that the requirements of those annotations are met.")] - private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) route) { - if (x.viewModel is null) + if (route.viewModel is null) { Content = DefaultContent; return; } var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = (viewLocator.ResolveView(x.viewModel, x.contract) ?? viewLocator.ResolveView(x.viewModel)) - ?? throw new InvalidOperationException($"Couldn't find view for '{x.viewModel}'."); - view.ViewModel = x.viewModel; + var view = (viewLocator.ResolveView(route.viewModel, route.contract) ?? viewLocator.ResolveView(route.viewModel)) + ?? throw new InvalidOperationException($"Couldn't find view for '{route.viewModel}'."); + view.ViewModel = route.viewModel; Content = view; } } diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs index ddd0d83a68..bf158a711b 100644 --- a/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs @@ -11,8 +11,6 @@ #else using ReactiveUI.Maui.Internal; #endif -using Splat; - #if REACTIVE_SHIM namespace ReactiveUI.Reactive; #else @@ -27,7 +25,7 @@ namespace ReactiveUI; /// /// The type of the view model. Must have a public parameterless constructor and implement IRoutableViewModel. public partial class RoutedViewHost< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TransitioningContentControl, IActivatableView, IEnableLogger + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TransitioningContentControl, IActivatableView, IMauiRoutedViewHost where TViewModel : class, IRoutableViewModel { /// The router dependency property. @@ -58,14 +56,7 @@ public RoutedViewHost() HorizontalContentAlignment = HorizontalAlignment.Stretch; VerticalContentAlignment = VerticalAlignment.Stretch; - MauiReactiveHelpers.InitializeRoutedViewHost( - (this, this.Log(), observable => ViewContractObservable = observable), - (nameof(Router), RouterProperty, () => Router), - (nameof(ViewContractObservable), ViewContractObservableProperty, () => ViewContractObservable), - () => ViewContract, - contract => _viewContract = contract, - ResolveViewForViewModel, - _subscriptions); + MauiReactiveHelpers.InitializeRoutedViewHost(this, RouterProperty, ViewContractObservableProperty, _subscriptions, ResolveViewForViewModel); } /// Gets or sets the view locator. @@ -109,14 +100,17 @@ public string? ViewContract } } + /// + void IMauiRoutedViewHost.SetObservedViewContract(string? contract) => _viewContract = contract; + /// /// Resolves and displays the view for the given view model and contract. /// This method uses the generic ViewLocator.ResolveView{TViewModel} which is AOT-safe. /// - /// Tuple containing the view model and contract. - private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + /// Tuple containing the view model and contract. + private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) route) { - if (x.viewModel is null) + if (route.viewModel is null) { Content = DefaultContent; return; @@ -125,9 +119,9 @@ private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? con var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; // Use the generic ResolveView method - this is AOT-safe! - var view = viewLocator.ResolveView(x.contract) ?? viewLocator.ResolveView() + var view = viewLocator.ResolveView(route.contract) ?? viewLocator.ResolveView() ?? throw new InvalidOperationException($"Couldn't find view for '{nameof(TViewModel)}'."); - view.ViewModel = x.viewModel as TViewModel; + view.ViewModel = route.viewModel as TViewModel; Content = view; } } diff --git a/src/ReactiveUI.Maui/Internal/IMauiRoutedViewHost.cs b/src/ReactiveUI.Maui/Internal/IMauiRoutedViewHost.cs new file mode 100644 index 0000000000..bf84c1df7b --- /dev/null +++ b/src/ReactiveUI.Maui/Internal/IMauiRoutedViewHost.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Splat; + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive.Maui.Internal; +#else +namespace ReactiveUI.Maui.Internal; +#endif + +/// Supplies the host-specific operations used to initialize a WinUI routed host. +internal interface IMauiRoutedViewHost : IEnableLogger +{ + /// Gets the router. + RoutingState Router { get; } + + /// Gets or sets the view-contract observable. + IObservable ViewContractObservable { get; set; } + + /// Gets the current view contract. + string? ViewContract { get; } + + /// Stores the latest observed view contract without replacing its source observable. + /// The observed contract. + void SetObservedViewContract(string? contract); +} diff --git a/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs b/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs index 96045b4e63..6a1f687c4f 100644 --- a/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs +++ b/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs @@ -141,29 +141,27 @@ internal static IObservable CreatePropertyValueObservable( } /// Initializes a WinUI routed host and its view-contract subscriptions. - /// The host, logger, and view-contract setter. - /// The router property metadata and accessor. - /// The view-contract observable property metadata and accessor. - /// Gets the current view contract. - /// Stores the latest view contract. - /// Resolves a routed view model and contract. + /// The routed-host type. + /// The host to initialize. + /// The host's router dependency property. + /// The host's view-contract observable dependency property. /// Collects the host subscription. - internal static void InitializeRoutedViewHost( - (FrameworkElement Source, IFullLogger Logger, Action> SetViewContractObservable) host, - (string Name, DependencyProperty Property, Func GetValue) router, - (string Name, DependencyProperty Property, Func> GetValue) viewContractObservable, - Func getViewContract, - Action setViewContract, - Action<(IRoutableViewModel? viewModel, string? contract)> resolveView, - MultipleDisposable subscriptions) + /// Resolves a routed view model and contract. + internal static void InitializeRoutedViewHost( + THost host, + DependencyProperty routerProperty, + DependencyProperty viewContractObservableProperty, + MultipleDisposable subscriptions, + Action<(IRoutableViewModel? viewModel, string? contract)> resolveView) + where THost : FrameworkElement, IMauiRoutedViewHost { - host.SetViewContractObservable(CreateViewContractObservable(host.Source, host.Logger)); + host.ViewContractObservable = CreateViewContractObservable(host, host.Log()); SubscribeRoutedViewHost( - host.Source, - router, - viewContractObservable, - getViewContract, - setViewContract, + host, + (nameof(host.Router), routerProperty, () => host.Router), + (nameof(host.ViewContractObservable), viewContractObservableProperty, () => host.ViewContractObservable), + () => host.ViewContract, + host.SetObservedViewContract, resolveView, subscriptions); } diff --git a/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj b/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj index 3df0c92aed..3ea128d3ef 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj +++ b/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj @@ -9,6 +9,7 @@ $(ReactiveUIModernTargets) Exe true + false enable enable net10.0-android;net10.0-ios Exe + true + false $(NoWarn);CS1591 diff --git a/src/tests/ReactiveUI.Routing.Tests/ReactiveUI.Routing.Tests.csproj b/src/tests/ReactiveUI.Routing.Tests/ReactiveUI.Routing.Tests.csproj index 8f581e5280..6e67acca76 100644 --- a/src/tests/ReactiveUI.Routing.Tests/ReactiveUI.Routing.Tests.csproj +++ b/src/tests/ReactiveUI.Routing.Tests/ReactiveUI.Routing.Tests.csproj @@ -9,6 +9,8 @@ $(ReactiveUITestingTargets) Exe ReactiveUI.Tests + true + false diff --git a/src/tests/ReactiveUI.Splat.Tests/ReactiveUI.Splat.Tests.csproj b/src/tests/ReactiveUI.Splat.Tests/ReactiveUI.Splat.Tests.csproj index 72ec181213..e839f5d667 100644 --- a/src/tests/ReactiveUI.Splat.Tests/ReactiveUI.Splat.Tests.csproj +++ b/src/tests/ReactiveUI.Splat.Tests/ReactiveUI.Splat.Tests.csproj @@ -2,6 +2,7 @@ $(ReactiveUITestingTargets) Exe + true false diff --git a/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj b/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj index 410e7b9135..e5921809c7 100644 --- a/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj +++ b/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj @@ -2,6 +2,8 @@ $(ReactiveUITestingTargets) Exe + true + false netstandard2.0 $(TargetFramework) diff --git a/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj b/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj index 0cb93a8579..607e00b719 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj +++ b/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj @@ -8,6 +8,8 @@ $(ReactiveUITestingTargets) Exe + true + false diff --git a/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj b/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj index 9eae3ee806..45c7aa557c 100644 --- a/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj +++ b/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj @@ -8,6 +8,8 @@ $(ReactiveUITestingUITargets) Exe + true + false diff --git a/src/tests/ReactiveUI.Wpf.Tests/ReactiveUI.Wpf.Tests.csproj b/src/tests/ReactiveUI.Wpf.Tests/ReactiveUI.Wpf.Tests.csproj index 431e00a13b..d7dcfd7f1f 100644 --- a/src/tests/ReactiveUI.Wpf.Tests/ReactiveUI.Wpf.Tests.csproj +++ b/src/tests/ReactiveUI.Wpf.Tests/ReactiveUI.Wpf.Tests.csproj @@ -9,6 +9,8 @@ $(ReactiveUITestingUITargets) Exe ReactiveUI.Tests + true + false