Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace OneSignalSDK.Android.Notifications.Models
{
public sealed class AndroidDisplayableNotification : Notification, IDisplayableNotification
{
public AndroidJavaObject NotifJO { get; set; }
internal AndroidJavaObject NotifJO { get; set; }

public void Display() => NotifJO?.Call("display");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,36 +256,62 @@ public void onClick(AndroidJavaObject clickEvent)
private static AndroidDisplayableNotification _getNotification(AndroidJavaObject notifJO)
{
var notification = notifJO.ToSerializable<AndroidDisplayableNotification>();

var dataJson = notifJO.Call<AndroidJavaObject>("getAdditionalData");
if (dataJson != null)
{
var dataJsonStr = dataJson.Call<string>("toString");
notification.additionalData =
Json.Deserialize(dataJsonStr) as Dictionary<string, object>;
}
_fillNotificationDictionaries(notification, notifJO);

var groupedNotificationsJson = notifJO.Call<AndroidJavaObject>(
"getGroupedNotifications"
);
if (groupedNotificationsJson != null)
notification.groupedNotifications = _getGroupedNotifications(
groupedNotificationsJson
);

// attach the Java-Object to the notifification just built.
notification.NotifJO = notifJO;

return notification;
}

private static List<NotificationBase> _getGroupedNotifications(
AndroidJavaObject groupedNotifications
)
{
var count = groupedNotifications.Call<int>("size");
var notifications = new List<NotificationBase>(count);
for (var index = 0; index < count; index++)
{
var groupedNotificationsStr = groupedNotificationsJson.Call<string>("toString");
notification.groupedNotifications =
Json.Deserialize(groupedNotificationsStr) as List<NotificationBase>;
using var notificationJO = groupedNotifications.Call<AndroidJavaObject>(
"get",
index
);
var notification = notificationJO.ToSerializable<NotificationBase>();
_fillNotificationDictionaries(notification, notificationJO);
notifications.Add(notification);
}

return notifications;
}

private static void _fillNotificationDictionaries(
NotificationBase notification,
AndroidJavaObject notificationJO
)
{
var dataJson = notificationJO.Call<AndroidJavaObject>("getAdditionalData");
if (dataJson != null)
{
var dataJsonStr = dataJson.Call<string>("toString");
notification.additionalData =
Json.Deserialize(dataJsonStr) as Dictionary<string, object>;
}

var rawPayloadJson = notifJO.Call<AndroidJavaObject>("getRawPayload");
var rawPayloadJson = notificationJO.Call<AndroidJavaObject>("getRawPayload");
if (rawPayloadJson != null)
{
var rawPayloadJsonStr = rawPayloadJson.Call<string>("toString");
notification.rawPayload = rawPayloadJsonStr;
notification.rawPayload =
Json.Deserialize(rawPayloadJsonStr) as Dictionary<string, object>;
}

// attach the Java-Object to the notifification just built.
notification.NotifJO = notifJO;

return notification;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ private static Settings Load()
try
{
var values =
Json.Deserialize(File.ReadAllText(_settingsPath))
as Dictionary<string, object>;
Json.Deserialize(File.ReadAllText(_settingsPath)) as Dictionary<string, object>;
if (
values != null
&& values.TryGetValue(nameof(Settings.disableLocation), out var value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ public sealed class ActionButton : IActionButton
public string Id => id;
public string Text => text;
public string Icon => icon;
public string TemplateIcon => templateIcon;
public string SystemIcon => systemIcon;

#region Native Field Handling
public string id;
public string text;
public string icon;
public string templateIcon;
public string systemIcon;
#endregion
}

Expand Down Expand Up @@ -121,12 +125,12 @@ public class NotificationBase : INotificationBase
/// <summary>
/// List of action buttons on the notification
/// </summary>
public List<IActionButton> ActionButtons => actionButtons.ToList<IActionButton>();
public List<IActionButton> ActionButtons => actionButtons?.ToList<IActionButton>();

/// <summary>
/// Raw JSON payload string received from OneSignal
/// Raw payload received from OneSignal
/// </summary>
public string RawPayload => rawPayload;
public IDictionary<string, object> RawPayload => rawPayload;

#region Android
/// <summary>
Expand Down Expand Up @@ -282,7 +286,7 @@ public class NotificationBase : INotificationBase
public string collapseId;
public IDictionary<string, object> additionalData;
public List<ActionButton> actionButtons;
public string rawPayload;
public IDictionary<string, object> rawPayload;
public int androidNotificationId;
public string smallIcon;
public string largeIcon;
Expand Down Expand Up @@ -316,7 +320,7 @@ public class Notification : NotificationBase, INotification
/// </summary>
/// <remarks>Android only</remarks>
public List<INotificationBase> GroupedNotifications =>
groupedNotifications.ToList<INotificationBase>();
groupedNotifications?.ToList<INotificationBase>();

#region Native Field Handling
public List<NotificationBase> groupedNotifications;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ public interface IActionButton
string Id { get; }
string Text { get; }
string Icon { get; }

/// <summary>
/// Template image name for the action button icon
/// </summary>
/// <remarks>iOS only</remarks>
string TemplateIcon { get; }

/// <summary>
/// SF Symbol name for the action button icon
/// </summary>
/// <remarks>iOS only</remarks>
string SystemIcon { get; }
}

/// <summary>
Expand Down Expand Up @@ -118,9 +130,9 @@ public interface INotificationBase
List<IActionButton> ActionButtons { get; }

/// <summary>
/// Raw JSON payload string received from OneSignal
/// Raw payload received from OneSignal
/// </summary>
string RawPayload { get; }
IDictionary<string, object> RawPayload { get; }

#region Android
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private static void _fillNotifFromObj(ref iOSDisplayableNotification notif, obje
notifDict.ContainsKey("rawPayload")
&& notifDict["rawPayload"] is Dictionary<string, object> payloadDict
)
notif.rawPayload = Json.Serialize(payloadDict);
notif.rawPayload = payloadDict;
}

[AOT.MonoPInvokeCallback(typeof(BooleanResponseDelegate))]
Expand Down
59 changes: 58 additions & 1 deletion examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,23 @@ public class BuildPostProcessor : IPostprocessBuildWithReport

private static readonly string SoundsSourceDir = Path.Combine("iOS", "Sounds");
private static readonly string[] CustomSoundFiles = new string[] { "vine_boom.wav" };
private static readonly string NotificationIconsSourceDir = Path.Combine(
"iOS",
"NotificationIcons"
);
private static readonly string[] NotificationIconFiles = new string[]
{
"template-bookmark-icon.png",
"template-share-icon.png",
};
private const string NotificationServiceExtensionTargetName =
"OneSignalNotificationServiceExtension";

/// <summary>
/// must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and that it's
/// added before "pod install" (50)
/// </summary>
public int callbackOrder => 45;
public int callbackOrder => 46;

public void OnPostprocessBuild(BuildReport report)
{
Expand All @@ -80,6 +91,7 @@ public void OnPostprocessBuild(BuildReport report)
EnableAppForLiveActivities(report.summary.outputPath);
CreateWidgetExtension(report.summary.outputPath);
AddCustomSoundsToMainTarget(report.summary.outputPath);
AddNotificationIconsToTargets(report.summary.outputPath);

Debug.Log("BuildPostProcessor.OnPostprocessBuild complete");
}
Expand Down Expand Up @@ -143,6 +155,51 @@ static void AddCustomSoundsToMainTarget(string outputPath)
project.WriteToFile(projectPath);
}

static void AddNotificationIconsToTargets(string outputPath)
{
var project = new PBXProject();
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
project.ReadFromString(File.ReadAllText(projectPath));

var mainTargetGuid = project.GetUnityMainTargetGuid();
var extensionTargetGuid = project.TargetGuidByName(
NotificationServiceExtensionTargetName
);

if (string.IsNullOrEmpty(extensionTargetGuid))
{
Debug.LogWarning(
$"{NotificationServiceExtensionTargetName} target is missing; notification icons will only be bundled with the main app."
);
}

foreach (var fileName in NotificationIconFiles)
{
var sourcePath = Path.Combine(NotificationIconsSourceDir, fileName);
if (!File.Exists(sourcePath))
{
Debug.LogWarning(
$"Notification action icon missing at {sourcePath}; skipping iOS bundling."
);
continue;
}

var destAbsolutePath = Path.Combine(outputPath, fileName);
File.Copy(sourcePath, destAbsolutePath, true);

var fileGuid = project.FindFileGuidByProjectPath(fileName);
if (string.IsNullOrEmpty(fileGuid))
fileGuid = project.AddFile(fileName, fileName);

project.AddFileToBuild(mainTargetGuid, fileGuid);

if (!string.IsNullOrEmpty(extensionTargetGuid))
project.AddFileToBuild(extensionTargetGuid, fileGuid);
}

project.WriteToFile(projectPath);
}

static void AddWidgetExtensionToProject(string outputPath)
{
var project = new PBXProject();
Expand Down
93 changes: 89 additions & 4 deletions examples/demo/Assets/Scripts/AppBootstrapper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System.Collections;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OneSignalDemo.Services;
using OneSignalDemo.ViewModels;
using OneSignalSDK;
Expand All @@ -16,6 +20,13 @@ public class AppBootstrapper : MonoBehaviour
private const string PlaceholderAppId = "your-onesignal-app-id";
private const string Tag = "AppBootstrapper";

private static readonly JsonSerializerSettings EventLogJsonSettings = new()
{
ContractResolver = new PropertiesOnlyCamelCaseContractResolver(),
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};

[SerializeField]
private AppViewModel _viewModel;

Expand Down Expand Up @@ -179,16 +190,90 @@ private void OnIamDidDismiss(object sender, InAppMessageDidDismissEventArgs e)
private void OnIamClicked(object sender, InAppMessageClickEventArgs e) =>
Debug.Log($"[{Tag}] IAM clicked: {e.Result.ActionId}");

private void OnNotificationClicked(object sender, NotificationClickEventArgs e) =>
Debug.Log($"[{Tag}] Notification clicked: {e.Result.ActionId}");
private void OnNotificationClicked(object sender, NotificationClickEventArgs e)
{
Debug.Log($"[OneSignal] Notification click: {e.Notification.Title ?? string.Empty}");

// uncomment to see the full event object
// LogJson("[OneSignal] click event:", e);
}

private void OnNotificationForegroundWillDisplay(
object sender,
NotificationWillDisplayEventArgs e
)
{
Debug.Log($"[{Tag}] Notification received in foreground");
e.Notification.Display();
Debug.Log(
$"[OneSignal] Notification foregroundWillDisplay: {e.Notification.Title ?? string.Empty}"
);

// uncomment to see the full notification object
// LogJson("[OneSignal] will display event:", e.Notification);

// uncomment to test preventing the default display behavior
// e.PreventDefault();

// call this after PreventDefault() (within about 25 seconds) to force display
// e.Notification.Display();

// example with a delay (assumes PreventDefault() was called)
// StartCoroutine(DisplayNotificationAfterDelay(e.Notification, 24));
}

private static IEnumerator DisplayNotificationAfterDelay(
IDisplayableNotification notification,
int seconds
)
{
Debug.Log($"[OneSignal] Forcing notification display in {seconds} seconds");

while (seconds > 0)
{
Debug.Log($"[OneSignal] Displaying notification in {seconds} seconds");
yield return new WaitForSecondsRealtime(1);
seconds--;
}

Debug.Log("[OneSignal] Displaying notification");
notification.Display();
}

private static void LogJson(string label, object value)
{
var json =
$"{label}\n"
+ JsonConvert.SerializeObject(value, Formatting.Indented, EventLogJsonSettings);

#if UNITY_ANDROID && !UNITY_EDITOR
const int maxChunkLength = 3000;
using var androidLog = new AndroidJavaClass("android.util.Log");

for (var offset = 0; offset < json.Length; )
{
var length = System.Math.Min(maxChunkLength, json.Length - offset);
if (char.IsHighSurrogate(json[offset + length - 1]))
length--;

androidLog.CallStatic<int>("i", "OneSignal", json.Substring(offset, length));
offset += length;
}
#else
Debug.Log(json);
#endif
}

private sealed class PropertiesOnlyCamelCaseContractResolver
: CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization
)
{
var property = base.CreateProperty(member, memberSerialization);
property.Ignored = member.MemberType != MemberTypes.Property;
return property;
}
}
}
}
Loading