From 0547948f136cf5d2c2e9c735d2efe2cfb6603f4e Mon Sep 17 00:00:00 2001 From: levimatheri Date: Thu, 11 Nov 2021 12:10:57 -0700 Subject: [PATCH 1/5] Closes #153. Add additional FCM options for Android Notification (#203) * Add additional FCM Android notification options * Add additional FCM Android notification options --- .../Messaging/MessageTest.cs | 67 ++++ .../Messaging/AndroidNotification.cs | 326 ++++++++++++++++++ .../FirebaseAdmin/Messaging/LightSettings.cs | 123 +++++++ .../Messaging/LightSettingsColor.cs | 34 ++ .../Messaging/Util/TimeConverter.cs | 42 +++ 5 files changed, 592 insertions(+) create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/Util/TimeConverter.cs diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 51141c26..81d84d04 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -274,6 +274,23 @@ public void AndroidConfig() BodyLocKey = "body-loc-key", BodyLocArgs = new List() { "arg3", "arg4" }, ChannelId = "channel-id", + Ticker = "ticker", + Sticky = false, + EventTimestamp = DateTime.Parse("2020-06-27T16:29:06.032691000-04:00"), + LocalOnly = true, + Priority = AndroidNotification.PriorityType.HIGH, + VibrateTimingsMillis = new long[] { 1000L, 1001L }, + DefaultVibrateTimings = false, + DefaultSound = true, + LightSettings = new LightSettings + { + Color = new LightSettingsColor { Red = 0.2f, Green = 0.4f, Blue = 0.6f }, + LightOnDurationMillis = 1002L, + LightOffDurationMillis = 1003L, + }, + DefaultLightSettings = false, + Visibility = AndroidNotification.VisibilityType.PUBLIC, + NotificationCount = 10, }, FcmOptions = new AndroidFcmOptions() { @@ -308,6 +325,23 @@ public void AndroidConfig() { "body_loc_key", "body-loc-key" }, { "body_loc_args", new JArray() { "arg3", "arg4" } }, { "channel_id", "channel-id" }, + { "ticker", "ticker" }, + { "sticky", false }, + { "local_only", true }, + { "default_vibrate_timings", false }, + { "default_sound", true }, + { + "light_settings", new JObject() + { + { "color", "#336699" }, { "light_on_duration", "1.002000000s" }, { "light_off_duration", "1.003000000s" }, + } + }, + { "default_light_settings", false }, + { "notification_count", 10 }, + { "notification_priority", "PRIORITY_HIGH" }, + { "visibility", "PUBLIC" }, + { "vibrate_timings", new JArray() { "1s", "1.001000000s" } }, + { "event_time", "2020-06-27T20:29:06.032691000Z" }, } }, { @@ -378,6 +412,7 @@ public void AndroidConfigDeserialization() Notification = new AndroidNotification() { Title = "title", + EventTimestamp = DateTime.Parse("2020-06-27T20:29:06.032691000Z"), }, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); @@ -422,6 +457,23 @@ public void AndroidNotificationDeserialization() BodyLocKey = "body-loc-key", BodyLocArgs = new List() { "arg3", "arg4" }, ChannelId = "channel-id", + Ticker = "ticker", + Sticky = false, + EventTimestamp = DateTime.Parse("2020-06-27T16:29:06.032691000-04:00"), + LocalOnly = true, + Priority = AndroidNotification.PriorityType.HIGH, + VibrateTimingsMillis = new long[] { 1000L, 1001L }, + DefaultVibrateTimings = false, + DefaultSound = true, + LightSettings = new LightSettings + { + Color = new LightSettingsColor { Red = 0.2F, Green = 0.4F, Blue = 0.6F, Alpha = 1.0F }, + LightOnDurationMillis = 1002L, + LightOffDurationMillis = 1003L, + }, + DefaultLightSettings = false, + Visibility = AndroidNotification.VisibilityType.PUBLIC, + NotificationCount = 10, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); @@ -438,6 +490,21 @@ public void AndroidNotificationDeserialization() Assert.Equal(original.BodyLocKey, copy.BodyLocKey); Assert.Equal(original.BodyLocArgs, copy.BodyLocArgs); Assert.Equal(original.ChannelId, copy.ChannelId); + Assert.Equal(original.Ticker, copy.Ticker); + Assert.Equal(original.Sticky, copy.Sticky); + Assert.Equal(original.EventTimestamp, copy.EventTimestamp); + Assert.Equal(original.LocalOnly, copy.LocalOnly); + Assert.Equal(original.Priority, copy.Priority); + Assert.Equal(original.VibrateTimingsMillis, copy.VibrateTimingsMillis); + Assert.Equal(original.DefaultVibrateTimings, copy.DefaultVibrateTimings); + Assert.Equal(original.DefaultSound, copy.DefaultSound); + Assert.Equal(original.LightSettings.Color.Red, copy.LightSettings.Color.Red); + Assert.Equal(original.LightSettings.Color.Blue, copy.LightSettings.Color.Blue); + Assert.Equal(original.LightSettings.Color.Green, copy.LightSettings.Color.Green); + Assert.Equal(original.LightSettings.Color.Alpha, copy.LightSettings.Color.Alpha); + Assert.Equal(original.DefaultLightSettings, copy.DefaultLightSettings); + Assert.Equal(original.Visibility, copy.Visibility); + Assert.Equal(original.NotificationCount, copy.NotificationCount); } [Fact] diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs index 4742a848..c640c382 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs @@ -14,8 +14,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using FirebaseAdmin.Messaging.Util; +using Google.Apis.Util; using Newtonsoft.Json; namespace FirebaseAdmin.Messaging @@ -26,6 +29,58 @@ namespace FirebaseAdmin.Messaging /// public sealed class AndroidNotification { + /// + /// Priority levels that can be set on an . + /// + public enum PriorityType + { + /// + /// Minimum priority notification. + /// + MIN, + + /// + /// Low priority notification. + /// + LOW, + + /// + /// Default priority notification. + /// + DEFAULT, + + /// + /// High priority notification. + /// + HIGH, + + /// + /// Maximum priority notification. + /// + MAX, + } + + /// + /// Visibility levels that can be set on an . + /// + public enum VisibilityType + { + /// + /// Private visibility. + /// + PRIVATE, + + /// + /// Public visibility. + /// + PUBLIC, + + /// + /// Secret visibility. + /// + SECRET, + } + /// /// Gets or sets the title of the Android notification. When provided, overrides the title /// set via . @@ -118,6 +173,265 @@ public sealed class AndroidNotification [JsonProperty("channel_id")] public string ChannelId { get; set; } + /// + /// Gets or sets the "ticker" text which is sent to accessibility services. Prior to API level 21 + /// (Lollipop), gets or sets the text that is displayed in the status bar when the notification + /// first arrives. + /// + [JsonProperty("ticker")] + public string Ticker { get; set; } + + /// + /// Gets or sets a value indicating whether the notification is automatically dismissed + /// or persists when the user clicks it in the panel. When set to false, + /// the notification is automatically dismissed. When set to true, the notification persists. + /// + [JsonProperty("sticky")] + public bool Sticky { get; set; } + + /// + /// Gets or sets the time that the event in the notification occurred for notifications + /// that inform users about events with an absolute time reference. Notifications in the panel + /// are sorted by this time. + /// + [JsonIgnore] + public DateTime EventTimestamp { get; set; } + + /// + /// Gets or sets a value indicating whether or not this notification is relevant only to + /// the current device. Some notifications can be bridged to other devices for remote display, + /// such as a Wear OS watch. This hint can be set to recommend this notification not be bridged. + /// See Wear OS guides. + /// + [JsonProperty("local_only")] + public bool LocalOnly { get; set; } + + /// + /// Gets or sets the relative priority for this notification. Priority is an indication of how much of + /// the user's attention should be consumed by this notification. Low-priority notifications + /// may be hidden from the user in certain situations, while the user might be interrupted + /// for a higher-priority notification. + /// + [JsonIgnore] + public AndroidNotification.PriorityType? Priority { get; set; } + + /// + /// Gets or sets a list of vibration timings in milliseconds in the array to use. The first value in the + /// array indicates the duration to wait before turning the vibrator on. The next value + /// indicates the duration to keep the vibrator on. Subsequent values alternate between + /// duration to turn the vibrator off and to turn the vibrator on. If is set and + /// is set to true, the default value is used instead of + /// the user-specified vibrate_timings. A duration in seconds with up to nine fractional digits, + /// terminated by 's'.Example: "3.5s". + /// + [JsonIgnore] + public long[] VibrateTimingsMillis { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use the default vibration timings. If set to true, use the Android + /// Sets the whether to use the default vibration timings. If set to true, use the Android + /// in config.xml. + /// If is set to true and is also set, + /// the default value is used instead of the user-specified . + /// + [JsonProperty("default_vibrate_timings")] + public bool DefaultVibrateTimings { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use the default sound. If set to true, use the Android framework's + /// default sound for the notification. Default values are specified in config.xml. + /// + [JsonProperty("default_sound")] + public bool DefaultSound { get; set; } + + /// + /// Gets or sets the settings to control the notification's LED blinking rate and color if LED is + /// available on the device. The total blinking time is controlled by the OS. + /// + [JsonProperty("light_settings")] + public LightSettings LightSettings { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use the default light settings. + /// If set to true, use the Android framework's default LED light settings for the notification. Default values are + /// specified in config.xml. If is set to true and is also set, + /// the user-specified is used instead of the default value. + /// + [JsonProperty("default_light_settings")] + public bool DefaultLightSettings { get; set; } + + /// + /// Gets or sets the visibility of this notification. + /// + [JsonIgnore] + public AndroidNotification.VisibilityType? Visibility { get; set; } + + /// + /// Gets or sets the number of items this notification represents. May be displayed as a badge + /// count for launchers that support badging. If not invoked then notification count is left unchanged. + /// For example, this might be useful if you're using just one notification to represent + /// multiple new messages but you want the count here to represent the number of total + /// new messages.If zero or unspecified, systems that support badging use the default, + /// which is to increment a number displayed on the long-press menu each time a new notification arrives. + /// + [JsonProperty("notification_count")] + public int? NotificationCount { get; set; } + + /// + /// Gets or sets the string representation of the property. + /// + [JsonProperty("notification_priority")] + private string PriorityString + { + get + { + switch (this.Priority) + { + case PriorityType.MIN: + return "PRIORITY_MIN"; + case PriorityType.LOW: + return "PRIORITY_LOW"; + case PriorityType.DEFAULT: + return "PRIORITY_DEFAULT"; + case PriorityType.HIGH: + return "PRIORITY_HIGH"; + case PriorityType.MAX: + return "PRIORITY_MAX"; + default: + return null; + } + } + + set + { + switch (value) + { + case "PRIORITY_MIN": + this.Priority = PriorityType.MIN; + return; + case "PRIORITY_LOW": + this.Priority = PriorityType.LOW; + return; + case "PRIORITY_DEFAULT": + this.Priority = PriorityType.DEFAULT; + return; + case "PRIORITY_HIGH": + this.Priority = PriorityType.HIGH; + return; + case "PRIORITY_MAX": + this.Priority = PriorityType.MAX; + return; + default: + throw new ArgumentException( + $"Invalid priority value: {value}. Only 'PRIORITY_MIN', 'PRIORITY_LOW', ''PRIORITY_DEFAULT' " + + "'PRIORITY_HIGH','PRIORITY_MAX' are allowed."); + } + } + } + + /// + /// Gets or sets the string representation of the property. + /// + [JsonProperty("visibility")] + private string VisibilityString + { + get + { + switch (this.Visibility) + { + case VisibilityType.PUBLIC: + return "PUBLIC"; + case VisibilityType.PRIVATE: + return "PRIVATE"; + case VisibilityType.SECRET: + return "SECRET"; + default: + return null; + } + } + + set + { + switch (value) + { + case "PUBLIC": + this.Visibility = VisibilityType.PUBLIC; + return; + case "PRIVATE": + this.Visibility = VisibilityType.PRIVATE; + return; + case "SECRET": + this.Visibility = VisibilityType.SECRET; + return; + default: + throw new ArgumentException( + $"Invalid visibility value: {value}. Only 'PUBLIC', 'PRIVATE', ''SECRET' are allowed."); + } + } + } + + /// + /// Gets or sets the string representation of the property. + /// + [JsonProperty("vibrate_timings")] + private List VibrateTimingsString + { + get + { + var timingsStringList = new List(); + if (this.VibrateTimingsMillis == null) + { + return null; + } + + foreach (var value in this.VibrateTimingsMillis) + { + timingsStringList.Add(TimeConverter.LongMillisToString(value)); + } + + return timingsStringList; + } + + set + { + if (value.Count == 0) + { + throw new ArgumentException("Invalid VibrateTimingsMillis. VibrateTimingsMillis should be a non-empty list of strings"); + } + + var timingsLongList = new List(); + + foreach (var timingString in value) + { + timingsLongList.Add(TimeConverter.StringToLongMillis(timingString)); + } + + this.VibrateTimingsMillis = timingsLongList.ToArray(); + } + } + + /// + /// Gets or sets the string representation of the property. + /// + [JsonProperty("event_time")] + private string EventTimeString + { + get + { + return this.EventTimestamp.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.ffffff000'Z'"); + } + + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Invalid event timestamp. Event timestamp should be a non-empty string"); + } + + this.EventTimestamp = DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.None); + } + } + /// /// Copies this notification, and validates the content of it to ensure that it can be /// serialized into the JSON format expected by the FCM service. @@ -139,6 +453,18 @@ internal AndroidNotification CopyAndValidate() BodyLocKey = this.BodyLocKey, BodyLocArgs = this.BodyLocArgs?.ToList(), ChannelId = this.ChannelId, + Ticker = this.Ticker, + Sticky = this.Sticky, + EventTimestamp = this.EventTimestamp, + LocalOnly = this.LocalOnly, + Priority = this.Priority, + VibrateTimingsMillis = this.VibrateTimingsMillis, + DefaultVibrateTimings = this.DefaultVibrateTimings, + DefaultSound = this.DefaultSound, + LightSettings = this.LightSettings, + DefaultLightSettings = this.DefaultLightSettings, + Visibility = this.Visibility, + NotificationCount = this.NotificationCount, }; if (copy.Color != null && !Regex.Match(copy.Color, "^#[0-9a-fA-F]{6}$").Success) { diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs new file mode 100644 index 00000000..2b1128f2 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs @@ -0,0 +1,123 @@ +// Copyright 2020, Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Text; +using System.Text.RegularExpressions; +using FirebaseAdmin.Messaging.Util; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents light settings in an Android Notification. + /// + public sealed class LightSettings + { + /// + /// Gets or sets the lightSettingsColor value in the light settings. + /// + [JsonIgnore] + public LightSettingsColor Color { get; set; } + + /// + /// Gets or sets the light on duration in milliseconds. + /// + [JsonIgnore] + public long LightOnDurationMillis { get; set; } + + /// + /// Gets or sets the light off duration in milliseconds. + /// + [JsonIgnore] + public long LightOffDurationMillis { get; set; } + + /// + /// Gets or sets a string representation of . + /// + [JsonProperty("color")] + private string LightSettingsColorString + { + get + { + var colorStringBuilder = new StringBuilder(); + + colorStringBuilder + .Append("#") + .Append(Convert.ToInt32(this.Color.Red * 255).ToString("X")) + .Append(Convert.ToInt32(this.Color.Green * 255).ToString("X")) + .Append(Convert.ToInt32(this.Color.Blue * 255).ToString("X")); + + return colorStringBuilder.ToString(); + } + + set + { + var pattern = new Regex("^#[0-9a-fA-F]{6}$"); + + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Invalid LightSettingsColor. LightSettingsColor annot be null or empty"); + } + + if (!pattern.IsMatch(value)) + { + throw new ArgumentException($"Invalid LightSettingsColor {value}. LightSettingsColor must be in the form #RRGGBB"); + } + + this.Color = new LightSettingsColor + { + Red = Convert.ToInt32(value.Substring(1, 2), 16) / 255.0f, + Green = Convert.ToInt32(value.Substring(3, 2), 16) / 255.0f, + Blue = Convert.ToInt32(value.Substring(5, 2), 16) / 255.0f, + Alpha = 1.0f, + }; + } + } + + /// + /// Gets or sets the string representation of . + /// + [JsonProperty("light_on_duration")] + private string LightOnDurationMillisString + { + get + { + return TimeConverter.LongMillisToString(this.LightOnDurationMillis); + } + + set + { + this.LightOnDurationMillis = TimeConverter.StringToLongMillis(value); + } + } + + /// + /// Gets or sets the string representation of . + /// + [JsonProperty("light_off_duration")] + private string LightOffDurationMillisString + { + get + { + return TimeConverter.LongMillisToString(this.LightOffDurationMillis); + } + + set + { + this.LightOffDurationMillis = TimeConverter.StringToLongMillis(value); + } + } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs new file mode 100644 index 00000000..c6eb3a5d --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// A class representing color in LightSettings. + /// + public class LightSettingsColor + { + /// + /// Gets or sets the red component. + /// + public float Red { get; set; } + + /// + /// Gets or sets the green component. + /// + public float Green { get; set; } + + /// + /// Gets or sets the blue component. + /// + public float Blue { get; set; } + + /// + /// Gets or sets the alpha component. + /// + public float Alpha { get; set; } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/Util/TimeConverter.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/Util/TimeConverter.cs new file mode 100644 index 00000000..45be63d3 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/Util/TimeConverter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FirebaseAdmin.Messaging.Util +{ + /// + /// Converter from long milliseconds to string and vice versa. + /// + internal static class TimeConverter + { + /// + /// Converts long milliseconds to the FCM string representation. + /// + /// Milliseconds as a long. + /// An FCM string representation of the long milliseconds. + public static string LongMillisToString(long longMillis) + { + var seconds = Math.Floor(Convert.ToDouble(longMillis / 1000)); + var subsecondNanos = Convert.ToDecimal((longMillis - (seconds * 1000L)) * 1E6); + + if (subsecondNanos > 0) + { + return string.Format("{0:0}.{1:000000000}s", seconds, subsecondNanos); + } + else + { + return string.Format("{0}s", seconds); + } + } + + /// + /// Converts an FCM representation of time into milliseconds of type long. + /// + /// An FCM representation of time. + /// The string time as a long. + public static long StringToLongMillis(string timingString) + { + return Convert.ToInt64(Convert.ToDouble(timingString.TrimEnd('s')) * 1000); + } + } +} From 32821c00b6ef67d85e343cb370367918e639d367 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 3 Mar 2022 19:34:55 -0500 Subject: [PATCH 2/5] Update API to match the approved changes --- .gitignore | 1 + .../Messaging/MessageTest.cs | 27 ++-- .../Messaging/AndroidNotification.cs | 4 +- .../FirebaseAdmin/Messaging/LightSettings.cs | 135 +++++++++++++----- .../Messaging/LightSettingsColor.cs | 34 ----- 5 files changed, 124 insertions(+), 77 deletions(-) delete mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs diff --git a/.gitignore b/.gitignore index a5f45866..efde37c5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ obj/ .vscode/ .vs/ .idea/ +.DS_Store diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 81d84d04..f610d135 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -282,9 +282,9 @@ public void AndroidConfig() VibrateTimingsMillis = new long[] { 1000L, 1001L }, DefaultVibrateTimings = false, DefaultSound = true, - LightSettings = new LightSettings + LightSettings = new LightSettings() { - Color = new LightSettingsColor { Red = 0.2f, Green = 0.4f, Blue = 0.6f }, + Color = "#aabbccdd", LightOnDurationMillis = 1002L, LightOffDurationMillis = 1003L, }, @@ -333,7 +333,17 @@ public void AndroidConfig() { "light_settings", new JObject() { - { "color", "#336699" }, { "light_on_duration", "1.002000000s" }, { "light_off_duration", "1.003000000s" }, + { "light_on_duration", "1.002000000s" }, + { "light_off_duration", "1.003000000s" }, + { + "color", new JObject() + { + { "red", 0.6666667 }, + { "green", 0.733333349 }, + { "blue", 0.8 }, + { "alpha", 0.8666667 }, + } + }, } }, { "default_light_settings", false }, @@ -465,9 +475,9 @@ public void AndroidNotificationDeserialization() VibrateTimingsMillis = new long[] { 1000L, 1001L }, DefaultVibrateTimings = false, DefaultSound = true, - LightSettings = new LightSettings + LightSettings = new LightSettings() { - Color = new LightSettingsColor { Red = 0.2F, Green = 0.4F, Blue = 0.6F, Alpha = 1.0F }, + Color = "#AABBCCDD", LightOnDurationMillis = 1002L, LightOffDurationMillis = 1003L, }, @@ -498,10 +508,9 @@ public void AndroidNotificationDeserialization() Assert.Equal(original.VibrateTimingsMillis, copy.VibrateTimingsMillis); Assert.Equal(original.DefaultVibrateTimings, copy.DefaultVibrateTimings); Assert.Equal(original.DefaultSound, copy.DefaultSound); - Assert.Equal(original.LightSettings.Color.Red, copy.LightSettings.Color.Red); - Assert.Equal(original.LightSettings.Color.Blue, copy.LightSettings.Color.Blue); - Assert.Equal(original.LightSettings.Color.Green, copy.LightSettings.Color.Green); - Assert.Equal(original.LightSettings.Color.Alpha, copy.LightSettings.Color.Alpha); + Assert.Equal(original.LightSettings.Color, copy.LightSettings.Color); + Assert.Equal(original.LightSettings.LightOnDurationMillis, copy.LightSettings.LightOnDurationMillis); + Assert.Equal(original.LightSettings.LightOffDurationMillis, copy.LightSettings.LightOffDurationMillis); Assert.Equal(original.DefaultLightSettings, copy.DefaultLightSettings); Assert.Equal(original.Visibility, copy.Visibility); Assert.Equal(original.NotificationCount, copy.NotificationCount); diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs index c640c382..12465e5a 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Text.RegularExpressions; using FirebaseAdmin.Messaging.Util; -using Google.Apis.Util; using Newtonsoft.Json; namespace FirebaseAdmin.Messaging @@ -461,7 +460,6 @@ internal AndroidNotification CopyAndValidate() VibrateTimingsMillis = this.VibrateTimingsMillis, DefaultVibrateTimings = this.DefaultVibrateTimings, DefaultSound = this.DefaultSound, - LightSettings = this.LightSettings, DefaultLightSettings = this.DefaultLightSettings, Visibility = this.Visibility, NotificationCount = this.NotificationCount, @@ -486,6 +484,8 @@ internal AndroidNotification CopyAndValidate() throw new ArgumentException($"Malformed image URL string: {copy.ImageUrl}."); } + copy.LightSettings = this.LightSettings?.CopyAndValidate(); + return copy; } } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs index 2b1128f2..cd16b4bb 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs @@ -25,11 +25,24 @@ namespace FirebaseAdmin.Messaging /// public sealed class LightSettings { + private LightSettingsColor lightColor = new LightSettingsColor(); + /// - /// Gets or sets the lightSettingsColor value in the light settings. + /// Gets or sets the color value of the light settings. /// [JsonIgnore] - public LightSettingsColor Color { get; set; } + public string Color + { + get + { + return this.lightColor.ColorString(); + } + + set + { + this.lightColor = LightSettingsColor.FromString(value); + } + } /// /// Gets or sets the light on duration in milliseconds. @@ -44,45 +57,19 @@ public sealed class LightSettings public long LightOffDurationMillis { get; set; } /// - /// Gets or sets a string representation of . + /// Gets or sets the light settings color representation as accepted by the FCM backend service. /// [JsonProperty("color")] - private string LightSettingsColorString + private LightSettingsColor LightColor { get { - var colorStringBuilder = new StringBuilder(); - - colorStringBuilder - .Append("#") - .Append(Convert.ToInt32(this.Color.Red * 255).ToString("X")) - .Append(Convert.ToInt32(this.Color.Green * 255).ToString("X")) - .Append(Convert.ToInt32(this.Color.Blue * 255).ToString("X")); - - return colorStringBuilder.ToString(); + return this.lightColor; } set { - var pattern = new Regex("^#[0-9a-fA-F]{6}$"); - - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException("Invalid LightSettingsColor. LightSettingsColor annot be null or empty"); - } - - if (!pattern.IsMatch(value)) - { - throw new ArgumentException($"Invalid LightSettingsColor {value}. LightSettingsColor must be in the form #RRGGBB"); - } - - this.Color = new LightSettingsColor - { - Red = Convert.ToInt32(value.Substring(1, 2), 16) / 255.0f, - Green = Convert.ToInt32(value.Substring(3, 2), 16) / 255.0f, - Blue = Convert.ToInt32(value.Substring(5, 2), 16) / 255.0f, - Alpha = 1.0f, - }; + this.lightColor = value; } } @@ -119,5 +106,89 @@ private string LightOffDurationMillisString this.LightOffDurationMillis = TimeConverter.StringToLongMillis(value); } } + + /// + /// Copies this Light Settings, and validates the content of it to ensure that it can be + /// serialized into the JSON format expected by the FCM service. + /// + internal LightSettings CopyAndValidate() + { + // Copy and validate the leaf-level properties + var copy = new LightSettings() + { + Color = this.Color, + LightOnDurationMillis = this.LightOnDurationMillis, + LightOffDurationMillis = this.LightOffDurationMillis, + }; + + return copy; + } + + /// + /// The LightSettings Color object as expected by the FCM backend service. + /// + private class LightSettingsColor + { + /// + /// Gets or sets the red component. + /// + [JsonProperty("red")] + internal float Red { get; set; } + + /// + /// Gets or sets the green component. + /// + [JsonProperty("green")] + internal float Green { get; set; } + + /// + /// Gets or sets the blue component. + /// + [JsonProperty("blue")] + internal float Blue { get; set; } + + /// + /// Gets or sets the alpha component. + /// + [JsonProperty("alpha")] + internal float Alpha { get; set; } + + internal static LightSettingsColor FromString(string color) + { + if (string.IsNullOrEmpty(color)) + { + throw new ArgumentException("Light settings color must not be null or empty"); + } + + if (!Regex.Match(color, "^#[0-9a-fA-F]{6}$").Success && !Regex.Match(color, "^#[0-9a-fA-F]{8}$").Success) + { + throw new ArgumentException($"Invalid Light Settings Color {color}. Must be in the form of #RRGGBB or #RRGGBBAA."); + } + + var colorString = color.Length == 7 ? color + "FF" : color; + + return new LightSettingsColor() + { + Red = Convert.ToInt32(colorString.Substring(1, 2), 16) / 255.0f, + Green = Convert.ToInt32(colorString.Substring(3, 2), 16) / 255.0f, + Blue = Convert.ToInt32(colorString.Substring(5, 2), 16) / 255.0f, + Alpha = Convert.ToInt32(colorString.Substring(7, 2), 16) / 255.0f, + }; + } + + internal string ColorString() + { + var colorStringBuilder = new StringBuilder(); + + colorStringBuilder + .Append("#") + .Append(Convert.ToInt32(this.Red * 255).ToString("X")) + .Append(Convert.ToInt32(this.Green * 255).ToString("X")) + .Append(Convert.ToInt32(this.Blue * 255).ToString("X")) + .Append(Convert.ToInt32(this.Alpha * 255).ToString("X")); + + return colorStringBuilder.ToString(); + } + } } } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs deleted file mode 100644 index c6eb3a5d..00000000 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettingsColor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -namespace FirebaseAdmin.Messaging -{ - /// - /// A class representing color in LightSettings. - /// - public class LightSettingsColor - { - /// - /// Gets or sets the red component. - /// - public float Red { get; set; } - - /// - /// Gets or sets the green component. - /// - public float Green { get; set; } - - /// - /// Gets or sets the blue component. - /// - public float Blue { get; set; } - - /// - /// Gets or sets the alpha component. - /// - public float Alpha { get; set; } - } -} From 031faff9711092babd2c18c317fdd8ad3729c5c4 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 4 Mar 2022 14:37:40 -0500 Subject: [PATCH 3/5] Trigger CI From 49a161da4f598450a11e15696eacb33454fe8837 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Mon, 14 Mar 2022 15:31:59 -0400 Subject: [PATCH 4/5] Move enum types out of the nested class --- .../Messaging/MessageTest.cs | 8 +- .../Messaging/AndroidNotification.cs | 92 ++++--------------- .../Messaging/NotificationPriority.cs | 47 ++++++++++ .../Messaging/NotificationVisibility.cs | 37 ++++++++ 4 files changed, 108 insertions(+), 76 deletions(-) create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/NotificationPriority.cs create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/NotificationVisibility.cs diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index f610d135..b53e87b6 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -278,7 +278,7 @@ public void AndroidConfig() Sticky = false, EventTimestamp = DateTime.Parse("2020-06-27T16:29:06.032691000-04:00"), LocalOnly = true, - Priority = AndroidNotification.PriorityType.HIGH, + Priority = NotificationPriority.HIGH, VibrateTimingsMillis = new long[] { 1000L, 1001L }, DefaultVibrateTimings = false, DefaultSound = true, @@ -289,7 +289,7 @@ public void AndroidConfig() LightOffDurationMillis = 1003L, }, DefaultLightSettings = false, - Visibility = AndroidNotification.VisibilityType.PUBLIC, + Visibility = NotificationVisibility.PUBLIC, NotificationCount = 10, }, FcmOptions = new AndroidFcmOptions() @@ -471,7 +471,7 @@ public void AndroidNotificationDeserialization() Sticky = false, EventTimestamp = DateTime.Parse("2020-06-27T16:29:06.032691000-04:00"), LocalOnly = true, - Priority = AndroidNotification.PriorityType.HIGH, + Priority = NotificationPriority.HIGH, VibrateTimingsMillis = new long[] { 1000L, 1001L }, DefaultVibrateTimings = false, DefaultSound = true, @@ -482,7 +482,7 @@ public void AndroidNotificationDeserialization() LightOffDurationMillis = 1003L, }, DefaultLightSettings = false, - Visibility = AndroidNotification.VisibilityType.PUBLIC, + Visibility = NotificationVisibility.PUBLIC, NotificationCount = 10, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs index 12465e5a..493ffffb 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs @@ -28,58 +28,6 @@ namespace FirebaseAdmin.Messaging /// public sealed class AndroidNotification { - /// - /// Priority levels that can be set on an . - /// - public enum PriorityType - { - /// - /// Minimum priority notification. - /// - MIN, - - /// - /// Low priority notification. - /// - LOW, - - /// - /// Default priority notification. - /// - DEFAULT, - - /// - /// High priority notification. - /// - HIGH, - - /// - /// Maximum priority notification. - /// - MAX, - } - - /// - /// Visibility levels that can be set on an . - /// - public enum VisibilityType - { - /// - /// Private visibility. - /// - PRIVATE, - - /// - /// Public visibility. - /// - PUBLIC, - - /// - /// Secret visibility. - /// - SECRET, - } - /// /// Gets or sets the title of the Android notification. When provided, overrides the title /// set via . @@ -212,7 +160,7 @@ public enum VisibilityType /// for a higher-priority notification. /// [JsonIgnore] - public AndroidNotification.PriorityType? Priority { get; set; } + public NotificationPriority? Priority { get; set; } /// /// Gets or sets a list of vibration timings in milliseconds in the array to use. The first value in the @@ -263,7 +211,7 @@ public enum VisibilityType /// Gets or sets the visibility of this notification. /// [JsonIgnore] - public AndroidNotification.VisibilityType? Visibility { get; set; } + public NotificationVisibility? Visibility { get; set; } /// /// Gets or sets the number of items this notification represents. May be displayed as a badge @@ -277,7 +225,7 @@ public enum VisibilityType public int? NotificationCount { get; set; } /// - /// Gets or sets the string representation of the property. + /// Gets or sets the string representation of the property. /// [JsonProperty("notification_priority")] private string PriorityString @@ -286,15 +234,15 @@ private string PriorityString { switch (this.Priority) { - case PriorityType.MIN: + case NotificationPriority.MIN: return "PRIORITY_MIN"; - case PriorityType.LOW: + case NotificationPriority.LOW: return "PRIORITY_LOW"; - case PriorityType.DEFAULT: + case NotificationPriority.DEFAULT: return "PRIORITY_DEFAULT"; - case PriorityType.HIGH: + case NotificationPriority.HIGH: return "PRIORITY_HIGH"; - case PriorityType.MAX: + case NotificationPriority.MAX: return "PRIORITY_MAX"; default: return null; @@ -306,19 +254,19 @@ private string PriorityString switch (value) { case "PRIORITY_MIN": - this.Priority = PriorityType.MIN; + this.Priority = NotificationPriority.MIN; return; case "PRIORITY_LOW": - this.Priority = PriorityType.LOW; + this.Priority = NotificationPriority.LOW; return; case "PRIORITY_DEFAULT": - this.Priority = PriorityType.DEFAULT; + this.Priority = NotificationPriority.DEFAULT; return; case "PRIORITY_HIGH": - this.Priority = PriorityType.HIGH; + this.Priority = NotificationPriority.HIGH; return; case "PRIORITY_MAX": - this.Priority = PriorityType.MAX; + this.Priority = NotificationPriority.MAX; return; default: throw new ArgumentException( @@ -329,7 +277,7 @@ private string PriorityString } /// - /// Gets or sets the string representation of the property. + /// Gets or sets the string representation of the property. /// [JsonProperty("visibility")] private string VisibilityString @@ -338,11 +286,11 @@ private string VisibilityString { switch (this.Visibility) { - case VisibilityType.PUBLIC: + case NotificationVisibility.PUBLIC: return "PUBLIC"; - case VisibilityType.PRIVATE: + case NotificationVisibility.PRIVATE: return "PRIVATE"; - case VisibilityType.SECRET: + case NotificationVisibility.SECRET: return "SECRET"; default: return null; @@ -354,13 +302,13 @@ private string VisibilityString switch (value) { case "PUBLIC": - this.Visibility = VisibilityType.PUBLIC; + this.Visibility = NotificationVisibility.PUBLIC; return; case "PRIVATE": - this.Visibility = VisibilityType.PRIVATE; + this.Visibility = NotificationVisibility.PRIVATE; return; case "SECRET": - this.Visibility = VisibilityType.SECRET; + this.Visibility = NotificationVisibility.SECRET; return; default: throw new ArgumentException( diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationPriority.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationPriority.cs new file mode 100644 index 00000000..5b476a8f --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationPriority.cs @@ -0,0 +1,47 @@ +// Copyright 2022, Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace FirebaseAdmin.Messaging +{ + /// + /// Priority levels that can be set on an . + /// + public enum NotificationPriority + { + /// + /// Minimum priority notification. + /// + MIN, + + /// + /// Low priority notification. + /// + LOW, + + /// + /// Default priority notification. + /// + DEFAULT, + + /// + /// High priority notification. + /// + HIGH, + + /// + /// Maximum priority notification. + /// + MAX, + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationVisibility.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationVisibility.cs new file mode 100644 index 00000000..b3400801 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/NotificationVisibility.cs @@ -0,0 +1,37 @@ +// Copyright 2022, Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace FirebaseAdmin.Messaging +{ + /// + /// Visibility levels that can be set on an . + /// + public enum NotificationVisibility + { + /// + /// Private visibility. + /// + PRIVATE, + + /// + /// Public visibility. + /// + PUBLIC, + + /// + /// Secret visibility. + /// + SECRET, + } +} From 89bed0c9952c266d20d108b47b168b4796f86359 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Mon, 14 Mar 2022 15:52:46 -0400 Subject: [PATCH 5/5] Fix docs --- FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs | 2 +- FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs index 493ffffb..5d3f5e13 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs @@ -168,7 +168,7 @@ public sealed class AndroidNotification /// indicates the duration to keep the vibrator on. Subsequent values alternate between /// duration to turn the vibrator off and to turn the vibrator on. If is set and /// is set to true, the default value is used instead of - /// the user-specified vibrate_timings. A duration in seconds with up to nine fractional digits, + /// the user-specified . A duration in seconds with up to nine fractional digits, /// terminated by 's'.Example: "3.5s". /// [JsonIgnore] diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs index cd16b4bb..8cf8e24b 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/LightSettings.cs @@ -108,7 +108,7 @@ private string LightOffDurationMillisString } /// - /// Copies this Light Settings, and validates the content of it to ensure that it can be + /// Copies this object, and validates the content of it to ensure that it can be /// serialized into the JSON format expected by the FCM service. /// internal LightSettings CopyAndValidate() @@ -125,7 +125,7 @@ internal LightSettings CopyAndValidate() } /// - /// The LightSettings Color object as expected by the FCM backend service. + /// Represents the light settings color as expected by the FCM backend service. /// private class LightSettingsColor {