Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding MAUI Navigation tests #3119

Merged
merged 6 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_namespace_declarations = file_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
Expand Down
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute;
System.Runtime.CompilerServices.IsExternalInit;
</PolySharpIncludeGeneratedTypes>
<WarningsAsErrors>$(WarningsAsErrors);IDE0003</WarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<PropertyGroup>
Expand Down
7 changes: 5 additions & 2 deletions e2e/Maui/MauiModule/Views/ViewB.xaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
prism:DynamicTab.Title="Tab B"
prism:DynamicTab.IconImageSource="profile.png"
x:Class="MauiModule.Views.ViewB"
Title="{Binding Title}"
IconImageSource="profile.png"
IconImageSource="home.png"
BackgroundColor="White">
<Grid RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
Expand Down
31 changes: 25 additions & 6 deletions e2e/Maui/PrismMauiDemo/ViewModels/RootPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace PrismMauiDemo.ViewModels;
using MauiModule.ViewModels;

namespace PrismMauiDemo.ViewModels;

public class RootPageViewModel
{
Expand All @@ -7,14 +9,31 @@ public class RootPageViewModel
public RootPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateCommand = new DelegateCommand<string>(OnNavigateCommandExecuted);
NavigateCommand = new AsyncDelegateCommand<string>(OnNavigateCommandExecuted);
}

public DelegateCommand<string> NavigateCommand { get; }
public AsyncDelegateCommand<string> NavigateCommand { get; }

private void OnNavigateCommandExecuted(string uri)
private async Task OnNavigateCommandExecuted(string uri)
{
_navigationService.NavigateAsync(uri)
.OnNavigationError(ex => Console.WriteLine(ex));
if (uri == "TabbedPage")
{
var result = await _navigationService.CreateBuilder()
.AddTabbedSegment(s => s.CreateTab<ViewAViewModel>()
.CreateTab(t => t.AddNavigationPage().AddSegment<ViewBViewModel>())
.CreateTab("ViewC")
.CreateTab("ViewD"))
.NavigateAsync();
if (result.Exception is not null)
{
Console.WriteLine(result.Exception);
System.Diagnostics.Debugger.Break();
}
}
else
{
_navigationService.NavigateAsync(uri)
.OnNavigationError(ex => Console.WriteLine(ex));
}
}
}
4 changes: 2 additions & 2 deletions e2e/Maui/PrismMauiDemo/Views/RootPage.xaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PrismMauiDemo.Views.RootPage"
Expand All @@ -13,7 +13,7 @@
CommandParameter="/MainPage/NavigationPage/ViewA/ViewB/ViewC/ViewD" />
<Button Text="Tabbed Page"
Command="{Binding NavigateCommand}"
CommandParameter="/TabbedPage?createTab=ViewA&amp;createTab=ViewB&amp;createTab=ViewC&amp;createTab=ViewD" />
CommandParameter="TabbedPage" />
<Button Text="Regions"
Command="{Binding NavigateCommand}"
CommandParameter="/RegionHome/NavigationPage/ContentRegionPage" />
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<IsPackable Condition=" '$(IsFormsProject)' ">!$(DisableFormsPublish)</IsPackable>
<IsPackable Condition=" '$(IsUnoProject)' ">!$(DisableUnoPublish)</IsPackable>
<ContinuousIntegrationBuild Condition="'$(BUILD_BUILDID)' != ''">true</ContinuousIntegrationBuild>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<Choose>
Expand Down
1 change: 0 additions & 1 deletion src/Maui/Prism.Maui.Rx/GlobalNavigationObserver.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Reactive.Subjects;
using Prism.Events;

Expand Down
3 changes: 1 addition & 2 deletions src/Maui/Prism.Maui.Rx/IGlobalNavigationObserver.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;

namespace Prism.Navigation;


public interface IGlobalNavigationObserver
{
IObservable<NavigationRequestContext> NavigationRequest { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Prism.Ioc;

namespace Prism.Navigation;

public static class NavigationObserverRegistrationExtensions
Expand Down Expand Up @@ -29,4 +27,4 @@ public static PrismAppBuilder AddGlobalNavigationObserver(this PrismAppBuilder b
{
addObservable(c, c.Resolve<IGlobalNavigationObserver>().NavigationRequest);
});
}
}
71 changes: 37 additions & 34 deletions src/Maui/Prism.Maui/Behaviors/NavigationPageTabbedParentBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,59 @@
namespace Prism.Behaviors;
using System.ComponentModel;
using Prism.Xaml;

namespace Prism.Behaviors;

/// <summary>
/// Adds a behavior to use the RootPage Title and IconImageSource if they are not set on the NavigaitonPage
/// when the NavigationPage has a TabbedPage parent.
/// </summary>
public sealed class NavigationPageTabbedParentBehavior : BehaviorBase<NavigationPage>
{
private static readonly BindableProperty NavigationPageRootPageMonitorTitleProperty =
BindableProperty.CreateAttached("NavigationPageRootPageMonitorTitle", typeof(bool), typeof(NavigationPageTabbedParentBehavior), false);

private static readonly BindableProperty NavigationPageRootPageMonitorIconImageSourceProperty =
BindableProperty.CreateAttached("NavigationPageRootPageMonitorIconImageSource", typeof(bool), typeof(NavigationPageTabbedParentBehavior), false);

private static bool GetNavigationPageRootPageMonitorTitle(BindableObject bindable) =>
(bool)bindable.GetValue(NavigationPageRootPageMonitorTitleProperty);

private static void SetNavigationPageRootPageMonitorTitle(BindableObject bindable, bool monitorTitle) =>
bindable.SetValue(NavigationPageRootPageMonitorTitleProperty, monitorTitle);

private static bool GetNavigationPageRootPageMonitorIconImageSource(BindableObject bindable) =>
(bool)bindable.GetValue(NavigationPageRootPageMonitorIconImageSourceProperty);

private static void SetNavigationPageRootPageMonitorIconImageSource(BindableObject bindable, bool monitorTitle) =>
bindable.SetValue(NavigationPageRootPageMonitorIconImageSourceProperty, monitorTitle);

/// <inheritdoc />
protected override void OnAttachedTo(NavigationPage bindable)
{
base.OnAttachedTo(bindable);
SetNavigationPageRootPageMonitorTitle(bindable, !bindable.IsSet(NavigationPage.TitleProperty));
SetNavigationPageRootPageMonitorIconImageSource(bindable, !bindable.IsSet(NavigationPage.IconImageSourceProperty));
bindable.ParentChanged += OnParentChanged;
if (bindable.Parent is TabbedPage)
OnParentChanged(bindable, EventArgs.Empty);
bindable.PropertyChanged += OnRootPageSet;
}

private void OnRootPagePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is not Page page || page.Parent is not NavigationPage navigationPage)
{
return;
}

if (e.PropertyName == DynamicTab.TitleProperty.PropertyName)
{
navigationPage.Title = DynamicTab.GetTitle(page);
}

if (e.PropertyName == DynamicTab.IconImageSourceProperty.PropertyName)
{
navigationPage.IconImageSource = DynamicTab.GetIconImageSource(page);
}
}

/// <inheritdoc />
protected override void OnDetachingFrom(NavigationPage bindable)
{
base.OnDetachingFrom(bindable);
bindable.ParentChanged -= OnParentChanged;
// Sanity Check
bindable.PropertyChanged -= OnRootPageSet;
if (bindable.RootPage is not null)
{
bindable.RootPage.PropertyChanged -= OnRootPagePropertyChanged;
}
}

private void OnParentChanged(object sender, EventArgs e)
private void OnRootPageSet(object sender, PropertyChangedEventArgs e)
{
if (sender is not NavigationPage navigationPage || navigationPage.Parent is not TabbedPage)
return;

if (GetNavigationPageRootPageMonitorTitle(navigationPage))
navigationPage.SetBinding(NavigationPage.TitleProperty, new Binding("RootPage.Title", BindingMode.OneWay, source: navigationPage));

if (GetNavigationPageRootPageMonitorIconImageSource(navigationPage))
navigationPage.SetBinding(NavigationPage.IconImageSourceProperty, new Binding("RootPage.IconImageSource", BindingMode.OneWay, source: navigationPage));
if (sender is NavigationPage navigationPage && navigationPage.RootPage is not null)
{
navigationPage.PropertyChanged -= OnRootPageSet;
navigationPage.RootPage.PropertyChanged += OnRootPagePropertyChanged;
navigationPage.Title = DynamicTab.GetTitle(navigationPage.RootPage);
navigationPage.IconImageSource = DynamicTab.GetIconImageSource(navigationPage.RootPage);
}
}
}
1 change: 1 addition & 0 deletions src/Maui/Prism.Maui/Controls/PrismNavigationPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ private async void HandleBackButtonPressed(object sender, EventArgs args)
}

#if IOS
/// <inheritdoc/>
protected override async void OnDisappearing()
{
var presentationStyle = Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetModalPresentationStyle(this);
Expand Down
66 changes: 66 additions & 0 deletions src/Maui/Prism.Maui/Dialogs/DialogContainerPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,46 @@

namespace Prism.Dialogs;

/// <summary>
/// Represents a page that serves as a container for dialogs in Prism.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class DialogContainerPage : ContentPage, IDialogContainer
{
/// <summary>
/// The name of the automation ID for the dialog container page.
/// </summary>
public const string AutomationIdName = "PrismDialogModal";

/// <summary>
/// Initializes a new instance of the <see cref="DialogContainerPage"/> class.
/// </summary>
public DialogContainerPage()
{
AutomationId = AutomationIdName;
BackgroundColor = Colors.Transparent;
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.OverFullScreen);
}

/// <summary>
/// Gets the dialog view displayed in the container page.
/// </summary>
public View DialogView { get; private set; }

/// <summary>
/// Gets the command used to dismiss the dialog.
/// </summary>
public ICommand Dismiss { get; private set; }

/// <summary>
/// Configures the layout of the dialog container page.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <param name="dialogView">The dialog view to be displayed.</param>
/// <param name="hideOnBackgroundTapped">A flag indicating whether the dialog should be hidden when the background is tapped.</param>
/// <param name="dismissCommand">The command to be executed when the dialog is dismissed.</param>
/// <param name="parameters">The parameters passed to the dialog.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task ConfigureLayout(Page currentPage, View dialogView, bool hideOnBackgroundTapped, ICommand dismissCommand, IDialogParameters parameters)
{
Dismiss = dismissCommand;
Expand All @@ -34,16 +58,35 @@ public async Task ConfigureLayout(Page currentPage, View dialogView, bool hideOn
await DoPush(currentPage);
}

/// <summary>
/// Performs the push operation to display the dialog container page.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <returns>A task representing the asynchronous operation.</returns>
protected virtual async Task DoPush(Page currentPage)
{
await currentPage.Navigation.PushModalAsync(this, false);
}

/// <summary>
/// Performs the pop operation to dismiss the dialog container page.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public virtual async Task DoPop(Page currentPage)
{
await currentPage.Navigation.PopModalAsync(false);
}

/// <summary>
/// Gets the content layout for the dialog container page.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <param name="dialogView">The dialog view to be displayed.</param>
/// <param name="hideOnBackgroundTapped">A flag indicating whether the dialog should be hidden when the background is tapped.</param>
/// <param name="dismissCommand">The command to be executed when the dialog is dismissed.</param>
/// <param name="parameters">The parameters passed to the dialog.</param>
/// <returns>The content layout for the dialog container page.</returns>
protected virtual View GetContentLayout(Page currentPage, View dialogView, bool hideOnBackgroundTapped, ICommand dismissCommand, IDialogParameters parameters)
{
var overlay = new AbsoluteLayout();
Expand Down Expand Up @@ -92,6 +135,14 @@ protected virtual View GetContentLayout(Page currentPage, View dialogView, bool
return overlay;
}

/// <summary>
/// Gets the mask view for the dialog container page.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <param name="dialogView">The dialog view to be displayed.</param>
/// <param name="hideOnBackgroundTapped">A flag indicating whether the dialog should be hidden when the background is tapped.</param>
/// <param name="dismissCommand">The command to be executed when the dialog is dismissed.</param>
/// <returns>The mask view for the dialog container page.</returns>
private View GetMask(Page currentPage, View dialogView, bool hideOnBackgroundTapped, ICommand dismissCommand)
{
View mask = DialogLayout.GetMask(dialogView);
Expand Down Expand Up @@ -130,6 +181,12 @@ private View GetMask(Page currentPage, View dialogView, bool hideOnBackgroundTap
return mask;
}

/// <summary>
/// Gets the overlay style for the dialog container page.
/// </summary>
/// <param name="popupView">The popup view.</param>
/// <param name="currentPage">The current page.</param>
/// <returns>The overlay style for the dialog container page.</returns>
private Style GetOverlayStyle(View popupView, Page currentPage)
{
var style = DialogLayout.GetMaskStyle(popupView);
Expand All @@ -141,6 +198,11 @@ private Style GetOverlayStyle(View popupView, Page currentPage)
return GetStyle(currentPage);
}

/// <summary>
/// Gets the style for the specified element.
/// </summary>
/// <param name="element">The element.</param>
/// <returns>The style for the specified element.</returns>
private static Style GetStyle(Element element)
{
if (element is Page page && page.Resources.ContainsKey(DialogLayout.PopupOverlayStyle) && page.Resources[DialogLayout.PopupOverlayStyle] is Style pageStyle)
Expand All @@ -165,6 +227,10 @@ private static Style GetStyle(Element element)
return GetStyle(element.Parent);
}

/// <summary>
/// Gets the default overlay style for the dialog container page.
/// </summary>
/// <returns>The default overlay style for the dialog container page.</returns>
private static Style DefaultStyle()
{
var overlayStyle = new Style(typeof(BoxView));
Expand Down
Loading
Loading