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

StackedNotificationsBehavior port #170

Merged
merged 16 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion components/Behaviors/samples/Behaviors.Samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Assets\ToolkitIcon.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
77 changes: 77 additions & 0 deletions components/Behaviors/samples/StackedNotificationsBehavior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: StackedNotificationsBehavior
author: vgromfeld
description: A behavior to add stacked notifications to a WinUI InfoBar control.
keywords: StackedNotificationsBehavior, Control, Layout, InfoBar, Behavior
dev_langs:
- csharp
category: Xaml
subcategory: Behaviors
discussion-id: 0
issue-id: 0
icon: Assets/StackedNotificationsBehavior.png
---

The `StackedNotificationsBehavior` allows you to provide notifications within your app using an `InfoBar` control. This is a replacement for the prior `InAppNotification` control in the Toolkit.

With the default settings, a notification will be displayed until it is dismissed by the user. Any subsequent notifications will be displayed
in the order of being sent afterwards one-by-one.

## Example

Clicking on the button multiple times will queue up multiple messages to be displayed one after another.

> [!Sample StackedNotificationsBehaviorCustomSample]

## Notification options

By default, the properties provided on the attached `InfoBar` will be used, like `ContentTemplate` or `IsIconVisible`.

However, there are a number of options available on the `Notification` class to override these. When set, these will override any defaults
or modified properties set on the parent `InfoBar` itself. They will be restored to the previously set value on the `InfoBar` after the message has been displayed.

> [!WARNING]
> Properties set on the `InfoBar` will be modified directly by the behavior with notification overrides, this means any bindings will
> be broken by that change when it is overridden or restored by the notification. Therefore, it is best to only provide constants on the
> parent `InfoBar` itself that will be consistent for all messages and set any dynamic options in the `Notification` options.

When a `Duration` is provided, if the user has their pointer over the message, it will not be dismissed. It will instead reset the time before
being dismissed once the pointer has left the active notification.

## Migrating from InAppNotification

If you previously used the `InAppNotification` component from the Windows Community Toolkit, like so:

```xml
<controls:InAppNotification x:Name="ExampleInAppNotification"/>
```

You can simply replace it with an `InfoBar` control and add the attached behavior:

```xml
<muxc:InfoBar>
<interactivity:Interaction.Behaviors>
<behaviors:StackedNotificationsBehavior x:Name="ExampleInAppNotification" />
</interactivity:Interaction.Behaviors>
</muxc:InfoBar>
```

There are some changes to the `Show` method, however a simple text based one has been provided for backwards compatibility,
otherwise it's best to construct your own `Notification` object for greater flexibility or set common properties on the
parent `InfoBar` itself.

> [!NOTE]
> There is no `StackMode` property to control the behavior of the queue. Providing a stable queue of messages one after another
> provides the best user experience as it reduces the risk when interacting with a notification for a new one to suddenly appear
> and replace the one being displayed.

The `ShowDismissButton` property should be mapped to the `InfoBar.IsClosable` property instead. Similar to any adjustments to position
should be handled by the layout of the `InfoBar` control itself within the XAML layout.

The `Closing` and `Closed` events can be mapped to those on the `InfoBar` as well.

### Complete example

This example shows sending simple text based notifications that will appear only for 2 seconds:

> [!Sample StackedNotificationsBehaviorToolkitSample]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!-- 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 more information. -->
<Page x:Class="BehaviorsExperiment.Samples.StackedNotificationsBehaviorCustomSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">

<Grid MinHeight="186">
<Button HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click"
Content="Send notification" />

<muxc:InfoBar MaxWidth="480"
Margin="24"
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<interactivity:Interaction.Behaviors>
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
</interactivity:Interaction.Behaviors>
</muxc:InfoBar>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 more information.

using CommunityToolkit.WinUI.Behaviors;

namespace BehaviorsExperiment.Samples;

[ToolkitSample(id: nameof(StackedNotificationsBehaviorCustomSample), "Stacked Notifications", description: $"A sample for showing how to create and use a {nameof(StackedNotificationsBehavior)} custom behavior.")]
public sealed partial class StackedNotificationsBehaviorCustomSample : Page
{
public StackedNotificationsBehaviorCustomSample()
{
this.InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
var notification = new Notification
{
Title = $"Notification {DateTimeOffset.Now}",
Message = GetRandomText(),
Severity = MUXC.InfoBarSeverity.Informational,
};

NotificationQueue.Show(notification);
}

private static int _current = 0;

private static string GetRandomText() => (_current++ % 4) switch
{
1 => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sollicitudin bibendum enim at tincidunt. Praesent egestas ipsum ligula, nec tincidunt lacus semper non.",
2 => "Pellentesque in risus eget leo rhoncus ultricies nec id ante.",
3 => "Sed quis nisi quis nunc condimentum varius id consectetur metus. Duis mauris sapien, commodo eget erat ac, efficitur iaculis magna. Morbi eu velit nec massa pharetra cursus.",
_ => "Fusce non quam egestas leo finibus interdum eu ac massa. Quisque nec justo leo. Aenean scelerisque placerat ultrices. Sed accumsan lorem at arcu commodo tristique.",
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- 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 more information. -->
<Page x:Class="BehaviorsExperiment.Samples.StackedNotificationsBehaviorToolkitSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">

<StackPanel MinHeight="136"
Orientation="Vertical"
Spacing="24">
<Button HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click"
Content="Send notification" />

<muxc:InfoBar HorizontalAlignment="Left"
VerticalAlignment="Bottom"
IsIconVisible="False">
<interactivity:Interaction.Behaviors>
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
</interactivity:Interaction.Behaviors>
</muxc:InfoBar>
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 more information.

using CommunityToolkit.WinUI.Behaviors;

namespace BehaviorsExperiment.Samples;

[ToolkitSample(id: nameof(StackedNotificationsBehaviorToolkitSample), "Stacked Notification Migration", description: $"A sample for showing how to create and use a {nameof(StackedNotificationsBehavior)} custom behavior upgrading from InAppNotification.")]
public sealed partial class StackedNotificationsBehaviorToolkitSample : Page
{
public StackedNotificationsBehaviorToolkitSample()
{
this.InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
// Show our notification for 2 seconds
NotificationQueue.Show(GetRandomText(), 2000);
}

private static int _current = 0;

private static string GetRandomText() => (_current++ % 4) switch
{
1 => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sollicitudin bibendum enim at tincidunt. Praesent egestas ipsum ligula, nec tincidunt lacus semper non.",
2 => "Pellentesque in risus eget leo rhoncus ultricies nec id ante.",
3 => "Sed quis nisi quis nunc condimentum varius id consectetur metus. Duis mauris sapien, commodo eget erat ac, efficitur iaculis magna. Morbi eu velit nec massa pharetra cursus.",
_ => "Fusce non quam egestas leo finibus interdum eu ac massa. Quisque nec justo leo. Aenean scelerisque placerat ultrices. Sed accumsan lorem at arcu commodo tristique.",
};
}
138 changes: 138 additions & 0 deletions components/Behaviors/src/Notification/Notification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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 more information.

namespace CommunityToolkit.WinUI.Behaviors;

/// <summary>
/// The content of a notification to display in <see cref="StackedNotificationsBehavior"/>.
/// The <see cref="Message"/> and <see cref="Duration"/> values will
/// always be applied to the targeted <see cref="MUXC.InfoBar"/>.
/// The <see cref="Title"/>, <see cref="Severity"/>, <see cref="IsIconVisible"/>, <see cref="IconSource"/>, <see cref="Content"/>, <see cref="ContentTemplate"/> and <see cref="ActionButton"/> values
/// will be applied only if set, otherwise the parent <see cref="MUXC.InfoBar"/> values will be used, if available.
/// </summary>
public class Notification
{
private NotificationOverrides _overrides;
private MUXC.InfoBarSeverity? _severity;
private bool _isIconVisible = true; // Default for InfoBar
private MUXC.IconSource? _iconSource;
private object? _content;
private DataTemplate? _contentTemplate;
private ButtonBase? _actionButton;

/// <summary>
/// Gets or sets the notification title.
/// </summary>
public string? Title { get; set; }

/// <summary>
/// Gets or sets the notification message.
/// </summary>
public string? Message { get; set; }

/// <summary>
/// Gets or sets the duration of the notification.
/// Set to null for persistent notification (default).
/// </summary>
public TimeSpan? Duration { get; set; }

/// <summary>
/// Gets or sets the type of the <see cref="MUXC.InfoBar.Severity"/> to apply consistent status color, icon,
/// and assistive technology settings dependent on the criticality of the notification.
/// By default the parent <see cref="MUXC.InfoBar.Severity"/> property's value will be used.
/// </summary>
public MUXC.InfoBarSeverity? Severity
{
get => _severity;
set
{
_severity = value;
_overrides |= NotificationOverrides.Severity;
}
}

/// <summary>
/// Gets or sets a value indicating whether the icon is visible or not.
/// True if the icon is visible; otherwise, false. The default is true.
/// </summary>
public bool IsIconVisible
{
get => _isIconVisible;
set
{
_isIconVisible = value;
_overrides |= NotificationOverrides.IconVisible;
}
}

/// <summary>
/// Gets or sets a value for an <see cref="MUXC.IconSource"/> to use as the <see cref="MUXC.InfoBar.IconSource"/> of the <see cref="MUXC.InfoBar"/> for this notification.
/// </summary>
public MUXC.IconSource? IconSource
{
get => _iconSource;
set
{
_iconSource = value;
_overrides |= NotificationOverrides.IconSource;
}
}

/// <summary>
/// Gets or sets the XAML Content that is displayed below the title and message in
/// the InfoBar.
/// </summary>
public object? Content
{
get => _content;
set
{
_content = value;
_overrides |= NotificationOverrides.Content;
}
}

/// <summary>
/// Gets or sets the data template for the <see cref="Content"/>.
/// </summary>
public DataTemplate? ContentTemplate
{
get => _contentTemplate;
set
{
_contentTemplate = value;
_overrides |= NotificationOverrides.ContentTemplate;
}
}

/// <summary>
/// Gets or sets the action button of the InfoBar.
/// </summary>
public ButtonBase? ActionButton
{
get => _actionButton;
set
{
_actionButton = value;
_overrides |= NotificationOverrides.ActionButton;
}
}

internal NotificationOverrides Overrides => _overrides;
}

/// <summary>
/// The overrides which should be set on the notification.
/// </summary>
[Flags]
internal enum NotificationOverrides
{
None,
Severity,
IconVisible,
IconSource,
Content,
ContentTemplate,
ActionButton,
}
Loading