Skip to content
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
5 changes: 5 additions & 0 deletions src/SharedMauiCoreLibrary/Interfaces/IShellNavigator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ namespace AndreasReitberger.Shared.Core.Interfaces
{
public interface IShellNavigator
{
#region Instance
public static IShellNavigator? Instance { get; private set; }
#endregion

#region Properties
public string CurrentRoute { get; }
public string PreviousRoute { get; set; }
Expand All @@ -18,6 +22,7 @@ public interface IShellNavigator
public Task<bool> GoToRootAsync(string target, Dictionary<string, object>? parameters = null, bool? flyoutIsPresented = null, int delay = -1, bool animate = false, string rootPrefix = "///");
public Task<bool> GoBackAsync(Dictionary<string, object>? parameters = null, bool? flyoutIsPresented = null, int delay = -1, bool animate = false, bool confirm = false, Func<Task<bool>>? confirmFunction = null);
public Task<bool> DisplayAlertAsync(string title, string message, string ok, string? cancel = null);
public Task<string?> DisplayActionSheetAsync(string title, string cancel, string? destruction = null, params string[] buttons);
public Task<string?> DisplayPromptAsync(string title, string message, string ok, string cancel = "Cancel", string? placeholder = null, int maxLength = -1, Keyboard? keyboard = null, string? initialValue = null);
bool IsCurrentPathRoot();
public void SubscribeNavigated();
Expand Down
140 changes: 128 additions & 12 deletions src/SharedMauiCoreLibrary/Models/NavigationManager/ShellNavigator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,8 @@ public static IShellNavigator Instance
#region Constructor
public ShellNavigator()
{
Dispatcher = DispatcherProvider.Current.GetForCurrentThread();
Shell.Current?.Navigated += (a, b) =>
{
string msg = $"Navigation: From '{b.Previous?.Location}' to '{b.Current?.Location}'. Source = '{b.Source}'";
OnNavigationDone(new NavigationDoneEventArgs()
{
NavigatedFrom = b.Previous?.Location,
NavigatedTo = b.Current?.Location,
Source = b.Source,
});

};
Dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
SubscribeNavigated();
}

public ShellNavigator(string rootPage) : this()
Expand All @@ -88,6 +78,13 @@ public ShellNavigator(string rootPage, IDispatcher dispatcher) : this(rootPage)
}
#endregion

#region Dtor
~ShellNavigator()
{
UnsubscribeNavigated();
}
#endregion

#region Methods

/// <summary>
Expand Down Expand Up @@ -143,7 +140,16 @@ async Task<bool> navigationAction()
bool succeeded = false;
if (Dispatcher is not null)
{
#if DEBUG
// Show when a dispatching was done or not
Debug.WriteLine($"{nameof(ShellNavigator)}: Dispatcher => '{(Dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
if (Dispatcher.IsDispatchRequired)
{
// Just for a breaking point
}
#endif
succeeded = Dispatcher.IsDispatchRequired ? await Dispatcher.DispatchAsync(navigationAction) : await navigationAction();

}
else
{
Expand Down Expand Up @@ -183,6 +189,22 @@ public async Task<bool> GoBackAsync(Dictionary<string, object>? parameters = nul
return false;
}

/// <summary>
/// Displays an alert dialog UIThread safe with the specified title, message, and buttons, and returns a value indicating
/// which button was pressed.
/// </summary>
/// <remarks>If <paramref name="cancel"/> is <see langword="null"/>, the alert displays only the
/// confirmation button and always returns <see langword="true"/> when dismissed. If <paramref name="cancel"/>
/// is specified, the alert displays both buttons and returns <see langword="true"/> if the confirmation button
/// is pressed, or <see langword="false"/> if the cancel button is pressed. The method returns <see
/// langword="false"/> if an error occurs or if the alert cannot be displayed.</remarks>
/// <param name="title">The title text to display at the top of the alert dialog.</param>
/// <param name="message">The message content to display in the alert dialog.</param>
/// <param name="ok">The text for the confirmation button. Selecting this button returns <see langword="true"/>.</param>
/// <param name="cancel">The text for the cancel button. If specified, the alert will display both confirmation and cancel buttons;
/// otherwise, only the confirmation button is shown. Selecting this button returns <see langword="false"/>.</param>
/// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the
/// confirmation button was pressed; otherwise, <see langword="false"/>.</returns>
public async Task<bool> DisplayAlertAsync(string title, string message, string ok, string? cancel = null)
{
try
Expand Down Expand Up @@ -215,6 +237,14 @@ await Shell.Current
bool succeeded = false;
if (Dispatcher is not null)
{
#if DEBUG
// Show when a dispatching was done or not
Debug.WriteLine($"{nameof(ShellNavigator)}: Dispatcher => '{(Dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
if (Dispatcher.IsDispatchRequired)
{
// Just for a breaking point
}
#endif
succeeded = Dispatcher.IsDispatchRequired ? await Dispatcher.DispatchAsync(action) : await action();
}
else
Expand All @@ -231,6 +261,84 @@ await Shell.Current
}
}

/// <summary>
/// Displays an action sheet UIThread safe to the user with a set of options and returns the selected option asynchronously.
/// </summary>
/// <remarks>If both cancel and destruction are null or empty, only the provided options are
/// shown. The method may return null if the action sheet is dismissed or if an error occurs. This method must
/// be called from a context where UI interaction is permitted.</remarks>
/// <param name="title">The title to display at the top of the action sheet. Can be null or empty for no title.</param>
/// <param name="cancel">The text for the cancel button. If null or empty, no cancel button is shown.</param>
/// <param name="destruction">The text for the destruction button, typically used for a destructive action. If null or empty, no
/// destruction button is shown.</param>
/// <param name="buttons">An array of button labels representing the available options for the user to select. Cannot be null; may be
/// empty for no options.</param>
/// <returns>A task that represents the asynchronous operation. The result is the label of the button selected by the
/// user, or null if the action sheet was dismissed without selection.</returns>
public async Task<string?> DisplayActionSheetAsync(string title, string cancel, string? destruction = null, params string[] buttons)
{
try
{
string? prompt = null;
async Task<string?> action()
{
try
{
if (Shell.Current is null) return null;
return await Shell.Current
.DisplayActionSheetAsync(title, cancel, destruction, buttons)
.ConfigureAwait(false);
}
catch (Exception exc)
{
OnError(new ShellErrorEventArgs(exc));
return null;
}
}
if (Dispatcher is not null)
{
#if DEBUG
// Show when a dispatching was done or not
Debug.WriteLine($"{nameof(ShellNavigator)}: Dispatcher => '{(Dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
if (Dispatcher.IsDispatchRequired)
{
// Just for a breaking point
}
#endif
prompt = Dispatcher.IsDispatchRequired ? await Dispatcher.DispatchAsync(action) : await action();
}
else
{
prompt = await action();
}
return prompt;
}
catch (Exception exc)
{
// Log error
OnError(new ShellErrorEventArgs(exc));
return null;
}
}

/// <summary>
/// Displays an asynchronous prompt dialog UIThread safe to the user and returns the entered text, or null if the prompt is
/// canceled.
/// </summary>
/// <remarks>If an error occurs while displaying the prompt, the method returns null and triggers
/// an error event. The prompt dialog is dispatched to the UI thread if required. The method supports
/// customization of the prompt's appearance and behavior through its parameters.</remarks>
/// <param name="title">The title text displayed at the top of the prompt dialog.</param>
/// <param name="message">The message or question presented to the user within the prompt dialog.</param>
/// <param name="ok">The text for the confirmation button that submits the entered value.</param>
/// <param name="cancel">The text for the cancel button that dismisses the prompt without submitting a value. Defaults to "Cancel".</param>
/// <param name="placeholder">The placeholder text shown in the input field when it is empty. If null, no placeholder is displayed.</param>
/// <param name="maxLength">The maximum number of characters allowed in the input field. Specify -1 for no limit.</param>
/// <param name="keyboard">The keyboard type to use for the input field, such as numeric or email. If null, the default keyboard is
/// used.</param>
/// <param name="initialValue">The initial value displayed in the input field when the prompt appears. If null, the field is empty.</param>
/// <returns>A task that represents the asynchronous operation. The task result is the text entered by the user, or null
/// if the prompt is canceled or an error occurs.</returns>
public async Task<string?> DisplayPromptAsync(string title, string message, string ok, string cancel = "Cancel", string? placeholder = null, int maxLength = -1, Keyboard? keyboard = null, string? initialValue = null)
{
try
Expand All @@ -253,6 +361,14 @@ await Shell.Current
}
if (Dispatcher is not null)
{
#if DEBUG
// Show when a dispatching was done or not
Debug.WriteLine($"{nameof(ShellNavigator)}: Dispatcher => '{(Dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
if (Dispatcher.IsDispatchRequired)
{
// Just for a breaking point
}
#endif
prompt = Dispatcher.IsDispatchRequired ? await Dispatcher.DispatchAsync(action) : await action();
}
else
Expand Down
Loading