diff --git a/src/SharedMauiCoreLibrary/Interfaces/IShellNavigator.cs b/src/SharedMauiCoreLibrary/Interfaces/IShellNavigator.cs index b214eaf..0eeb24f 100644 --- a/src/SharedMauiCoreLibrary/Interfaces/IShellNavigator.cs +++ b/src/SharedMauiCoreLibrary/Interfaces/IShellNavigator.cs @@ -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; } @@ -18,6 +22,7 @@ public interface IShellNavigator public Task GoToRootAsync(string target, Dictionary? parameters = null, bool? flyoutIsPresented = null, int delay = -1, bool animate = false, string rootPrefix = "///"); public Task GoBackAsync(Dictionary? parameters = null, bool? flyoutIsPresented = null, int delay = -1, bool animate = false, bool confirm = false, Func>? confirmFunction = null); public Task DisplayAlertAsync(string title, string message, string ok, string? cancel = null); + public Task DisplayActionSheetAsync(string title, string cancel, string? destruction = null, params string[] buttons); public Task 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(); diff --git a/src/SharedMauiCoreLibrary/Models/NavigationManager/ShellNavigator.cs b/src/SharedMauiCoreLibrary/Models/NavigationManager/ShellNavigator.cs index 1e60a0f..2756161 100644 --- a/src/SharedMauiCoreLibrary/Models/NavigationManager/ShellNavigator.cs +++ b/src/SharedMauiCoreLibrary/Models/NavigationManager/ShellNavigator.cs @@ -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() @@ -88,6 +78,13 @@ public ShellNavigator(string rootPage, IDispatcher dispatcher) : this(rootPage) } #endregion + #region Dtor + ~ShellNavigator() + { + UnsubscribeNavigated(); + } + #endregion + #region Methods /// @@ -143,7 +140,16 @@ async Task 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 { @@ -183,6 +189,22 @@ public async Task GoBackAsync(Dictionary? parameters = nul return false; } + /// + /// Displays an alert dialog UIThread safe with the specified title, message, and buttons, and returns a value indicating + /// which button was pressed. + /// + /// If is , the alert displays only the + /// confirmation button and always returns when dismissed. If + /// is specified, the alert displays both buttons and returns if the confirmation button + /// is pressed, or if the cancel button is pressed. The method returns if an error occurs or if the alert cannot be displayed. + /// The title text to display at the top of the alert dialog. + /// The message content to display in the alert dialog. + /// The text for the confirmation button. Selecting this button returns . + /// 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 . + /// A task that represents the asynchronous operation. The task result is if the + /// confirmation button was pressed; otherwise, . public async Task DisplayAlertAsync(string title, string message, string ok, string? cancel = null) { try @@ -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 @@ -231,6 +261,84 @@ await Shell.Current } } + /// + /// Displays an action sheet UIThread safe to the user with a set of options and returns the selected option asynchronously. + /// + /// 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. + /// The title to display at the top of the action sheet. Can be null or empty for no title. + /// The text for the cancel button. If null or empty, no cancel button is shown. + /// The text for the destruction button, typically used for a destructive action. If null or empty, no + /// destruction button is shown. + /// An array of button labels representing the available options for the user to select. Cannot be null; may be + /// empty for no options. + /// 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. + public async Task DisplayActionSheetAsync(string title, string cancel, string? destruction = null, params string[] buttons) + { + try + { + string? prompt = null; + async Task 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; + } + } + + /// + /// Displays an asynchronous prompt dialog UIThread safe to the user and returns the entered text, or null if the prompt is + /// canceled. + /// + /// 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. + /// The title text displayed at the top of the prompt dialog. + /// The message or question presented to the user within the prompt dialog. + /// The text for the confirmation button that submits the entered value. + /// The text for the cancel button that dismisses the prompt without submitting a value. Defaults to "Cancel". + /// The placeholder text shown in the input field when it is empty. If null, no placeholder is displayed. + /// The maximum number of characters allowed in the input field. Specify -1 for no limit. + /// The keyboard type to use for the input field, such as numeric or email. If null, the default keyboard is + /// used. + /// The initial value displayed in the input field when the prompt appears. If null, the field is empty. + /// 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. public async Task DisplayPromptAsync(string title, string message, string ok, string cancel = "Cancel", string? placeholder = null, int maxLength = -1, Keyboard? keyboard = null, string? initialValue = null) { try @@ -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