Skip to content

Commit

Permalink
Merge pull request #74 from lucacivale/#73
Browse files Browse the repository at this point in the history
Add asynchronous navigation methods
  • Loading branch information
lucacivale authored Mar 10, 2025
2 parents 8111c73 + 91f2b30 commit 1c77c89
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,30 @@ private void OpenNonModalShowcase()
[RelayCommand]
private void OpenShowcasePageAsBottomSheet()
{
_bottomSheetNavigationService.NavigateTo("Showcase");
_bottomSheetNavigationService.NavigateToAsync("Showcase").SafeFireAndForget();
}

[RelayCommand]
private void OpenCustomHeaderShowcaseViewAsBottomSheet()
{
_bottomSheetNavigationService.NavigateTo<CustomHeaderShowcaseViewModel>("CustomHeaderShowcase");
_bottomSheetNavigationService.NavigateToAsync<CustomHeaderShowcaseViewModel>("CustomHeaderShowcase").SafeFireAndForget();
}

[RelayCommand]
private void OpenSomeBottomSheet()
{
_bottomSheetNavigationService.NavigateTo("SomeBottomSheet");
_bottomSheetNavigationService.NavigateToAsync("SomeBottomSheet").SafeFireAndForget();
}

[RelayCommand]
private void CloseAllOpenSheets()
{
_bottomSheetNavigationService.ClearBottomSheetStack();
_bottomSheetNavigationService.ClearBottomSheetStackAsync().SafeFireAndForget();
}

[RelayCommand]
private void CloseCurrentSheet()
{
_bottomSheetNavigationService.GoBack();
_bottomSheetNavigationService.GoBackAsync().SafeFireAndForget();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ namespace Plugin.Maui.BottomSheet.Handlers;
/// </summary>
internal sealed partial class BottomSheetHandler : ViewHandler<IBottomSheet, MauiBottomSheet>
{
internal partial Task OpenAsync()
{
return PlatformView.OpenAsync();
}

internal partial Task CloseAsync()
{
return PlatformView.CloseAsync();
}

/// <inheritdoc/>
protected override void ConnectHandler(MauiBottomSheet platformView)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,16 @@ public override void SetVirtualView(IView view)
base.SetVirtualView(view);
IsConnecting = false;
}

/// <summary>
/// Open bottom sheet.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
internal partial Task OpenAsync();

/// <summary>
/// Close bottom sheet.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
internal partial Task CloseAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ namespace Plugin.Maui.BottomSheet.Handlers;
/// </summary>
internal sealed partial class BottomSheetHandler : ViewHandler<IBottomSheet, Platform.MaciOS.MauiBottomSheet>
{
internal partial Task OpenAsync()
{
return PlatformView.OpenAsync();
}

internal partial Task CloseAsync()
{
return PlatformView.CloseAsync();
}

/// <inheritdoc/>
protected override void ConnectHandler(Platform.MaciOS.MauiBottomSheet platformView)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ namespace Plugin.Maui.BottomSheet.Handlers;
/// </summary>
internal sealed partial class BottomSheetHandler : ViewHandler<IBottomSheet, object>
{
internal partial Task OpenAsync()
{
throw new NotImplementedException();
}

internal partial Task CloseAsync()
{
throw new NotImplementedException();
}

/// <inheritdoc/>
protected override object CreatePlatformView()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,11 @@ public BottomSheetNavigationService(IServiceProvider serviceProvider)
public IServiceProvider ServiceProvider { get; }

/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Validated.")]
public void NavigateTo(IBottomSheet bottomSheet, object? viewModel = null, IBottomSheetNavigationParameters? parameters = null, Action<IBottomSheet>? configure = null)
{
var page = Application.Current?.Windows.LastOrDefault()?.Page ?? throw new InvalidOperationException("Application.Current?.Windows.LastOrDefault()?.Page cannot be null.");
var mauiContext = page.Handler?.MauiContext ?? throw new InvalidOperationException("Page.Handler?.MauiContext cannot be null.");

Dispatch(() =>
{
bottomSheet.Handler = new Handlers.BottomSheetHandler(mauiContext);
bottomSheet.Parent = page;
bottomSheet.Closed += OnClose;

if (viewModel is not null)
{
bottomSheet.BindingContext = viewModel;
}

configure?.Invoke(bottomSheet);
PrepareBottomSheetForNavigation(bottomSheet, viewModel, configure);

bottomSheet.IsOpen = true;

Expand All @@ -50,14 +37,36 @@ public void NavigateTo(IBottomSheet bottomSheet, object? viewModel = null, IBott
}

/// <inheritdoc/>
public void GoBack(IBottomSheetNavigationParameters? parameters = null)
public Task NavigateToAsync(IBottomSheet bottomSheet, object? viewModel = null, IBottomSheetNavigationParameters? parameters = null, Action<IBottomSheet>? configure = null)
{
Dispatch(() =>
return DispatchAsync(async () =>
{
DoGoBack(parameters);
PrepareBottomSheetForNavigation(bottomSheet, viewModel, configure);

if (bottomSheet.Handler is Handlers.BottomSheetHandler bottomSheetHandler)
{
await bottomSheetHandler.OpenAsync().ConfigureAwait(true);
_bottomSheetStack.Add(bottomSheet);
}

if (bottomSheet.BindingContext is IQueryAttributable queryAttributable)
{
ApplyAttributes(queryAttributable, parameters);
}
});
}

/// <inheritdoc/>
public void GoBack(IBottomSheetNavigationParameters? parameters = null)
{
Dispatch(() => DoGoBack(parameters));
}

public Task GoBackAsync(IBottomSheetNavigationParameters? parameters = null)
{
return DispatchAsync(() => DoGoBackAsync(parameters));
}

/// <inheritdoc/>
public void ClearBottomSheetStack()
{
Expand All @@ -70,14 +79,60 @@ public void ClearBottomSheetStack()
});
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Validated.")]
private void Dispatch(Action action)
public Task ClearBottomSheetStackAsync()
{
var dispatcher = _bottomSheetStack is { IsEmpty: false, Current: BindableObject bindable } ? bindable.Dispatcher : Application.Current?.Windows.LastOrDefault()?.Page?.Dispatcher;
return DispatchAsync(async () =>
{
while (!_bottomSheetStack.IsEmpty)
{
await DoGoBackAsync().ConfigureAwait(true);
}
});
}

ArgumentNullException.ThrowIfNull(dispatcher);
private static void ApplyAttributes(IQueryAttributable? attributable, IBottomSheetNavigationParameters? parameters)
{
if (parameters is not null
&& attributable is not null)
{
attributable.ApplyQueryAttributes(parameters);
}
}

private void ApplyGoBackParameters(IBottomSheet bottomSheet, IBottomSheetNavigationParameters? parameters)
{
var parent = bottomSheet.Parent;
IQueryAttributable? queryAttributable = null;

if (_bottomSheetStack.IsEmpty
&& parent.BindingContext is IQueryAttributable parentBindingContext)
{
queryAttributable = parentBindingContext;
}
else if (_bottomSheetStack is { IsEmpty: false, Current.BindingContext: IQueryAttributable sheetBindingContext })
{
queryAttributable = sheetBindingContext;
}

ApplyAttributes(queryAttributable, parameters);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Improved readability.")]
private void PrepareBottomSheetForNavigation(IBottomSheet bottomSheet, object? viewModel = null, Action<IBottomSheet>? configure = null)
{
var page = Application.Current?.Windows.LastOrDefault()?.Page ?? throw new InvalidOperationException("Application.Current?.Windows.LastOrDefault()?.Page cannot be null.");
var mauiContext = page.Handler?.MauiContext ?? throw new InvalidOperationException("Page.Handler?.MauiContext cannot be null.");

bottomSheet.Handler = new Handlers.BottomSheetHandler(mauiContext);
bottomSheet.Parent = page;
bottomSheet.Closed += OnClose;

if (viewModel is not null)
{
bottomSheet.BindingContext = viewModel;
}

dispatcher.Dispatch(action);
configure?.Invoke(bottomSheet);
}

private void DoGoBack(IBottomSheetNavigationParameters? parameters = null)
Expand All @@ -87,34 +142,58 @@ private void DoGoBack(IBottomSheetNavigationParameters? parameters = null)
return;
}

var parent = _bottomSheetStack.Current.Parent;

_bottomSheetStack.Current.Closed -= OnClose;
_bottomSheetStack.Current.IsOpen = false;
_bottomSheetStack.Current.Handler?.DisconnectHandler();
_bottomSheetStack.Remove();
var bottomSheet = _bottomSheetStack.Remove();

IQueryAttributable? queryAttributable = null;
ApplyGoBackParameters(bottomSheet, parameters);
}

if (_bottomSheetStack.IsEmpty
&& parent.BindingContext is IQueryAttributable parentBindingContext)
{
queryAttributable = parentBindingContext;
}
else if (_bottomSheetStack is { IsEmpty: false, Current.BindingContext: IQueryAttributable sheetBindingContext })
private async Task DoGoBackAsync(IBottomSheetNavigationParameters? parameters = null)
{
if (_bottomSheetStack.IsEmpty)
{
queryAttributable = sheetBindingContext;
return;
}

if (parameters is not null
&& queryAttributable is not null)
_bottomSheetStack.Current.Closed -= OnClose;

if (_bottomSheetStack.Current.Handler is Handlers.BottomSheetHandler bottomSheetHandler)
{
queryAttributable.ApplyQueryAttributes(parameters);
await bottomSheetHandler.CloseAsync().ConfigureAwait(true);
}

_bottomSheetStack.Current.Handler?.DisconnectHandler();
var bottomSheet = _bottomSheetStack.Remove();

ApplyGoBackParameters(bottomSheet, parameters);
}

private void OnClose(object? sender, EventArgs e)
{
GoBack();
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Validated.")]
private IDispatcher GetDispatcher()
{
var dispatcher = _bottomSheetStack is { IsEmpty: false, Current: BindableObject bindable } ? bindable.Dispatcher : Application.Current?.Windows.LastOrDefault()?.Page?.Dispatcher;

ArgumentNullException.ThrowIfNull(dispatcher);

return dispatcher;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Validated.")]
private void Dispatch(Action action)
{
GetDispatcher().Dispatch(action);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1826:Use property instead of Linq Enumerable method", Justification = "Validated.")]
private Task DispatchAsync(Func<Task> action)
{
return GetDispatcher().DispatchAsync(action);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,46 @@ public interface IBottomSheetNavigationService
/// <param name="viewModel">BindingContext of <see cref="BottomSheet"/>.</param>
/// <param name="parameters">Navigation parameters.</param>
/// <param name="configure">Action to modify the <see cref="BottomSheet"/>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous navigation operation.</returns>
Task NavigateToAsync(IBottomSheet bottomSheet, object? viewModel = null, IBottomSheetNavigationParameters? parameters = null, Action<IBottomSheet>? configure = null);

/// <summary>
/// Open a <see cref="BottomSheet"/>.
/// If <paramref name="viewModel"/> isn't null it'll be assigned to BindingContext.
/// If <paramref name="viewModel"/> implements <see cref="IQueryAttributable"/> <paramref name="parameters"/> will be applied on navigation.
/// </summary>
/// <param name="bottomSheet"><see cref="BottomSheet"/> to be opened.</param>
/// <param name="viewModel">BindingContext of <see cref="BottomSheet"/>.</param>
/// <param name="parameters">Navigation parameters.</param>
/// <param name="configure">Action to modify the <see cref="BottomSheet"/>.</param>
[Obsolete("Use NavigateToAsync instead.")]

Check warning on line 39 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)

Check warning on line 39 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)
void NavigateTo(IBottomSheet bottomSheet, object? viewModel = null, IBottomSheetNavigationParameters? parameters = null, Action<IBottomSheet>? configure = null);

/// <summary>
/// Close current <see cref="BottomSheet"/>.
/// If BindingContext implements <see cref="IQueryAttributable"/> <paramref name="parameters"/> will be applied on navigation.
/// </summary>
/// <param name="parameters">Navigation parameters.</param>
[Obsolete("Use GoBackAsync instead.")]

Check warning on line 47 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)

Check warning on line 47 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)
void GoBack(IBottomSheetNavigationParameters? parameters = null);

/// <summary>
/// Close current <see cref="BottomSheet"/>.
/// If BindingContext implements <see cref="IQueryAttributable"/> <paramref name="parameters"/> will be applied on navigation.
/// </summary>
/// <param name="parameters">Navigation parameters.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task GoBackAsync(IBottomSheetNavigationParameters? parameters = null);

/// <summary>
/// Close all BottomSheets.
/// </summary>
[Obsolete("Use ClearBottomSheetAsync instead.")]

Check warning on line 61 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)

Check warning on line 61 in src/Plugin.Maui.BottomSheet/Plugin.Maui.BottomSheet/Navigation/IBottomSheetNavigationService.cs

View workflow job for this annotation

GitHub Actions / Build-Plugin_Maui_BottomSheet

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)
void ClearBottomSheetStack();

/// <summary>
/// Close all BottomSheets.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task ClearBottomSheetStackAsync();
}
Loading

0 comments on commit 1c77c89

Please sign in to comment.