diff --git a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/AdaptiveProgressBarValue.cs b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/AdaptiveProgressBarValue.cs
index a0daade80a6..7436f0261ab 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/AdaptiveProgressBarValue.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/AdaptiveProgressBarValue.cs
@@ -11,6 +11,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
///
public sealed class AdaptiveProgressBarValue
{
+ ///
+ /// Gets or sets the property name to bind to.
+ ///
+ public string BindingName { get; set; }
+
///
/// Gets or sets the value (0-1) representing the percent complete.
///
@@ -35,6 +40,11 @@ internal string ToXmlString()
return "indeterminate";
}
+ if (BindingName != null)
+ {
+ return "{" + BindingName + "}";
+ }
+
return Value.ToString();
}
@@ -69,5 +79,18 @@ public static AdaptiveProgressBarValue FromValue(double d)
Value = d
};
}
+
+ ///
+ /// Returns a progress bar value using the specified binding name.
+ ///
+ /// The property to bind to.
+ /// A progress bar value.
+ public static AdaptiveProgressBarValue FromBinding(string bindingName)
+ {
+ return new AdaptiveProgressBarValue()
+ {
+ BindingName = bindingName
+ };
+ }
}
}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj
index a138bad89c5..d2331a5378e 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj
+++ b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj
@@ -25,23 +25,17 @@
- $(DefineConstants);WINDOWS_UWP
+ $(DefineConstants);WINDOWS_UWP;WIN32
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileCommon.cs b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileCommon.cs
index c2aabf2f040..c40e030f69d 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileCommon.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileCommon.cs
@@ -15,21 +15,21 @@ public enum TileSize
///
/// Small Square Tile
///
- Small = 0,
+ Small = 1,
///
/// Medium Square Tile
///
- Medium = 1,
+ Medium = 2,
///
/// Wide Rectangle Tile
///
- Wide = 2,
+ Wide = 4,
///
/// Large Square Tile
///
- Large = 4
+ Large = 8
}
}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/CustomizeToast.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/CustomizeToast.cs
new file mode 100644
index 00000000000..43ce87c53f1
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/CustomizeToast.cs
@@ -0,0 +1,39 @@
+// 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.
+
+#if WINDOWS_UWP
+
+using Windows.Foundation;
+using Windows.UI.Notifications;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Allows you to set additional properties on the object before the toast is displayed.
+ ///
+ /// The toast to modify that will be displayed.
+ public delegate void CustomizeToast(ToastNotification toast);
+
+ ///
+ /// Allows you to set additional properties on the object before the toast is displayed.
+ ///
+ /// The toast to modify that will be displayed.
+ /// An operation.
+ public delegate IAsyncAction CustomizeToastAsync(ToastNotification toast);
+
+ ///
+ /// Allows you to set additional properties on the object before the toast is scheduled.
+ ///
+ /// The scheduled toast to modify that will be scheduled.
+ public delegate void CustomizeScheduledToast(ScheduledToastNotification toast);
+
+ ///
+ /// Allows you to set additional properties on the object before the toast is scheduled.
+ ///
+ /// The scheduled toast to modify that will be scheduled.
+ /// An operation.
+ public delegate IAsyncAction CustomizeScheduledToastAsync(ScheduledToastNotification toast);
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Actions.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Actions.cs
index 031861ccbec..32e50870076 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Actions.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Actions.cs
@@ -8,13 +8,16 @@
namespace Microsoft.Toolkit.Uwp.Notifications
{
-#if !WINRT
#pragma warning disable SA1008
#pragma warning disable SA1009
///
/// Builder class used to create
///
- public partial class ToastContentBuilder
+ public
+#if WINRT
+ sealed
+#endif
+ partial class ToastContentBuilder
{
private IToastActions Actions
{
@@ -45,6 +48,36 @@ private IList InputList
}
}
+ private string SerializeArgumentsIncludingGeneric(ToastArguments arguments)
+ {
+ if (_genericArguments.Count == 0)
+ {
+ return arguments.ToString();
+ }
+
+ foreach (var genericArg in _genericArguments)
+ {
+ if (!arguments.Contains(genericArg.Key))
+ {
+ arguments.Add(genericArg.Key, genericArg.Value);
+ }
+ }
+
+ return arguments.ToString();
+ }
+
+ ///
+ /// Add a button to the current toast.
+ ///
+ /// Text to display on the button.
+ /// Type of activation this button will use when clicked. Defaults to Foreground.
+ /// App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.
+ /// The current instance of
+ public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments)
+ {
+ return AddButton(content, activationType, arguments, default);
+ }
+
///
/// Add a button to the current toast.
///
@@ -53,7 +86,10 @@ private IList InputList
/// App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.
/// Optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).
/// The current instance of
- public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+#endif
+ public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri)
{
// Add new button
ToastButton button = new ToastButton(content, arguments)
@@ -61,7 +97,7 @@ private IList InputList
ActivationType = activationType
};
- if (imageUri != default(Uri))
+ if (imageUri != default)
{
button.ImageUri = imageUri.OriginalString;
}
@@ -76,17 +112,46 @@ private IList InputList
/// The current instance of
public ToastContentBuilder AddButton(IToastButton button)
{
+ if (button is ToastButton toastButton && toastButton.Content == null)
+ {
+ throw new InvalidOperationException("Content is required on button.");
+ }
+
// List has max 5 buttons
if (ButtonList.Count == 5)
{
throw new InvalidOperationException("A toast can't have more than 5 buttons");
}
+ if (button is ToastButton b && b.CanAddArguments())
+ {
+ foreach (var arg in _genericArguments)
+ {
+ if (!b.ContainsArgument(arg.Key))
+ {
+ b.AddArgument(arg.Key, arg.Value);
+ }
+ }
+ }
+
ButtonList.Add(button);
return this;
}
+ ///
+ /// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
+ ///
+ /// ID of an existing in order to have this button display to the right of the input, achieving a quick reply scenario.
+ /// Text to display on the button.
+ /// Type of activation this button will use when clicked. Defaults to Foreground.
+ /// App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.
+ /// The current instance of
+ public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments)
+ {
+ return AddButton(textBoxId, content, activationType, arguments, default);
+ }
+
///
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
///
@@ -96,7 +161,7 @@ public ToastContentBuilder AddButton(IToastButton button)
/// App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.
/// An optional image icon for the button to display (required for buttons adjacent to inputs like quick reply)
/// The current instance of
- public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
+ public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri)
{
// Add new button
ToastButton button = new ToastButton(content, arguments)
@@ -105,7 +170,7 @@ public ToastContentBuilder AddButton(IToastButton button)
TextBoxId = textBoxId
};
- if (imageUri != default(Uri))
+ if (imageUri != default)
{
button.ImageUri = imageUri.OriginalString;
}
@@ -113,6 +178,29 @@ public ToastContentBuilder AddButton(IToastButton button)
return AddButton(button);
}
+#if WINRT
+ ///
+ /// Add an input text box that the user can type into.
+ ///
+ /// Required ID property so that developers can retrieve user input once the app is activated.
+ /// The current instance of
+ public ToastContentBuilder AddInputTextBox(string id)
+ {
+ return AddInputTextBox(id, default, default);
+ }
+
+ ///
+ /// Add an input text box that the user can type into.
+ ///
+ /// Required ID property so that developers can retrieve user input once the app is activated.
+ /// Placeholder text to be displayed on the text box when the user hasn't typed any text yet.
+ /// The current instance of
+ public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent)
+ {
+ return AddInputTextBox(id, placeHolderContent, default);
+ }
+#endif
+
///
/// Add an input text box that the user can type into.
///
@@ -120,16 +208,24 @@ public ToastContentBuilder AddButton(IToastButton button)
/// Placeholder text to be displayed on the text box when the user hasn't typed any text yet.
/// Title text to display above the text box.
/// The current instance of
- public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent = default(string), string title = default(string))
+ public ToastContentBuilder AddInputTextBox(
+ string id,
+#if WINRT
+ string placeHolderContent,
+ string title)
+#else
+ string placeHolderContent = default,
+ string title = default)
+#endif
{
var inputTextBox = new ToastTextBox(id);
- if (placeHolderContent != default(string))
+ if (placeHolderContent != default)
{
inputTextBox.PlaceholderContent = placeHolderContent;
}
- if (title != default(string))
+ if (title != default)
{
inputTextBox.Title = title;
}
@@ -137,6 +233,7 @@ public ToastContentBuilder AddButton(IToastButton button)
return AddToastInput(inputTextBox);
}
+#if !WINRT
///
/// Add a combo box / drop-down menu that contain options for user to select.
///
@@ -145,7 +242,7 @@ public ToastContentBuilder AddButton(IToastButton button)
/// The current instance of
public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
{
- return AddComboBox(id, default(string), choices);
+ return AddComboBox(id, default, choices);
}
///
@@ -157,7 +254,7 @@ public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId,
/// The current instance of
public ToastContentBuilder AddComboBox(string id, string defaultSelectionBoxItemId, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
{
- return AddComboBox(id, default(string), defaultSelectionBoxItemId, choices);
+ return AddComboBox(id, default, defaultSelectionBoxItemId, choices);
}
///
@@ -185,12 +282,12 @@ public ToastContentBuilder AddComboBox(string id, string title, string defaultSe
{
var box = new ToastSelectionBox(id);
- if (defaultSelectionBoxItemId != default(string))
+ if (defaultSelectionBoxItemId != default)
{
box.DefaultSelectionBoxItemId = defaultSelectionBoxItemId;
}
- if (title != default(string))
+ if (title != default)
{
box.Title = title;
}
@@ -203,6 +300,7 @@ public ToastContentBuilder AddComboBox(string id, string title, string defaultSe
return AddToastInput(box);
}
+#endif
///
/// Add an input option to the Toast.
@@ -218,5 +316,4 @@ public ToastContentBuilder AddToastInput(IToastInput input)
}
#pragma warning restore SA1008
#pragma warning restore SA1009
-#endif
}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Visuals.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Visuals.cs
index 0de3804d214..715c5f46d08 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Visuals.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Visuals.cs
@@ -13,7 +13,6 @@
namespace Microsoft.Toolkit.Uwp.Notifications
{
-#if !WINRT
///
/// Builder class used to create
///
@@ -81,7 +80,7 @@ private IList VisualChildren
}
#if WINDOWS_UWP
-
+#if !WINRT
///
/// Create an instance of NotificationData that can be used to update toast that has a progress bar.
///
@@ -93,7 +92,7 @@ private IList VisualChildren
/// A status string, which is displayed underneath the progress bar on the left. Default to empty.
/// A sequence number to prevent out-of-order updates, or assign 0 to indicate "always update".
/// An instance of NotificationData that can be used to update the toast.
- public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default(string), double? value = null, string valueStringOverride = default(string), string status = default(string), uint sequence = 0)
+ public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default, double? value = default, string valueStringOverride = default, string status = default, uint sequence = 0)
{
var progressBar = toast.Visual.BindingGeneric.Children.Where(c => c is AdaptiveProgressBar).ElementAt(index) as AdaptiveProgressBar;
if (progressBar == null)
@@ -104,45 +103,56 @@ private IList VisualChildren
NotificationData data = new NotificationData();
data.SequenceNumber = sequence;
- if (progressBar.Title is BindableString bindableTitle && title != default(string))
+ // Native C++ doesn't support BindableString
+ if (progressBar.Title is BindableString bindableTitle && title != default)
{
data.Values[bindableTitle.BindingName] = title;
}
- if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != null)
+ if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != default)
{
data.Values[bindableProgressValue.BindingName] = value.ToString();
}
- if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default(string))
+ if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default)
{
data.Values[bindableValueStringOverride.BindingName] = valueStringOverride;
}
- if (progressBar.Status is BindableString bindableStatus && status != default(string))
+ if (progressBar.Status is BindableString bindableStatus && status != default)
{
data.Values[bindableStatus.BindingName] = status;
}
return data;
}
-
+#endif
#endif
+ ///
+ /// Add an Attribution Text to be displayed on the toast.
+ ///
+ /// Text to be displayed as Attribution Text
+ /// The current instance of
+ public ToastContentBuilder AddAttributionText(string text)
+ {
+ return AddAttributionText(text, default);
+ }
+
///
/// Add an Attribution Text to be displayed on the toast.
///
/// Text to be displayed as Attribution Text
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR".
/// The current instance of
- public ToastContentBuilder AddAttributionText(string text, string language = default(string))
+ public ToastContentBuilder AddAttributionText(string text, string language)
{
AttributionText = new ToastGenericAttributionText()
{
Text = text
};
- if (language != default(string))
+ if (language != default)
{
AttributionText.Language = language;
}
@@ -150,6 +160,41 @@ private IList VisualChildren
return this;
}
+#if WINRT
+ ///
+ /// Override the app logo with custom image of choice that will be displayed on the toast.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// The current instance of
+ public ToastContentBuilder AddAppLogoOverride(Uri uri)
+ {
+ return AddAppLogoOverride(uri, default);
+ }
+
+ ///
+ /// Override the app logo with custom image of choice that will be displayed on the toast.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// Specify how the image should be cropped.
+ /// The current instance of
+ public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop)
+ {
+ return AddAppLogoOverride(uri, hintCrop, default);
+ }
+
+ ///
+ /// Override the app logo with custom image of choice that will be displayed on the toast.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// Specify how the image should be cropped.
+ /// A description of the image, for users of assistive technologies.
+ /// The current instance of
+ public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop, string alternateText)
+ {
+ return AddAppLogoOverride(uri, hintCrop, alternateText, default);
+ }
+#endif
+
///
/// Override the app logo with custom image of choice that will be displayed on the toast.
///
@@ -158,24 +203,34 @@ private IList VisualChildren
/// A description of the image, for users of assistive technologies.
/// A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.
/// The current instance of
- public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop = null, string alternateText = default(string), bool? addImageQuery = default(bool?))
+ public ToastContentBuilder AddAppLogoOverride(
+ Uri uri,
+#if WINRT
+ ToastGenericAppLogoCrop? hintCrop,
+ string alternateText,
+ bool? addImageQuery)
+#else
+ ToastGenericAppLogoCrop? hintCrop = default,
+ string alternateText = default,
+ bool? addImageQuery = default)
+#endif
{
AppLogoOverrideUri = new ToastGenericAppLogo()
{
Source = uri.OriginalString
};
- if (hintCrop != null)
+ if (hintCrop != default)
{
AppLogoOverrideUri.HintCrop = hintCrop.Value;
}
- if (alternateText != default(string))
+ if (alternateText != default)
{
AppLogoOverrideUri.AlternateText = alternateText;
}
- if (addImageQuery != default(bool?))
+ if (addImageQuery != default)
{
AppLogoOverrideUri.AddImageQuery = addImageQuery;
}
@@ -183,6 +238,29 @@ private IList VisualChildren
return this;
}
+#if WINRT
+ ///
+ /// Add a hero image to the toast.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// The current instance of
+ public ToastContentBuilder AddHeroImage(Uri uri)
+ {
+ return AddHeroImage(uri, default);
+ }
+
+ ///
+ /// Add a hero image to the toast.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// A description of the image, for users of assistive technologies.
+ /// The current instance of
+ public ToastContentBuilder AddHeroImage(Uri uri, string alternateText)
+ {
+ return AddHeroImage(uri, alternateText, default);
+ }
+#endif
+
///
/// Add a hero image to the toast.
///
@@ -190,19 +268,27 @@ private IList VisualChildren
/// A description of the image, for users of assistive technologies.
/// A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.
/// The current instance of
- public ToastContentBuilder AddHeroImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?))
+ public ToastContentBuilder AddHeroImage(
+ Uri uri,
+#if WINRT
+ string alternateText,
+ bool? addImageQuery)
+#else
+ string alternateText = default,
+ bool? addImageQuery = default)
+#endif
{
HeroImage = new ToastGenericHeroImage()
{
Source = uri.OriginalString
};
- if (alternateText != default(string))
+ if (alternateText != default)
{
HeroImage.AlternateText = alternateText;
}
- if (addImageQuery != default(bool?))
+ if (addImageQuery != default)
{
HeroImage.AddImageQuery = addImageQuery;
}
@@ -210,6 +296,56 @@ private IList VisualChildren
return this;
}
+#if WINRT
+ ///
+ /// Add an image inline with other toast content.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// The current instance of
+ public ToastContentBuilder AddInlineImage(Uri uri)
+ {
+ return AddInlineImage(uri, default);
+ }
+
+ ///
+ /// Add an image inline with other toast content.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// A description of the image, for users of assistive technologies.
+ /// The current instance of
+ public ToastContentBuilder AddInlineImage(Uri uri, string alternateText)
+ {
+ return AddInlineImage(uri, alternateText, default);
+ }
+
+ ///
+ /// Add an image inline with other toast content.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// A description of the image, for users of assistive technologies.
+ /// A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.
+ /// The current instance of
+ public ToastContentBuilder AddInlineImage(Uri uri, string alternateText, bool? addImageQuery)
+ {
+ return AddInlineImage(uri, alternateText, addImageQuery, default);
+ }
+#endif
+
+#if WINRT
+ ///
+ /// Add an image inline with other toast content.
+ ///
+ /// The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.
+ /// A description of the image, for users of assistive technologies.
+ /// A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.
+ /// A value whether a margin is removed. images have an 8px margin around them.
+ /// The current instance of
+ public ToastContentBuilder AddInlineImage(
+ Uri uri,
+ string alternateText,
+ bool? addImageQuery,
+ AdaptiveImageCrop? hintCrop)
+#else
///
/// Add an image inline with other toast content.
///
@@ -217,9 +353,15 @@ private IList VisualChildren
/// A description of the image, for users of assistive technologies.
/// A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.
/// A value whether a margin is removed. images have an 8px margin around them.
- /// the horizontal alignment of the image.This is only supported when inside an .
+ /// This property is not used. Setting this has no impact.
/// The current instance of
- public ToastContentBuilder AddInlineImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?), AdaptiveImageCrop? hintCrop = null, bool? hintRemoveMargin = default(bool?))
+ public ToastContentBuilder AddInlineImage(
+ Uri uri,
+ string alternateText = default,
+ bool? addImageQuery = default,
+ AdaptiveImageCrop? hintCrop = default,
+ bool? hintRemoveMargin = default)
+#endif
{
var inlineImage = new AdaptiveImage()
{
@@ -231,24 +373,20 @@ private IList VisualChildren
inlineImage.HintCrop = hintCrop.Value;
}
- if (alternateText != default(string))
+ if (alternateText != default)
{
inlineImage.AlternateText = alternateText;
}
- if (addImageQuery != default(bool?))
+ if (addImageQuery != default)
{
inlineImage.AddImageQuery = addImageQuery;
}
- if (hintRemoveMargin != default(bool?))
- {
- inlineImage.HintRemoveMargin = hintRemoveMargin;
- }
-
return AddVisualChild(inlineImage);
}
+#if !WINRT
///
/// Add a progress bar to the toast.
///
@@ -259,7 +397,7 @@ private IList VisualChildren
/// A status string which is displayed underneath the progress bar. This string should reflect the status of the operation, like "Downloading..." or "Installing...". Default to empty.
/// The current instance of
/// More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-progress-bar
- public ToastContentBuilder AddProgressBar(string title = default(string), double? value = null, bool isIndeterminate = false, string valueStringOverride = default(string), string status = default(string))
+ public ToastContentBuilder AddProgressBar(string title = default, double? value = null, bool isIndeterminate = false, string valueStringOverride = default, string status = default)
{
int index = VisualChildren.Count(c => c is AdaptiveProgressBar);
@@ -267,7 +405,7 @@ private IList VisualChildren
{
};
- if (title == default(string))
+ if (title == default)
{
progressBar.Title = new BindableString($"progressBarTitle_{index}");
}
@@ -289,7 +427,7 @@ private IList VisualChildren
progressBar.Value = value.Value;
}
- if (valueStringOverride == default(string))
+ if (valueStringOverride == default)
{
progressBar.ValueStringOverride = new BindableString($"progressValueString_{index}");
}
@@ -298,7 +436,7 @@ private IList VisualChildren
progressBar.ValueStringOverride = valueStringOverride;
}
- if (status == default(string))
+ if (status == default)
{
progressBar.Status = new BindableString($"progressStatus_{index}");
}
@@ -309,16 +447,41 @@ private IList VisualChildren
return AddVisualChild(progressBar);
}
+#endif
+
+#if WINRT
+ ///
+ /// Add text to the toast.
+ ///
+ /// Custom text to display on the tile.
+ /// The current instance of
+ /// Throws when attempting to add/reserve more than 4 lines on a single toast.
+ /// More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements
+ public ToastContentBuilder AddText(string text)
+ {
+ return AddText(text, default, default);
+ }
///
/// Add text to the toast.
///
/// Custom text to display on the tile.
- /// Style that controls the text's font size, weight, and opacity.
- /// Indicating whether text wrapping is enabled. For Tiles, this is false by default.
- /// The maximum number of lines the text element is allowed to display. For Tiles, this is infinity by default
- /// The minimum number of lines the text element must display.
- /// The horizontal alignment of the text
+ /// The maximum number of lines the text element is allowed to display.
+ /// The current instance of
+ /// Throws when attempting to add/reserve more than 4 lines on a single toast.
+ /// More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements
+ public ToastContentBuilder AddText(string text, int? hintMaxLines)
+ {
+ return AddText(text, hintMaxLines, default);
+ }
+#endif
+
+#if WINRT
+ ///
+ /// Add text to the toast.
+ ///
+ /// Custom text to display on the tile.
+ /// The maximum number of lines the text element is allowed to display.
///
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
///
@@ -326,7 +489,36 @@ private IList VisualChildren
/// Throws when attempting to add/reserve more than 4 lines on a single toast.
/// Throws when value is larger than 2.
/// More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements
- public ToastContentBuilder AddText(string text, AdaptiveTextStyle? hintStyle = null, bool? hintWrap = default(bool?), int? hintMaxLines = default(int?), int? hintMinLines = default(int?), AdaptiveTextAlign? hintAlign = null, string language = default(string))
+ public ToastContentBuilder AddText(
+ string text,
+ int? hintMaxLines,
+ string language)
+#else
+ ///
+ /// Add text to the toast.
+ ///
+ /// Custom text to display on the tile.
+ /// This property is not used. Setting this has no effect.
+ /// This property is not used. Setting this has no effect. If you need to disable wrapping, set hintMaxLines to 1.
+ /// The maximum number of lines the text element is allowed to display.
+ /// hintMinLines is not used. Setting this has no effect.
+ /// hintAlign is not used. Setting this has no effect.
+ ///
+ /// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
+ ///
+ /// The current instance of
+ /// Throws when attempting to add/reserve more than 4 lines on a single toast.
+ /// Throws when value is larger than 2.
+ /// More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements
+ public ToastContentBuilder AddText(
+ string text,
+ AdaptiveTextStyle? hintStyle = null,
+ bool? hintWrap = default,
+ int? hintMaxLines = default,
+ int? hintMinLines = default,
+ AdaptiveTextAlign? hintAlign = null,
+ string language = default)
+#endif
{
int lineCount = GetCurrentTextLineCount();
if (GetCurrentTextLineCount() == 4)
@@ -340,22 +532,7 @@ private IList VisualChildren
Text = text
};
- if (hintStyle != null)
- {
- adaptive.HintStyle = hintStyle.Value;
- }
-
- if (hintAlign != null)
- {
- adaptive.HintAlign = hintAlign.Value;
- }
-
- if (hintWrap != default(bool?))
- {
- adaptive.HintWrap = hintWrap;
- }
-
- if (hintMaxLines != default(int?))
+ if (hintMaxLines != default)
{
if (hintMaxLines > 2)
{
@@ -369,12 +546,7 @@ private IList VisualChildren
adaptive.HintMaxLines = hintMaxLines;
}
- if (hintMinLines != default(int?) && hintMinLines > 0)
- {
- adaptive.HintMinLines = hintMinLines;
- }
-
- if (language != default(string))
+ if (language != default)
{
adaptive.Language = language;
}
@@ -419,6 +591,4 @@ private int GetCurrentTextLineCount()
return count;
}
}
-
-#endif
}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.cs
index 213591ae0a7..0850a1b013d 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.cs
@@ -4,16 +4,21 @@
using System;
using System.Collections.Generic;
-using System.Text;
namespace Microsoft.Toolkit.Uwp.Notifications
{
-#if !WINRT
///
/// Builder class used to create
///
public partial class ToastContentBuilder
+#if !WINRT
+ : IToastActivateableBuilder
+#endif
{
+ private Dictionary _genericArguments = new Dictionary();
+
+ private bool _customArgumentsUsedOnToastItself;
+
///
/// Gets internal instance of . This is equivalent to the call to .
///
@@ -35,13 +40,34 @@ public ToastContentBuilder()
///
/// Custom Time to be displayed on the toast
/// The current instance of
- public ToastContentBuilder AddCustomTimeStamp(DateTime dateTime)
+ public ToastContentBuilder AddCustomTimeStamp(
+#if WINRT
+ DateTimeOffset dateTime)
+#else
+ DateTime dateTime)
+#endif
{
Content.DisplayTimestamp = dateTime;
return this;
}
+ ///
+ /// Add a header to a toast.
+ ///
+ /// A developer-created identifier that uniquely identifies this header. If two notifications have the same header id, they will be displayed underneath the same header in Action Center.
+ /// A title for the header.
+ /// Developer-defined arguments that are returned to the app when the user clicks this header.
+ /// The current instance of
+ /// More info about toast header: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-headers
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+#endif
+ public ToastContentBuilder AddHeader(string id, string title, ToastArguments arguments)
+ {
+ return AddHeader(id, title, arguments.ToString());
+ }
+
///
/// Add a header to a toast.
///
@@ -58,7 +84,201 @@ public ToastContentBuilder AddHeader(string id, string title, string arguments)
}
///
- /// Add info that can be used by the application when the app was activated/launched by the toast.
+ /// Adds a key (without value) to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key.
+ /// The current instance of
+ public ToastContentBuilder AddArgument(string key)
+ {
+ return AddArgumentHelper(key, null);
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
+#endif
+ public ToastContentBuilder AddArgument(string key, string value)
+ {
+ return AddArgumentHelper(key, value);
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
+#endif
+ public ToastContentBuilder AddArgument(string key, int value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
+#endif
+ public ToastContentBuilder AddArgument(string key, double value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
+#endif
+ public ToastContentBuilder AddArgument(string key, float value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
+#endif
+ public ToastContentBuilder AddArgument(string key, bool value)
+ {
+ return AddArgumentHelper(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
+ }
+
+#if !WINRT
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.
+ /// The current instance of
+ public ToastContentBuilder AddArgument(string key, Enum value)
+ {
+ return AddArgumentHelper(key, ((int)(object)value).ToString());
+ }
+#endif
+
+ private ToastContentBuilder AddArgumentHelper(string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ bool alreadyExists = _genericArguments.ContainsKey(key);
+
+ _genericArguments[key] = value;
+
+ if (Content.ActivationType != ToastActivationType.Protocol && !_customArgumentsUsedOnToastItself)
+ {
+ Content.Launch = alreadyExists ? SerializeArgumentsHelper(_genericArguments) : AddArgumentHelper(Content.Launch, key, value);
+ }
+
+ if (Content.Actions is ToastActionsCustom actions)
+ {
+ foreach (var button in actions.Buttons)
+ {
+ if (button is ToastButton b && b.CanAddArguments() && !b.ContainsArgument(key))
+ {
+ b.AddArgument(key, value);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ private string SerializeArgumentsHelper(IDictionary arguments)
+ {
+ var args = new ToastArguments();
+
+ foreach (var a in arguments)
+ {
+ args.Add(a.Key, a.Value);
+ }
+
+ return args.ToString();
+ }
+
+ private string AddArgumentHelper(string existing, string key, string value)
+ {
+ string pair = ToastArguments.EncodePair(key, value);
+
+ if (existing == null)
+ {
+ return pair;
+ }
+ else
+ {
+ return existing + ToastArguments.Separator + pair;
+ }
+ }
+
+ ///
+ /// Configures the toast notification to launch the specified url when the toast body is clicked.
+ ///
+ /// The protocol to launch.
+ /// The current instance of
+ public ToastContentBuilder SetProtocolActivation(Uri protocol)
+ {
+ return SetProtocolActivation(protocol, default);
+ }
+
+ ///
+ /// Configures the toast notification to launch the specified url when the toast body is clicked.
+ ///
+ /// The protocol to launch.
+ /// New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.
+ /// The current instance of
+ public ToastContentBuilder SetProtocolActivation(Uri protocol, string targetApplicationPfn)
+ {
+ Content.Launch = protocol.ToString();
+ Content.ActivationType = ToastActivationType.Protocol;
+
+ if (targetApplicationPfn != null)
+ {
+ if (Content.ActivationOptions == null)
+ {
+ Content.ActivationOptions = new ToastActivationOptions();
+ }
+
+ Content.ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Configures the toast notification to use background activation when the toast body is clicked.
+ ///
+ /// The current instance of
+ public ToastContentBuilder SetBackgroundActivation()
+ {
+ Content.ActivationType = ToastActivationType.Background;
+ return this;
+ }
+
+ ///
+ /// Instead of this method, for foreground/background activation, it is suggested to use and optionally . For protocol activation, you should use . Add info that can be used by the application when the app was activated/launched by the toast.
///
/// Custom app-defined launch arguments to be passed along on toast activation
/// Set the activation type that will be used when the user click on this toast
@@ -67,6 +287,7 @@ public ToastContentBuilder AddToastActivationInfo(string launchArgs, ToastActiva
{
Content.Launch = launchArgs;
Content.ActivationType = activationType;
+ _customArgumentsUsedOnToastItself = true;
return this;
}
@@ -93,6 +314,30 @@ public ToastContentBuilder SetToastScenario(ToastScenario scenario)
return this;
}
+#if WINRT
+ ///
+ /// Set custom audio to go along with the toast.
+ ///
+ /// Source to the media that will be played when the toast is pop
+ /// The current instance of
+ [Windows.Foundation.Metadata.DefaultOverload]
+ public ToastContentBuilder AddAudio(Uri src)
+ {
+ return AddAudio(src, default, default);
+ }
+
+ ///
+ /// Set custom audio to go along with the toast.
+ ///
+ /// Source to the media that will be played when the toast is pop
+ /// Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).
+ /// The current instance of
+ public ToastContentBuilder AddAudio(Uri src, bool? loop)
+ {
+ return AddAudio(src, loop, default);
+ }
+#endif
+
///
/// Set custom audio to go along with the toast.
///
@@ -100,7 +345,15 @@ public ToastContentBuilder SetToastScenario(ToastScenario scenario)
/// Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).
/// Indicating whether sound is muted; false to allow the Toast notification sound to play (default).
/// The current instance of
- public ToastContentBuilder AddAudio(Uri src, bool? loop = null, bool? silent = null)
+ public ToastContentBuilder AddAudio(
+ Uri src,
+#if WINRT
+ bool? loop,
+ bool? silent)
+#else
+ bool? loop = default,
+ bool? silent = default)
+#endif
{
if (!src.IsFile)
{
@@ -110,12 +363,12 @@ public ToastContentBuilder AddAudio(Uri src, bool? loop = null, bool? silent = n
Content.Audio = new ToastAudio();
Content.Audio.Src = src;
- if (loop != null)
+ if (loop != default)
{
Content.Audio.Loop = loop.Value;
}
- if (silent != null)
+ if (silent != default)
{
Content.Audio.Silent = silent.Value;
}
@@ -123,6 +376,17 @@ public ToastContentBuilder AddAudio(Uri src, bool? loop = null, bool? silent = n
return this;
}
+ ///
+ /// Set custom audio to go along with the toast.
+ ///
+ /// The to set.
+ /// The current instance of
+ public ToastContentBuilder AddAudio(ToastAudio audio)
+ {
+ Content.Audio = audio;
+ return this;
+ }
+
///
/// Get the instance of that has been built by the builder with specified configuration so far.
///
@@ -131,7 +395,111 @@ public ToastContent GetToastContent()
{
return Content;
}
- }
+#if WINDOWS_UWP
+ ///
+ /// Retrieves the notification XML content as a WinRT XmlDocument, so that it can be used with a local Toast notification's constructor on either or .
+ ///
+ /// The notification XML content as a WinRT XmlDocument.
+ public Windows.Data.Xml.Dom.XmlDocument GetXml()
+ {
+ return GetToastContent().GetXml();
+ }
+
+ ///
+ /// Shows a new toast notification with the current content.
+ ///
+ public void Show()
+ {
+ CustomizeToast customize = null;
+ Show(customize);
+ }
+
+ ///
+ /// Shows a new toast notification with the current content.
+ ///
+ /// Allows you to set additional properties on the object.
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+#endif
+ public void Show(CustomizeToast customize)
+ {
+ var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
+ customize?.Invoke(notif);
+
+ ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
+ }
+
+ ///
+ /// Shows a new toast notification with the current content.
+ ///
+ /// Allows you to set additional properties on the object.
+ /// An operation that completes after your async customizations have completed.
+ public Windows.Foundation.IAsyncAction Show(CustomizeToastAsync customize)
+ {
+ return ShowAsyncHelper(customize).AsAsyncAction();
+ }
+
+ private async System.Threading.Tasks.Task ShowAsyncHelper(CustomizeToastAsync customize)
+ {
+ var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
+
+ if (customize != null)
+ {
+ await customize.Invoke(notif);
+ }
+
+ ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
+ }
+
+ ///
+ /// Schedules the notification.
+ ///
+ /// The date and time that Windows should display the toast notification. This time must be in the future.
+ public void Schedule(DateTimeOffset deliveryTime)
+ {
+ CustomizeScheduledToast customize = null;
+ Schedule(deliveryTime, customize);
+ }
+
+ ///
+ /// Schedules the notification.
+ ///
+ /// The date and time that Windows should display the toast notification. This time must be in the future.
+ /// Allows you to set additional properties on the object.
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
#endif
+ public void Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToast customize)
+ {
+ var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
+ customize?.Invoke(notif);
+
+ ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
+ }
+
+ ///
+ /// Schedules the notification.
+ ///
+ /// The date and time that Windows should display the toast notification. This time must be in the future.
+ /// Allows you to set additional properties on the object.
+ /// An operation that completes after your async customizations have completed.
+ public Windows.Foundation.IAsyncAction Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize)
+ {
+ return ScheduleAsyncHelper(deliveryTime, customize).AsAsyncAction();
+ }
+
+ private async System.Threading.Tasks.Task ScheduleAsyncHelper(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize = null)
+ {
+ var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
+
+ if (customize != null)
+ {
+ await customize.Invoke(notif);
+ }
+
+ ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
+ }
+#endif
+ }
}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs
new file mode 100644
index 00000000000..6134db08869
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs
@@ -0,0 +1,102 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Windows.ApplicationModel;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
+ ///
+ internal class DesktopBridgeHelpers
+ {
+ private const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName);
+
+ private static bool? _hasIdentity;
+
+ public static bool HasIdentity()
+ {
+ if (_hasIdentity == null)
+ {
+ if (IsWindows7OrLower)
+ {
+ _hasIdentity = false;
+ }
+ else
+ {
+ int length = 0;
+ var sb = new StringBuilder(0);
+ GetCurrentPackageFullName(ref length, sb);
+
+ sb = new StringBuilder(length);
+ int error = GetCurrentPackageFullName(ref length, sb);
+
+ _hasIdentity = error != APPMODEL_ERROR_NO_PACKAGE;
+ }
+ }
+
+ return _hasIdentity.Value;
+ }
+
+ private static bool? _isContainerized;
+
+ ///
+ /// Returns true if the app is running in a container (MSIX) or false if not running in a container (sparse or plain Win32)
+ ///
+ /// Boolean
+ public static bool IsContainerized()
+ {
+ if (_isContainerized == null)
+ {
+ // If MSIX or sparse
+ if (HasIdentity())
+ {
+ // Sparse is identified if EXE is running outside of installed package location
+ var packageInstalledLocation = Package.Current.InstalledLocation.Path;
+ var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
+
+ // If inside package location
+ if (actualExeFullPath.StartsWith(packageInstalledLocation))
+ {
+ _isContainerized = true;
+ }
+ else
+ {
+ _isContainerized = false;
+ }
+ }
+
+ // Plain Win32
+ else
+ {
+ _isContainerized = false;
+ }
+ }
+
+ return _isContainerized.Value;
+ }
+
+ private static bool IsWindows7OrLower
+ {
+ get
+ {
+ int versionMajor = Environment.OSVersion.Version.Major;
+ int versionMinor = Environment.OSVersion.Version.Minor;
+ double version = versionMajor + ((double)versionMinor / 10);
+ return version <= 6.1;
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationHistoryCompat.cs
similarity index 95%
rename from Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs
rename to Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationHistoryCompat.cs
index 77d9f29c154..611ab96edc7 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationHistoryCompat.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#if WIN32
+
+using System;
using System.Collections.Generic;
using Windows.UI.Notifications;
@@ -10,6 +13,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
///
/// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
///
+ [Obsolete("We recommend switching to the new ToastNotificationManagerCompat and ToastNotificationHistoryCompat.")]
public sealed class DesktopNotificationHistoryCompat
{
private string _aumid;
@@ -99,4 +103,6 @@ public void RemoveGroup(string group)
}
}
}
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs
similarity index 96%
rename from Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs
rename to Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs
index f70be4537fb..38359aa56dc 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs
@@ -2,18 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#if WIN32
+
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Windows.UI.Notifications;
-using static Microsoft.Toolkit.Uwp.Notifications.NotificationActivator;
namespace Microsoft.Toolkit.Uwp.Notifications
{
///
/// Helper for .NET Framework applications to display toast notifications and respond to toast events
///
+ [Obsolete("We recommend switching to the new ToastNotificationManagerCompat. For Win32 apps, it no longer requires a Start menu shortcut, and activation now uses a straightforward event handler (no NotificationActivator class and COM GUIDs needed)!")]
public class DesktopNotificationManagerCompat
{
///
@@ -131,7 +133,7 @@ public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
// Create the instance of the .NET object
ppvObject = Marshal.GetComInterfaceForObject(
new T(),
- typeof(INotificationActivationCallback));
+ typeof(NotificationActivator.INotificationActivationCallback));
}
else
{
@@ -225,7 +227,7 @@ public static bool CanUseHttpImages
}
///
- /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs
+ /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
///
private class DesktopBridgeHelpers
{
@@ -273,3 +275,5 @@ private static bool IsWindows7OrLower
}
}
}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/InternalNotificationActivator.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/InternalNotificationActivator.cs
new file mode 100644
index 00000000000..2e1dae172ef
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/InternalNotificationActivator.cs
@@ -0,0 +1,71 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications.Internal
+{
+ ///
+ /// Do not use this class. It must be public so that reflection can properly activate it, but consider it internal.
+ ///
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public abstract class InternalNotificationActivator : InternalNotificationActivator.INotificationActivationCallback
+ {
+ ///
+ public void Activate(string appUserModelId, string invokedArgs, InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data, uint dataCount)
+ {
+ ToastNotificationManagerCompat.OnActivatedInternal(invokedArgs, data, appUserModelId);
+ }
+
+ ///
+ /// A single user input key/value pair.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ [Serializable]
+ public struct NOTIFICATION_USER_INPUT_DATA
+ {
+ ///
+ /// The key of the user input.
+ ///
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string Key;
+
+ ///
+ /// The value of the user input.
+ ///
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string Value;
+ }
+
+ ///
+ /// The COM callback that is triggered when your notification is clicked.
+ ///
+ [ComImport]
+ [Guid("53E31837-6600-4A81-9395-75CFFE746F94")]
+ [ComVisible(true)]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface INotificationActivationCallback
+ {
+ ///
+ /// The method called when your notification is clicked.
+ ///
+ /// The app id of the app that sent the toast.
+ /// The activation arguments from the toast.
+ /// The user input from the toast.
+ /// The number of user inputs.
+ void Activate(
+ [In, MarshalAs(UnmanagedType.LPWStr)]
+ string appUserModelId,
+ [In, MarshalAs(UnmanagedType.LPWStr)]
+ string invokedArgs, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] NOTIFICATION_USER_INPUT_DATA[] data,
+ [In, MarshalAs(UnmanagedType.U4)]
+ uint dataCount);
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ManifestHelper.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ManifestHelper.cs
new file mode 100644
index 00000000000..214b248548e
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ManifestHelper.cs
@@ -0,0 +1,91 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Xml;
+using Windows.ApplicationModel;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ internal class ManifestHelper
+ {
+ private XmlDocument _doc;
+ private XmlNamespaceManager _namespaceManager;
+
+ public ManifestHelper()
+ {
+ var appxManifestPath = Path.Combine(Package.Current.InstalledLocation.Path, "AppxManifest.xml");
+ var doc = new XmlDocument();
+ doc.Load(appxManifestPath);
+
+ var namespaceManager = new XmlNamespaceManager(doc.NameTable);
+ namespaceManager.AddNamespace("default", "http://schemas.microsoft.com/appx/manifest/foundation/windows10");
+ namespaceManager.AddNamespace("desktop", "http://schemas.microsoft.com/appx/manifest/desktop/windows10");
+ namespaceManager.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");
+
+ _doc = doc;
+ _namespaceManager = namespaceManager;
+ }
+
+ internal string GetAumidFromPackageManifest()
+ {
+ var appNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]", _namespaceManager);
+
+ if (appNode == null)
+ {
+ throw new InvalidOperationException("Your MSIX app manifest must have an entry.");
+ }
+
+ return Package.Current.Id.FamilyName + "!" + appNode.Attributes["Id"].Value;
+ }
+
+ internal string GetClsidFromPackageManifest()
+ {
+ var activatorClsidNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]/default:Extensions/desktop:Extension[@Category='windows.toastNotificationActivation']/desktop:ToastNotificationActivation/@ToastActivatorCLSID", _namespaceManager);
+
+ if (activatorClsidNode == null)
+ {
+ throw new InvalidOperationException("Your app manifest must have a toastNotificationActivation extension with a valid ToastActivatorCLSID specified.");
+ }
+
+ var clsid = activatorClsidNode.Value;
+
+ // Make sure they have a COM class registration matching the CLSID
+ var comClassNode = _doc.SelectSingleNode($"/default:Package/default:Applications/default:Application[1]/default:Extensions/com:Extension[@Category='windows.comServer']/com:ComServer/com:ExeServer/com:Class[@Id='{clsid}']", _namespaceManager);
+
+ if (comClassNode == null)
+ {
+ throw new InvalidOperationException("Your app manifest must have a comServer extension with a class ID matching your toastNotificationActivator's CLSID.");
+ }
+
+ // Make sure they have a COM class registration matching their current process executable
+ var comExeServerNode = comClassNode.ParentNode;
+
+ var specifiedExeRelativePath = comExeServerNode.Attributes["Executable"].Value;
+ var specifiedExeFullPath = Path.Combine(Package.Current.InstalledLocation.Path, specifiedExeRelativePath);
+ var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
+
+ if (specifiedExeFullPath != actualExeFullPath)
+ {
+ var correctExeRelativePath = actualExeFullPath.Substring(Package.Current.InstalledLocation.Path.Length + 1);
+ throw new InvalidOperationException($"Your app manifest's comServer extension's Executable value is incorrect. It should be \"{correctExeRelativePath}\".");
+ }
+
+ // Make sure their arguments are set correctly
+ var argumentsNode = comExeServerNode.Attributes.GetNamedItem("Arguments");
+ if (argumentsNode == null || argumentsNode.Value != "-ToastActivated")
+ {
+ throw new InvalidOperationException("Your app manifest's comServer extension for toast activation must have its Arguments set exactly to \"-ToastActivated\"");
+ }
+
+ return clsid;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/CAppResolver.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/CAppResolver.cs
new file mode 100644
index 00000000000..0752d77f4fa
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/CAppResolver.cs
@@ -0,0 +1,20 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [ComImport]
+ [Guid("660b90c8-73a9-4b58-8cae-355b7f55341b")]
+ [ClassInterface(ClassInterfaceType.None)]
+ internal class CAppResolver
+ {
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IApplicationResolver.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IApplicationResolver.cs
new file mode 100644
index 00000000000..436f47dc18c
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IApplicationResolver.cs
@@ -0,0 +1,80 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [ComImport]
+ [Guid("DE25675A-72DE-44b4-9373-05170450C140")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IApplicationResolver
+ {
+ void GetAppIDForShortcut(
+ IntPtr psi,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
+
+ void GetAppIDForShortcutObject(
+ IntPtr psl,
+ IntPtr psi,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
+
+ void GetAppIDForWindow(
+ int hwnd,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
+ [Out] out bool pfPinningPrevented,
+ [Out] out bool pfExplicitAppID,
+ [Out] out bool pfEmbeddedShortcutValid);
+
+ ///
+ /// Main way to obtain app ID for any process. Calls GetShortcutPathOrAppIdFromPid
+ ///
+ void GetAppIDForProcess(
+ uint dwProcessID,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
+ [Out] out bool pfPinningPrevented,
+ [Out] out bool pfExplicitAppID,
+ [Out] out bool pfEmbeddedShortcutValid);
+
+ void GetShortcutForProcess(
+ uint dwProcessID,
+ [Out] out IntPtr ppsi);
+
+ void GetBestShortcutForAppID(
+ string pszAppID,
+ [Out] out IShellItem ppsi);
+
+ void GetBestShortcutAndAppIDForAppPath(
+ string pszAppPath,
+ [Out] out IntPtr ppsi,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
+
+ void CanPinApp(IntPtr psi);
+
+ void CanPinAppShortcut(
+ IntPtr psl,
+ IntPtr psi);
+
+ void GetRelaunchProperties(
+ int hwnd,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszCmdLine,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszIconResource,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplayNameResource,
+ [Out] bool pfPinnable);
+
+ void GenerateShortcutFromWindowProperties(
+ int hwnd,
+ [Out] out IntPtr ppsi);
+
+ void GenerateShortcutFromItemProperties(
+ IntPtr psi2,
+ [Out] out IntPtr ppsi);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItem.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItem.cs
new file mode 100644
index 00000000000..25db4a5faa0
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItem.cs
@@ -0,0 +1,41 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [ComImport]
+ [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IShellItem
+ {
+ void BindToHandler(
+ IntPtr pbc,
+ IntPtr bhid,
+ IntPtr riid,
+ [Out] out IntPtr ppv);
+
+ void GetParent(
+ [Out] out IShellItem ppsi);
+
+ void GetDisplayName(
+ int sigdnName,
+ [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
+
+ void GetAttributes(
+ int sfgaoMask,
+ [Out] out int psfgaoAttribs);
+
+ void Compare(
+ IShellItem psi,
+ int hint,
+ [Out] out int piOrder);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItemImageFactory.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItemImageFactory.cs
new file mode 100644
index 00000000000..91630eeae4b
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/IShellItemImageFactory.cs
@@ -0,0 +1,24 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [ComImport]
+ [Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IShellItemImageFactory
+ {
+ void GetImage(
+ [In, MarshalAs(UnmanagedType.Struct)] SIZE size,
+ [In] SIIGBF flags,
+ [Out] out IntPtr phbm);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIIGBF.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIIGBF.cs
new file mode 100644
index 00000000000..ca2fcb3aa0c
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIIGBF.cs
@@ -0,0 +1,24 @@
+// 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.
+
+#if WIN32
+
+using System;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [Flags]
+ internal enum SIIGBF
+ {
+ ResizeToFit = 0x00,
+ BiggerSizeOk = 0x01,
+ MemoryOnly = 0x02,
+ IconOnly = 0x04,
+ ThumbnailOnly = 0x08,
+ InCacheOnly = 0x10,
+ ScaleUp = 0x00000100
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIZE.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIZE.cs
new file mode 100644
index 00000000000..d66af5743eb
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/SIZE.cs
@@ -0,0 +1,25 @@
+// 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.
+
+#if WIN32
+
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SIZE
+ {
+ internal int X;
+ internal int Y;
+
+ internal SIZE(int x, int y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationActivator.cs
similarity index 93%
rename from Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs
rename to Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationActivator.cs
index b8cf3c8ae27..bad53c9e1fd 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationActivator.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#if WIN32
+
using System;
using System.Collections;
using System.Collections.Generic;
@@ -16,6 +18,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
///
/// Apps must implement this activator to handle notification activation.
///
+ [Obsolete("You can now subscribe to activation by simpy using the ToastNotificationManagerCompat.OnActivated event. We recommend deleting your NotificationActivator and switching to using the event.")]
public abstract class NotificationActivator : NotificationActivator.INotificationActivationCallback
{
///
@@ -77,4 +80,6 @@ void Activate(
uint dataCount);
}
}
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationUserInput.cs
similarity index 92%
rename from Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs
rename to Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationUserInput.cs
index 0ef54e3ec39..4a9c36770f7 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/NotificationUserInput.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#if WIN32
+
using System;
using System.Collections;
using System.Collections.Generic;
@@ -18,9 +20,13 @@ namespace Microsoft.Toolkit.Uwp.Notifications
///
public class NotificationUserInput : IReadOnlyDictionary
{
+#pragma warning disable CS0618 // Type or member is obsolete
private NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] _data;
+#pragma warning restore CS0618 // Type or member is obsolete
+#pragma warning disable CS0618 // Type or member is obsolete
internal NotificationUserInput(NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data)
+#pragma warning restore CS0618 // Type or member is obsolete
{
_data = data;
}
@@ -92,4 +98,6 @@ IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
}
}
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/OnActivated.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/OnActivated.cs
new file mode 100644
index 00000000000..55e4204cc95
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/OnActivated.cs
@@ -0,0 +1,16 @@
+// 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.
+
+#if WIN32
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Event triggered when a notification is clicked.
+ ///
+ /// Arguments that specify what action was taken and the user inputs.
+ public delegate void OnActivated(ToastNotificationActivatedEventArgsCompat e);
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ToastNotificationActivatedEventArgsCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ToastNotificationActivatedEventArgsCompat.cs
new file mode 100644
index 00000000000..df60a4721f1
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/ToastNotificationActivatedEventArgsCompat.cs
@@ -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.
+
+#if WIN32
+
+using Windows.Foundation.Collections;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Provides information about an event that occurs when the app is activated because a user tapped on the body of a toast notification or performed an action inside a toast notification.
+ ///
+ public class ToastNotificationActivatedEventArgsCompat
+ {
+ internal ToastNotificationActivatedEventArgsCompat()
+ {
+ }
+
+ ///
+ /// Gets the arguments from the toast XML payload related to the action that was invoked on the toast.
+ ///
+ public string Argument { get; internal set; }
+
+ ///
+ /// Gets a set of values that you can use to obtain the user input from an interactive toast notification.
+ ///
+ public ValueSet UserInput { get; internal set; }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs
new file mode 100644
index 00000000000..6dce097f717
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs
@@ -0,0 +1,254 @@
+// 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.
+
+#if WIN32
+
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ internal class Win32AppInfo
+ {
+ public string Aumid { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public string IconPath { get; set; }
+
+ public static Win32AppInfo Get()
+ {
+ var process = Process.GetCurrentProcess();
+
+ // First get the app ID
+ IApplicationResolver appResolver = (IApplicationResolver)new CAppResolver();
+ appResolver.GetAppIDForProcess(Convert.ToUInt32(process.Id), out string appId, out _, out _, out _);
+
+ // Then try to get the shortcut (for display name and icon)
+ IShellItem shortcutItem = null;
+ try
+ {
+ appResolver.GetBestShortcutForAppID(appId, out shortcutItem);
+ }
+ catch
+ {
+ }
+
+ string displayName = null;
+ string iconPath = null;
+
+ // First we attempt to use display assets from the shortcut itself
+ if (shortcutItem != null)
+ {
+ try
+ {
+ shortcutItem.GetDisplayName(0, out displayName);
+
+ ((IShellItemImageFactory)shortcutItem).GetImage(new SIZE(48, 48), SIIGBF.IconOnly | SIIGBF.BiggerSizeOk, out IntPtr nativeHBitmap);
+
+ if (nativeHBitmap != IntPtr.Zero)
+ {
+ try
+ {
+ Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
+
+ if (IsAlphaBitmap(bmp, out var bmpData))
+ {
+ var alphaBitmap = GetAlphaBitmapFromBitmapData(bmpData);
+ iconPath = SaveIconToAppPath(alphaBitmap, appId);
+ }
+ else
+ {
+ iconPath = SaveIconToAppPath(bmp, appId);
+ }
+ }
+ catch
+ {
+ }
+
+ DeleteObject(nativeHBitmap);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ // If we didn't get a display name from shortcut
+ if (string.IsNullOrWhiteSpace(displayName))
+ {
+ // We use the one from the process
+ displayName = GetDisplayNameFromCurrentProcess(process);
+ }
+
+ // If we didn't get an icon from shortcut
+ if (string.IsNullOrWhiteSpace(iconPath))
+ {
+ // We use the one from the process
+ iconPath = ExtractAndObtainIconFromCurrentProcess(process, appId);
+ }
+
+ return new Win32AppInfo()
+ {
+ Aumid = appId,
+ DisplayName = displayName,
+ IconPath = iconPath
+ };
+ }
+
+ private static string GetDisplayNameFromCurrentProcess(Process process)
+ {
+ // If AssemblyTitle is set, use that
+ var assemblyTitleAttr = Assembly.GetEntryAssembly().GetCustomAttribute();
+ if (assemblyTitleAttr != null)
+ {
+ return assemblyTitleAttr.Title;
+ }
+
+ // Otherwise, fall back to process name
+ return process.ProcessName;
+ }
+
+ private static string ExtractAndObtainIconFromCurrentProcess(Process process, string appId)
+ {
+ return ExtractAndObtainIconFromPath(process.MainModule.FileName, appId);
+ }
+
+ private static string ExtractAndObtainIconFromPath(string pathToExtract, string appId)
+ {
+ try
+ {
+ // Extract the icon
+ var icon = Icon.ExtractAssociatedIcon(pathToExtract);
+
+ using (var bmp = icon.ToBitmap())
+ {
+ return SaveIconToAppPath(bmp, appId);
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static string SaveIconToAppPath(Bitmap bitmap, string appId)
+ {
+ try
+ {
+ var path = Path.Combine(GetAppDataFolderPath(appId), "Icon.png");
+
+ // Ensure the directories exist
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+ bitmap.Save(path, ImageFormat.Png);
+
+ return path;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Gets the app data folder path within the ToastNotificationManagerCompat folder, used for storing icon assets and any additional data.
+ ///
+ /// Returns a string of the absolute folder path.
+ public static string GetAppDataFolderPath(string appId)
+ {
+ string conciseAumid = appId.Contains("\\") ? GenerateGuid(appId) : appId;
+
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ToastNotificationManagerCompat", "Apps", conciseAumid);
+ }
+
+ // From https://stackoverflow.com/a/9291151
+ private static Bitmap GetAlphaBitmapFromBitmapData(BitmapData bmpData)
+ {
+ return new Bitmap(
+ bmpData.Width,
+ bmpData.Height,
+ bmpData.Stride,
+ PixelFormat.Format32bppArgb,
+ bmpData.Scan0);
+ }
+
+ // From https://stackoverflow.com/a/9291151
+ private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
+ {
+ var bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
+
+ bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
+
+ try
+ {
+ for (int y = 0; y <= bmpData.Height - 1; y++)
+ {
+ for (int x = 0; x <= bmpData.Width - 1; x++)
+ {
+ var pixelColor = Color.FromArgb(
+ Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));
+
+ if (pixelColor.A > 0 & pixelColor.A < 255)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ finally
+ {
+ bmp.UnlockBits(bmpData);
+ }
+
+ return false;
+ }
+
+ [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool DeleteObject([In] IntPtr hObject);
+
+ ///
+ /// From https://stackoverflow.com/a/41622689/1454643
+ /// Generates Guid based on String. Key assumption for this algorithm is that name is unique (across where it it's being used)
+ /// and if name byte length is less than 16 - it will be fetched directly into guid, if over 16 bytes - then we compute sha-1
+ /// hash from string and then pass it to guid.
+ ///
+ /// Unique name which is unique across where this guid will be used.
+ /// For example "706C7567-696E-7300-0000-000000000000" for "plugins"
+ public static string GenerateGuid(string name)
+ {
+ byte[] buf = Encoding.UTF8.GetBytes(name);
+ byte[] guid = new byte[16];
+ if (buf.Length < 16)
+ {
+ Array.Copy(buf, guid, buf.Length);
+ }
+ else
+ {
+ using (SHA1 sha1 = SHA1.Create())
+ {
+ byte[] hash = sha1.ComputeHash(buf);
+
+ // Hash is 20 bytes, but we need 16. We loose some of "uniqueness", but I doubt it will be fatal
+ Array.Copy(hash, guid, 16);
+ }
+ }
+
+ // Don't use Guid constructor, it tends to swap bytes. We want to preserve original string as hex dump.
+ string guidS = $"{guid[0]:X2}{guid[1]:X2}{guid[2]:X2}{guid[3]:X2}-{guid[4]:X2}{guid[5]:X2}-{guid[6]:X2}{guid[7]:X2}-{guid[8]:X2}{guid[9]:X2}-{guid[10]:X2}{guid[11]:X2}{guid[12]:X2}{guid[13]:X2}{guid[14]:X2}{guid[15]:X2}";
+
+ return guidS;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationHistoryCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationHistoryCompat.cs
new file mode 100644
index 00000000000..0dbcec4c1ac
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationHistoryCompat.cs
@@ -0,0 +1,101 @@
+// 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.
+
+#if WINDOWS_UWP
+
+using System.Collections.Generic;
+using Windows.UI.Notifications;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
+ ///
+ public class ToastNotificationHistoryCompat
+ {
+ private string _aumid;
+ private ToastNotificationHistory _history;
+
+ internal ToastNotificationHistoryCompat(string aumid)
+ {
+ _aumid = aumid;
+ _history = ToastNotificationManager.History;
+ }
+
+ ///
+ /// Removes all notifications sent by this app from action center.
+ ///
+ public void Clear()
+ {
+ if (_aumid != null)
+ {
+ _history.Clear(_aumid);
+ }
+ else
+ {
+ _history.Clear();
+ }
+ }
+
+ ///
+ /// Gets all notifications sent by this app that are currently still in Action Center.
+ ///
+ /// A collection of toasts.
+ public IReadOnlyList GetHistory()
+ {
+ return _aumid != null ? _history.GetHistory(_aumid) : _history.GetHistory();
+ }
+
+ ///
+ /// Removes an individual toast, with the specified tag label, from action center.
+ ///
+ /// The tag label of the toast notification to be removed.
+ public void Remove(string tag)
+ {
+ if (_aumid != null)
+ {
+ _history.Remove(tag, string.Empty, _aumid);
+ }
+ else
+ {
+ _history.Remove(tag);
+ }
+ }
+
+ ///
+ /// Removes a toast notification from the action using the notification's tag and group labels.
+ ///
+ /// The tag label of the toast notification to be removed.
+ /// The group label of the toast notification to be removed.
+ public void Remove(string tag, string group)
+ {
+ if (_aumid != null)
+ {
+ _history.Remove(tag, group, _aumid);
+ }
+ else
+ {
+ _history.Remove(tag, group);
+ }
+ }
+
+ ///
+ /// Removes a group of toast notifications, identified by the specified group label, from action center.
+ ///
+ /// The group label of the toast notifications to be removed.
+ public void RemoveGroup(string group)
+ {
+ if (_aumid != null)
+ {
+ _history.RemoveGroup(group, _aumid);
+ }
+ else
+ {
+ _history.RemoveGroup(group);
+ }
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs
new file mode 100644
index 00000000000..fd4f3929a23
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs
@@ -0,0 +1,478 @@
+// 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.
+
+#if WINDOWS_UWP
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
+using Windows.ApplicationModel;
+using Windows.Foundation.Collections;
+using Windows.UI.Notifications;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Provides access to sending and managing toast notifications. Works for all types of apps, even Win32 non-MSIX/sparse apps.
+ ///
+ public static class ToastNotificationManagerCompat
+ {
+#if WIN32
+ private const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated";
+
+ private const int CLASS_E_NOAGGREGATION = -2147221232;
+ private const int E_NOINTERFACE = -2147467262;
+ private const int CLSCTX_LOCAL_SERVER = 4;
+ private const int REGCLS_MULTIPLEUSE = 1;
+ private const int S_OK = 0;
+ private static readonly Guid IUnknownGuid = new Guid("00000000-0000-0000-C000-000000000046");
+
+ private static bool _registeredOnActivated;
+ private static List _onActivated = new List();
+
+ ///
+ /// Event that is triggered when a notification or notification button is clicked. Subscribe to this event in your app's initial startup code.
+ ///
+ public static event OnActivated OnActivated
+ {
+ add
+ {
+ lock (_onActivated)
+ {
+ if (!_registeredOnActivated)
+ {
+ // Desktop Bridge apps will dynamically register upon first subscription to event
+ try
+ {
+ CreateAndRegisterActivator();
+ }
+ catch (Exception ex)
+ {
+ _initializeEx = ex;
+ }
+ }
+
+ _onActivated.Add(value);
+ }
+ }
+
+ remove
+ {
+ lock (_onActivated)
+ {
+ _onActivated.Remove(value);
+ }
+ }
+ }
+
+ internal static void OnActivatedInternal(string args, Internal.InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] input, string aumid)
+ {
+ ValueSet userInput = new ValueSet();
+
+ if (input != null)
+ {
+ foreach (var val in input)
+ {
+ userInput.Add(val.Key, val.Value);
+ }
+ }
+
+ var e = new ToastNotificationActivatedEventArgsCompat()
+ {
+ Argument = args,
+ UserInput = userInput
+ };
+
+ OnActivated[] listeners;
+ lock (_onActivated)
+ {
+ listeners = _onActivated.ToArray();
+ }
+
+ foreach (var listener in listeners)
+ {
+ listener(e);
+ }
+ }
+
+ private static string _win32Aumid;
+ private static string _clsid;
+ private static Exception _initializeEx;
+
+ static ToastNotificationManagerCompat()
+ {
+ try
+ {
+ Initialize();
+ }
+ catch (Exception ex)
+ {
+ // We catch the exception so that things like subscribing to the event handler doesn't crash app
+ _initializeEx = ex;
+ }
+ }
+
+ private static void Initialize()
+ {
+ // If containerized
+ if (DesktopBridgeHelpers.IsContainerized())
+ {
+ // No need to do anything additional, already registered through manifest
+ return;
+ }
+
+ Win32AppInfo win32AppInfo = null;
+
+ // If sparse
+ if (DesktopBridgeHelpers.HasIdentity())
+ {
+ _win32Aumid = new ManifestHelper().GetAumidFromPackageManifest();
+ }
+ else
+ {
+ win32AppInfo = Win32AppInfo.Get();
+ _win32Aumid = win32AppInfo.Aumid;
+ }
+
+ // Create and register activator
+ var activatorType = CreateAndRegisterActivator();
+
+ // Register via registry
+ using (var rootKey = Registry.CurrentUser.CreateSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid))
+ {
+ // If they don't have identity, we need to specify the display assets
+ if (!DesktopBridgeHelpers.HasIdentity())
+ {
+ // Set the display name and icon uri
+ rootKey.SetValue("DisplayName", win32AppInfo.DisplayName);
+
+ if (win32AppInfo.IconPath != null)
+ {
+ rootKey.SetValue("IconUri", win32AppInfo.IconPath);
+ }
+ else
+ {
+ if (rootKey.GetValue("IconUri") != null)
+ {
+ rootKey.DeleteValue("IconUri");
+ }
+ }
+
+ // Background color only appears in the settings page, format is
+ // hex without leading #, like "FFDDDDDD"
+ rootKey.SetValue("IconBackgroundColor", "FFDDDDDD");
+ }
+
+ rootKey.SetValue("CustomActivator", string.Format("{{{0}}}", activatorType.GUID));
+ }
+ }
+
+ private static Type CreateActivatorType()
+ {
+ // https://stackoverflow.com/questions/24069352/c-sharp-typebuilder-generate-class-with-function-dynamically
+ // For .NET Core we use https://stackoverflow.com/questions/36937276/is-there-any-replace-of-assemblybuilder-definedynamicassembly-in-net-core
+ AssemblyName aName = new AssemblyName("DynamicComActivator");
+ AssemblyBuilder aBuilder = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
+
+ // For a single-module assembly, the module name is usually the assembly name plus an extension.
+ ModuleBuilder mb = aBuilder.DefineDynamicModule(aName.Name);
+
+ // Create class which extends NotificationActivator
+ TypeBuilder tb = mb.DefineType(
+ name: "MyNotificationActivator",
+ attr: TypeAttributes.Public,
+ parent: typeof(Internal.InternalNotificationActivator),
+ interfaces: new Type[0]);
+
+ if (DesktopBridgeHelpers.IsContainerized())
+ {
+ _clsid = new ManifestHelper().GetClsidFromPackageManifest();
+ }
+ else
+ {
+ _clsid = Win32AppInfo.GenerateGuid(_win32Aumid);
+ }
+
+ tb.SetCustomAttribute(new CustomAttributeBuilder(
+ con: typeof(GuidAttribute).GetConstructor(new Type[] { typeof(string) }),
+ constructorArgs: new object[] { _clsid }));
+
+ tb.SetCustomAttribute(new CustomAttributeBuilder(
+ con: typeof(ComVisibleAttribute).GetConstructor(new Type[] { typeof(bool) }),
+ constructorArgs: new object[] { true }));
+
+ tb.SetCustomAttribute(new CustomAttributeBuilder(
+#pragma warning disable CS0618 // Type or member is obsolete
+ con: typeof(ComSourceInterfacesAttribute).GetConstructor(new Type[] { typeof(Type) }),
+#pragma warning restore CS0618 // Type or member is obsolete
+ constructorArgs: new object[] { typeof(Internal.InternalNotificationActivator.INotificationActivationCallback) }));
+
+ tb.SetCustomAttribute(new CustomAttributeBuilder(
+ con: typeof(ClassInterfaceAttribute).GetConstructor(new Type[] { typeof(ClassInterfaceType) }),
+ constructorArgs: new object[] { ClassInterfaceType.None }));
+
+ return tb.CreateType();
+ }
+
+ private static Type CreateAndRegisterActivator()
+ {
+ var activatorType = CreateActivatorType();
+ RegisterActivator(activatorType);
+ _registeredOnActivated = true;
+ return activatorType;
+ }
+
+ private static void RegisterActivator(Type activatorType)
+ {
+ if (!DesktopBridgeHelpers.IsContainerized())
+ {
+ string exePath = Process.GetCurrentProcess().MainModule.FileName;
+ RegisterComServer(activatorType, exePath);
+ }
+
+ // Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest
+ var uuid = activatorType.GUID;
+ CoRegisterClassObject(
+ uuid,
+ new NotificationActivatorClassFactory(activatorType),
+ CLSCTX_LOCAL_SERVER,
+ REGCLS_MULTIPLEUSE,
+ out _);
+ }
+
+ private static void RegisterComServer(Type activatorType, string exePath)
+ {
+ // We register the EXE to start up when the notification is activated
+ string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", activatorType.GUID);
+ var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString);
+
+ // Include a flag so we know this was a toast activation and should wait for COM to process
+ // We also wrap EXE path in quotes for extra security
+ key.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
+ }
+
+ ///
+ /// Gets whether the current process was activated due to a toast activation. If so, the OnActivated event will be triggered soon after process launch.
+ ///
+ /// True if the current process was activated due to a toast activation, otherwise false.
+ public static bool WasCurrentProcessToastActivated()
+ {
+ return Environment.GetCommandLineArgs().Contains(TOAST_ACTIVATED_LAUNCH_ARG);
+ }
+
+ [ComImport]
+ [Guid("00000001-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IClassFactory
+ {
+ [PreserveSig]
+ int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
+
+ [PreserveSig]
+ int LockServer(bool fLock);
+ }
+
+ private class NotificationActivatorClassFactory : IClassFactory
+ {
+ private Type _activatorType;
+
+ public NotificationActivatorClassFactory(Type activatorType)
+ {
+ _activatorType = activatorType;
+ }
+
+ public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
+ {
+ ppvObject = IntPtr.Zero;
+
+ if (pUnkOuter != IntPtr.Zero)
+ {
+ Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
+ }
+
+ if (riid == _activatorType.GUID || riid == IUnknownGuid)
+ {
+ // Create the instance of the .NET object
+ ppvObject = Marshal.GetComInterfaceForObject(
+ Activator.CreateInstance(_activatorType),
+ typeof(Internal.InternalNotificationActivator.INotificationActivationCallback));
+ }
+ else
+ {
+ // The object that ppvObject points to does not support the
+ // interface identified by riid.
+ Marshal.ThrowExceptionForHR(E_NOINTERFACE);
+ }
+
+ return S_OK;
+ }
+
+ public int LockServer(bool fLock)
+ {
+ return S_OK;
+ }
+ }
+
+ [DllImport("ole32.dll")]
+ private static extern int CoRegisterClassObject(
+ [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
+ [MarshalAs(UnmanagedType.IUnknown)] object pUnk,
+ uint dwClsContext,
+ uint flags,
+ out uint lpdwRegister);
+#endif
+
+ ///
+ /// Creates a toast notifier.
+ ///
+ ///
+ public static ToastNotifier CreateToastNotifier()
+ {
+#if WIN32
+ if (_initializeEx != null)
+ {
+ throw _initializeEx;
+ }
+
+ if (DesktopBridgeHelpers.HasIdentity())
+ {
+ return ToastNotificationManager.CreateToastNotifier();
+ }
+ else
+ {
+ return ToastNotificationManager.CreateToastNotifier(_win32Aumid);
+ }
+#else
+ return ToastNotificationManager.CreateToastNotifier();
+#endif
+ }
+
+ ///
+ /// Gets the object.
+ ///
+ public static ToastNotificationHistoryCompat History
+ {
+ get
+ {
+#if WIN32
+ if (_initializeEx != null)
+ {
+ throw _initializeEx;
+ }
+
+ return new ToastNotificationHistoryCompat(DesktopBridgeHelpers.HasIdentity() ? null : _win32Aumid);
+#else
+ return new ToastNotificationHistoryCompat(null);
+#endif
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether http images can be used within toasts. This is true if running with package identity (UWP, MSIX, or sparse package).
+ ///
+ public static bool CanUseHttpImages
+ {
+ get
+ {
+#if WIN32
+ return DesktopBridgeHelpers.HasIdentity();
+#else
+ return true;
+#endif
+ }
+ }
+
+#if WIN32
+ ///
+ /// If you're not using MSIX, call this when your app is being uninstalled to properly clean up all notifications and notification-related resources. Note that this must be called from your app's main EXE (the one that you used notifications for) and not a separate uninstall EXE. If called from a MSIX app, this method no-ops.
+ ///
+ public static void Uninstall()
+ {
+ if (DesktopBridgeHelpers.IsContainerized())
+ {
+ // Packaged containerized apps automatically clean everything up already
+ return;
+ }
+
+ if (!DesktopBridgeHelpers.HasIdentity())
+ {
+ try
+ {
+ // Remove all scheduled notifications (do this first before clearing current notifications)
+ var notifier = CreateToastNotifier();
+ foreach (var scheduled in CreateToastNotifier().GetScheduledToastNotifications())
+ {
+ try
+ {
+ notifier.RemoveFromSchedule(scheduled);
+ }
+ catch
+ {
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ // Clear all current notifications
+ History.Clear();
+ }
+ catch
+ {
+ }
+ }
+
+ try
+ {
+ // Remove registry key
+ if (_win32Aumid != null)
+ {
+ Registry.CurrentUser.DeleteSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid);
+ }
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ if (_clsid != null)
+ {
+ Registry.CurrentUser.DeleteSubKey(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", _clsid));
+ }
+ }
+ catch
+ {
+ }
+
+ if (!DesktopBridgeHelpers.HasIdentity() && _win32Aumid != null)
+ {
+ try
+ {
+ // Delete any of the app files
+ var appDataFolderPath = Win32AppInfo.GetAppDataFolderPath(_win32Aumid);
+ if (Directory.Exists(appDataFolderPath))
+ {
+ Directory.Delete(appDataFolderPath, recursive: true);
+ }
+ }
+ catch
+ {
+ }
+ }
+ }
+#endif
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/IToastActivateableBuilder.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/IToastActivateableBuilder.cs
new file mode 100644
index 00000000000..6e62a0a7e8b
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/IToastActivateableBuilder.cs
@@ -0,0 +1,96 @@
+// 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 System;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// Interfaces for classes that can have activation info added to them.
+ ///
+ /// The type of the host object.
+ internal interface IToastActivateableBuilder
+ {
+ ///
+ /// Adds a key (without value) to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key.
+ /// The current instance of the object.
+ T AddArgument(string key);
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of the object.
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+#endif
+ T AddArgument(string key, string value);
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of the object.
+ T AddArgument(string key, int value);
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of the object.
+ T AddArgument(string key, double value);
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of the object.
+ T AddArgument(string key, float value);
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of the object.
+ T AddArgument(string key, bool value);
+
+#if !WINRT
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the content is clicked.
+ ///
+ /// The key for this value.
+ /// The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.
+ /// The current instance of the object.
+ T AddArgument(string key, Enum value);
+#endif
+
+ ///
+ /// Configures the content to use background activation when it is clicked.
+ ///
+ /// The current instance of the object.
+ T SetBackgroundActivation();
+
+ ///
+ /// Configures the content to use protocol activation when it is clicked.
+ ///
+ /// The protocol to launch.
+ /// The current instance of the object.
+ T SetProtocolActivation(Uri protocol);
+
+ ///
+ /// Configures the content to use protocol activation when it is clicked.
+ ///
+ /// The protocol to launch.
+ /// New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.
+ /// The current instance of the object.
+ T SetProtocolActivation(Uri protocol, string targetApplicationPfn);
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastArguments.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastArguments.cs
new file mode 100644
index 00000000000..f5dd09b95f4
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastArguments.cs
@@ -0,0 +1,462 @@
+// 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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Toolkit.Uwp.Notifications
+{
+ ///
+ /// A class that supports serializing simple key/value pairs into a format that's friendly for being used within toast notifications. The serialized format is similar to a query string, however optimized for being placed within an XML property (uses semicolons instead of ampersands since those don't need to be XML-escaped, doesn't url-encode all special characters since not being used within a URL, etc).
+ ///
+ public sealed class ToastArguments : IEnumerable>
+ {
+ private Dictionary _dictionary = new Dictionary();
+
+ internal ToastArguments Clone()
+ {
+ return new ToastArguments()
+ {
+ _dictionary = new Dictionary(_dictionary)
+ };
+ }
+
+#if !WINRT
+ ///
+ /// Gets the value of the specified key. Throws if the key could not be found.
+ ///
+ /// The key to find.
+ /// The value of the specified key.
+ public string this[string key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (TryGetValue(key, out string value))
+ {
+ return value;
+ }
+
+ throw new KeyNotFoundException($"A key with name '{key}' could not be found.");
+ }
+
+ set
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ _dictionary[key] = value;
+ }
+ }
+#endif
+
+ ///
+ /// Attempts to get the value of the specified key. If no key exists, returns false.
+ ///
+ /// The key to find.
+ /// The key's value will be written here if found.
+ /// True if found the key and set the value, otherwise false.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("found")]
+#endif
+ public bool TryGetValue(string key, out string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return _dictionary.TryGetValue(key, out value);
+ }
+
+#if !WINRT
+ ///
+ /// Attempts to get the value of the specified key. If no key exists, returns false.
+ ///
+ /// The enum to parse.
+ /// The key to find.
+ /// The key's value will be written here if found.
+ /// True if found the key and set the value, otherwise false.
+ public bool TryGetValue(string key, out T value)
+ where T : struct, Enum
+ {
+ if (TryGetValue(key, out string strValue))
+ {
+ return Enum.TryParse(strValue, out value);
+ }
+
+ value = default(T);
+ return false;
+ }
+#endif
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public string Get(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (_dictionary.TryGetValue(key, out string value))
+ {
+ return value;
+ }
+
+ throw new KeyNotFoundException();
+ }
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public int GetInt(string key)
+ {
+ return int.Parse(Get(key));
+ }
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public double GetDouble(string key)
+ {
+ return double.Parse(Get(key));
+ }
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public float GetFloat(string key)
+ {
+ return float.Parse(Get(key));
+ }
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public byte GetByte(string key)
+ {
+ return byte.Parse(Get(key));
+ }
+
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The key to get.
+ /// The value of the key.
+ public bool GetBool(string key)
+ {
+ return Get(key) == "1" ? true : false;
+ }
+
+#if !WINRT
+ ///
+ /// Gets the value of the specified key, or throws if key didn't exist.
+ ///
+ /// The enum to parse.
+ /// The key to get.
+ /// The value of the key.
+ public T GetEnum(string key)
+ where T : struct, Enum
+ {
+ if (TryGetValue(key, out T value))
+ {
+ return value;
+ }
+
+ throw new KeyNotFoundException();
+ }
+#endif
+
+ ///
+ /// Gets the number of key/value pairs contained in the toast arguments.
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ /// Adds a key. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The current object.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ _dictionary[key] = null;
+
+ return this;
+ }
+
+ ///
+ /// Adds a key and optional value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The optional value of the key.
+ /// The current object.
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ _dictionary[key] = value;
+
+ return this;
+ }
+
+ ///
+ /// Adds a key and value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The value of the key.
+ /// The current object.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key, int value)
+ {
+ return AddHelper(key, value);
+ }
+
+ ///
+ /// Adds a key and value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The value of the key.
+ /// The current object.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key, double value)
+ {
+ return AddHelper(key, value);
+ }
+
+ ///
+ /// Adds a key and value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The value of the key.
+ /// The current object.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key, float value)
+ {
+ return AddHelper(key, value);
+ }
+
+ ///
+ /// Adds a key and value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The value of the key.
+ /// The current object.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
+#endif
+ public ToastArguments Add(string key, bool value)
+ {
+ return Add(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
+ }
+
+#if !WINRT
+ ///
+ /// Adds a key and value. If there is an existing key, it is replaced.
+ ///
+ /// The key.
+ /// The value of the key. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.
+ /// The current object.
+ public ToastArguments Add(string key, Enum value)
+ {
+ return Add(key, (int)(object)value);
+ }
+#endif
+
+ private ToastArguments AddHelper(string key, object value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ _dictionary[key] = value.ToString();
+
+ return this;
+ }
+
+ ///
+ /// Determines if the specified key is present.
+ ///
+ /// The key to look for.
+ /// True if the key is present, otherwise false.
+ public bool Contains(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return _dictionary.ContainsKey(key);
+ }
+
+ ///
+ /// Determines if specified key and value are present.
+ ///
+ /// The key to look for.
+ /// The value to look for when the key has been matched.
+ /// True if the key and value were found, else false.
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("doesContain")]
+#endif
+ public bool Contains(string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return _dictionary.TryGetValue(key, out string actualValue) && actualValue == value;
+ }
+
+ ///
+ /// Removes the specified key and its associated value.
+ ///
+ /// The key to remove.
+ /// True if the key was removed, else false.
+ public bool Remove(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return _dictionary.Remove(key);
+ }
+
+ private static string Encode(string str)
+ {
+ return str
+ .Replace("%", "%25")
+ .Replace(";", "%3B")
+ .Replace("=", "%3D");
+ }
+
+ private static string Decode(string str)
+ {
+ return str
+ .Replace("%25", "%")
+ .Replace("%3B", ";")
+ .Replace("%3D", "=");
+ }
+
+ ///
+ /// Parses a string that was generated using ToastArguments into a object.
+ ///
+ /// The toast arguments string to deserialize.
+ /// The parsed toast arguments.
+ public static ToastArguments Parse(string toastArgumentsStr)
+ {
+ if (string.IsNullOrWhiteSpace(toastArgumentsStr))
+ {
+ return new ToastArguments();
+ }
+
+ string[] pairs = toastArgumentsStr.Split(';');
+
+ ToastArguments answer = new ToastArguments();
+
+ foreach (string pair in pairs)
+ {
+ string name;
+ string value;
+
+ int indexOfEquals = pair.IndexOf('=');
+
+ if (indexOfEquals == -1)
+ {
+ name = Decode(pair);
+ value = null;
+ }
+ else
+ {
+ name = Decode(pair.Substring(0, indexOfEquals));
+ value = Decode(pair.Substring(indexOfEquals + 1));
+ }
+
+ answer.Add(name, value);
+ }
+
+ return answer;
+ }
+
+ ///
+ /// Serializes the key-value pairs into a string that can be used within a toast notification.
+ ///
+ /// A string that can be used within a toast notification.
+ public sealed override string ToString()
+ {
+ return string.Join(Separator, this.Select(pair => EncodePair(pair.Key, pair.Value)));
+ }
+
+ internal static string EncodePair(string key, string value)
+ {
+ // Key
+ return Encode(key) +
+
+ // Write value if not null
+ ((value == null) ? string.Empty : ("=" + Encode(value)));
+ }
+
+ internal const string Separator = ";";
+
+ ///
+ /// Gets an enumerator to enumerate the arguments. Note that order of the arguments is NOT preserved.
+ ///
+ /// An enumeartor of the key/value pairs.
+ public IEnumerator> GetEnumerator()
+ {
+ return _dictionary.GetEnumerator();
+ }
+
+ ///
+ /// Gets an enumerator to enumerate the query string parameters.
+ ///
+ /// An enumeartor of the key/value pairs.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastButton.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastButton.cs
index 522a5c254c9..a564d274984 100644
--- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastButton.cs
+++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/ToastButton.cs
@@ -3,14 +3,23 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
namespace Microsoft.Toolkit.Uwp.Notifications
{
///
/// A button that the user can click on a Toast notification.
///
- public sealed class ToastButton : IToastButton
+ public sealed class ToastButton :
+#if !WINRT
+ IToastActivateableBuilder,
+#endif
+ IToastButton
{
+ private Dictionary _arguments = new Dictionary();
+
+ private bool _usingCustomArguments;
+
///
/// Initializes a new instance of the class.
///
@@ -30,6 +39,17 @@ public ToastButton(string content, string arguments)
Content = content;
Arguments = arguments;
+
+ _usingCustomArguments = arguments.Length > 0;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ToastButton()
+ {
+ // Arguments are required (we'll initialize to empty string which is fine).
+ Arguments = string.Empty;
}
///
@@ -41,7 +61,7 @@ public ToastButton(string content, string arguments)
/// Gets app-defined string of arguments that the app can later retrieve once it is
/// activated when the user clicks the button. Required
///
- public string Arguments { get; private set; }
+ public string Arguments { get; internal set; }
///
/// Gets or sets what type of activation this button will use when clicked. Defaults to Foreground.
@@ -71,6 +91,272 @@ public ToastButton(string content, string arguments)
///
public string HintActionId { get; set; }
+ ///
+ /// Sets the text to display on the button.
+ ///
+ /// The text to display on the button.
+ /// The current instance of the .
+ public ToastButton SetContent(string content)
+ {
+ Content = content;
+ return this;
+ }
+
+ ///
+ /// Adds a key (without value) to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key.
+ /// The current instance of
+ public ToastButton AddArgument(string key)
+ {
+ return AddArgumentHelper(key, null);
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [Windows.Foundation.Metadata.DefaultOverload]
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
+#endif
+ public ToastButton AddArgument(string key, string value)
+ {
+ return AddArgumentHelper(key, value);
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
+#endif
+ public ToastButton AddArgument(string key, int value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
+#endif
+ public ToastButton AddArgument(string key, double value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
+#endif
+ public ToastButton AddArgument(string key, float value)
+ {
+ return AddArgumentHelper(key, value.ToString());
+ }
+
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself.
+ /// The current instance of
+#if WINRT
+ [return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
+#endif
+ public ToastButton AddArgument(string key, bool value)
+ {
+ return AddArgumentHelper(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
+ }
+
+#if !WINRT
+ ///
+ /// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
+ ///
+ /// The key for this value.
+ /// The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.
+ /// The current instance of
+ public ToastButton AddArgument(string key, Enum value)
+ {
+ return AddArgumentHelper(key, ((int)(object)value).ToString());
+ }
+#endif
+
+ private ToastButton AddArgumentHelper(string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (_usingCustomArguments)
+ {
+ throw new InvalidOperationException("You cannot use the AddArgument methods if you've set the Arguments property. Use the default ToastButton constructor instead.");
+ }
+
+ if (ActivationType == ToastActivationType.Protocol)
+ {
+ throw new InvalidOperationException("You cannot use the AddArgument methods when using protocol activation.");
+ }
+
+ bool alreadyExists = _arguments.ContainsKey(key);
+
+ _arguments[key] = value;
+
+ Arguments = alreadyExists ? SerializeArgumentsHelper(_arguments) : AddArgumentHelper(Arguments, key, value);
+
+ return this;
+ }
+
+ private string SerializeArgumentsHelper(IDictionary arguments)
+ {
+ var args = new ToastArguments();
+
+ foreach (var a in arguments)
+ {
+ args.Add(a.Key, a.Value);
+ }
+
+ return args.ToString();
+ }
+
+ private string AddArgumentHelper(string existing, string key, string value)
+ {
+ string pair = ToastArguments.EncodePair(key, value);
+
+ if (string.IsNullOrEmpty(existing))
+ {
+ return pair;
+ }
+ else
+ {
+ return existing + ToastArguments.Separator + pair;
+ }
+ }
+
+ ///
+ /// Configures the button to launch the specified url when the button is clicked.
+ ///
+ /// The protocol to launch.
+ /// The current instance of
+ public ToastButton SetProtocolActivation(Uri protocol)
+ {
+ return SetProtocolActivation(protocol, default);
+ }
+
+ ///
+ /// Configures the button to launch the specified url when the button is clicked.
+ ///
+ /// The protocol to launch.
+ /// New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.
+ /// The current instance of
+ public ToastButton SetProtocolActivation(Uri protocol, string targetApplicationPfn)
+ {
+ if (_arguments.Count > 0)
+ {
+ throw new InvalidOperationException("SetProtocolActivation cannot be used in conjunction with AddArgument");
+ }
+
+ Arguments = protocol.ToString();
+ ActivationType = ToastActivationType.Protocol;
+
+ if (targetApplicationPfn != null)
+ {
+ if (ActivationOptions == null)
+ {
+ ActivationOptions = new ToastActivationOptions();
+ }
+
+ ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Configures the button to use background activation when the button is clicked.
+ ///
+ /// The current instance of
+ public ToastButton SetBackgroundActivation()
+ {
+ ActivationType = ToastActivationType.Background;
+ return this;
+ }
+
+ ///
+ /// Sets the behavior that the toast should use when the user invokes this button. Desktop-only, supported in builds 16251 or higher. New in Fall Creators Update.
+ ///
+ /// The behavior that the toast should use when the user invokes this button.
+ /// The current instance of
+ public ToastButton SetAfterActivationBehavior(ToastAfterActivationBehavior afterActivationBehavior)
+ {
+ if (ActivationOptions == null)
+ {
+ ActivationOptions = new ToastActivationOptions();
+ }
+
+ ActivationOptions.AfterActivationBehavior = afterActivationBehavior;
+
+ return this;
+ }
+
+ ///
+ /// Sets an identifier used in telemetry to identify your category of action. This should be something like "Delete", "Reply", or "Archive". In the upcoming toast telemetry dashboard in Dev Center, you will be able to view how frequently your actions are being clicked.
+ ///
+ /// An identifier used in telemetry to identify your category of action.
+ /// The current instance of
+ public ToastButton SetHintActionId(string actionId)
+ {
+ HintActionId = actionId;
+ return this;
+ }
+
+ ///
+ /// Sets an optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).
+ ///
+ /// An optional image icon for the button to display.
+ /// The current instance of
+ public ToastButton SetImageUri(Uri imageUri)
+ {
+ ImageUri = imageUri.ToString();
+ return this;
+ }
+
+ ///
+ /// Sets the ID of an existing in order to have this button display to the right of the input, achieving a quick reply scenario.
+ ///
+ /// The ID of an existing .
+ /// The current instance of
+ public ToastButton SetTextBoxId(string textBoxId)
+ {
+ TextBoxId = textBoxId;
+ return this;
+ }
+
+ internal bool CanAddArguments()
+ {
+ return ActivationType != ToastActivationType.Protocol && !_usingCustomArguments;
+ }
+
+ internal bool ContainsArgument(string key)
+ {
+ return _arguments.ContainsKey(key);
+ }
+
internal Element_ToastAction ConvertToElement()
{
var el = new Element_ToastAction()
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BackgroundTaskHelper/TestBackgroundTask.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BackgroundTaskHelper/TestBackgroundTask.cs
index a523d439fb7..ed2822e90de 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BackgroundTaskHelper/TestBackgroundTask.cs
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BackgroundTaskHelper/TestBackgroundTask.cs
@@ -4,7 +4,6 @@
using Microsoft.Toolkit.Uwp.Notifications;
using Windows.ApplicationModel.Background;
-using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
@@ -12,28 +11,9 @@ internal class TestBackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
- // Create content of the toast notification
- var toastContent = new ToastContent()
- {
- Scenario = ToastScenario.Default,
- Visual = new ToastVisual
- {
- BindingGeneric = new ToastBindingGeneric
- {
- Children =
- {
- new AdaptiveText
- {
- Text = "New toast notification (BackgroundTaskHelper)."
- }
- }
- }
- }
- };
-
- // Create & show toast notification
- var toastNotification = new ToastNotification(toastContent.GetXml());
- ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
+ new ToastContentBuilder()
+ .AddText("New toast notification (BackgroundTaskHelper).")
+ .Show();
}
}
}
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastCode.bind
index 652201abfc7..7d5a3bafa44 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastCode.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastCode.bind
@@ -1,14 +1,10 @@
private void PopToast()
{
// Generate the toast notification content and pop the toast
- ToastContent content = GenerateToastContent();
- ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
-}
-
-public static ToastContent GenerateToastContent()
-{
- var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
- .AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
+ new ToastContentBuilder()
+ .SetToastScenario(ToastScenario.Reminder)
+ .AddArgument("action", "viewEvent")
+ .AddArgument("eventId", 1983)
.AddText("Adaptive Tiles Meeting")
.AddText("Conf Room 2001 / Building 135")
.AddText("10:00 AM - 10:30 AM")
@@ -18,7 +14,6 @@ public static ToastContent GenerateToastContent()
("240", "4 hours"),
("1440", "1 day"))
.AddButton(new ToastButtonSnooze() { SelectionBoxId = "snoozeTime" })
- .AddButton(new ToastButtonDismiss());
-
- return builder.Content;
-}
\ No newline at end of file
+ .AddButton(new ToastButtonDismiss())
+ .Show();
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastPage.xaml.cs
index e1815e625ee..896795bec19 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastPage.xaml.cs
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Toast/ToastPage.xaml.cs
@@ -28,8 +28,10 @@ public ToastPage()
public static ToastContent GenerateToastContent()
{
- var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
- .AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
+ var builder = new ToastContentBuilder()
+ .SetToastScenario(ToastScenario.Reminder)
+ .AddArgument("action", "viewEvent")
+ .AddArgument("eventId", 1983)
.AddText("Adaptive Tiles Meeting")
.AddText("Conf Room 2001 / Building 135")
.AddText("10:00 AM - 10:30 AM")
@@ -54,7 +56,7 @@ private void ButtonPopToast_Click(object sender, RoutedEventArgs e)
private void PopToast()
{
- ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
+ ToastNotificationManagerCompat.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
}
private void Initialize()
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastCode.bind
index 0a9fcc65645..31d86950b79 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastCode.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastCode.bind
@@ -1,40 +1,11 @@
private void PopToast()
{
- // Generate the toast notification content and pop the toast
- ToastContent content = GenerateToastContent();
- ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
-}
-
-private async void PinTile()
-{
- SecondaryTile tile = new SecondaryTile(DateTime.Now.Ticks.ToString())
- {
- DisplayName = "WeatherSample",
- Arguments = "args"
- };
- tile.VisualElements.ShowNameOnSquare150x150Logo = true;
- tile.VisualElements.ShowNameOnSquare310x310Logo = true;
- tile.VisualElements.ShowNameOnWide310x150Logo = true;
- tile.VisualElements.Square150x150Logo = Constants.Square150x150Logo;
- tile.VisualElements.Wide310x150Logo = Constants.Wide310x150Logo;
- tile.VisualElements.Square310x310Logo = Constants.Square310x310Logo;
-
- if (!await tile.RequestCreateAsync())
- {
- return;
- }
-
- // Generate the tile notification content and update the tile
- TileContent content = GenerateTileContent();
- TileUpdateManager.CreateTileUpdaterForSecondaryTile(tile.TileId).Update(new TileNotification(content.GetXml()));
-}
-
-public static ToastContent GenerateToastContent()
-{
+ // Generate the toast notification content
ToastContentBuilder builder = new ToastContentBuilder();
// Include launch string so we know what to open when user clicks toast
- builder.AddToastActivationInfo("action=viewForecast&zip=98008", ToastActivationType.Foreground);
+ builder.AddArgument("action", "viewForecast");
+ builder.AddArgument("zip", 98008);
// We'll always have this summary text on our toast notification
// (it is required that your toast starts with a text element)
@@ -68,8 +39,33 @@ public static ToastContent GenerateToastContent()
// Set the base URI for the images, so we don't redundantly specify the entire path
builder.Content.Visual.BaseUri = new Uri("Assets/NotificationAssets/", UriKind.Relative);
- return builder.Content;
-}
+ // Show the toast
+ builder.Show();
+}
+
+private async void PinTile()
+{
+ SecondaryTile tile = new SecondaryTile(DateTime.Now.Ticks.ToString())
+ {
+ DisplayName = "WeatherSample",
+ Arguments = "args"
+ };
+ tile.VisualElements.ShowNameOnSquare150x150Logo = true;
+ tile.VisualElements.ShowNameOnSquare310x310Logo = true;
+ tile.VisualElements.ShowNameOnWide310x150Logo = true;
+ tile.VisualElements.Square150x150Logo = Constants.Square150x150Logo;
+ tile.VisualElements.Wide310x150Logo = Constants.Wide310x150Logo;
+ tile.VisualElements.Square310x310Logo = Constants.Square310x310Logo;
+
+ if (!await tile.RequestCreateAsync())
+ {
+ return;
+ }
+
+ // Generate the tile notification content and update the tile
+ TileContent content = GenerateTileContent();
+ TileUpdateManager.CreateTileUpdaterForSecondaryTile(tile.TileId).Update(new TileNotification(content.GetXml()));
+}
public static TileContent GenerateTileContent()
{
@@ -78,8 +74,8 @@ public static TileContent GenerateTileContent()
// Small Tile
builder.AddTile(Notifications.TileSize.Small)
.SetTextStacking(TileTextStacking.Center, Notifications.TileSize.Small)
- .AddText("Mon", hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
- .AddText("63°", hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
+ .AddText("Mon", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
+ .AddText("63°", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
// Medium Tile
builder.AddTile(Notifications.TileSize.Medium)
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastPage.xaml.cs
index e827a77ca91..3efea5c98d4 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastPage.xaml.cs
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToastPage.xaml.cs
@@ -29,7 +29,8 @@ public static ToastContent GenerateToastContent()
ToastContentBuilder builder = new ToastContentBuilder();
// Include launch string so we know what to open when user clicks toast
- builder.AddToastActivationInfo("action=viewForecast&zip=98008", ToastActivationType.Foreground);
+ builder.AddArgument("action", "viewForecast");
+ builder.AddArgument("zip", 98008);
// We'll always have this summary text on our toast notification
// (it is required that your toast starts with a text element)
@@ -73,8 +74,8 @@ public static TileContent GenerateTileContent()
// Small Tile
builder.AddTile(Notifications.TileSize.Small)
.SetTextStacking(TileTextStacking.Center, Notifications.TileSize.Small)
- .AddText("Mon", hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
- .AddText("63°", hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
+ .AddText("Mon", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
+ .AddText("63°", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
// Medium Tile
builder.AddTile(Notifications.TileSize.Medium)
@@ -285,7 +286,7 @@ private void ButtonPopToast_Click(object sender, RoutedEventArgs e)
private void PopToast()
{
- ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
+ ToastNotificationManagerCompat.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
}
private void Initialize()
diff --git a/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/TestBackgroundTask.cs b/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/TestBackgroundTask.cs
index 20682eceaf5..085295d1b57 100644
--- a/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/TestBackgroundTask.cs
+++ b/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/TestBackgroundTask.cs
@@ -33,7 +33,7 @@ public void Run(IBackgroundTaskInstance taskInstance)
// Create & show toast notification
var toastNotification = new ToastNotification(toastContent.GetXml());
- ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
+ ToastNotificationManagerCompat.CreateToastNotifier().Show(toastNotification);
}
}
}
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/LockScreenLogo.scale-200.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/LockScreenLogo.scale-200.png
new file mode 100644
index 00000000000..735f57adb5d
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/LockScreenLogo.scale-200.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/SplashScreen.scale-200.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/SplashScreen.scale-200.png
new file mode 100644
index 00000000000..023e7f1feda
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/SplashScreen.scale-200.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square150x150Logo.scale-200.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square150x150Logo.scale-200.png
new file mode 100644
index 00000000000..af49fec1a54
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square150x150Logo.scale-200.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.scale-200.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.scale-200.png
new file mode 100644
index 00000000000..ce342a2ec8a
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.scale-200.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.targetsize-24_altform-unplated.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 00000000000..f6c02ce97e0
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/StoreLogo.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/StoreLogo.png
new file mode 100644
index 00000000000..7385b56c0e4
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/StoreLogo.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Wide310x150Logo.scale-200.png b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Wide310x150Logo.scale-200.png
new file mode 100644
index 00000000000..288995b397f
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Wide310x150Logo.scale-200.png differ
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject.wapproj b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject.wapproj
new file mode 100644
index 00000000000..5453cbfaa55
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject.wapproj
@@ -0,0 +1,80 @@
+
+
+
+ 15.0
+
+
+
+ Debug
+ x86
+
+
+ Release
+ x86
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+ Debug
+ ARM
+
+
+ Release
+ ARM
+
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
+
+ Debug
+ AnyCPU
+
+
+ Release
+ AnyCPU
+
+
+
+ $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
+
+
+
+ 54349ab0-9e41-4aa6-849c-ec9ce80cdd2a
+ 10.0.18362.0
+ 10.0.17763.0
+ en-US
+ false
+ ..\Microsoft.Toolkit.Win32.WpfCore.SampleApp\Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Package.appxmanifest b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Package.appxmanifest
new file mode 100644
index 00000000000..7ff22287bad
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Package.appxmanifest
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+ Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject
+ aleader
+ Images\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml
new file mode 100644
index 00000000000..8cde026f920
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml.cs b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml.cs
new file mode 100644
index 00000000000..67d9c4e29f5
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/App.xaml.cs
@@ -0,0 +1,151 @@
+// 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 System.Windows;
+using Microsoft.Toolkit.Uwp.Notifications;
+
+namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ // Listen to toast notification activations
+ ToastNotificationManagerCompat.OnActivated += this.ToastNotificationManagerCompat_OnActivated;
+
+ // If launched from a toast
+ if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
+ {
+ // Our OnActivated callback will run after this completes,
+ // and will show a window if necessary.
+ }
+ else
+ {
+ // Show the window
+ // In App.xaml, be sure to remove the StartupUri so that a window doesn't
+ // get created by default, since we're creating windows ourselves (and sometimes we
+ // don't want to create a window if handling a background activation).
+ new MainWindow().Show();
+ }
+ }
+
+ private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ // If arguments are empty, that means the app title within Action Center was clicked.
+ if (e.Argument.Length == 0)
+ {
+ OpenWindowIfNeeded();
+ return;
+ }
+
+ // Parse the toast arguments
+ ToastArguments args = ToastArguments.Parse(e.Argument);
+
+ int conversationId = args.GetInt("conversationId");
+
+ // If no specific action, view the conversation
+ if (args.TryGetValue("action", out MyToastActions action))
+ {
+ switch (action)
+ {
+ // View conversation
+ case MyToastActions.ViewConversation:
+
+ // Make sure we have a window open and in foreground
+ OpenWindowIfNeeded();
+
+ // And then show the conversation
+ (Current.Windows[0] as MainWindow).ShowConversation(conversationId);
+
+ break;
+
+ // Open the image
+ case MyToastActions.ViewImage:
+
+ // The URL retrieved from the toast args
+ string imageUrl = args["imageUrl"];
+
+ // Make sure we have a window open and in foreground
+ OpenWindowIfNeeded();
+
+ // And then show the image
+ (Current.Windows[0] as MainWindow).ShowImage(imageUrl);
+
+ break;
+
+ // Background: Quick reply to the conversation
+ case MyToastActions.Reply:
+
+ // Get the response the user typed
+ string msg = e.UserInput["tbReply"] as string;
+
+ // And send this message
+ ShowToast("Message sent: " + msg + "\nconversationId: " + conversationId);
+
+ // If there's no windows open, exit the app
+ if (Current.Windows.Count == 0)
+ {
+ Current.Shutdown();
+ }
+
+ break;
+
+ // Background: Send a like
+ case MyToastActions.Like:
+
+ ShowToast($"Like sent to conversation {conversationId}!");
+
+ // If there's no windows open, exit the app
+ if (Current.Windows.Count == 0)
+ {
+ Current.Shutdown();
+ }
+
+ break;
+
+ default:
+
+ OpenWindowIfNeeded();
+
+ break;
+ }
+ }
+ });
+ }
+
+ private void OpenWindowIfNeeded()
+ {
+ // Make sure we have a window open (in case user clicked toast while app closed)
+ if (App.Current.Windows.Count == 0)
+ {
+ new MainWindow().Show();
+ }
+
+ // Activate the window, bringing it to focus
+ App.Current.Windows[0].Activate();
+
+ // And make sure to maximize the window too, in case it was currently minimized
+ App.Current.Windows[0].WindowState = WindowState.Normal;
+ }
+
+ protected override void OnExit(ExitEventArgs e)
+ {
+ // If your app has an installer, you should call this when your app is uninstalled. Otherwise, if your app is a "portable app" and you no longer need notifications while the app is closed, you can call this upon exit.
+ // ToastNotificationManagerCompat.Uninstall();
+ }
+
+ private void ShowToast(string msg)
+ {
+ // Construct the visuals of the toast and show it!
+ new ToastContentBuilder()
+ .AddText(msg)
+ .Show();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/AssemblyInfo.cs b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/AssemblyInfo.cs
new file mode 100644
index 00000000000..0f6e98fda31
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+// 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 System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
+]
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml
new file mode 100644
index 00000000000..cf32e0773b6
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml.cs b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml.cs
new file mode 100644
index 00000000000..9f106fb59c7
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MainWindow.xaml.cs
@@ -0,0 +1,148 @@
+// 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 System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+using Microsoft.Toolkit.Uwp.Notifications;
+
+namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ // IMPORTANT: Look at App.xaml.cs for handling toast activation
+ }
+
+ private async void ButtonPopToast_Click(object sender, RoutedEventArgs e)
+ {
+ string title = "Andrew sent you a picture";
+ string content = "Check this out, The Enchantments!";
+ string image = "https://picsum.photos/364/202?image=883";
+ int conversationId = 5;
+
+ // Construct the toast content and show it!
+ new ToastContentBuilder()
+
+ // Arguments that are returned when the user clicks the toast or a button
+ .AddArgument("action", MyToastActions.ViewConversation)
+ .AddArgument("conversationId", conversationId)
+
+ // Visual content
+ .AddText(title)
+ .AddText(content)
+ .AddInlineImage(new Uri(await DownloadImageToDisk(image)))
+ .AddAppLogoOverride(new Uri(await DownloadImageToDisk("https://unsplash.it/64?image=1005")), ToastGenericAppLogoCrop.Circle)
+
+ // Text box for typing a reply
+ .AddInputTextBox("tbReply", "Type a reply")
+
+ // Buttons
+ .AddButton(new ToastButton()
+ .SetContent("Reply")
+ .AddArgument("action", MyToastActions.Reply)
+ .SetBackgroundActivation())
+
+ .AddButton(new ToastButton()
+ .SetContent("Like")
+ .AddArgument("action", MyToastActions.Like)
+ .SetBackgroundActivation())
+
+ .AddButton(new ToastButton()
+ .SetContent("View")
+ .AddArgument("action", MyToastActions.ViewImage)
+ .AddArgument("imageUrl", image))
+
+ // And show the toast!
+ .Show();
+ }
+
+ private static bool _hasPerformedCleanup;
+
+ private static async Task DownloadImageToDisk(string httpImage)
+ {
+ // Toasts can live for up to 3 days, so we cache images for up to 3 days.
+ // Note that this is a very simple cache that doesn't account for space usage, so
+ // this could easily consume a lot of space within the span of 3 days.
+ try
+ {
+ if (ToastNotificationManagerCompat.CanUseHttpImages)
+ {
+ return httpImage;
+ }
+
+ var directory = Directory.CreateDirectory(System.IO.Path.GetTempPath() + "WindowsNotifications.DesktopToasts.Images");
+
+ if (!_hasPerformedCleanup)
+ {
+ // First time we run, we'll perform cleanup of old images
+ _hasPerformedCleanup = true;
+
+ foreach (var d in directory.EnumerateDirectories())
+ {
+ if (d.CreationTimeUtc.Date < DateTime.UtcNow.Date.AddDays(-3))
+ {
+ d.Delete(true);
+ }
+ }
+ }
+
+ var dayDirectory = directory.CreateSubdirectory(DateTime.UtcNow.Day.ToString());
+ string imagePath = dayDirectory.FullName + "\\" + (uint)httpImage.GetHashCode();
+
+ if (File.Exists(imagePath))
+ {
+ return imagePath;
+ }
+
+ HttpClient c = new HttpClient();
+ using (var stream = await c.GetStreamAsync(httpImage))
+ {
+ using (var fileStream = File.OpenWrite(imagePath))
+ {
+ stream.CopyTo(fileStream);
+ }
+ }
+
+ return imagePath;
+ }
+ catch
+ {
+ return string.Empty;
+ }
+ }
+
+ internal void ShowConversation(int conversationId)
+ {
+ ContentBody.Content = new TextBlock()
+ {
+ Text = "You've just opened conversation " + conversationId,
+ FontWeight = FontWeights.Bold
+ };
+ }
+
+ internal void ShowImage(string imageUrl)
+ {
+ ContentBody.Content = new Image()
+ {
+ Source = new BitmapImage(new Uri(imageUrl))
+ };
+ }
+
+ private void ButtonClearToasts_Click(object sender, RoutedEventArgs e)
+ {
+ ToastNotificationManagerCompat.History.Clear();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj
new file mode 100644
index 00000000000..aa80e5130f5
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj
@@ -0,0 +1,15 @@
+
+
+
+ WinExe
+ netcoreapp3.1
+ true
+ ToolkitIcon.ico
+ false
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MyToastActions.cs b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MyToastActions.cs
new file mode 100644
index 00000000000..13711b8ffdf
--- /dev/null
+++ b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/MyToastActions.cs
@@ -0,0 +1,29 @@
+// 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 Microsoft.Toolkit.Win32.WpfCore.SampleApp
+{
+ public enum MyToastActions
+ {
+ ///
+ /// View the conversation
+ ///
+ ViewConversation,
+
+ ///
+ /// Inline reply to a message
+ ///
+ Reply,
+
+ ///
+ /// Like a message
+ ///
+ Like,
+
+ ///
+ /// View the image included in the message
+ ///
+ ViewImage
+ }
+}
diff --git a/Microsoft.Toolkit.Win32.WpfCore.SampleApp/ToolkitIcon.ico b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/ToolkitIcon.ico
new file mode 100644
index 00000000000..e3aaf6fccfb
Binary files /dev/null and b/Microsoft.Toolkit.Win32.WpfCore.SampleApp/ToolkitIcon.ico differ
diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt
index 359e4249972..ec7322617dd 100644
--- a/ThirdPartyNotices.txt
+++ b/ThirdPartyNotices.txt
@@ -10,6 +10,7 @@ This project incorporates components from the projects listed below. The origina
3. lbugnion/mvvmlight commit 4cbf77c (https://github.com/lbugnion/mvvmlight), from which some APIs from the `Microsoft.Toolkit.Mvvm` package take inspiration from.
4. PrivateObject/PrivateType (https://github.com/microsoft/testfx/tree/664ac7c2ac9dbfbee9d2a0ef560cfd72449dfe34/src/TestFramework/Extension.Desktop), included in UnitTests.
5. QuinnDamerell/UniversalMarkdown (https://github.com/QuinnDamerell/UniversalMarkdown) contributed by Quinn Damerell and Paul Bartrum for the MarkdownTextBlock control, relicensed to this .NET Foundation project under the MIT license upon contribution in https://github.com/windows-toolkit/WindowsCommunityToolkit/pull/772.
+6. qmatteoq/DesktopBridgeHelpers commit e278153 (https://github.com/qmatteoq/DesktopBridgeHelpers), contributed by Matteo Pagani to identify if running with identity in DesktopNotificationManagerCompat.cs and DesktopBridgeHelpers.cs, relicensed to this .NET Foundation project under the MIT license upon contribution in https://github.com/windows-toolkit/WindowsCommunityToolkit/pull/3457.
%% PedroLamas/DeferredEvents NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -112,4 +113,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
-END OF PrivateOject/PrivateType NOTICES AND INFORMATION
+END OF PrivateOject/PrivateType NOTICES AND INFORMATION
\ No newline at end of file
diff --git a/UnitTests/UnitTests.Notifications.Shared/TestToastArguments.cs b/UnitTests/UnitTests.Notifications.Shared/TestToastArguments.cs
new file mode 100644
index 00000000000..d7d9abec910
--- /dev/null
+++ b/UnitTests/UnitTests.Notifications.Shared/TestToastArguments.cs
@@ -0,0 +1,510 @@
+// 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 System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Toolkit.Uwp.Notifications;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace UnitTests.Notifications
+{
+ [TestClass]
+ public class TestToastArguments
+ {
+ [TestMethod]
+ public void TestAddExceptions_NullName()
+ {
+ ToastArguments query = new ToastArguments();
+
+ try
+ {
+ query.Add(null, "value");
+ }
+ catch (ArgumentNullException)
+ {
+ return;
+ }
+
+ Assert.Fail("Adding null name shouldn't be allowed.");
+ }
+
+ [TestMethod]
+ public void TestParsing()
+ {
+ AssertParse(new ToastArguments(), string.Empty);
+ AssertParse(new ToastArguments(), " ");
+ AssertParse(new ToastArguments(), "\n");
+ AssertParse(new ToastArguments(), "\t \n");
+ AssertParse(new ToastArguments(), null);
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "isBook" }
+ }, "isBook");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "isBook" },
+ { "isRead" }
+ }, "isBook;isRead");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "isBook" },
+ { "isRead" },
+ { "isLiked" }
+ }, "isBook;isRead;isLiked");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "name", "Andrew" }
+ }, "name=Andrew");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "name", "Andrew" },
+ { "isAdult" }
+ }, "name=Andrew;isAdult");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "name", "Andrew" },
+ { "isAdult" }
+ }, "isAdult;name=Andrew");
+
+ AssertParse(
+ new ToastArguments()
+ {
+ { "name", "Andrew" },
+ { "age", "22" }
+ }, "age=22;name=Andrew");
+ }
+
+ [TestMethod]
+ public void TestToString_ExactString()
+ {
+ Assert.AreEqual(string.Empty, new ToastArguments().ToString());
+
+ Assert.AreEqual("isBook", new ToastArguments()
+ {
+ { "isBook" }
+ }.ToString());
+
+ Assert.AreEqual("name=Andrew", new ToastArguments()
+ {
+ { "name", "Andrew" }
+ }.ToString());
+ }
+
+ [TestMethod]
+ public void TestSpecialCharacters()
+ {
+ Assert.AreEqual("full name=Andrew Leader", new ToastArguments()
+ {
+ { "full name", "Andrew Leader" }
+ }.ToString());
+
+ Assert.AreEqual("name%3Bcompany=Andrew%3BMicrosoft", new ToastArguments()
+ {
+ { "name;company", "Andrew;Microsoft" }
+ }.ToString());
+
+ Assert.AreEqual("name/company=Andrew/Microsoft", new ToastArguments()
+ {
+ { "name/company", "Andrew/Microsoft" }
+ }.ToString());
+
+ Assert.AreEqual("message=Dinner?", new ToastArguments()
+ {
+ { "message", "Dinner?" }
+ }.ToString());
+
+ Assert.AreEqual("message=to: Andrew", new ToastArguments()
+ {
+ { "message", "to: Andrew" }
+ }.ToString());
+
+ Assert.AreEqual("email=andrew@live.com", new ToastArguments()
+ {
+ { "email", "andrew@live.com" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=food%3Dyummy", new ToastArguments()
+ {
+ { "messsage", "food=yummy" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=$$$", new ToastArguments()
+ {
+ { "messsage", "$$$" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=-_.!~*'()", new ToastArguments()
+ {
+ { "messsage", "-_.!~*'()" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=this & that", new ToastArguments()
+ {
+ { "messsage", "this & that" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=20%25 off!", new ToastArguments()
+ {
+ { "messsage", "20% off!" }
+ }.ToString());
+
+ Assert.AreEqual("messsage=Nonsense %2526 %2525 %253D", new ToastArguments()
+ {
+ { "messsage", "Nonsense %26 %25 %3D" }
+ }.ToString());
+ }
+
+ [TestMethod]
+ public void TestDecoding()
+ {
+ AssertDecode("Hello world", "Hello world");
+
+ AssertDecode(";/?:@&=+$%", "%3B/?:@&%3D+$%25");
+ AssertDecode("-_.!~*'()", "-_.!~*'()");
+ }
+
+#if !WINRT
+ [TestMethod]
+ public void TestIndexer_NullException()
+ {
+ try
+ {
+ string val = new ToastArguments()[null];
+ }
+ catch (ArgumentNullException)
+ {
+ return;
+ }
+
+ Assert.Fail("Exception should have been thrown.");
+ }
+
+ [TestMethod]
+ public void TestIndexer_NotFoundException()
+ {
+ try
+ {
+ var args = new ToastArguments()
+ {
+ { "name", "Andrew" }
+ };
+
+ _ = args["Name"];
+ }
+ catch (KeyNotFoundException)
+ {
+ return;
+ }
+
+ Assert.Fail("Exception should have been thrown.");
+ }
+
+ [TestMethod]
+ public void TestIndexer()
+ {
+ AssertIndexer(null, "isBook;name=Andrew", "isBook");
+
+ AssertIndexer("Andrew", "isBook;name=Andrew", "name");
+
+ AssertIndexer("Andrew", "count=2;name=Andrew", "name");
+ }
+#endif
+
+ [TestMethod]
+ public void TestRemove_OnlyKey()
+ {
+ ToastArguments qs = new ToastArguments()
+ {
+ { "name", "Andrew" },
+ { "age", "22" }
+ };
+
+ Assert.IsTrue(qs.Remove("age"));
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "name", "Andrew" }
+ }, qs);
+
+ Assert.IsFalse(qs.Remove("age"));
+ }
+
+ [TestMethod]
+ public void TestContains()
+ {
+ ToastArguments qs = new ToastArguments();
+
+ Assert.IsFalse(qs.Contains("name"));
+ Assert.IsFalse(qs.Contains("name", "Andrew"));
+
+ qs.Add("isBook");
+
+ Assert.IsFalse(qs.Contains("name"));
+ Assert.IsFalse(qs.Contains("name", "Andrew"));
+
+ Assert.IsTrue(qs.Contains("isBook"));
+ Assert.IsTrue(qs.Contains("isBook", null));
+ Assert.IsFalse(qs.Contains("isBook", "True"));
+
+ qs.Add("isBook", "True");
+
+ Assert.IsTrue(qs.Contains("isBook"));
+ Assert.IsFalse(qs.Contains("isBook", null));
+ Assert.IsTrue(qs.Contains("isBook", "True"));
+
+ qs.Add("name", "Andrew");
+
+ Assert.IsTrue(qs.Contains("name"));
+ Assert.IsFalse(qs.Contains("name", null)); // Value doesn't exist
+ Assert.IsTrue(qs.Contains("name", "Andrew"));
+ Assert.IsFalse(qs.Contains("Name", "Andrew")); // Wrong case on name
+ Assert.IsFalse(qs.Contains("name", "andrew")); // Wrong case on value
+ Assert.IsFalse(qs.Contains("Name")); // Wrong case on name
+ }
+
+ [TestMethod]
+ public void TestAdd()
+ {
+ ToastArguments qs = new ToastArguments();
+
+ qs.Add("name", "Andrew");
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "name", "Andrew" }
+ }, qs);
+
+ qs.Add("age", "22");
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "name", "Andrew" },
+ { "age", "22" }
+ }, qs);
+
+ qs.Add("name", "Lei");
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "name", "Lei" },
+ { "age", "22" }
+ }, qs);
+
+ string nullStr = null;
+ qs.Add("name", nullStr);
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "name" },
+ { "age", "22" }
+ }, qs);
+ }
+
+ [TestMethod]
+ public void TestEnumerator()
+ {
+ KeyValuePair[] parameters = ToastArguments.Parse("name=Andrew;age=22;isOld").ToArray();
+
+ Assert.AreEqual(3, parameters.Length);
+ Assert.AreEqual(1, parameters.Count(i => i.Key.Equals("name")));
+ Assert.AreEqual(1, parameters.Count(i => i.Key.Equals("age")));
+ Assert.AreEqual(1, parameters.Count(i => i.Key.Equals("isOld")));
+ Assert.IsTrue(parameters.Any(i => i.Key.Equals("name") && i.Value.Equals("Andrew")));
+ Assert.IsTrue(parameters.Any(i => i.Key.Equals("age") && i.Value.Equals("22")));
+ Assert.IsTrue(parameters.Any(i => i.Key.Equals("isOld") && i.Value == null));
+ }
+
+ [TestMethod]
+ public void TestCount()
+ {
+ ToastArguments qs = new ToastArguments();
+
+ Assert.AreEqual(0, qs.Count);
+
+ qs.Add("name", "Andrew");
+
+ Assert.AreEqual(1, qs.Count);
+
+ qs.Add("age", "22");
+
+ Assert.AreEqual(2, qs.Count);
+
+ qs.Remove("age");
+
+ Assert.AreEqual(1, qs.Count);
+
+ qs.Remove("name");
+
+ Assert.AreEqual(0, qs.Count);
+ }
+
+ [TestMethod]
+ public void TestStronglyTyped()
+ {
+ ToastArguments args = new ToastArguments()
+ .Add("isAdult", true)
+ .Add("isPremium", false)
+ .Add("age", 22)
+ .Add("level", 0)
+ .Add("gpa", 3.97)
+ .Add("percent", 97.3f);
+
+#if !WINRT
+ args.Add("activationKind", ToastActivationType.Background);
+#endif
+
+ AssertEqual(
+ new ToastArguments()
+ {
+ { "isAdult", "1" },
+ { "isPremium", "0" },
+ { "age", "22" },
+ { "level", "0" },
+ { "gpa", "3.97" },
+ { "percent", "97.3" },
+#if !WINRT
+ { "activationKind", "1" }
+#endif
+ }, args);
+
+ Assert.AreEqual(true, args.GetBool("isAdult"));
+ Assert.AreEqual("1", args.Get("isAdult"));
+
+ Assert.AreEqual(false, args.GetBool("isPremium"));
+ Assert.AreEqual("0", args.Get("isPremium"));
+
+ Assert.AreEqual(22, args.GetInt("age"));
+ Assert.AreEqual(22d, args.GetDouble("age"));
+ Assert.AreEqual(22f, args.GetFloat("age"));
+ Assert.AreEqual("22", args.Get("age"));
+
+ Assert.AreEqual(0, args.GetInt("level"));
+
+ Assert.AreEqual(3.97d, args.GetDouble("gpa"));
+
+ Assert.AreEqual(97.3f, args.GetFloat("percent"));
+
+#if !WINRT
+ Assert.AreEqual(ToastActivationType.Background, args.GetEnum("activationKind"));
+
+ if (args.TryGetValue("activationKind", out ToastActivationType activationType))
+ {
+ Assert.AreEqual(ToastActivationType.Background, activationType);
+ }
+ else
+ {
+ Assert.Fail("TryGetValue as enum failed");
+ }
+
+ // Trying to get enum that isn't an enum should return false
+ Assert.IsFalse(args.TryGetValue("percent", out activationType));
+#endif
+
+ // After serializing and deserializing, the same should work
+ args = ToastArguments.Parse(args.ToString());
+
+ Assert.AreEqual(true, args.GetBool("isAdult"));
+ Assert.AreEqual("1", args.Get("isAdult"));
+
+ Assert.AreEqual(false, args.GetBool("isPremium"));
+ Assert.AreEqual("0", args.Get("isPremium"));
+
+ Assert.AreEqual(22, args.GetInt("age"));
+ Assert.AreEqual(22d, args.GetDouble("age"));
+ Assert.AreEqual(22f, args.GetFloat("age"));
+ Assert.AreEqual("22", args.Get("age"));
+
+ Assert.AreEqual(0, args.GetInt("level"));
+
+ Assert.AreEqual(3.97d, args.GetDouble("gpa"));
+
+ Assert.AreEqual(97.3f, args.GetFloat("percent"));
+
+#if !WINRT
+ Assert.AreEqual(ToastActivationType.Background, args.GetEnum("activationKind"));
+
+ if (args.TryGetValue("activationKind", out activationType))
+ {
+ Assert.AreEqual(ToastActivationType.Background, activationType);
+ }
+ else
+ {
+ Assert.Fail("TryGetValue as enum failed");
+ }
+
+ // Trying to get enum that isn't an enum should return false
+ Assert.IsFalse(args.TryGetValue("percent", out activationType));
+#endif
+ }
+
+#if !WINRT
+ private static void AssertIndexer(string expected, string queryString, string paramName)
+ {
+ ToastArguments q = ToastArguments.Parse(queryString);
+
+ Assert.AreEqual(expected, q[paramName]);
+ }
+#endif
+
+ private static void AssertDecode(string expected, string encoded)
+ {
+ Assert.AreEqual(expected, ToastArguments.Parse("message=" + encoded).Get("message"));
+ }
+
+ private static void AssertParse(ToastArguments expected, string inputQueryString)
+ {
+ Assert.IsTrue(IsSame(expected, ToastArguments.Parse(inputQueryString)), "Expected: " + expected + "\nActual: " + inputQueryString);
+ }
+
+ private static void AssertEqual(ToastArguments expected, ToastArguments actual)
+ {
+ Assert.IsTrue(IsSame(expected, actual), "Expected: " + expected + "\nActual: " + actual);
+
+ Assert.IsTrue(IsSame(expected, ToastArguments.Parse(actual.ToString())), "After serializing and parsing actual, result changed.\n\nExpected: " + expected + "\nActual: " + ToastArguments.Parse(actual.ToString()));
+ Assert.IsTrue(IsSame(ToastArguments.Parse(expected.ToString()), actual), "After serializing and parsing expected, result changed.\n\nExpected: " + ToastArguments.Parse(expected.ToString()) + "\nActual: " + actual);
+ Assert.IsTrue(IsSame(ToastArguments.Parse(expected.ToString()), ToastArguments.Parse(actual.ToString())), "After serializing and parsing both, result changed.\n\nExpected: " + ToastArguments.Parse(expected.ToString()) + "\nActual: " + ToastArguments.Parse(actual.ToString()));
+ }
+
+ private static bool IsSame(ToastArguments expected, ToastArguments actual)
+ {
+ if (expected.Count != actual.Count)
+ {
+ return false;
+ }
+
+ foreach (var pair in expected)
+ {
+ if (!actual.Contains(pair.Key))
+ {
+ return false;
+ }
+
+ if (actual.Get(pair.Key) != pair.Value)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/UnitTests.Notifications.Shared/TestToastContentBuilder.cs b/UnitTests/UnitTests.Notifications.Shared/TestToastContentBuilder.cs
index 83a28ef286c..74af322dee5 100644
--- a/UnitTests/UnitTests.Notifications.Shared/TestToastContentBuilder.cs
+++ b/UnitTests/UnitTests.Notifications.Shared/TestToastContentBuilder.cs
@@ -47,6 +47,27 @@ public void AddHeaderTest_WithExpectedArgs_ReturnSelfWithHeaderAdded()
Assert.AreEqual(testToastArguments, builder.Content.Header.Arguments);
}
+ [TestMethod]
+ public void AddHeaderTest_WithExpectedArgsAndToastArguments_ReturnSelfWithHeaderAdded()
+ {
+ // Arrange
+ string testToastHeaderId = "Test Header ID";
+ string testToastTitle = "Test Toast Title";
+ ToastArguments testToastArguments = new ToastArguments()
+ .Add("arg1", 5)
+ .Add("arg2", "tacos");
+
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder.AddHeader(testToastHeaderId, testToastTitle, testToastArguments);
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual(testToastHeaderId, builder.Content.Header.Id);
+ Assert.AreEqual(testToastTitle, builder.Content.Header.Title);
+ Assert.AreEqual(testToastArguments.ToString(), builder.Content.Header.Arguments);
+ }
+
[TestMethod]
public void AddToastActivationInfoTest_WithExpectedArgs_ReturnSelfWithActivationInfoAdded()
{
@@ -64,6 +85,345 @@ public void AddToastActivationInfoTest_WithExpectedArgs_ReturnSelfWithActivation
Assert.AreEqual(testToastActivationType, builder.Content.ActivationType);
}
+ [TestMethod]
+ public void AddArgumentTest_Basic_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddArgument("userId", 542)
+ .AddArgument("name", "Andrew");
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("userId=542;name=Andrew", builder.Content.Launch);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_NoValue_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddArgument("isPurelyInformational");
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("isPurelyInformational", builder.Content.Launch);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_Escaping_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddArgument("user;Id", "andrew=leader%26bares");
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("user%3BId=andrew%3Dleader%2526bares", builder.Content.Launch);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_Replacing_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddArgument("userId", 542)
+ .AddArgument("name", "Andrew")
+ .AddArgument("userId", 601);
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("userId=601;name=Andrew", builder.Content.Launch);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_Generic_ReturnSelfWithArgumentsAdded()
+ {
+ // Arrange
+ const string userIdKey = "userId";
+ const int userIdValue = 542;
+
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddButton(new ToastButton()
+ .SetContent("Accept")
+ .AddArgument("action", "accept")
+ .SetBackgroundActivation())
+ .AddButton(new ToastButtonSnooze())
+ .AddButton("View", ToastActivationType.Protocol, "https://msn.com")
+
+ // Add generic arguments halfway through (should be applied to existing buttons and to any subsequent buttons added later)
+ .AddArgument(userIdKey, userIdValue)
+
+ .AddButton(new ToastButton()
+ .SetContent("Decline")
+ .AddArgument("action", "decline")
+ .SetBackgroundActivation())
+ .AddButton(new ToastButton()
+ .SetContent("Report")
+ .SetProtocolActivation(new Uri("https://microsoft.com")));
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+
+ // Top level arguments should be present
+ Assert.AreEqual("userId=542", builder.Content.Launch);
+
+ // All foreground/background activation buttons should have received generic arguments. Protocol and system activation buttons shouldn't have had any arguments changed.
+ var actions = builder.Content.Actions as ToastActionsCustom;
+
+ var button1 = actions.Buttons[0] as ToastButton;
+ Assert.AreEqual("Accept", button1.Content);
+ Assert.AreEqual("action=accept;userId=542", button1.Arguments);
+
+ var button2 = actions.Buttons[1];
+ Assert.IsInstanceOfType(button2, typeof(ToastButtonSnooze));
+
+ var button3 = actions.Buttons[2] as ToastButton;
+ Assert.AreEqual("View", button3.Content);
+ Assert.AreEqual("https://msn.com", button3.Arguments);
+
+ var button4 = actions.Buttons[3] as ToastButton;
+ Assert.AreEqual("Decline", button4.Content);
+ Assert.AreEqual("action=decline;userId=542", button4.Arguments);
+
+ var button5 = actions.Buttons[4] as ToastButton;
+ Assert.AreEqual("Report", button5.Content);
+ Assert.AreEqual("https://microsoft.com/", button5.Arguments);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_ReplacingWithinButton_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddButton(new ToastButton()
+ .SetContent("Accept")
+ .AddArgument("action", "accept")
+ .AddArgument("userId", 601)
+ .SetBackgroundActivation())
+
+ // Add generic arguments halfway through (in this case shouldn't overwrite anything)
+ .AddArgument("userId", 542)
+
+ .AddButton(new ToastButton()
+ .SetContent("Decline")
+ .AddArgument("action", "decline")
+ .AddArgument("userId", 601)
+ .SetBackgroundActivation());
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+
+ // Top level arguments should be present
+ Assert.AreEqual("userId=542", builder.Content.Launch);
+
+ // Buttons should have overridden the generic userId
+ var actions = builder.Content.Actions as ToastActionsCustom;
+
+ var button1 = actions.Buttons[0] as ToastButton;
+ Assert.AreEqual("Accept", button1.Content);
+ Assert.AreEqual("action=accept;userId=601", button1.Arguments);
+
+ var button2 = actions.Buttons[1] as ToastButton;
+ Assert.AreEqual("Decline", button2.Content);
+ Assert.AreEqual("action=decline;userId=601", button2.Arguments);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_AvoidModifyingCustomButtons_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddToastActivationInfo("myCustomLaunchStr", ToastActivationType.Foreground)
+
+ .AddButton("Accept", ToastActivationType.Background, "myAcceptStr")
+
+ // userId shouldn't be added to any of these except view
+ .AddArgument("userId", 542)
+
+ .AddButton("Decline", ToastActivationType.Background, "myDeclineStr")
+
+ .AddButton(new ToastButton()
+ .SetContent("View")
+ .AddArgument("action", "view"));
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+
+ // Top level arguments should be the custom string since user set that
+ Assert.AreEqual("myCustomLaunchStr", builder.Content.Launch);
+
+ // Buttons should have their custom strings except the last
+ var actions = builder.Content.Actions as ToastActionsCustom;
+
+ var button1 = actions.Buttons[0] as ToastButton;
+ Assert.AreEqual("Accept", button1.Content);
+ Assert.AreEqual("myAcceptStr", button1.Arguments);
+
+ var button2 = actions.Buttons[1] as ToastButton;
+ Assert.AreEqual("Decline", button2.Content);
+ Assert.AreEqual("myDeclineStr", button2.Arguments);
+
+ var button3 = actions.Buttons[2] as ToastButton;
+ Assert.AreEqual("View", button3.Content);
+ Assert.AreEqual("action=view;userId=542", button3.Arguments);
+ }
+
+ [TestMethod]
+ public void AddArgumentTest_BackgroundActivation_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddArgument("userId", 542)
+ .SetBackgroundActivation();
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("userId=542", builder.Content.Launch);
+ Assert.AreEqual(ToastActivationType.Background, builder.Content.ActivationType);
+ }
+
+ [TestMethod]
+ public void SetProtocolActivationTest_ReturnSelfWithArgumentsAdded()
+ {
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder
+ .AddButton(new ToastButton()
+ .SetContent("Accept")
+ .AddArgument("action", "accept")
+ .SetBackgroundActivation())
+
+ .AddArgument("userId", 542)
+
+ .SetProtocolActivation(new Uri("https://msn.com/"))
+
+ .AddArgument("name", "Andrew")
+
+ .AddButton(new ToastButton()
+ .SetContent("Decline")
+ .AddArgument("action", "decline")
+ .SetBackgroundActivation());
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreEqual("https://msn.com/", builder.Content.Launch);
+ Assert.AreEqual(ToastActivationType.Protocol, builder.Content.ActivationType);
+
+ var actions = builder.Content.Actions as ToastActionsCustom;
+
+ var button1 = actions.Buttons[0] as ToastButton;
+ Assert.AreEqual("Accept", button1.Content);
+ Assert.AreEqual("action=accept;userId=542;name=Andrew", button1.Arguments);
+
+ var button2 = actions.Buttons[1] as ToastButton;
+ Assert.AreEqual("Decline", button2.Content);
+ Assert.AreEqual("action=decline;userId=542;name=Andrew", button2.Arguments);
+ }
+
+ [TestMethod]
+ public void ToastButtonBuilders_General_ReturnSelf()
+ {
+ ToastButton button = new ToastButton();
+ ToastButton anotherReference = button
+ .SetContent("View")
+ .AddArgument("action", "view")
+ .AddArgument("imageId", 601);
+
+ Assert.AreSame(button, anotherReference);
+ Assert.AreEqual("View", button.Content);
+ Assert.AreEqual("action=view;imageId=601", button.Arguments);
+ Assert.AreEqual(ToastActivationType.Foreground, button.ActivationType);
+ }
+
+ [TestMethod]
+ public void ToastButtonBuilders_AllProperties_ReturnSelf()
+ {
+ ToastButton button = new ToastButton();
+ ToastButton anotherReference = button
+ .SetContent("View")
+ .SetImageUri(new Uri("ms-appx:///Assets/view.png"))
+ .AddArgument("action", "view")
+ .SetBackgroundActivation()
+ .SetAfterActivationBehavior(ToastAfterActivationBehavior.PendingUpdate)
+ .SetHintActionId("viewImage");
+
+ Assert.AreSame(button, anotherReference);
+ Assert.AreEqual("View", button.Content);
+ Assert.AreEqual("action=view", button.Arguments);
+ Assert.AreEqual("ms-appx:///Assets/view.png", button.ImageUri);
+ Assert.AreEqual(ToastActivationType.Background, button.ActivationType);
+ Assert.AreEqual(ToastAfterActivationBehavior.PendingUpdate, button.ActivationOptions.AfterActivationBehavior);
+ Assert.AreEqual("viewImage", button.HintActionId);
+ }
+
+ [TestMethod]
+ public void ToastButtonBuilders_ProtocolActivation_ReturnSelf()
+ {
+ ToastButton button = new ToastButton();
+ ToastButton anotherReference = button
+ .SetContent("View")
+ .SetProtocolActivation(new Uri("https://msn.com"));
+
+ Assert.AreSame(button, anotherReference);
+ Assert.AreEqual("View", button.Content);
+ Assert.AreEqual("https://msn.com/", button.Arguments);
+ Assert.AreEqual(ToastActivationType.Protocol, button.ActivationType);
+ }
+
+ [TestMethod]
+ public void ToastButtonBuilders_ProtocolActivationWithPfn_ReturnSelf()
+ {
+ ToastButton button = new ToastButton();
+ ToastButton anotherReference = button
+ .SetContent("View")
+ .SetProtocolActivation(new Uri("https://msn.com"), "MyPfn");
+
+ Assert.AreSame(button, anotherReference);
+ Assert.AreEqual("View", button.Content);
+ Assert.AreEqual("https://msn.com/", button.Arguments);
+ Assert.AreEqual(ToastActivationType.Protocol, button.ActivationType);
+ Assert.AreEqual("MyPfn", button.ActivationOptions.ProtocolActivationTargetApplicationPfn);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(InvalidOperationException))]
+ public void ToastButtonBuilders_InvalidProtocolAfterArguments_ReturnSelf()
+ {
+ new ToastButton()
+ .SetContent("View")
+ .AddArgument("action", "view")
+ .SetProtocolActivation(new Uri("https://msn.com"));
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(InvalidOperationException))]
+ public void ToastButtonBuilders_InvalidArgumentsAfterProtocol_ReturnSelf()
+ {
+ new ToastButton()
+ .SetContent("View")
+ .SetProtocolActivation(new Uri("https://msn.com"))
+ .AddArgument("action", "view");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(InvalidOperationException))]
+ public void ToastButtonBuilders_InvalidArgumentsAfterCustomArguments_ReturnSelf()
+ {
+ var button = new ToastButton("View", "viewArgs");
+
+ button.AddArgument("action", "view");
+ }
+
[TestMethod]
public void SetToastDurationTest_WithCustomToastDuration_ReturnSelfWithCustomToastDurationSet()
{
@@ -128,6 +488,24 @@ public void AddAudioTest_WithFullArgs_ReturnSelfWithCustomAudioAddedWithAllOptio
Assert.AreEqual(testToastAudioSilent, builder.Content.Audio.Silent);
}
+ [TestMethod]
+ public void AddAudioTest_WithAudioObject_ReturnSelfWithCustomAudioAdded()
+ {
+ // Arrange
+ var audio = new ToastAudio()
+ {
+ Silent = true
+ };
+
+ // Act
+ ToastContentBuilder builder = new ToastContentBuilder();
+ ToastContentBuilder anotherReference = builder.AddAudio(audio);
+
+ // Assert
+ Assert.AreSame(builder, anotherReference);
+ Assert.AreSame(audio, builder.Content.Audio);
+ }
+
[TestMethod]
public void AddAttributionTextTest_WithSimpleText_ReturnSelfWithCustomAttributionTextAdded()
{
@@ -395,12 +773,14 @@ public void AddTextTest_WithTextAndFullOptions_ReturnSelfWithTextAndAllOptionsAd
var text = builder.Content.Visual.BindingGeneric.Children.First() as AdaptiveText;
Assert.AreEqual(testText, (string)text.Text);
- Assert.AreEqual(testStyle, text.HintStyle);
- Assert.AreEqual(testWrapHint, text.HintWrap);
Assert.AreEqual(testHintMaxLine, text.HintMaxLines);
- Assert.AreEqual(testHintMinLine, text.HintMinLines);
- Assert.AreEqual(testAlign, text.HintAlign);
Assert.AreEqual(testLanguage, text.Language);
+
+ // These values should still be the default values, since they aren't used for top-level text
+ Assert.AreEqual(AdaptiveTextStyle.Default, text.HintStyle);
+ Assert.IsNull(text.HintWrap);
+ Assert.IsNull(text.HintMinLines);
+ Assert.AreEqual(AdaptiveTextAlign.Default, text.HintAlign);
}
[TestMethod]
diff --git a/UnitTests/UnitTests.Notifications.Shared/UnitTests.Notifications.Shared.projitems b/UnitTests/UnitTests.Notifications.Shared/UnitTests.Notifications.Shared.projitems
index cb3dc791458..e3eda81fc0e 100644
--- a/UnitTests/UnitTests.Notifications.Shared/UnitTests.Notifications.Shared.projitems
+++ b/UnitTests/UnitTests.Notifications.Shared/UnitTests.Notifications.Shared.projitems
@@ -11,6 +11,7 @@
+
diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln
index 8a84a30043d..b16526502a3 100644
--- a/Windows Community Toolkit.sln
+++ b/Windows Community Toolkit.sln
@@ -117,6 +117,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj", "{67FE47A0-CA93-4680-B770-A0A48C1DBC40}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Win32.WpfCore.SampleApp", "Microsoft.Toolkit.Win32.WpfCore.SampleApp\Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj", "{0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}"
+EndProject
+Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject", "Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject\Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject.wapproj", "{54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITests.App", "UITests\UITests.App\UITests.App.csproj", "{05C83067-FA46-45E2-BEC4-EDEE84AD18D0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UITests", "UITests", "{6FAA1CFE-3368-4FD2-9DBD-F4700F69174C}"
@@ -824,6 +828,66 @@ Global
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.ActiveCfg = Release|x86
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Build.0 = Release|x86
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Deploy.0 = Release|x86
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|ARM.Build.0 = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|x64.Build.0 = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Debug|x86.Build.0 = Debug|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|Any CPU.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|Any CPU.Build.0 = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|ARM.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|ARM64.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|x64.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Native|x86.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|ARM.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|ARM.Build.0 = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|ARM64.Build.0 = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|x64.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|x64.Build.0 = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|x86.ActiveCfg = Release|Any CPU
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405}.Release|x86.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM.Build.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM.Deploy.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|ARM64.Deploy.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x64.Build.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x64.Deploy.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x86.Build.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Debug|x86.Deploy.0 = Debug|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|Any CPU.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|Any CPU.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|Any CPU.Deploy.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|ARM.ActiveCfg = Release|ARM
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|ARM64.ActiveCfg = Release|ARM64
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|x64.ActiveCfg = Release|x64
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Native|x86.ActiveCfg = Release|x86
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM.Deploy.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM64.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|ARM64.Deploy.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x64.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x64.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x64.Deploy.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x86.ActiveCfg = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x86.Build.0 = Release|Any CPU
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A}.Release|x86.Deploy.0 = Release|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -979,6 +1043,8 @@ Global
{C79029AF-2E9B-4466-BAC4-1A41B281EAE6} = {B30036C4-D514-4E5B-A323-587A061772CE}
{804D0681-52F6-4E61-864A-699F0AB44B20} = {C79029AF-2E9B-4466-BAC4-1A41B281EAE6}
{88C6FFBE-322D-4CEA-842B-B2CB281D357D} = {CFA75BE0-5A44-45DE-8114-426A605B062B}
+ {0037E4C9-7AF3-4ADD-8156-5AEFA6C36405} = {9AD30620-667D-433C-9961-8D885EE7B762}
+ {54349AB0-9E41-4AA6-849C-EC9CE80CDD2A} = {9AD30620-667D-433C-9961-8D885EE7B762}
{6FEDF199-B052-49DD-8F3E-2A9224998E0F} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC}
{67FE47A0-CA93-4680-B770-A0A48C1DBC40} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC}
{05C83067-FA46-45E2-BEC4-EDEE84AD18D0} = {6FAA1CFE-3368-4FD2-9DBD-F4700F69174C}