using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; namespace Unity.Notifications.iOS { /// /// Constants indicating how to present a notification in a foreground app /// [Flags] public enum PresentationOption { /// /// No options are set. /// None = 0, /// /// Apply the notification's badge value to the app’s icon. /// Badge = 1 << 0, /// /// Play the sound associated with the notification. /// Sound = 1 << 1, /// /// Display the alert using the content provided by the notification. /// Alert = 1 << 2, } /// /// The type of sound to use for the notification. /// See Apple documentation for details. /// /// public enum NotificationSoundType { /// /// Play the default sound. /// Default = 0, /// /// Critical sound (bypass Do Not Disturb) /// Critical = 1, /// /// Ringtone sound. /// Ringtone = 2, /// /// No sound. /// None = 4, } [StructLayout(LayoutKind.Sequential)] internal struct TimeTriggerData { public Int32 interval; public Byte repeats; } [StructLayout(LayoutKind.Sequential)] internal struct CalendarTriggerData { public Int32 year; public Int32 month; public Int32 day; public Int32 hour; public Int32 minute; public Int32 second; public Byte repeats; } [StructLayout(LayoutKind.Sequential)] internal struct LocationTriggerData { public double latitude; public double longitude; public float radius; public Byte notifyOnEntry; public Byte notifyOnExit; public Byte repeats; } [StructLayout(LayoutKind.Explicit)] internal struct TriggerData { [FieldOffset(0)] public TimeTriggerData timeInterval; [FieldOffset(0)] public CalendarTriggerData calendar; [FieldOffset(0)] public LocationTriggerData location; } [StructLayout(LayoutKind.Sequential)] internal struct iOSNotificationData { public string identifier; public string title; public string body; public Int32 badge; public string subtitle; public string categoryIdentifier; public string threadIdentifier; public Int32 soundType; public float soundVolume; public string soundName; public IntPtr userInfo; public IntPtr attachments; // Trigger public Int32 triggerType; public TriggerData trigger; } /// /// The iOSNotification class is used schedule local notifications. It includes the content of the notification and the trigger conditions for delivery. /// An instance of this class is also returned when receiving remote notifications.. /// /// /// Create an instance of this class when you want to schedule the delivery of a local notification. It contains the entire notification payload to be delivered /// (which corresponds to UNNotificationContent) and also the NotificationTrigger object with the conditions that trigger the delivery of the notification. /// To schedule the delivery of your notification, pass an instance of this class to the method. /// public class iOSNotification { /// /// The unique identifier for this notification request. /// /// /// If not explicitly specified the identifier will be automatically generated when creating the notification. /// public string Identifier { get { return data.identifier; } set { data.identifier = value; } } /// /// The identifier of the app-defined category object. /// public string CategoryIdentifier { get { return data.categoryIdentifier; } set { data.categoryIdentifier = value; } } /// /// An identifier that used to group related notifications together. /// /// /// Automatic notification grouping according to the thread identifier is only supported on iOS 12 and above. /// public string ThreadIdentifier { get { return data.threadIdentifier; } set { data.threadIdentifier = value; } } /// /// A short description of the reason for the notification. /// public string Title { get { return data.title; } set { data.title = value; } } /// /// A secondary description of the reason for the notification. /// public string Subtitle { get { return data.subtitle; } set { data.subtitle = value; } } /// /// The message displayed in the notification alert. /// public string Body { get { return data.body; } set { data.body = value; } } /// /// Indicates whether the notification alert should be shown when the app is open. /// /// /// Subscribe to the event to receive a callback when the notification is triggered. /// public bool ShowInForeground { get { string value; if (userInfo.TryGetValue("showInForeground", out value)) return value == "YES"; return false; } set { userInfo["showInForeground"] = value ? "YES" : "NO"; } } /// /// Presentation options for displaying the local of notification when the app is running. Only works if is enabled and user has allowed enabled the requested options for your app. /// public PresentationOption ForegroundPresentationOption { get { try { string value; if (userInfo.TryGetValue("showInForegroundPresentationOptions", out value)) return (PresentationOption)Int32.Parse(value); return default; } catch (Exception) { return default; } } set { userInfo["showInForegroundPresentationOptions"] = ((int)value).ToString(); } } /// /// The number to display as a badge on the app’s icon. /// public int Badge { get { return data.badge; } set { data.badge = value; } } /// /// The type of sound to be played. /// public NotificationSoundType SoundType { get { return (NotificationSoundType)data.soundType; } set { data.soundType = (int)value; } } /// /// The name of the sound to be played. Use null for system default sound. /// See Apple documentation for named sounds and sound file placement. /// public string SoundName { get { return data.soundName; } set { data.soundName = value; } } /// /// The volume for the sound. Use null to use the default volume. /// See Apple documentation for supported values. /// /// public float? SoundVolume { get; set; } /// /// Arbitrary string data which can be retrieved when the notification is used to open the app or is received while the app is running. /// public string Data { get { string value; userInfo.TryGetValue("data", out value); return value; } set { userInfo["data"] = value; } } /// /// Key-value collection sent or received with the notification. /// Note, that some of the other notification properties are transfered using this collection, it is not recommended to modify existing items. /// public Dictionary UserInfo { get { return userInfo; } } /// /// A list of notification attachments. /// Notification attachments can be images, audio or video files. Refer to Apple documentation on supported formats. /// /// public List Attachments { get; set; } /// /// The conditions that trigger the delivery of the notification. /// For notification that were already delivered and whose instance was returned by or /// use this property to determine what caused the delivery to occur. You can do this by comparing to any of the notification trigger types that implement it, such as /// , , , . /// /// /// /// notification.Trigger is iOSNotificationPushTrigger /// /// public iOSNotificationTrigger Trigger { set { switch (value.Type) { case iOSNotificationTriggerType.TimeInterval: { var trigger = (iOSNotificationTimeIntervalTrigger)value; data.trigger.timeInterval.interval = trigger.timeInterval; if (trigger.Repeats && trigger.timeInterval < 60) throw new ArgumentException("Time interval must be 60 seconds or greater for repeating notifications."); data.trigger.timeInterval.repeats = (byte)(trigger.Repeats ? 1 : 0); break; } case iOSNotificationTriggerType.Calendar: { var trigger = ((iOSNotificationCalendarTrigger)value); if (userInfo == null) userInfo = new Dictionary(); userInfo["OriginalUtc"] = trigger.UtcTime ? "1" : "0"; if (!trigger.UtcTime) trigger = trigger.ToUtc(); data.trigger.calendar.year = trigger.Year != null ? trigger.Year.Value : -1; data.trigger.calendar.month = trigger.Month != null ? trigger.Month.Value : -1; data.trigger.calendar.day = trigger.Day != null ? trigger.Day.Value : -1; data.trigger.calendar.hour = trigger.Hour != null ? trigger.Hour.Value : -1; data.trigger.calendar.minute = trigger.Minute != null ? trigger.Minute.Value : -1; data.trigger.calendar.second = trigger.Second != null ? trigger.Second.Value : -1; data.trigger.calendar.repeats = (byte)(trigger.Repeats ? 1 : 0); break; } case iOSNotificationTriggerType.Location: { var trigger = (iOSNotificationLocationTrigger)value; data.trigger.location.latitude = trigger.Latitude; data.trigger.location.longitude = trigger.Longitude; data.trigger.location.notifyOnEntry = (byte)(trigger.NotifyOnEntry ? 1 : 0); data.trigger.location.notifyOnExit = (byte)(trigger.NotifyOnExit ? 1 : 0); data.trigger.location.radius = trigger.Radius; data.trigger.location.repeats = (byte)(trigger.Repeats ? 1 : 0); break; } case iOSNotificationTriggerType.Push: break; default: throw new Exception($"Unknown trigger type {value.Type}"); } data.triggerType = (int)value.Type; } get { switch ((iOSNotificationTriggerType)data.triggerType) { case iOSNotificationTriggerType.TimeInterval: return new iOSNotificationTimeIntervalTrigger() { timeInterval = data.trigger.timeInterval.interval, Repeats = data.trigger.timeInterval.repeats != 0, }; case iOSNotificationTriggerType.Calendar: { var trigger = new iOSNotificationCalendarTrigger() { Year = (data.trigger.calendar.year > 0) ? (int?)data.trigger.calendar.year : null, Month = (data.trigger.calendar.month > 0) ? (int?)data.trigger.calendar.month : null, Day = (data.trigger.calendar.day > 0) ? (int?)data.trigger.calendar.day : null, Hour = (data.trigger.calendar.hour >= 0) ? (int?)data.trigger.calendar.hour : null, Minute = (data.trigger.calendar.minute >= 0) ? (int?)data.trigger.calendar.minute : null, Second = (data.trigger.calendar.second >= 0) ? (int?)data.trigger.calendar.second : null, UtcTime = true, Repeats = data.trigger.calendar.repeats != 0 }; if (userInfo != null) { string utc; if (userInfo.TryGetValue("OriginalUtc", out utc)) { if (utc == "0") trigger = trigger.ToLocal(); } else trigger.UtcTime = false; } else trigger.UtcTime = false; return trigger; } case iOSNotificationTriggerType.Location: return new iOSNotificationLocationTrigger() { Latitude = data.trigger.location.latitude, Longitude = data.trigger.location.longitude, Radius = data.trigger.location.radius, NotifyOnEntry = data.trigger.location.notifyOnEntry != 0, NotifyOnExit = data.trigger.location.notifyOnExit != 0, Repeats = data.trigger.location.repeats != 0, }; case iOSNotificationTriggerType.Push: return new iOSNotificationPushTrigger(); default: throw new Exception($"Unknown trigger type {data.triggerType}"); } } } private static string GenerateUniqueID() { return Math.Abs(DateTime.Now.ToString("yyMMddHHmmssffffff").GetHashCode()).ToString(); } /// /// Create a new instance of and automatically generate an unique string for with all optional fields set to default values. /// public iOSNotification() : this(GenerateUniqueID()) { } /// /// Specify a and create a notification object with all optional fields set to default values. /// /// Unique identifier for the local notification tha can later be used to track or change it's status. public iOSNotification(string identifier) { data = new iOSNotificationData(); data.identifier = identifier; data.title = ""; data.body = ""; data.badge = -1; data.subtitle = ""; data.categoryIdentifier = ""; data.threadIdentifier = ""; data.triggerType = -1; data.userInfo = IntPtr.Zero; userInfo = new Dictionary(); Data = ""; ShowInForeground = false; ForegroundPresentationOption = PresentationOption.Alert | PresentationOption.Sound; } internal iOSNotification(iOSNotificationWithUserInfo data) { this.data = data.data; userInfo = data.userInfo; Attachments = data.attachments; } iOSNotificationData data; Dictionary userInfo; internal iOSNotificationWithUserInfo GetDataForSending() { if (data.identifier == null) data.identifier = GenerateUniqueID(); if (SoundVolume.HasValue) data.soundVolume = SoundVolume.Value; else data.soundVolume = -1.0f; iOSNotificationWithUserInfo ret; ret.data = data; ret.userInfo = userInfo; ret.attachments = Attachments; return ret; } } }