Explorar o código

Added support for repeating notifications on Windows (WIP)

Sebastián Katzer %!s(int64=8) %!d(string=hai) anos
pai
achega
839409688d

+ 28 - 2
src/windows/LocalNotificationProxy.js

@@ -347,20 +347,46 @@ exports.clone = function (obj) {
  * @return [ LocalNotification.Options ]
  */
 exports.parseOptions = function (obj) {
-    var opts  = new LocalNotification.Options();
+    var opts   = new LocalNotification.Options(),
+        ignore = ['actions', 'trigger'];
 
     for (var prop in opts) {
-        if (prop != 'actions' && obj[prop]) {
+        if (!ignore.includes(prop) && obj[prop]) {
             opts[prop] = obj[prop];
         }
     }
 
+    var trigger  = exports.parseTrigger(obj);
+    opts.trigger = trigger;
+
     var actions  = exports.parseActions(obj);
     opts.actions = actions;
 
     return opts;
 };
 
+/**
+ * Parse trigger spec into instance of prefered type.
+ *
+ * @param [ Object ] obj The notification options map.
+ *
+ * @return [ LocalNotification.Trigger ]
+ */
+exports.parseTrigger = function (obj) {
+    var trigger = new LocalNotification.Trigger(),
+        spec    = obj.trigger, val;
+
+    if (!spec) return trigger;
+
+    for (var prop in trigger) {
+        val = spec[prop];
+        if (!val) continue;
+        trigger[prop] = prop == 'every' ? val.toString() : val;
+    }
+
+    return trigger;
+};
+
 /**
  * Parse action specs into instances of prefered types.
  *

+ 25 - 14
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotification/Builder.cs

@@ -30,21 +30,26 @@ namespace LocalNotificationProxy.LocalNotification
         /// Initializes a new instance of the <see cref="Builder"/> class.
         /// </summary>
         /// <param name="options">Notification properties to set.</param>
-        internal Builder(Options options)
+        public Builder(Options options)
         {
             this.Content = new Notification(options);
         }
 
         /// <summary>
-        /// Gets content
+        /// Gets the content.
         /// </summary>
         public Notification Content { get; private set; }
 
         /// <summary>
-        /// Gets options
+        /// Gets the options.
         /// </summary>
         private Options Options { get => this.Content.Options; }
 
+        /// <summary>
+        /// Gets the trigger.
+        /// </summary>
+        private Trigger Trigger { get => this.Options.Trigger; }
+
         /// <summary>
         /// Build a toast notification specified by the options.
         /// </summary>
@@ -59,6 +64,17 @@ namespace LocalNotificationProxy.LocalNotification
             return this.ConvertToastToNotification(toast);
         }
 
+        /// <summary>
+        /// If there is at least one more toast variant to build.
+        /// </summary>
+        /// <returns>True if there are more toasts to build.</returns>
+        public bool HasNext() => this.Trigger.Count > this.Trigger.Occurrence;
+
+        /// <summary>
+        /// Moves the flag to the next toast variant.
+        /// </summary>
+        public void MoveNext() => this.Trigger.Occurrence += this.HasNext() ? 1 : 0;
+
         /// <summary>
         /// Gets the initialize skeleton for a toast notification.
         /// </summary>
@@ -137,21 +153,16 @@ namespace LocalNotificationProxy.LocalNotification
         {
             var xml = toast.GetXml();
             var at = this.Content.Date;
-            ScheduledToastNotification notification;
+            var notification = new ScheduledToastNotification(xml, at);
 
-            if (this.Content.IsRepeating())
-            {
-                var interval = this.Content.Interval;
-                notification = new ScheduledToastNotification(xml, at, interval, 5);
-            }
-            else
+            notification.Id = this.Content.Id;
+            notification.Tag = this.Options.Id.ToString();
+
+            if (this.Trigger.Occurrence > 1)
             {
-                notification = new ScheduledToastNotification(xml, at);
+                notification.Group = notification.Tag;
             }
 
-            notification.Id = this.Options.ID.ToString();
-            notification.Tag = notification.Id;
-
             return notification;
         }
     }

+ 55 - 16
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotification/Manager.cs

@@ -46,8 +46,15 @@ namespace LocalNotificationProxy.LocalNotification
         {
             foreach (Options options in notifications)
             {
-                var toast = new Builder(options).Build();
-                ToastNotifier.AddToSchedule(toast);
+                var builder = new Builder(options);
+                ScheduledToastNotification toast;
+
+                do
+                {
+                    toast = builder.Build();
+                    ToastNotifier.AddToSchedule(toast);
+                    builder.MoveNext();
+                } while (builder.HasNext());
             }
         }
 
@@ -98,29 +105,29 @@ namespace LocalNotificationProxy.LocalNotification
         public List<Options> Cancel(int[] ids)
         {
             var scheduled = ToastNotifier.GetScheduledToastNotifications();
-            var tags = new List<int>(ids);
+            var toRemove = new List<int>(ids);
+            var toClear = new List<int>(ids);
             var toasts = new List<Options>();
 
             foreach (var toast in scheduled)
             {
                 var id = int.Parse(toast.Tag);
 
-                if (tags.Contains(id))
+                if (toRemove.Contains(id))
                 {
-                    tags.Remove(id);
-                    toasts.Add(new Notification(toast).Options);
+                    toClear.Remove(id);
                     ToastNotifier.RemoveFromSchedule(toast);
-                }
 
-                if (tags.Count == 0)
-                {
-                    break;
+                    if (!this.IsPhantom(toast))
+                    {
+                        toasts.Add(new Notification(toast).Options);
+                    }
                 }
             }
 
-            if (tags.Count > 0)
+            if (toClear.Count > 0)
             {
-                toasts.AddRange(this.Clear(tags.ToArray()));
+                toasts.AddRange(this.Clear(toClear.ToArray()));
             }
 
             return toasts;
@@ -165,7 +172,10 @@ namespace LocalNotificationProxy.LocalNotification
 
                 foreach (var toast in toasts)
                 {
-                    ids.Add(int.Parse(toast.Tag));
+                    if (!this.IsPhantom(toast))
+                    {
+                        ids.Add(int.Parse(toast.Tag));
+                    }
                 }
             }
 
@@ -175,7 +185,10 @@ namespace LocalNotificationProxy.LocalNotification
 
                 foreach (var toast in toasts)
                 {
-                    ids.Add(int.Parse(toast.Tag));
+                    if (!this.IsPhantom(toast))
+                    {
+                        ids.Add(int.Parse(toast.Tag));
+                    }
                 }
             }
 
@@ -206,7 +219,10 @@ namespace LocalNotificationProxy.LocalNotification
 
                 foreach (var toast in toasts)
                 {
-                    notifications.Add(new Notification(toast));
+                    if (!this.IsPhantom(toast))
+                    {
+                        notifications.Add(new Notification(toast));
+                    }
                 }
             }
 
@@ -216,7 +232,10 @@ namespace LocalNotificationProxy.LocalNotification
 
                 foreach (var toast in toasts)
                 {
-                    notifications.Add(new Notification(toast));
+                    if (!this.IsPhantom(toast))
+                    {
+                        notifications.Add(new Notification(toast));
+                    }
                 }
             }
 
@@ -358,5 +377,25 @@ namespace LocalNotificationProxy.LocalNotification
 
             return Notification.Type.Unknown;
         }
+
+        /// <summary>
+        /// If the specified toast is only for internal reason.
+        /// </summary>
+        /// <param name="toast">The toast notification to test for.</param>
+        /// <returns>True if its an internal ("phantom") one.</returns>
+        private bool IsPhantom(ToastNotification toast)
+        {
+            return toast.Group.Length > 0;
+        }
+
+        /// <summary>
+        /// If the specified toast is only for internal reason.
+        /// </summary>
+        /// <param name="toast">The toast notification to test for.</param>
+        /// <returns>True if its an internal ("phantom") one.</returns>
+        private bool IsPhantom(ScheduledToastNotification toast)
+        {
+            return toast.Group.Length > 0;
+        }
     }
 }

+ 14 - 13
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotification/Notification.cs

@@ -49,8 +49,16 @@
             All, Scheduled, Triggered, Unknown
         }
 
+        /// <summary>
+        /// Gets the wrapped notification options.
+        /// </summary>
         public Options Options { get; private set; }
 
+        /// <summary>
+        /// Gets the unique identifier for the toast.
+        /// </summary>
+        public string Id => $"{this.Options.Id}#{this.Options.Trigger.Occurrence}";
+
         /// <summary>
         /// Gets a ToastAudio object based on the specified sound uri.
         /// </summary>
@@ -260,9 +268,13 @@
         {
             get
             {
-                var date = DateTimeOffset.FromUnixTimeMilliseconds(this.Options.At * 1000).LocalDateTime;
+                var trigger = this.Options.Trigger;
+                var time = trigger.At;
+                var date = DateTimeOffset.FromUnixTimeMilliseconds(time * 1000).LocalDateTime;
                 var minDate = DateTime.Now.AddSeconds(0.1);
 
+                date = date.AddTicks(this.Interval.Ticks * (trigger.Occurrence - 1));
+
                 return (date < minDate) ? minDate : date;
             }
         }
@@ -274,7 +286,7 @@
         {
             get
             {
-                switch (this.Options.Every)
+                switch (this.Options.Trigger.Every)
                 {
                     case "minute":
                         return new TimeSpan(TimeSpan.TicksPerMinute);
@@ -296,16 +308,5 @@
         {
             return this.Options.GetXml();
         }
-
-        /// <summary>
-        /// If the notification shall be repeating.
-        /// </summary>
-        /// <returns>True if the Every property has some value.</returns>
-        public bool IsRepeating()
-        {
-            var every = this.Options.Every;
-
-            return every != null && every.Length > 0 && !every.Equals("0");
-        }
     }
 }

+ 5 - 22
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotification/Options.cs

@@ -33,7 +33,7 @@ namespace LocalNotificationProxy.LocalNotification
         /// <summary>
         /// Gets or sets notification ID.
         /// </summary>
-        public int ID { get; set; }
+        public int Id { get; set; }
 
         /// <summary>
         /// Gets or sets notification title.
@@ -61,14 +61,9 @@ namespace LocalNotificationProxy.LocalNotification
         public string Image { get; set; }
 
         /// <summary>
-        /// Gets or sets the notification fire date.
+        /// Gets or sets the notification trigger.
         /// </summary>
-        public long At { get; set; }
-
-        /// <summary>
-        /// Gets or sets the notification repeat interval.
-        /// </summary>
-        public string Every { get; set; }
+        public Trigger Trigger { get; set; }
 
         /// <summary>
         /// Gets or sets the notification user data.
@@ -98,9 +93,8 @@ namespace LocalNotificationProxy.LocalNotification
             var options = new Options();
             var node = doc.DocumentElement;
 
-            options.ID = int.Parse(node.GetAttribute("id"));
+            options.Id = int.Parse(node.GetAttribute("id"));
             options.Badge = int.Parse(node.GetAttribute("badge"));
-            options.At = int.Parse(node.GetAttribute("at"));
 
             if (node.GetAttributeNode("text") != null)
             {
@@ -122,11 +116,6 @@ namespace LocalNotificationProxy.LocalNotification
                 options.Image = node.GetAttribute("image");
             }
 
-            if (node.GetAttributeNode("every") != null)
-            {
-                options.Every = node.GetAttribute("every");
-            }
-
             if (node.GetAttributeNode("data") != null)
             {
                 options.Data = node.GetAttribute("data");
@@ -158,9 +147,8 @@ namespace LocalNotificationProxy.LocalNotification
         {
             var node = new XmlDocument().CreateElement("options");
 
-            node.SetAttribute("id", this.ID.ToString());
+            node.SetAttribute("id", this.Id.ToString());
             node.SetAttribute("badge", this.Badge.ToString());
-            node.SetAttribute("at", this.At.ToString());
 
             if (this.Title != null)
             {
@@ -182,11 +170,6 @@ namespace LocalNotificationProxy.LocalNotification
                 node.SetAttribute("image", this.Image);
             }
 
-            if (this.Every != null)
-            {
-                node.SetAttribute("every", this.Every);
-            }
-
             if (this.Data != null)
             {
                 node.SetAttribute("data", this.Data);

+ 51 - 0
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotification/Trigger.cs

@@ -0,0 +1,51 @@
+/*
+ * Apache 2.0 License
+ *
+ * Copyright (c) Sebastian Katzer 2017
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apache License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ */
+
+namespace LocalNotificationProxy.LocalNotification
+{
+    public sealed class Trigger
+    {
+        /// <summary>
+        /// Gets trigger type.
+        /// </summary>
+        public string Type { get; } = "calendar";
+
+        /// <summary>
+        /// Gets or sets trigger date.
+        /// </summary>
+        public long At { get; set; }
+
+        /// <summary>
+        /// Gets or sets trigger count.
+        /// </summary>
+        public int Count { get; set; } = 1;
+
+        /// <summary>
+        /// Gets trigger occurrence.
+        /// </summary>
+        public int Occurrence { get; internal set; } = 1;
+
+        /// <summary>
+        /// Gets or sets trigger interval.
+        /// </summary>
+        public string Every { get; set; }
+    }
+}

+ 1 - 0
src/windows/LocalNotificationProxy/LocalNotificationProxy/LocalNotificationProxy.csproj

@@ -118,6 +118,7 @@
     <Compile Include="LocalNotification\Input.cs" />
     <Compile Include="LocalNotification\Manager.cs" />
     <Compile Include="LocalNotification\Options.cs" />
+    <Compile Include="LocalNotification\Trigger.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>

+ 21 - 20
src/windows/Microsoft.Toolkit.Uwp.Notifications.UWP/project.lock.json

@@ -1,7 +1,7 @@
 {
-  "version": 2,
+  "version": 3,
   "targets": {
-    "UAP,Version=v10.0": {
+    "UAP,Version=v10.0.10240": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -2173,7 +2173,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-arm": {
+    "UAP,Version=v10.0.10240/win10-arm": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -4428,7 +4428,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-arm-aot": {
+    "UAP,Version=v10.0.10240/win10-arm-aot": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -5006,9 +5006,6 @@
       },
       "runtime.win10-arm-aot.runtime.native.System.IO.Compression/4.0.1": {
         "type": "package",
-        "compile": {
-          "runtimes/win10-arm-aot/lib/netcore50/_._": {}
-        },
         "runtime": {
           "runtimes/win10-arm-aot/lib/netcore50/clrcompression.dll": {}
         }
@@ -6722,7 +6719,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-x64": {
+    "UAP,Version=v10.0.10240/win10-x64": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -8986,7 +8983,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-x64-aot": {
+    "UAP,Version=v10.0.10240/win10-x64-aot": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -9567,9 +9564,6 @@
       },
       "runtime.win10-x64-aot.runtime.native.System.IO.Compression/4.0.1": {
         "type": "package",
-        "compile": {
-          "runtimes/win10-x64-aot/lib/netcore50/_._": {}
-        },
         "runtime": {
           "runtimes/win10-x64-aot/lib/netcore50/clrcompression.dll": {}
         }
@@ -11289,7 +11283,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-x86": {
+    "UAP,Version=v10.0.10240/win10-x86": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -13553,7 +13547,7 @@
         }
       }
     },
-    "UAP,Version=v10.0/win10-x86-aot": {
+    "UAP,Version=v10.0.10240/win10-x86-aot": {
       "Microsoft.CSharp/4.0.1": {
         "type": "package",
         "dependencies": {
@@ -14134,9 +14128,6 @@
       },
       "runtime.win10-x86-aot.runtime.native.System.IO.Compression/4.0.1": {
         "type": "package",
-        "compile": {
-          "runtimes/win10-x86-aot/lib/netcore50/_._": {}
-        },
         "runtime": {
           "runtimes/win10-x86-aot/lib/netcore50/clrcompression.dll": {}
         }
@@ -22044,7 +22035,7 @@
       "Microsoft.NETCore.UniversalWindowsPlatform >= 5.2.3",
       "StyleCop.Analyzers >= 1.0.0"
     ],
-    "UAP,Version=v10.0": []
+    "UAP,Version=v10.0.10240": []
   },
   "packageFolders": {
     "C:\\Users\\katze\\.nuget\\packages\\": {}
@@ -22055,14 +22046,24 @@
       "projectName": "Microsoft.Toolkit.Uwp.Notifications.UWP",
       "projectPath": "Z:\\Documents\\github\\cordova-plugin-local-notifications\\src\\windows\\Microsoft.Toolkit.Uwp.Notifications.UWP\\Microsoft.Toolkit.Uwp.Notifications.UWP.csproj",
       "projectJsonPath": "Z:\\Documents\\github\\cordova-plugin-local-notifications\\src\\windows\\Microsoft.Toolkit.Uwp.Notifications.UWP\\project.json",
-      "projectStyle": "ProjectJson"
+      "packagesPath": "C:\\Users\\katze\\.nuget\\packages\\",
+      "outputPath": "Z:\\Documents\\github\\cordova-plugin-local-notifications\\src\\windows\\Microsoft.Toolkit.Uwp.Notifications.UWP\\obj\\",
+      "projectStyle": "ProjectJson",
+      "configFilePaths": [
+        "C:\\Users\\katze\\AppData\\Roaming\\NuGet\\NuGet.Config",
+        "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
+      ],
+      "sources": {
+        "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
+        "https://api.nuget.org/v3/index.json": {}
+      }
     },
     "dependencies": {
       "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.3",
       "StyleCop.Analyzers": "1.0.0"
     },
     "frameworks": {
-      "uap10.0": {}
+      "uap10.0.10240": {}
     },
     "runtimes": {
       "win10-arm": {

+ 8 - 0
www/local-notification-util.js

@@ -213,6 +213,14 @@ exports.convertTrigger = function (options) {
         trigger.every = options.every;
     }
 
+    if (!trigger.count && device.platform == 'windows') {
+        trigger.count = trigger.every ? 5 : 1;
+    }
+
+    if (trigger.every && device.platform == 'windows') {
+        trigger.every = trigger.every.toString();
+    }
+
     if (!isCal) {
         trigger.notifyOnEntry = !!trigger.notifyOnEntry;
         trigger.notifyOnExit  = trigger.notifyOnExit === true;