2025-05-18 01:04:31 +08:00

488 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Unity.Notifications.iOS
{
/// <summary>
/// Constants indicating how to present a notification in a foreground app
/// </summary>
[Flags]
public enum PresentationOption
{
/// <summary>
/// No options are set.
/// </summary>
None = 0,
/// <summary>
/// Apply the notification's badge value to the apps icon.
/// </summary>
Badge = 1 << 0,
/// <summary>
/// Play the sound associated with the notification.
/// </summary>
Sound = 1 << 1,
/// <summary>
/// Display the alert using the content provided by the notification.
/// </summary>
Alert = 1 << 2,
}
/// <summary>
/// The type of sound to use for the notification.
/// See Apple documentation for details.
/// </summary>
/// <see href="https://developer.apple.com/documentation/usernotifications/unnotificationsound?language=objc"/>
public enum NotificationSoundType
{
/// <summary>
/// Play the default sound.
/// </summary>
Default = 0,
/// <summary>
/// Critical sound (bypass Do Not Disturb)
/// </summary>
Critical = 1,
/// <summary>
/// Ringtone sound.
/// </summary>
Ringtone = 2,
/// <summary>
/// No sound.
/// </summary>
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;
}
/// <summary>
/// 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..
/// </summary>
/// <remarks>
/// 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 <see cref="iOSNotificationCenter.ScheduleNotification"/> method.
/// </remarks>
public class iOSNotification
{
/// <summary>
/// The unique identifier for this notification request.
/// </summary>
/// <remarks>
/// If not explicitly specified the identifier will be automatically generated when creating the notification.
/// </remarks>
public string Identifier
{
get { return data.identifier; }
set { data.identifier = value; }
}
/// <summary>
/// The identifier of the app-defined category object.
/// </summary>
public string CategoryIdentifier
{
get { return data.categoryIdentifier; }
set { data.categoryIdentifier = value; }
}
/// <summary>
/// An identifier that used to group related notifications together.
/// </summary>
/// <remarks>
/// Automatic notification grouping according to the thread identifier is only supported on iOS 12 and above.
/// </remarks>
public string ThreadIdentifier
{
get { return data.threadIdentifier; }
set { data.threadIdentifier = value; }
}
/// <summary>
/// A short description of the reason for the notification.
/// </summary>
public string Title
{
get { return data.title; }
set { data.title = value; }
}
/// <summary>
/// A secondary description of the reason for the notification.
/// </summary>
public string Subtitle
{
get { return data.subtitle; }
set { data.subtitle = value; }
}
/// <summary>
/// The message displayed in the notification alert.
/// </summary>
public string Body
{
get { return data.body; }
set { data.body = value; }
}
/// <summary>
/// Indicates whether the notification alert should be shown when the app is open.
/// </summary>
/// <remarks>
/// Subscribe to the <see cref="iOSNotificationCenter.OnNotificationReceived"/> event to receive a callback when the notification is triggered.
/// </remarks>
public bool ShowInForeground
{
get
{
string value;
if (userInfo.TryGetValue("showInForeground", out value))
return value == "YES";
return false;
}
set { userInfo["showInForeground"] = value ? "YES" : "NO"; }
}
/// <summary>
/// Presentation options for displaying the local of notification when the app is running. Only works if <see cref="iOSNotification.ShowInForeground"/> is enabled and user has allowed enabled the requested options for your app.
/// </summary>
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(); }
}
/// <summary>
/// The number to display as a badge on the apps icon.
/// </summary>
public int Badge
{
get { return data.badge; }
set { data.badge = value; }
}
/// <summary>
/// The type of sound to be played.
/// </summary>
public NotificationSoundType SoundType
{
get { return (NotificationSoundType)data.soundType; }
set { data.soundType = (int)value; }
}
/// <summary>
/// The name of the sound to be played. Use null for system default sound.
/// See Apple documentation for named sounds and sound file placement.
/// </summary>
public string SoundName
{
get { return data.soundName; }
set { data.soundName = value; }
}
/// <summary>
/// The volume for the sound. Use null to use the default volume.
/// See Apple documentation for supported values.
/// </summary>
/// <see href="https://developer.apple.com/documentation/usernotifications/unnotificationsound/2963118-defaultcriticalsoundwithaudiovol?language=objc"/>
public float? SoundVolume { get; set; }
/// <summary>
/// Arbitrary string data which can be retrieved when the notification is used to open the app or is received while the app is running.
/// </summary>
public string Data
{
get
{
string value;
userInfo.TryGetValue("data", out value);
return value;
}
set { userInfo["data"] = value; }
}
/// <summary>
/// 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.
/// </summary>
public Dictionary<string, string> UserInfo
{
get { return userInfo; }
}
/// <summary>
/// A list of notification attachments.
/// Notification attachments can be images, audio or video files. Refer to Apple documentation on supported formats.
/// </summary>
/// <see cref="https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649857-attachments?language=objc"/>
public List<iOSNotificationAttachment> Attachments { get; set; }
/// <summary>
/// The conditions that trigger the delivery of the notification.
/// For notification that were already delivered and whose instance was returned by <see cref="iOSNotificationCenter.OnRemoteNotificationReceived"/> or <see cref="iOSNotificationCenter.OnRemoteNotificationReceived"/>
/// use this property to determine what caused the delivery to occur. You can do this by comparing <see cref="iOSNotification.Trigger"/> to any of the notification trigger types that implement it, such as
/// <see cref="iOSNotificationLocationTrigger"/>, <see cref="iOSNotificationPushTrigger"/>, <see cref="iOSNotificationTimeIntervalTrigger"/>, <see cref="iOSNotificationCalendarTrigger"/>.
/// </summary>
/// <example>
/// <code>
/// notification.Trigger is iOSNotificationPushTrigger
/// </code>
/// </example>
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<string, string>();
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();
}
/// <summary>
/// Create a new instance of <see cref="iOSNotification"/> and automatically generate an unique string for <see cref="iOSNotification.Identifier"/> with all optional fields set to default values.
/// </summary>
public iOSNotification() : this(GenerateUniqueID())
{
}
/// <summary>
/// Specify a <see cref="iOSNotification.Identifier"/> and create a notification object with all optional fields set to default values.
/// </summary>
/// <param name="identifier"> Unique identifier for the local notification tha can later be used to track or change it's status.</param>
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<string, string>();
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<string, string> 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;
}
}
}