Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
44c3756
chore(demo): prepare demo app for Appium E2E tests
fadi-george Apr 28, 2026
99a80dd
chore(demo): remove LogManager, use Debug.Log
fadi-george Apr 28, 2026
207de0d
chore(demo): standardize UI element names for Appium tests
fadi-george Apr 28, 2026
b225f34
chore(demo): replace loading overlay with inline loading states
fadi-george Apr 28, 2026
5d10a9a
chore(demo): improve UI element names for Appium tests
fadi-george Apr 28, 2026
5f59a7d
feat(demo): add accessibility bridge for Appium E2E
fadi-george Apr 28, 2026
ce4aaef
fix(demo): improve a11y bridge reliability
fadi-george May 1, 2026
976475e
fix(demo): force iOS keyboard dismiss on modal close
fadi-george May 1, 2026
44978e3
fix(demo): fix loading state in login/logout flow
fadi-george May 1, 2026
185d13d
fix(demo): fix info-icon taps in Appium E2E tests
fadi-george May 2, 2026
bd65c0b
fix(demo): add a11y support for SwitchToggle
fadi-george May 2, 2026
ba1e616
feat(demo): add iOS signing post-processor
fadi-george May 2, 2026
dd0d6de
fix(demo): source IAM paused state from prefs
fadi-george May 2, 2026
fad8cf5
fix(demo): clarify add button labels
fadi-george May 2, 2026
66d364e
fix(demo): align dialog to top with padding
fadi-george May 2, 2026
45acb10
fix(demo): add a11y label to toast for Appium
fadi-george May 2, 2026
cbd0970
wip
fadi-george May 2, 2026
cd675ec
fix(demo): remove bottom safe area padding
fadi-george May 2, 2026
e166afb
fix(demo): stabilize XCUITest taps on ScrollView
fadi-george May 2, 2026
90cb3b2
fix(demo): add named-tap fallback for E2E reliability
fadi-george May 4, 2026
89178bb
fix(demo): refactor E2E tap fallback to use target registry
fadi-george May 5, 2026
f6feee1
fix(demo): prevent double-submit and duplicate aliases
fadi-george May 6, 2026
144d88b
refactor(demo): consolidate upsert helpers into MergePairs
fadi-george May 6, 2026
5db7c85
feat(demo): add app bar shadow element
fadi-george May 6, 2026
73517f2
chore(demo): bump Unity editor and packages
fadi-george May 11, 2026
15eac3f
fix(demo): trigger E2E tap on pointer up
fadi-george May 12, 2026
0119424
feat(demo): sync accessibility on toast show/hide
fadi-george May 12, 2026
24c5076
fix(demo): use prefs for location shared state
fadi-george May 12, 2026
386b6de
fix(demo): use cached location shared state
fadi-george May 12, 2026
e5f7ac8
fix(demo): cancel E2E tap on click event
fadi-george May 12, 2026
459c708
feat(demo): add Android native accessibility bridge
fadi-george May 12, 2026
a9aa1ad
fix(demo): hide default checkbox indicator
fadi-george May 12, 2026
3654d95
fix(demo): suppress spell-check underline on E2E input
fadi-george May 12, 2026
f3832a0
feat(demo): add E2E gutter swipe scroll support
fadi-george May 12, 2026
a712965
fix(demo): skip a11y sync when nothing changed
fadi-george May 13, 2026
152d6da
fix(demo): use BaseBoolField for radio a11y support
fadi-george May 13, 2026
d119746
fix(demo): incremental a11y rebuild to preserve node identity
fadi-george May 13, 2026
acbfa62
fix(demo): pin widget pod to OneSignal version
fadi-george May 13, 2026
5f0312e
fix(demo): register SDK listeners before config
fadi-george May 13, 2026
9270923
refactor(demo): remove E2E tap fallback machinery
fadi-george May 13, 2026
71f3fc4
refactor(demo): rename E2E tap registry to AndroidClickTarget
fadi-george May 13, 2026
d163e13
fix(demo): set simulator arch to arm64 for Apple Silicon
fadi-george May 13, 2026
e16974f
refactor(demo): replace root PointerDown with ClickEvent
fadi-george May 13, 2026
d6cbdc5
fix(demo): restore panel-root PointerDown for iOS info icons
fadi-george May 15, 2026
ec0d7df
refactor(demo): disable tap-marker overlay
fadi-george May 15, 2026
ef04560
refactor(demo): remove ShowToast calls
fadi-george May 15, 2026
74a1c22
refactor(demo): trim inputs and extract RenderPairList
fadi-george May 15, 2026
3f8bb50
fix(demo): suppress dev console in E2E mode
fadi-george May 15, 2026
f7a94f6
fix(demo): force accessibility refresh on foreground
fadi-george May 15, 2026
c204543
fix: [SDK-4406] use monotonic row index in MultiPairInputDialog
fadi-george May 15, 2026
f8ba425
fix(demo): fix stale Android click targets and IAM WebView debugging
fadi-george May 16, 2026
c35fb86
fix(demo): address Unity demo PR feedback
fadi-george May 16, 2026
a213803
fix(demo): register back button as E2E tap target
fadi-george May 16, 2026
384badd
fix(demo): apply safe area to secondary screen
fadi-george May 16, 2026
96b8e5b
fix(demo): route push permission through OneSignal SDK
fadi-george May 18, 2026
b3c0c3c
fix(demo): make Android channel ID configurable
fadi-george May 18, 2026
b3e5f34
fix(demo): prompt push before loading data
fadi-george May 18, 2026
0ab8306
fix(demo): bundle vine_boom sound and restore EndLiveActivity guard
fadi-george May 18, 2026
bd69e62
chore: remove legacy notification.wav from Android SDK sample resources
fadi-george May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-keep class com.onesignal.** { *; }

# Work around for IllegalStateException with kotlinx-coroutines-android
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
Binary file not shown.

This file was deleted.

7 changes: 7 additions & 0 deletions examples/demo/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# Default App ID (used when ONESIGNAL_APP_ID is empty or missing): 77e32082-ea27-42e3-a898-c72e141824ef
ONESIGNAL_APP_ID=your-onesignal-app-id
ONESIGNAL_API_KEY=your_rest_api_key
E2E_MODE=false

# Optional: Android Notification Channel ID for the WITH SOUND test notification.
# Create one in your OneSignal dashboard under Settings > Android Notification Categories.
ONESIGNAL_ANDROID_CHANNEL_ID=
97 changes: 95 additions & 2 deletions examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using UnityEditor.iOS.Xcode.Extensions;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace App.Editor.iOS
{
Expand All @@ -53,6 +54,9 @@ public class BuildPostProcessor : IPostprocessBuildWithReport
"OneSignalWidgetLiveActivity.swift",
};

private static readonly string SoundsSourceDir = Path.Combine("iOS", "Sounds");
private static readonly string[] CustomSoundFiles = new string[] { "vine_boom.wav" };

/// <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)
Expand All @@ -76,6 +80,7 @@ public void OnPostprocessBuild(BuildReport report)

EnableAppForLiveActivities(report.summary.outputPath);
CreateWidgetExtension(report.summary.outputPath);
AddCustomSoundsToMainTarget(report.summary.outputPath);

Debug.Log("BuildPostProcessor.OnPostprocessBuild complete");
}
Expand All @@ -102,6 +107,43 @@ static void CreateWidgetExtension(string outputPath)
AddWidgetExtensionToPodFile(outputPath);
}

/// <summary>
/// Copies bundled .wav files into the Xcode project and registers them on the main
/// target's Resources build phase so `ios_sound` payload values resolve to a real
/// `UNNotificationSound` resource in the .app bundle.
/// </summary>
static void AddCustomSoundsToMainTarget(string outputPath)
{
var project = new PBXProject();
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
project.ReadFromString(File.ReadAllText(projectPath));

var mainTargetGuid = project.GetUnityMainTargetGuid();

foreach (var fileName in CustomSoundFiles)
{
var sourcePath = Path.Combine(SoundsSourceDir, fileName);
if (!File.Exists(sourcePath))
{
Debug.LogWarning(
$"Custom sound asset missing at {sourcePath}; skipping iOS bundling."
);
continue;
}

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

if (!string.IsNullOrEmpty(project.FindFileGuidByProjectPath(fileName)))
continue;

var fileGuid = project.AddFile(fileName, fileName);
project.AddFileToBuild(mainTargetGuid, fileGuid);
}

project.WriteToFile(projectPath);
}

static void AddWidgetExtensionToProject(string outputPath)
{
var project = new PBXProject();
Expand Down Expand Up @@ -159,12 +201,63 @@ static void AddWidgetExtensionToPodFile(string outputPath)
return;
}

// Keep the widget extension pinned to the same OneSignalXCFramework version as the
// core plugin so CocoaPods can resolve a single shared version across targets.
var requiredVersion = ResolveOneSignalXCFrameworkVersion();
var versionConstraint =
requiredVersion != null ? $"'{requiredVersion}'" : "'>= 5.0.2', '< 6.0.0'";
var requiredTarget =
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', {versionConstraint}\nend\n";

var podfile = File.ReadAllText(podfilePath);
podfile +=
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '>= 5.0.2', '< 6.0.0'\nend\n";
var podfileRegex = new Regex(
$@"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '(.+)'\nend\n"
);

if (!podfileRegex.IsMatch(podfile))
podfile += requiredTarget;
else
{
var podfileTarget = podfileRegex.Match(podfile).ToString();
podfile = podfile.Replace(podfileTarget, requiredTarget);
}

File.WriteAllText(podfilePath, podfile);
}

static string ResolveOneSignalXCFrameworkVersion()
{
var dependenciesFilePath = Path.Combine(
"Packages",
"com.onesignal.unity.ios",
"Editor",
"OneSignaliOSDependencies.xml"
);

if (!File.Exists(dependenciesFilePath))
{
Debug.LogWarning(
$"Could not find {dependenciesFilePath}; falling back to default OneSignalXCFramework version range."
);
return null;
}

var dependenciesFile = File.ReadAllText(dependenciesFilePath);
var dependenciesRegex = new Regex(
"(?<=<iosPod name=\"OneSignalXCFramework\" version=\")[^\"]+(?=\" addToAllTargets=\"true\" />)"
);

if (!dependenciesRegex.IsMatch(dependenciesFile))
{
Debug.LogWarning(
Comment thread
fadi-george marked this conversation as resolved.
$"Could not read OneSignalXCFramework version from {dependenciesFilePath}; falling back to default version range."
);
return null;
}

return dependenciesRegex.Match(dependenciesFile).ToString();
}

static void CopyFileOrDirectory(string sourcePath, string destinationPath)
{
var file = new FileInfo(sourcePath);
Expand Down
154 changes: 154 additions & 0 deletions examples/demo/Assets/App/Editor/iOS/SigningPostProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#if UNITY_IOS

using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace App.Editor.iOS
{
/// <summary>
/// Final iOS post-processor for the demo app. Runs AFTER the OneSignal
/// SDK and demo widget post-processors so it can correct things they set:
///
/// 1. Flips the main target's aps-environment from "production" (the SDK
/// default) to "development". The demo only ever runs on simulator or
/// a development device; "production" mismatches the simulator's APNS
/// environment and triggers iOS's "Keep receiving notifications?"
/// tuning prompt on first delivery (matches what the Flutter demo
/// ships with).
///
/// 2. Normalizes extension bundle IDs to short suffixes (`.NSE`, `.LA`)
/// to match the Flutter demo and keep provisioning profile names
/// consistent across SDKs.
///
/// 3. Pins DEVELOPMENT_TEAM on all targets so a future Manual signing
/// setup with the OneSignal-owned profiles works without manual
/// fix-up in Xcode.
/// </summary>
public class SigningPostProcessor : IPostprocessBuildWithReport
{
private const string AppleTeamId = "99SW8E36CT";
private const string ApsEnvironment = "development";

private const string NseTargetName = "OneSignalNotificationServiceExtension";
private const string WidgetTargetName = "OneSignalWidgetExtension";

// Short bundle-id suffixes (match the Flutter demo).
private const string NseBundleSuffix = "NSE";
private const string WidgetBundleSuffix = "LA";

// Run after both demo widget post-processor (45) and SDK
// post-processor (45). 100 puts us after pod install (50) too.
public int callbackOrder => 100;

public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.iOS)
return;

var outputPath = report.summary.outputPath;
FixupApsEnvironment(outputPath);
FixupSigningAndBundleIds(outputPath);
}

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

var mainTargetGuid = project.GetUnityMainTargetGuid();
var relPath = project.GetBuildPropertyForAnyConfig(
mainTargetGuid,
"CODE_SIGN_ENTITLEMENTS"
);

if (string.IsNullOrEmpty(relPath))
{
Debug.LogWarning(
"[SigningPostProcessor] Main target has no CODE_SIGN_ENTITLEMENTS; "
+ "skipping aps-environment fixup."
);
return;
}

var fullPath = Path.Combine(outputPath, relPath);
if (!File.Exists(fullPath))
{
Debug.LogWarning(
$"[SigningPostProcessor] Entitlements file not found at {fullPath}; skipping."
);
return;
}

var plist = new PlistDocument();
plist.ReadFromFile(fullPath);
plist.root.SetString("aps-environment", ApsEnvironment);
plist.WriteToFile(fullPath);

Debug.Log(
$"[SigningPostProcessor] Set aps-environment=\"{ApsEnvironment}\" in {relPath}"
);
}

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

var appId = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS);

ApplyTeamId(project, project.GetUnityMainTargetGuid(), "Unity-iPhone");

ApplyExtensionFixup(
project,
NseTargetName,
$"{appId}.{NseBundleSuffix}"
);
ApplyExtensionFixup(
project,
WidgetTargetName,
$"{appId}.{WidgetBundleSuffix}"
);

File.WriteAllText(projectPath, project.WriteToString());
}

private static void ApplyTeamId(PBXProject project, string targetGuid, string label)
{
if (string.IsNullOrEmpty(targetGuid))
return;

project.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", AppleTeamId);
Debug.Log($"[SigningPostProcessor] Pinned DEVELOPMENT_TEAM={AppleTeamId} on {label}");
}

private static void ApplyExtensionFixup(
PBXProject project,
string targetName,
string bundleId
)
{
var guid = project.TargetGuidByName(targetName);
if (string.IsNullOrEmpty(guid))
{
Debug.LogWarning(
$"[SigningPostProcessor] Target '{targetName}' not found; skipping."
);
return;
}

project.SetBuildProperty(guid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId);
ApplyTeamId(project, guid, targetName);
Debug.Log(
$"[SigningPostProcessor] Set {targetName} PRODUCT_BUNDLE_IDENTIFIER={bundleId}"
);
}
}
}

#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
-keep class com.onesignal.** { *; }

# Work around for IllegalStateException with kotlinx-coroutines-android
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}

# WorkManager initializes a Room database through AndroidX Startup before Unity starts.
# Unity release builds run R8, so keep the generated database implementation reachable.
-keep class androidx.work.impl.WorkDatabase* { *; }
-keep class androidx.work.impl.model.** { *; }
Loading