From 76040e4876bd191b597ce37dcbec53c719f33d4c Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 16:53:16 -0700 Subject: [PATCH 01/10] feat(demo): add Live Activities support --- examples/demo/.env.example | 1 + examples/demo/.gitignore | 3 + .../demo/Assets/Scripts/AppBootstrapper.cs | 12 ++ .../Repositories/OneSignalRepository.cs | 15 ++ .../Scripts/Services/OneSignalApiService.cs | 75 +++++++++ .../Assets/Scripts/UI/HomeScreenController.cs | 8 + .../LiveActivitiesSectionController.cs | 144 ++++++++++++++++ .../Assets/Scripts/ViewModels/AppViewModel.cs | 126 ++++++++++++++ .../ExampleWidgetLiveActivity.swift | 154 +++++++++++++----- 9 files changed, 499 insertions(+), 39 deletions(-) create mode 100644 examples/demo/.env.example create mode 100644 examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs diff --git a/examples/demo/.env.example b/examples/demo/.env.example new file mode 100644 index 000000000..674a938f9 --- /dev/null +++ b/examples/demo/.env.example @@ -0,0 +1 @@ +ONESIGNAL_API_KEY=your_rest_api_key diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore index 8ec7575ff..42d2dfc8b 100644 --- a/examples/demo/.gitignore +++ b/examples/demo/.gitignore @@ -64,6 +64,9 @@ crashlytics-build.properties # User-specific Unity Editor settings /[Uu]serSettings/ +# Environment files +.env + # Gradle template backup files *.backup *.backup.meta diff --git a/examples/demo/Assets/Scripts/AppBootstrapper.cs b/examples/demo/Assets/Scripts/AppBootstrapper.cs index 77b6cb06b..71f68141c 100644 --- a/examples/demo/Assets/Scripts/AppBootstrapper.cs +++ b/examples/demo/Assets/Scripts/AppBootstrapper.cs @@ -4,6 +4,7 @@ using OneSignalSDK; using OneSignalSDK.Debug.Models; using OneSignalSDK.InAppMessages; +using OneSignalSDK.LiveActivities; using OneSignalSDK.Notifications; using UnityEngine; @@ -40,12 +41,23 @@ private async void Start() } _apiService.SetAppId(appId); + _apiService.LoadApiKey(); OneSignal.Debug.LogLevel = LogLevel.Verbose; OneSignal.ConsentRequired = _prefs.ConsentRequired; OneSignal.ConsentGiven = _prefs.PrivacyConsent; OneSignal.Initialize(appId); +#if UNITY_IOS + OneSignal.LiveActivities.SetupDefault( + new LiveActivitySetupOptions + { + EnablePushToStart = true, + EnablePushToUpdate = true, + } + ); +#endif + OneSignal.InAppMessages.Paused = _prefs.IamPaused; OneSignal.Location.IsShared = _prefs.LocationShared; diff --git a/examples/demo/Assets/Scripts/Repositories/OneSignalRepository.cs b/examples/demo/Assets/Scripts/Repositories/OneSignalRepository.cs index 0c5b7968e..82b744336 100644 --- a/examples/demo/Assets/Scripts/Repositories/OneSignalRepository.cs +++ b/examples/demo/Assets/Scripts/Repositories/OneSignalRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using OneSignalDemo.Models; using OneSignalDemo.Services; using OneSignalSDK; @@ -112,5 +113,19 @@ public async Task SendCustomNotification(string title, string body) public async Task FetchUser(string onesignalId) => await _apiService.FetchUser(onesignalId); + + public bool HasApiKey() => _apiService.HasApiKey(); + + public void StartDefaultLiveActivity( + string activityId, + IDictionary attributes, + IDictionary content + ) => OneSignal.LiveActivities.StartDefault(activityId, attributes, content); + + public async Task UpdateLiveActivity( + string activityId, + string eventType, + JObject eventUpdates = null + ) => await _apiService.UpdateLiveActivity(activityId, eventType, eventUpdates); } } diff --git a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs index 7c5f66b43..925c0322b 100644 --- a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs +++ b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs @@ -1,8 +1,10 @@ using System; +using System.IO; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using OneSignalDemo.Models; +using UnityEngine; using UnityEngine.Networking; namespace OneSignalDemo.Services @@ -10,14 +12,41 @@ namespace OneSignalDemo.Services public class OneSignalApiService { private string _appId; + private string _apiKey; private const string NotificationImageUrl = "https://media.onesignal.com/automated_push_templates/ratings_template.png"; + private const string PlaceholderApiKey = "your_rest_api_key"; + public void SetAppId(string appId) => _appId = appId; public string GetAppId() => _appId; + public void LoadApiKey() + { + var envPath = Path.Combine(Application.dataPath, "..", ".env"); + if (!File.Exists(envPath)) + return; + + foreach (var line in File.ReadAllLines(envPath)) + { + var trimmed = line.Trim(); + if (trimmed.StartsWith("#") || !trimmed.Contains("=")) + continue; + + var eqIndex = trimmed.IndexOf('='); + var key = trimmed.Substring(0, eqIndex).Trim(); + var value = trimmed.Substring(eqIndex + 1).Trim(); + + if (key == "ONESIGNAL_API_KEY") + _apiKey = value; + } + } + + public bool HasApiKey() => + !string.IsNullOrEmpty(_apiKey) && _apiKey != PlaceholderApiKey; + public async Task SendNotification(NotificationType type, string subscriptionId) { if (string.IsNullOrEmpty(subscriptionId) || string.IsNullOrEmpty(_appId)) @@ -108,6 +137,52 @@ public async Task FetchUser(string onesignalId) return result; } + public async Task UpdateLiveActivity( + string activityId, + string eventType, + JObject eventUpdates = null + ) + { + if (string.IsNullOrEmpty(activityId) || string.IsNullOrEmpty(_appId) || !HasApiKey()) + return false; + + var url = + $"https://api.onesignal.com/apps/{_appId}/live_activities/{activityId}/notifications"; + + var payload = new JObject + { + ["event"] = eventType, + ["name"] = "Unity Demo Update", + ["priority"] = 10, + }; + + if (eventUpdates != null) + payload["event_updates"] = eventUpdates; + + if (eventType == "end") + { + var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + payload["dismissal_date"] = unixTimestamp; + } + + var jsonPayload = payload.ToString(); + var request = new UnityWebRequest(url, "POST"); + var bodyRaw = Encoding.UTF8.GetBytes(jsonPayload); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + request.SetRequestHeader("Authorization", $"Key {_apiKey}"); + + var tcs = new TaskCompletionSource(); + var operation = request.SendWebRequest(); + operation.completed += _ => tcs.TrySetResult(true); + await tcs.Task; + + bool success = request.responseCode >= 200 && request.responseCode < 300; + request.Dispose(); + return success; + } + private async Task PostNotification(string jsonPayload) { var request = new UnityWebRequest("https://onesignal.com/api/v1/notifications", "POST"); diff --git a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs index 0ee0157d3..68a914562 100644 --- a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs +++ b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs @@ -41,6 +41,7 @@ public class HomeScreenController : MonoBehaviour private TriggersSectionController _triggersSection; private TrackEventSectionController _trackEventSection; private LocationSectionController _locationSection; + private LiveActivitiesSectionController _liveActivitiesSection; private void OnEnable() { @@ -254,6 +255,12 @@ private void BuildSections() _locationSection.OnInfoTap = () => ShowTooltip("location"); _contentRoot.Add(_locationSection.Root); +#if UNITY_IOS + _liveActivitiesSection = new LiveActivitiesSectionController(_viewModel); + _liveActivitiesSection.OnInfoTap = () => ShowTooltip("liveActivities"); + _contentRoot.Add(_liveActivitiesSection.Root); +#endif + var nextButton = SectionBuilder.CreatePrimaryButton( "NEXT ACTIVITY", "next_activity_button", @@ -280,6 +287,7 @@ private void RefreshAll() _tagsSection?.Refresh(); _triggersSection?.Refresh(); _locationSection?.Refresh(); + _liveActivitiesSection?.Refresh(); var showLoading = _viewModel.IsLoading; _loadingOverlay.style.display = showLoading ? DisplayStyle.Flex : DisplayStyle.None; diff --git a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs new file mode 100644 index 000000000..2fbda7b66 --- /dev/null +++ b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs @@ -0,0 +1,144 @@ +using System; +using OneSignalDemo.ViewModels; +using UnityEngine.UIElements; + +namespace OneSignalDemo.UI.Sections +{ + public class LiveActivitiesSectionController + { + private readonly AppViewModel _viewModel; + private readonly VisualElement _root; + private TextField _activityIdField; + private TextField _orderNumberField; + private Button _startButton; + private Button _updateButton; + private Button _endButton; + + public Action OnInfoTap; + + public LiveActivitiesSectionController(AppViewModel viewModel) + { + _viewModel = viewModel; + _root = BuildSection(); + } + + public VisualElement Root => _root; + + private VisualElement BuildSection() + { + var section = SectionBuilder.CreateSection( + "Live Activities", + "live_activities_section", + () => OnInfoTap?.Invoke() + ); + + var inputCard = SectionBuilder.CreateCard("live_activities_input_card"); + + var activityIdRow = CreateInlineInputRow( + "Activity ID", + "order-1", + "live_activity_id_input" + ); + _activityIdField = activityIdRow.Q(); + inputCard.Add(activityIdRow); + + inputCard.Add(SectionBuilder.CreateDivider(true)); + + var orderNumberRow = CreateInlineInputRow( + "Order #", + "ORD-1234", + "live_activity_order_input" + ); + _orderNumberField = orderNumberRow.Q(); + inputCard.Add(orderNumberRow); + + section.Add(inputCard); + + _startButton = SectionBuilder.CreatePrimaryButton( + "START LIVE ACTIVITY", + "start_live_activity_button", + OnStartTap + ); + section.Add(_startButton); + + _updateButton = SectionBuilder.CreatePrimaryButton( + $"UPDATE \u2192 {_viewModel.NextStatusLabel}", + "update_live_activity_button", + OnUpdateTap + ); + section.Add(_updateButton); + + _endButton = SectionBuilder.CreateDestructiveButton( + "END LIVE ACTIVITY", + "end_live_activity_button", + OnEndTap + ); + section.Add(_endButton); + + RefreshButtonStates(); + return section; + } + + public void Refresh() + { + RefreshButtonStates(); + } + + private void RefreshButtonStates() + { + bool hasActivityId = !string.IsNullOrEmpty(_activityIdField?.value); + bool hasApiKey = _viewModel.HasApiKey; + + _startButton?.SetEnabled(hasActivityId); + + bool canUpdate = hasActivityId && hasApiKey && !_viewModel.IsLiveActivityUpdating; + _updateButton?.SetEnabled(canUpdate); + if (_updateButton != null) + _updateButton.text = $"UPDATE \u2192 {_viewModel.NextStatusLabel}"; + + _endButton?.SetEnabled(hasActivityId && hasApiKey); + } + + private void OnStartTap() + { + var activityId = _activityIdField?.value; + var orderNumber = _orderNumberField?.value ?? "ORD-1234"; + _viewModel.StartLiveActivity(activityId, orderNumber); + } + + private void OnUpdateTap() + { + var activityId = _activityIdField?.value; + _viewModel.UpdateLiveActivity(activityId); + } + + private void OnEndTap() + { + var activityId = _activityIdField?.value; + _viewModel.EndLiveActivity(activityId); + } + + private static VisualElement CreateInlineInputRow( + string label, + string defaultValue, + string name + ) + { + var row = new VisualElement(); + row.AddToClassList("toggle-row"); + + var labelElement = new Label(label); + labelElement.AddToClassList("toggle-label"); + labelElement.AddToClassList("text-toggle-label"); + row.Add(labelElement); + + var field = new TextField(); + field.name = name; + field.value = defaultValue; + field.AddToClassList("inline-input-field"); + row.Add(field); + + return row; + } + } +} diff --git a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs index 4f85cbe0f..331b6ed20 100644 --- a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs +++ b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using OneSignalDemo.Models; using OneSignalDemo.Repositories; using OneSignalDemo.Services; @@ -36,6 +37,19 @@ public class AppViewModel : MonoBehaviour private List> _tagsList = new(); private List> _triggersList = new(); + private int _liveActivityStatusIndex; + private bool _isLiveActivityUpdating; + + private static readonly string[] LiveActivityStatuses = { "preparing", "on_the_way", "delivered" }; + private static readonly string[] LiveActivityMessages = + { + "Your order is being prepared", + "Driver is heading your way", + "Order delivered!", + }; + private static readonly string[] LiveActivityETAs = { "15 min", "10 min", "" }; + private static readonly string[] LiveActivityStatusLabels = { "PREPARING", "ON THE WAY", "DELIVERED" }; + public string AppId => _appId; public bool ConsentRequired => _consentRequired; public bool PrivacyConsentGiven => _privacyConsentGiven; @@ -54,6 +68,19 @@ public class AppViewModel : MonoBehaviour public IReadOnlyList> Tags => _tagsList; public IReadOnlyList> Triggers => _triggersList; + public int LiveActivityStatusIndex => _liveActivityStatusIndex; + public bool IsLiveActivityUpdating => _isLiveActivityUpdating; + public bool HasApiKey => _repository?.HasApiKey() ?? false; + + public string NextStatusLabel + { + get + { + int nextIndex = (_liveActivityStatusIndex + 1) % LiveActivityStatuses.Length; + return LiveActivityStatusLabels[nextIndex]; + } + } + public event Action OnStateChanged; public event Action OnToastMessage; @@ -368,6 +395,105 @@ public async void SendCustomNotification(string title, string body) } } + public void StartLiveActivity(string activityId, string orderNumber) + { + if (string.IsNullOrEmpty(activityId)) + return; + + var attributes = new Dictionary { { "orderNumber", orderNumber } }; + var content = new Dictionary + { + { "status", LiveActivityStatuses[0] }, + { "message", LiveActivityMessages[0] }, + { "estimatedTime", LiveActivityETAs[0] }, + }; + + _repository.StartDefaultLiveActivity(activityId, attributes, content); + _liveActivityStatusIndex = 0; + + LogManager.Instance.Info(Tag, $"Started Live Activity: {activityId}"); + ShowToast($"Started Live Activity: {activityId}"); + NotifyStateChanged(); + } + + public async void UpdateLiveActivity(string activityId) + { + if (string.IsNullOrEmpty(activityId) || _isLiveActivityUpdating) + return; + + _isLiveActivityUpdating = true; + NotifyStateChanged(); + + try + { + int nextIndex = (_liveActivityStatusIndex + 1) % LiveActivityStatuses.Length; + var eventUpdates = new JObject + { + ["data"] = new JObject + { + ["status"] = LiveActivityStatuses[nextIndex], + ["message"] = LiveActivityMessages[nextIndex], + ["estimatedTime"] = LiveActivityETAs[nextIndex], + }, + }; + + bool success = await _repository.UpdateLiveActivity(activityId, "update", eventUpdates); + if (success) + { + _liveActivityStatusIndex = nextIndex; + LogManager.Instance.Info(Tag, $"Updated Live Activity: {activityId}"); + ShowToast($"Updated Live Activity: {activityId}"); + } + else + { + LogManager.Instance.Error(Tag, "Failed to update Live Activity"); + ShowToast("Failed to update Live Activity"); + } + } + catch (Exception ex) + { + LogManager.Instance.Error(Tag, $"Live Activity update error: {ex.Message}"); + ShowToast("Failed to update Live Activity"); + } + + _isLiveActivityUpdating = false; + NotifyStateChanged(); + } + + public async void EndLiveActivity(string activityId) + { + if (string.IsNullOrEmpty(activityId)) + return; + + try + { + var eventUpdates = new JObject + { + ["data"] = new JObject { ["message"] = "Ended" }, + }; + + bool success = await _repository.UpdateLiveActivity(activityId, "end", eventUpdates); + if (success) + { + _liveActivityStatusIndex = 0; + LogManager.Instance.Info(Tag, $"Ended Live Activity: {activityId}"); + ShowToast($"Ended Live Activity: {activityId}"); + } + else + { + LogManager.Instance.Error(Tag, "Failed to end Live Activity"); + ShowToast("Failed to end Live Activity"); + } + } + catch (Exception ex) + { + LogManager.Instance.Error(Tag, $"Live Activity end error: {ex.Message}"); + ShowToast("Failed to end Live Activity"); + } + + NotifyStateChanged(); + } + public void SetConsentRequired(bool required) { _consentRequired = required; diff --git a/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift b/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift index 6d627f527..141d85e45 100644 --- a/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift +++ b/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift @@ -1,68 +1,144 @@ -// -// ExampleWidgetLiveActivity.swift -// ExampleWidget -// -// Created by Brian Smith on 4/30/24. -// Copyright © 2024 The Chromium Authors. All rights reserved. -// - #if !targetEnvironment(macCatalyst) import ActivityKit import WidgetKit import SwiftUI import OneSignalLiveActivities +@available(iOS 16.2, *) struct ExampleWidgetLiveActivity: Widget { + + private func statusIcon(for status: String) -> String { + switch status { + case "on_the_way": return "box.truck.fill" + case "delivered": return "checkmark.circle.fill" + default: return "bag.fill" + } + } + + private func statusColor(for status: String) -> Color { + switch status { + case "on_the_way": return .blue + case "delivered": return .green + default: return .orange + } + } + + private func statusLabel(for status: String) -> String { + switch status { + case "on_the_way": return "On the Way" + case "delivered": return "Delivered" + default: return "Preparing" + } + } + var body: some WidgetConfiguration { ActivityConfiguration(for: DefaultLiveActivityAttributes.self) { context in - // Lock screen/banner UI goes here\VStack(alignment: .leading) { - VStack { - Spacer() - Text("UNITY: " + (context.attributes.data["title"]?.asString() ?? "")).font(.headline) - Spacer() + let orderNumber = context.attributes.data["orderNumber"]?.asString() ?? "Order" + let status = context.state.data["status"]?.asString() ?? "preparing" + let message = context.state.data["message"]?.asString() ?? "Your order is being prepared" + let eta = context.state.data["estimatedTime"]?.asString() ?? "" + + VStack(spacing: 10) { HStack { + Text(orderNumber) + .font(.caption) + .foregroundColor(.gray) Spacer() - Label { - Text(context.state.data["message"]?.asDict()?["en"]?.asString() ?? "") - } icon: { - Image("onesignaldemo") - .resizable() - .scaledToFit() - .frame(width: 40.0, height: 40.0) + if !eta.isEmpty { + Text(eta) + .font(.caption) + .foregroundColor(.white.opacity(0.7)) + } + } + + HStack(spacing: 12) { + Image(systemName: statusIcon(for: status)) + .font(.title2) + .foregroundColor(statusColor(for: status)) + + VStack(alignment: .leading, spacing: 2) { + Text(statusLabel(for: status)) + .font(.headline) + .foregroundColor(.white) + Text(message) + .font(.subheadline) + .foregroundColor(.white.opacity(0.8)) + .lineLimit(1) } Spacer() } - Text("INT: " + String(context.state.data["intValue"]?.asInt() ?? 0)) - Text("DBL: " + String(context.state.data["doubleValue"]?.asDouble() ?? 0.0)) - Text("BOL: " + String(context.state.data["boolValue"]?.asBool() ?? false)) - Spacer() + + DeliveryProgressBar(status: status) } - .activitySystemActionForegroundColor(.black) - .activityBackgroundTint(.white) - } dynamicIsland: { _ in - DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom + .padding() + .activityBackgroundTint(Color(red: 0.11, green: 0.13, blue: 0.19)) + .activitySystemActionForegroundColor(.white) + + } dynamicIsland: { context in + let status = context.state.data["status"]?.asString() ?? "preparing" + let message = context.state.data["message"]?.asString() ?? "Preparing" + let eta = context.state.data["estimatedTime"]?.asString() ?? "" + + return DynamicIsland { DynamicIslandExpandedRegion(.leading) { - Text("Leading") + Image(systemName: statusIcon(for: status)) + .font(.title2) + .foregroundColor(statusColor(for: status)) + } + DynamicIslandExpandedRegion(.center) { + Text(statusLabel(for: status)) + .font(.headline) } DynamicIslandExpandedRegion(.trailing) { - Text("Trailing") + if !eta.isEmpty { + Text(eta) + .font(.caption) + .foregroundColor(.secondary) + } } DynamicIslandExpandedRegion(.bottom) { - Text("Bottom") - // more content + Text(message) + .font(.caption) + .foregroundColor(.secondary) } } compactLeading: { - Text("L") + Image(systemName: statusIcon(for: status)) + .foregroundColor(statusColor(for: status)) } compactTrailing: { - Text("T") + Text(statusLabel(for: status)) + .font(.caption) } minimal: { - Text("Min") + Image(systemName: statusIcon(for: status)) + .foregroundColor(statusColor(for: status)) + } + } + } +} + +@available(iOS 16.2, *) +struct DeliveryProgressBar: View { + let status: String + + private var progress: CGFloat { + switch status { + case "on_the_way": return 0.6 + case "delivered": return 1.0 + default: return 0.25 + } + } + + var body: some View { + GeometryReader { geo in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 3) + .fill(Color.white.opacity(0.2)) + .frame(height: 6) + RoundedRectangle(cornerRadius: 3) + .fill(progress >= 1.0 ? Color.green : Color.blue) + .frame(width: geo.size.width * progress, height: 6) } - .widgetURL(URL(string: "http://www.apple.com")) - .keylineTint(Color.red) } + .frame(height: 6) } } #endif From 4e50ac33180ef2073c60d3ad3ec0fc80c3d75586 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 16:54:03 -0700 Subject: [PATCH 02/10] refactor(demo): rename ExampleWidget to OneSignalWidget --- .../App/Editor/iOS/BuildPostProcessor.cs | 12 ++++++------ .../ExampleWidget/ExampleWidgetBundle.swift | 18 ------------------ ...xampleWidget.meta => OneSignalWidget.meta} | 0 .../Assets.xcassets.meta | 0 .../Assets.xcassets/AccentColor.colorset.meta | 0 .../AccentColor.colorset/Contents.json | 0 .../AccentColor.colorset/Contents.json.meta | 0 .../Assets.xcassets/AppIcon.appiconset.meta | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Contents.json.meta | 0 .../Assets.xcassets/Contents.json | 0 .../Assets.xcassets/Contents.json.meta | 0 .../WidgetBackground.colorset.meta | 0 .../WidgetBackground.colorset/Contents.json | 0 .../Contents.json.meta | 0 .../onesignaldemo.imageset.meta | 0 .../onesignaldemo.imageset/Contents.json | 0 .../onesignaldemo.imageset/Contents.json.meta | 0 .../onesignaldemo.imageset/onesignal-logo.png | Bin .../onesignal-logo.png.meta | 0 .../Info.plist | 2 +- .../Info.plist.meta | 0 .../OneSignalWidgetBundle.swift | 11 +++++++++++ .../OneSignalWidgetBundle.swift.meta} | 0 .../OneSignalWidgetLiveActivity.swift} | 2 +- .../OneSignalWidgetLiveActivity.swift.meta} | 0 26 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift rename examples/demo/iOS/{ExampleWidget.meta => OneSignalWidget.meta} (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AccentColor.colorset.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AccentColor.colorset/Contents.json.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AppIcon.appiconset.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/AppIcon.appiconset/Contents.json.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/Contents.json (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/Contents.json.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/WidgetBackground.colorset.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/WidgetBackground.colorset/Contents.json (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/WidgetBackground.colorset/Contents.json.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/onesignaldemo.imageset.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/onesignaldemo.imageset/Contents.json (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/onesignaldemo.imageset/Contents.json.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png.meta (100%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Info.plist (96%) rename examples/demo/iOS/{ExampleWidget => OneSignalWidget}/Info.plist.meta (100%) create mode 100644 examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift rename examples/demo/iOS/{ExampleWidget/ExampleWidgetBundle.swift.meta => OneSignalWidget/OneSignalWidgetBundle.swift.meta} (100%) rename examples/demo/iOS/{ExampleWidget/ExampleWidgetLiveActivity.swift => OneSignalWidget/OneSignalWidgetLiveActivity.swift} (99%) rename examples/demo/iOS/{ExampleWidget/ExampleWidgetLiveActivity.swift.meta => OneSignalWidget/OneSignalWidgetLiveActivity.swift.meta} (100%) diff --git a/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs b/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs index 87662db2c..197cf6326 100644 --- a/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs +++ b/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs @@ -38,19 +38,19 @@ namespace App.Editor.iOS { /// - /// Adds the ExampleWidgetExtension to the iOS project frameworks to the iOS project and enables the main target + /// Adds the OneSignalWidgetExtension to the iOS project and enables the main target /// for Live Activities. /// public class BuildPostProcessor : IPostprocessBuildWithReport { - private static readonly string WdigetExtensionTargetRelativePath = "ExampleWidget"; - private static readonly string WidgetExtensionTargetName = "ExampleWidgetExtension"; - private static readonly string WidgetExtensionPath = Path.Combine("iOS", "ExampleWidget"); + private static readonly string WdigetExtensionTargetRelativePath = "OneSignalWidget"; + private static readonly string WidgetExtensionTargetName = "OneSignalWidgetExtension"; + private static readonly string WidgetExtensionPath = Path.Combine("iOS", "OneSignalWidget"); private static readonly string[] WidgetExtensionFiles = new string[] { "Assets.xcassets", - "ExampleWidgetBundle.swift", - "ExampleWidgetLiveActivity.swift", + "OneSignalWidgetBundle.swift", + "OneSignalWidgetLiveActivity.swift", }; /// diff --git a/examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift b/examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift deleted file mode 100644 index 5454c36b6..000000000 --- a/examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ExampleWidgetBundle.swift -// ExampleWidget -// -// Created by Brian Smith on 5/29/24. -// - -#if !targetEnvironment(macCatalyst) -import WidgetKit -import SwiftUI - -@main -struct ExampleWidgetBundle: WidgetBundle { - var body: some Widget { - ExampleWidgetLiveActivity() - } -} -#endif diff --git a/examples/demo/iOS/ExampleWidget.meta b/examples/demo/iOS/OneSignalWidget.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget.meta rename to examples/demo/iOS/OneSignalWidget.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset/Contents.json rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset/Contents.json.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset/Contents.json.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AccentColor.colorset/Contents.json.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AccentColor.colorset/Contents.json.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset/Contents.json.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/AppIcon.appiconset/Contents.json.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/Contents.json b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/Contents.json similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/Contents.json rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/Contents.json diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/Contents.json.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/Contents.json.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/Contents.json.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/Contents.json.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/Contents.json.meta diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png diff --git a/examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png.meta b/examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png.meta rename to examples/demo/iOS/OneSignalWidget/Assets.xcassets/onesignaldemo.imageset/onesignal-logo.png.meta diff --git a/examples/demo/iOS/ExampleWidget/Info.plist b/examples/demo/iOS/OneSignalWidget/Info.plist similarity index 96% rename from examples/demo/iOS/ExampleWidget/Info.plist rename to examples/demo/iOS/OneSignalWidget/Info.plist index f42024eb2..941dc7476 100644 --- a/examples/demo/iOS/ExampleWidget/Info.plist +++ b/examples/demo/iOS/OneSignalWidget/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - ExampleWidget + OneSignalWidget CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/examples/demo/iOS/ExampleWidget/Info.plist.meta b/examples/demo/iOS/OneSignalWidget/Info.plist.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/Info.plist.meta rename to examples/demo/iOS/OneSignalWidget/Info.plist.meta diff --git a/examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift new file mode 100644 index 000000000..04962e771 --- /dev/null +++ b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift @@ -0,0 +1,11 @@ +#if !targetEnvironment(macCatalyst) +import WidgetKit +import SwiftUI + +@main +struct OneSignalWidgetBundle: WidgetBundle { + var body: some Widget { + OneSignalWidgetLiveActivity() + } +} +#endif diff --git a/examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift.meta b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/ExampleWidgetBundle.swift.meta rename to examples/demo/iOS/OneSignalWidget/OneSignalWidgetBundle.swift.meta diff --git a/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetLiveActivity.swift similarity index 99% rename from examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift rename to examples/demo/iOS/OneSignalWidget/OneSignalWidgetLiveActivity.swift index 141d85e45..6421737b3 100644 --- a/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift +++ b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetLiveActivity.swift @@ -5,7 +5,7 @@ import SwiftUI import OneSignalLiveActivities @available(iOS 16.2, *) -struct ExampleWidgetLiveActivity: Widget { +struct OneSignalWidgetLiveActivity: Widget { private func statusIcon(for status: String) -> String { switch status { diff --git a/examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift.meta b/examples/demo/iOS/OneSignalWidget/OneSignalWidgetLiveActivity.swift.meta similarity index 100% rename from examples/demo/iOS/ExampleWidget/ExampleWidgetLiveActivity.swift.meta rename to examples/demo/iOS/OneSignalWidget/OneSignalWidgetLiveActivity.swift.meta From 8635bf083646418f6a5784c28a6f80275eaefbc6 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 16:58:55 -0700 Subject: [PATCH 03/10] refactor(demo): rename Activity to Screen in UI text --- examples/demo/Assets/Scripts/UI/HomeScreenController.cs | 4 ++-- examples/demo/Assets/Scripts/UI/SecondaryScreenController.cs | 4 ++-- .../UI/Sections/LiveActivitiesSectionController.cs.meta | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs.meta diff --git a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs index 68a914562..8e18d90a3 100644 --- a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs +++ b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs @@ -262,8 +262,8 @@ private void BuildSections() #endif var nextButton = SectionBuilder.CreatePrimaryButton( - "NEXT ACTIVITY", - "next_activity_button", + "NEXT SCREEN", + "next_screen_button", () => SceneManager.LoadScene("Secondary") ); _contentRoot.Add(nextButton); diff --git a/examples/demo/Assets/Scripts/UI/SecondaryScreenController.cs b/examples/demo/Assets/Scripts/UI/SecondaryScreenController.cs index 4b69d3441..8a3b808d9 100644 --- a/examples/demo/Assets/Scripts/UI/SecondaryScreenController.cs +++ b/examples/demo/Assets/Scripts/UI/SecondaryScreenController.cs @@ -30,7 +30,7 @@ private void OnEnable() backButton.AddToClassList("back-button"); appBar.Add(backButton); - var title = new Label("Secondary Activity"); + var title = new Label("Secondary Screen"); title.AddToClassList("app-bar-title"); appBar.Add(title); @@ -39,7 +39,7 @@ private void OnEnable() var content = new VisualElement(); content.AddToClassList("centered-content"); - var heading = new Label("Secondary Activity"); + var heading = new Label("Secondary Screen"); heading.name = "secondary_heading"; heading.AddToClassList("page-heading"); content.Add(heading); diff --git a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs.meta b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs.meta new file mode 100644 index 000000000..57cd4d245 --- /dev/null +++ b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ac01c2e8884d34f17bb76140778c7b7b \ No newline at end of file From bee8914f68688e7df5efbd071c2a312014911de6 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 17:21:16 -0700 Subject: [PATCH 04/10] feat(demo): add env file build support for builds --- examples/demo/.gitignore | 2 ++ examples/demo/Assets/Resources/Theme.uss | 8 +++++ .../Assets/Scripts/Editor/CopyEnvPreBuild.cs | 36 +++++++++++++++++++ .../Scripts/Editor/CopyEnvPreBuild.cs.meta | 2 ++ .../Scripts/Services/OneSignalApiService.cs | 5 +++ 5 files changed, 53 insertions(+) create mode 100644 examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs create mode 100644 examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs.meta diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore index 42d2dfc8b..c737feee4 100644 --- a/examples/demo/.gitignore +++ b/examples/demo/.gitignore @@ -66,6 +66,8 @@ crashlytics-build.properties # Environment files .env +Assets/StreamingAssets/.env +Assets/StreamingAssets/.env.meta # Gradle template backup files *.backup diff --git a/examples/demo/Assets/Resources/Theme.uss b/examples/demo/Assets/Resources/Theme.uss index 2722be03c..b03402fab 100644 --- a/examples/demo/Assets/Resources/Theme.uss +++ b/examples/demo/Assets/Resources/Theme.uss @@ -765,6 +765,14 @@ Scroller { max-height: 0; } +.inline-input-field > .unity-text-field__input { + background-color: transparent; + border-width: 0; + -unity-text-align: upper-right; + color: var(--os-text-primary); + font-size: 14px; +} + .unity-base-slider { display: none; } diff --git a/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs new file mode 100644 index 000000000..51ddccf1a --- /dev/null +++ b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs @@ -0,0 +1,36 @@ +using System.IO; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace OneSignalDemo.Editor +{ + public class CopyEnvPreBuild : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + var projectRoot = Path.GetDirectoryName(Application.dataPath); + var source = Path.Combine(projectRoot, ".env"); + var dest = Path.Combine(Application.streamingAssetsPath, ".env"); + + if (!File.Exists(source)) + { + Debug.LogWarning( + "[OneSignalDemo] No .env file found at project root. " + + "Live Activity API calls will be disabled. " + + "Copy .env.example to .env and add your key." + ); + if (File.Exists(dest)) + File.Delete(dest); + return; + } + + Directory.CreateDirectory(Application.streamingAssetsPath); + File.Copy(source, dest, overwrite: true); + Debug.Log("[OneSignalDemo] Copied .env to StreamingAssets"); + } + } +} diff --git a/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs.meta b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs.meta new file mode 100644 index 000000000..4fb392289 --- /dev/null +++ b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 22dfaece35ee843148765242503a6c02 \ No newline at end of file diff --git a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs index 925c0322b..85865c508 100644 --- a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs +++ b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs @@ -26,6 +26,11 @@ public class OneSignalApiService public void LoadApiKey() { var envPath = Path.Combine(Application.dataPath, "..", ".env"); +#if !UNITY_EDITOR + var streamingPath = Path.Combine(Application.streamingAssetsPath, ".env"); + if (File.Exists(streamingPath)) + envPath = streamingPath; +#endif if (!File.Exists(envPath)) return; From f9c206b3ba95ae4b801c766e6c0c4c204ece17bf Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 17:23:27 -0700 Subject: [PATCH 05/10] style(demo): add top margin to next button --- examples/demo/Assets/Scripts/UI/HomeScreenController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs index 8e18d90a3..80b23bca3 100644 --- a/examples/demo/Assets/Scripts/UI/HomeScreenController.cs +++ b/examples/demo/Assets/Scripts/UI/HomeScreenController.cs @@ -266,6 +266,7 @@ private void BuildSections() "next_screen_button", () => SceneManager.LoadScene("Secondary") ); + nextButton.style.marginTop = 24; _contentRoot.Add(nextButton); } From 80a9b0fbd39cbcbfddf37b494e984f69f403fe4b Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 6 Apr 2026 17:27:00 -0700 Subject: [PATCH 06/10] feat(liveactivities): deprecate ExitAsync method --- .../Runtime/AndroidLiveActivitiesManager.cs | 1 + .../Editor/Platform/LiveActivitiesManager.cs | 1 + .../Runtime/LiveActivities/ILiveActivitiesManager.cs | 1 + com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/com.onesignal.unity.android/Runtime/AndroidLiveActivitiesManager.cs b/com.onesignal.unity.android/Runtime/AndroidLiveActivitiesManager.cs index 806e6ab72..1787d17e8 100644 --- a/com.onesignal.unity.android/Runtime/AndroidLiveActivitiesManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidLiveActivitiesManager.cs @@ -40,6 +40,7 @@ public Task EnterAsync(string activityId, string token) return Task.FromResult(false); } + [System.Obsolete("Currently unsupported, avoid using this method.")] public Task ExitAsync(string activityId) { SDKDebug.Warn("This feature is only available for iOS."); diff --git a/com.onesignal.unity.core/Editor/Platform/LiveActivitiesManager.cs b/com.onesignal.unity.core/Editor/Platform/LiveActivitiesManager.cs index f51b896cb..ff0465c33 100644 --- a/com.onesignal.unity.core/Editor/Platform/LiveActivitiesManager.cs +++ b/com.onesignal.unity.core/Editor/Platform/LiveActivitiesManager.cs @@ -37,6 +37,7 @@ public Task EnterAsync(string activityId, string token) return Task.FromResult(false); } + [System.Obsolete("Currently unsupported, avoid using this method.")] public Task ExitAsync(string activityId) { return Task.FromResult(false); diff --git a/com.onesignal.unity.core/Runtime/LiveActivities/ILiveActivitiesManager.cs b/com.onesignal.unity.core/Runtime/LiveActivities/ILiveActivitiesManager.cs index 99191bc7e..a39f200fb 100644 --- a/com.onesignal.unity.core/Runtime/LiveActivities/ILiveActivitiesManager.cs +++ b/com.onesignal.unity.core/Runtime/LiveActivities/ILiveActivitiesManager.cs @@ -45,6 +45,7 @@ public interface ILiveActivitiesManager /// /// iOS Only /// Awaitable boolean of whether the operation succeeded or failed + [System.Obsolete("Currently unsupported, avoid using this method.")] Task ExitAsync(string activityId); /// diff --git a/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs b/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs index 2d0d47b3d..7562942f9 100644 --- a/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs @@ -75,6 +75,7 @@ public async Task EnterAsync(string activityId, string token) return await proxy; } + [System.Obsolete("Currently unsupported, avoid using this method.")] public async Task ExitAsync(string activityId) { var (proxy, hashCode) = WaitingProxy._setupProxy(); From 2be7ab94d2205673b75fc650dd31f86bc24d76e9 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 7 Apr 2026 09:45:33 -0700 Subject: [PATCH 07/10] fix(demo): handle quoted env values and empty fields --- examples/demo/Assets/Scripts/Services/OneSignalApiService.cs | 2 +- .../Scripts/UI/Sections/LiveActivitiesSectionController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs index 85865c508..342715b36 100644 --- a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs +++ b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs @@ -42,7 +42,7 @@ public void LoadApiKey() var eqIndex = trimmed.IndexOf('='); var key = trimmed.Substring(0, eqIndex).Trim(); - var value = trimmed.Substring(eqIndex + 1).Trim(); + var value = trimmed.Substring(eqIndex + 1).Trim().Trim('"', '\''); if (key == "ONESIGNAL_API_KEY") _apiKey = value; diff --git a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs index 2fbda7b66..37073aee1 100644 --- a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs +++ b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs @@ -102,7 +102,7 @@ private void RefreshButtonStates() private void OnStartTap() { var activityId = _activityIdField?.value; - var orderNumber = _orderNumberField?.value ?? "ORD-1234"; + var orderNumber = string.IsNullOrEmpty(_orderNumberField?.value) ? "ORD-1234" : _orderNumberField.value; _viewModel.StartLiveActivity(activityId, orderNumber); } From f28a3c92551410d3313a416491e2a60c8ed1fee8 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 7 Apr 2026 09:59:11 -0700 Subject: [PATCH 08/10] fix(demo): disable end button during live activity updates --- .../Scripts/UI/Sections/LiveActivitiesSectionController.cs | 2 +- examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs index 37073aee1..115277c8a 100644 --- a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs +++ b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs @@ -96,7 +96,7 @@ private void RefreshButtonStates() if (_updateButton != null) _updateButton.text = $"UPDATE \u2192 {_viewModel.NextStatusLabel}"; - _endButton?.SetEnabled(hasActivityId && hasApiKey); + _endButton?.SetEnabled(hasActivityId && hasApiKey && !_viewModel.IsLiveActivityUpdating); } private void OnStartTap() diff --git a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs index 331b6ed20..2846a1af8 100644 --- a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs +++ b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs @@ -462,9 +462,12 @@ public async void UpdateLiveActivity(string activityId) public async void EndLiveActivity(string activityId) { - if (string.IsNullOrEmpty(activityId)) + if (string.IsNullOrEmpty(activityId) || _isLiveActivityUpdating) return; + _isLiveActivityUpdating = true; + NotifyStateChanged(); + try { var eventUpdates = new JObject @@ -491,6 +494,7 @@ public async void EndLiveActivity(string activityId) ShowToast("Failed to end Live Activity"); } + _isLiveActivityUpdating = false; NotifyStateChanged(); } From 2ba66dc9ec63153032c491524a2b041c14d4e581 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 7 Apr 2026 10:36:24 -0700 Subject: [PATCH 09/10] fix(demo): address PR review feedback - Add _isLiveActivityUpdating guard to Start button - Add RegisterValueChangedCallback on Activity ID field - Strip inline comments from .env values - Fix typo: WdigetExtensionTargetRelativePath -> WidgetExtensionTargetRelativePath Made-with: Cursor --- examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs | 8 ++++---- .../demo/Assets/Scripts/Services/OneSignalApiService.cs | 3 +++ .../UI/Sections/LiveActivitiesSectionController.cs | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs b/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs index 197cf6326..1beb3d097 100644 --- a/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs +++ b/examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs @@ -43,7 +43,7 @@ namespace App.Editor.iOS /// public class BuildPostProcessor : IPostprocessBuildWithReport { - private static readonly string WdigetExtensionTargetRelativePath = "OneSignalWidget"; + private static readonly string WidgetExtensionTargetRelativePath = "OneSignalWidget"; private static readonly string WidgetExtensionTargetName = "OneSignalWidgetExtension"; private static readonly string WidgetExtensionPath = Path.Combine("iOS", "OneSignalWidget"); private static readonly string[] WidgetExtensionFiles = new string[] @@ -114,7 +114,7 @@ static void AddWidgetExtensionToProject(string outputPath) if (!string.IsNullOrEmpty(extensionGuid)) return; - var widgetDestPath = Path.Combine(outputPath, WdigetExtensionTargetRelativePath); + var widgetDestPath = Path.Combine(outputPath, WidgetExtensionTargetRelativePath); Directory.CreateDirectory(widgetDestPath); CopyFileOrDirectory( @@ -126,14 +126,14 @@ static void AddWidgetExtensionToProject(string outputPath) project.GetUnityMainTargetGuid(), WidgetExtensionTargetName, $"{PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS)}.{WidgetExtensionTargetName}", - $"{WdigetExtensionTargetRelativePath}/Info.plist" + $"{WidgetExtensionTargetRelativePath}/Info.plist" ); var buildPhaseID = project.AddSourcesBuildPhase(extensionGuid); foreach (var file in WidgetExtensionFiles) { - var destPathRelative = Path.Combine(WdigetExtensionTargetRelativePath, file); + var destPathRelative = Path.Combine(WidgetExtensionTargetRelativePath, file); var sourceFileGuid = project.AddFile(destPathRelative, destPathRelative); project.AddFileToBuildSection(extensionGuid, buildPhaseID, sourceFileGuid); CopyFileOrDirectory( diff --git a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs index 342715b36..3ebfabedc 100644 --- a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs +++ b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs @@ -43,6 +43,9 @@ public void LoadApiKey() var eqIndex = trimmed.IndexOf('='); var key = trimmed.Substring(0, eqIndex).Trim(); var value = trimmed.Substring(eqIndex + 1).Trim().Trim('"', '\''); + int commentIdx = value.IndexOf('#'); + if (commentIdx >= 0) + value = value.Substring(0, commentIdx).Trim(); if (key == "ONESIGNAL_API_KEY") _apiKey = value; diff --git a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs index 115277c8a..00413d6dc 100644 --- a/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs +++ b/examples/demo/Assets/Scripts/UI/Sections/LiveActivitiesSectionController.cs @@ -40,6 +40,7 @@ private VisualElement BuildSection() "live_activity_id_input" ); _activityIdField = activityIdRow.Q(); + _activityIdField.RegisterValueChangedCallback(_ => RefreshButtonStates()); inputCard.Add(activityIdRow); inputCard.Add(SectionBuilder.CreateDivider(true)); @@ -89,7 +90,7 @@ private void RefreshButtonStates() bool hasActivityId = !string.IsNullOrEmpty(_activityIdField?.value); bool hasApiKey = _viewModel.HasApiKey; - _startButton?.SetEnabled(hasActivityId); + _startButton?.SetEnabled(hasActivityId && !_viewModel.IsLiveActivityUpdating); bool canUpdate = hasActivityId && hasApiKey && !_viewModel.IsLiveActivityUpdating; _updateButton?.SetEnabled(canUpdate); From 36f771d2362343f7e2909be1c9485fb404b49ba1 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 7 Apr 2026 11:47:55 -0700 Subject: [PATCH 10/10] fix(demo): address second round of PR review feedback - URI-escape activityId in Live Activity API URL - Fix .env parser: strip inline comments before quotes - Include all content state fields in End event_updates - Clean up stale .meta file when deleting .env from StreamingAssets Made-with: Cursor fix(demo): restrict .env copy to iOS builds only Made-with: Cursor fix(demo): copy .env for all build platforms --- examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs | 5 +++++ .../demo/Assets/Scripts/Services/OneSignalApiService.cs | 6 ++++-- examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs index 51ddccf1a..17c5ae0d1 100644 --- a/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs +++ b/examples/demo/Assets/Scripts/Editor/CopyEnvPreBuild.cs @@ -24,7 +24,12 @@ public void OnPreprocessBuild(BuildReport report) + "Copy .env.example to .env and add your key." ); if (File.Exists(dest)) + { File.Delete(dest); + var metaPath = dest + ".meta"; + if (File.Exists(metaPath)) + File.Delete(metaPath); + } return; } diff --git a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs index 3ebfabedc..5ad84aadb 100644 --- a/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs +++ b/examples/demo/Assets/Scripts/Services/OneSignalApiService.cs @@ -42,10 +42,11 @@ public void LoadApiKey() var eqIndex = trimmed.IndexOf('='); var key = trimmed.Substring(0, eqIndex).Trim(); - var value = trimmed.Substring(eqIndex + 1).Trim().Trim('"', '\''); + var value = trimmed.Substring(eqIndex + 1).Trim(); int commentIdx = value.IndexOf('#'); if (commentIdx >= 0) value = value.Substring(0, commentIdx).Trim(); + value = value.Trim('"', '\''); if (key == "ONESIGNAL_API_KEY") _apiKey = value; @@ -154,8 +155,9 @@ public async Task UpdateLiveActivity( if (string.IsNullOrEmpty(activityId) || string.IsNullOrEmpty(_appId) || !HasApiKey()) return false; + var encodedId = Uri.EscapeDataString(activityId); var url = - $"https://api.onesignal.com/apps/{_appId}/live_activities/{activityId}/notifications"; + $"https://api.onesignal.com/apps/{_appId}/live_activities/{encodedId}/notifications"; var payload = new JObject { diff --git a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs index 2846a1af8..63fa18b89 100644 --- a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs +++ b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs @@ -472,7 +472,12 @@ public async void EndLiveActivity(string activityId) { var eventUpdates = new JObject { - ["data"] = new JObject { ["message"] = "Ended" }, + ["data"] = new JObject + { + ["status"] = "delivered", + ["message"] = "Ended", + ["estimatedTime"] = "", + }, }; bool success = await _repository.UpdateLiveActivity(activityId, "end", eventUpdates);