2021-02-14 11:32:08 +03:00
// Licensed to the .NET Foundation under one or more agreements.
2020-10-30 20:48:24 +03:00
// 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 ;
2020-10-29 17:40:35 +03:00
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
2021-03-19 01:31:02 +03:00
namespace CommunityToolkit.WinUI.Notifications
2020-10-29 17:40:35 +03:00
{
/// <summary>
2020-10-30 19:13:31 +03:00
/// 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).
2020-10-29 17:40:35 +03:00
/// </summary>
public sealed class ToastArguments : IEnumerable < KeyValuePair < string , string > >
{
private Dictionary < string , string > _dictionary = new Dictionary < string , string > ( ) ;
2020-11-06 02:12:32 +03:00
internal ToastArguments Clone ( )
{
return new ToastArguments ( )
{
_dictionary = new Dictionary < string , string > ( _dictionary )
} ;
}
2020-10-29 17:40:35 +03:00
#if ! WINRT
/// <summary>
/// Gets the value of the specified key. Throws <see cref="KeyNotFoundException"/> if the key could not be found.
/// </summary>
/// <param name="key">The key to find.</param>
/// <returns>The value of the specified key.</returns>
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
/// <summary>
/// Attempts to get the value of the specified key. If no key exists, returns false.
/// </summary>
/// <param name="key">The key to find.</param>
/// <param name="value">The key's value will be written here if found.</param>
/// <returns>True if found the key and set the value, otherwise false.</returns>
#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 ) ;
}
2020-10-30 19:13:31 +03:00
#if ! WINRT
2020-10-29 18:16:35 +03:00
/// <summary>
/// Attempts to get the value of the specified key. If no key exists, returns false.
/// </summary>
/// <typeparam name="T">The enum to parse.</typeparam>
/// <param name="key">The key to find.</param>
/// <param name="value">The key's value will be written here if found.</param>
/// <returns>True if found the key and set the value, otherwise false.</returns>
public bool TryGetValue < T > ( 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 ;
}
2020-10-30 19:13:31 +03:00
#endif
2020-10-29 18:16:35 +03:00
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
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 ( ) ;
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public int GetInt ( string key )
{
return int . Parse ( Get ( key ) ) ;
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public double GetDouble ( string key )
{
return double . Parse ( Get ( key ) ) ;
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public float GetFloat ( string key )
{
return float . Parse ( Get ( key ) ) ;
}
2020-10-30 19:13:31 +03:00
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public byte GetByte ( string key )
{
return byte . Parse ( Get ( key ) ) ;
}
2020-10-29 18:16:35 +03:00
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public bool GetBool ( string key )
{
return Get ( key ) = = "1" ? true : false ;
}
2020-10-30 19:13:31 +03:00
#if ! WINRT
2020-10-29 18:16:35 +03:00
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <typeparam name="T">The enum to parse.</typeparam>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public T GetEnum < T > ( string key )
where T : struct , Enum
{
if ( TryGetValue ( key , out T value ) )
{
return value ;
}
throw new KeyNotFoundException ( ) ;
}
2020-10-30 19:13:31 +03:00
#endif
2020-10-29 18:16:35 +03:00
2020-10-29 17:40:35 +03:00
/// <summary>
/// Gets the number of key/value pairs contained in the toast arguments.
/// </summary>
public int Count = > _dictionary . Count ;
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key. If there is an existing key, it is replaced.
2020-10-29 17:40:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
2020-10-30 19:13:31 +03:00
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key )
2020-10-29 17:40:35 +03:00
{
if ( key = = null )
{
throw new ArgumentNullException ( nameof ( key ) ) ;
}
_dictionary [ key ] = null ;
2020-10-30 19:13:31 +03:00
return this ;
2020-10-29 17:40:35 +03:00
}
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and optional value. If there is an existing key, it is replaced.
2020-10-29 17:40:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
2020-10-29 18:16:35 +03:00
/// <param name="value">The optional value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-10-30 19:13:31 +03:00
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , string value )
2020-10-29 17:40:35 +03:00
{
if ( key = = null )
{
throw new ArgumentNullException ( nameof ( key ) ) ;
}
_dictionary [ key ] = value ;
2020-10-29 18:16:35 +03:00
return this ;
}
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and value. If there is an existing key, it is replaced.
2020-10-29 18:16:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-10-30 19:13:31 +03:00
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , int value )
2020-10-29 18:16:35 +03:00
{
2020-11-06 02:12:32 +03:00
return AddHelper ( key , value ) ;
2020-10-29 18:16:35 +03:00
}
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and value. If there is an existing key, it is replaced.
2020-10-29 18:16:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-10-30 19:13:31 +03:00
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , double value )
2020-10-29 18:16:35 +03:00
{
2020-11-06 02:12:32 +03:00
return AddHelper ( key , value ) ;
2020-10-29 18:16:35 +03:00
}
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and value. If there is an existing key, it is replaced.
2020-10-29 18:16:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-10-30 19:13:31 +03:00
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , float value )
2020-10-29 18:16:35 +03:00
{
2020-11-06 02:12:32 +03:00
return AddHelper ( key , value ) ;
2020-10-29 18:16:35 +03:00
}
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and value. If there is an existing key, it is replaced.
2020-10-29 18:16:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-10-30 19:13:31 +03:00
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , bool value )
2020-10-29 18:16:35 +03:00
{
2020-11-06 02:12:32 +03:00
return Add ( key , value ? "1" : "0" ) ; // Encode as 1 or 0 to save string space
2020-10-29 18:16:35 +03:00
}
2020-10-30 19:13:31 +03:00
#if ! WINRT
2020-10-29 18:16:35 +03:00
/// <summary>
2020-11-06 02:12:32 +03:00
/// Adds a key and value. If there is an existing key, it is replaced.
2020-10-29 18:16:35 +03:00
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">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.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
2020-11-06 02:12:32 +03:00
public ToastArguments Add ( string key , Enum value )
2020-10-29 18:16:35 +03:00
{
2020-11-06 02:12:32 +03:00
return Add ( key , ( int ) ( object ) value ) ;
2020-10-29 18:16:35 +03:00
}
2020-10-30 19:13:31 +03:00
#endif
2020-10-29 18:16:35 +03:00
2020-11-06 02:12:32 +03:00
private ToastArguments AddHelper ( string key , object value )
2020-10-29 18:16:35 +03:00
{
if ( key = = null )
{
throw new ArgumentNullException ( nameof ( key ) ) ;
}
_dictionary [ key ] = value . ToString ( ) ;
return this ;
2020-10-29 17:40:35 +03:00
}
/// <summary>
/// Determines if the specified key is present.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>True if the key is present, otherwise false.</returns>
public bool Contains ( string key )
{
if ( key = = null )
{
throw new ArgumentNullException ( nameof ( key ) ) ;
}
return _dictionary . ContainsKey ( key ) ;
}
/// <summary>
/// Determines if specified key and value are present.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <param name="value">The value to look for when the key has been matched.</param>
/// <returns>True if the key and value were found, else false.</returns>
#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 ;
}
/// <summary>
/// Removes the specified key and its associated value.
/// </summary>
/// <param name="key">The key to remove.</param>
/// <returns>True if the key was removed, else false.</returns>
public bool Remove ( string key )
{
if ( key = = null )
{
throw new ArgumentNullException ( nameof ( key ) ) ;
}
return _dictionary . Remove ( key ) ;
}
2020-10-30 19:13:31 +03:00
private static string Encode ( string str )
2020-10-29 17:40:35 +03:00
{
2020-10-30 19:13:31 +03:00
return str
. Replace ( "%" , "%25" )
. Replace ( ";" , "%3B" )
. Replace ( "=" , "%3D" ) ;
2020-10-29 17:40:35 +03:00
}
2020-10-30 19:13:31 +03:00
private static string Decode ( string str )
2020-10-29 17:40:35 +03:00
{
2020-10-30 19:13:31 +03:00
return str
. Replace ( "%25" , "%" )
. Replace ( "%3B" , ";" )
. Replace ( "%3D" , "=" ) ;
2020-10-29 17:40:35 +03:00
}
/// <summary>
/// Parses a string that was generated using ToastArguments into a <see cref="ToastArguments"/> object.
/// </summary>
/// <param name="toastArgumentsStr">The toast arguments string to deserialize.</param>
/// <returns>The parsed toast arguments.</returns>
public static ToastArguments Parse ( string toastArgumentsStr )
{
if ( string . IsNullOrWhiteSpace ( toastArgumentsStr ) )
{
return new ToastArguments ( ) ;
}
2020-10-30 19:13:31 +03:00
string [ ] pairs = toastArgumentsStr . Split ( ';' ) ;
2020-10-29 17:40:35 +03:00
ToastArguments answer = new ToastArguments ( ) ;
foreach ( string pair in pairs )
{
string name ;
string value ;
int indexOfEquals = pair . IndexOf ( '=' ) ;
if ( indexOfEquals = = - 1 )
{
2020-10-30 19:13:31 +03:00
name = Decode ( pair ) ;
2020-10-29 17:40:35 +03:00
value = null ;
}
else
{
2020-10-30 19:13:31 +03:00
name = Decode ( pair . Substring ( 0 , indexOfEquals ) ) ;
value = Decode ( pair . Substring ( indexOfEquals + 1 ) ) ;
2020-10-29 17:40:35 +03:00
}
answer . Add ( name , value ) ;
}
return answer ;
}
/// <summary>
/// Serializes the key-value pairs into a string that can be used within a toast notification.
/// </summary>
/// <returns>A string that can be used within a toast notification.</returns>
public sealed override string ToString ( )
{
2020-11-06 02:12:32 +03:00
return string . Join ( Separator , this . Select ( pair = > EncodePair ( pair . Key , pair . Value ) ) ) ;
}
2020-10-29 17:40:35 +03:00
2020-11-06 02:12:32 +03:00
internal static string EncodePair ( string key , string value )
{
// Key
return Encode ( key ) +
2020-10-29 17:40:35 +03:00
2020-11-06 02:12:32 +03:00
// Write value if not null
( ( value = = null ) ? string . Empty : ( "=" + Encode ( value ) ) ) ;
2020-10-29 17:40:35 +03:00
}
2020-11-06 02:12:32 +03:00
internal const string Separator = ";" ;
2020-10-29 17:40:35 +03:00
/// <summary>
/// Gets an enumerator to enumerate the arguments. Note that order of the arguments is NOT preserved.
/// </summary>
/// <returns>An enumeartor of the key/value pairs.</returns>
public IEnumerator < KeyValuePair < string , string > > GetEnumerator ( )
{
return _dictionary . GetEnumerator ( ) ;
}
/// <summary>
/// Gets an enumerator to enumerate the query string parameters.
/// </summary>
/// <returns>An enumeartor of the key/value pairs.</returns>
IEnumerator IEnumerable . GetEnumerator ( )
{
return GetEnumerator ( ) ;
}
}
2021-02-14 11:32:05 +03:00
}