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;
}
}
}