Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
87 changes: 87 additions & 0 deletions examples/demo/Assets/Resources/Theme.uss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
--os-text-primary: #202124;
--os-text-secondary: #757575;
--os-text-hint: #9E9E9E;
--os-log-background: #1E1E1E;
--os-log-text: #D4D4D4;
--card-gap: 8px;
}

Expand Down Expand Up @@ -170,6 +172,91 @@
flex-shrink: 0;
}

.log-view {
background-color: var(--os-log-background);
flex-shrink: 0;
}

.log-header {
flex-direction: row;
align-items: center;
height: 24px;
padding: 0 16px;
}

.log-header-label {
flex-grow: 1;
padding: 0;
margin: 0;
font-size: 11px;
-unity-font-style: bold;
letter-spacing: 0.5px;
color: var(--os-grey-500);
-unity-font-definition: resource("DroidSansMono");
}

.log-header-button {
width: 24px;
height: 24px;
padding: 0;
margin: 0;
border-width: 0;
border-radius: 0;
background-color: rgba(0, 0, 0, 0);
color: var(--os-grey-500);
-unity-font-definition: resource("MaterialIcons-Regular");
-unity-text-align: middle-center;
}

.log-clear-button {
font-size: 14px;
}

/* 24px hit boxes; margins keep glyphs 12px apart and flush with the 16px padding. */
.log-toggle-button {
font-size: 18px;
margin: 0 -3px 0 4px;
}

.log-body {
height: 100px;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (Opus+Grok): this 100px ScrollView plus AccessibilityBridge.IsVisible (clips to contentViewport; DisplayStyle.None is hidden) drops older log_entry_{i} from the Appium tree once startup lines wrap or the panel is collapsed. Nodes stay in UI Toolkit; UiAutomator/XCUITest may not see them. Specs 02/13 passing does not cover a filled viewport.

}

.log-body-content {
padding: 0 16px 6px 16px;
}

.log-entry {
flex-direction: row;
align-items: flex-start;
}

.log-entry-time,
.log-entry-message,
.log-empty {
font-size: 8px;
padding: 0;
margin: 0;
-unity-font-definition: resource("DroidSansMono");
}

.log-entry-time,
.log-empty {
color: var(--os-grey-600);
}

.log-entry-time {
margin-right: 5px;
flex-shrink: 0;
}

.log-entry-message {
flex-shrink: 1;
flex-grow: 1;
white-space: normal;
color: var(--os-log-text);
}

.section-container {
}

Expand Down
18 changes: 11 additions & 7 deletions examples/demo/Assets/Scripts/AppBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,35 +159,39 @@ private void OnIamWillDisplay(object sender, InAppMessageWillDisplayEventArgs e)
#if UNITY_ANDROID && !UNITY_EDITOR
SetAndroidWebViewDebugging(true);
#endif
Debug.Log($"[{Tag}] IAM will display: {e.Message.MessageId}");
LogService.Show($"IAM willDisplay: {e.Message.MessageId}");
}

private void OnIamDidDisplay(object sender, InAppMessageDidDisplayEventArgs e) =>
Debug.Log($"[{Tag}] IAM did display: {e.Message.MessageId}");
LogService.Show($"IAM didDisplay: {e.Message.MessageId}");

private void OnIamWillDismiss(object sender, InAppMessageWillDismissEventArgs e) =>
Debug.Log($"[{Tag}] IAM will dismiss: {e.Message.MessageId}");
LogService.Show($"IAM willDismiss: {e.Message.MessageId}");

private void OnIamDidDismiss(object sender, InAppMessageDidDismissEventArgs e)
{
Debug.Log($"[{Tag}] IAM did dismiss: {e.Message.MessageId}");
LogService.Show($"IAM didDismiss: {e.Message.MessageId}");
#if UNITY_ANDROID && !UNITY_EDITOR
SetAndroidWebViewDebugging(false);
#endif
}

private void OnIamClicked(object sender, InAppMessageClickEventArgs e) =>
Debug.Log($"[{Tag}] IAM clicked: {e.Result.ActionId}");
LogService.Show($"IAM click: {e.Message.MessageId}");

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

private void OnNotificationForegroundWillDisplay(
object sender,
NotificationWillDisplayEventArgs e
)
{
Debug.Log($"[{Tag}] Notification received in foreground");
LogService.Show(
$"Notification foregroundWillDisplay: {e.Notification.NotificationId ?? "null"} {e.Notification.Title ?? ""}"
);
e.Notification.Display();
Comment on lines +192 to 195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (Opus+Grok): Show invokes LogView.Render (and an a11y resync) before Display(). If that rebuild throws, the notification is never shown. Same pattern in OnPushSubscriptionChanged before NotifyStateChanged(). Worth calling the side effect first, or isolating the log write.

}
}
Expand Down
58 changes: 58 additions & 0 deletions examples/demo/Assets/Scripts/Services/LogService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using UnityEngine;

namespace OneSignalDemo.Services
{
public readonly struct LogEntry
{
public readonly string Time;
public readonly string Message;

public LogEntry(string time, string message)
{
Time = time;
Message = message;
}
}

public static class LogService
{
private const int MaxEntries = 100;

// In memory only, so each launch starts empty and the Appium suite can assert per-launch counts.
private static readonly List<LogEntry> _entries = new();

public static IReadOnlyList<LogEntry> Entries => _entries;

public static event Action Changed;

public static void Info(string message) => Debug.Log($"[OneSignal] {message}");

public static void Show(string message)
{
Info(message);
AddToView(message);
}

public static void Error(string message)
{
Debug.LogError($"[OneSignal] {message}");
AddToView(message);
}

public static void Clear()
{
_entries.Clear();
Changed?.Invoke();
}

private static void AddToView(string message)
{
_entries.Add(new LogEntry(DateTime.Now.ToString("HH:mm:ss"), message));
if (_entries.Count > MaxEntries)
_entries.RemoveRange(0, _entries.Count - MaxEntries);
Changed?.Invoke();
}
}
}
2 changes: 2 additions & 0 deletions examples/demo/Assets/Scripts/Services/LogService.cs.meta

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

2 changes: 2 additions & 0 deletions examples/demo/Assets/Scripts/UI/HomeScreenController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ private void BuildScreen()

screenRoot.Add(appBar);

screenRoot.Add(new LogView().Root);

var appBarShadow = new VisualElement();
appBarShadow.AddToClassList("app-bar-shadow");
screenRoot.Add(appBarShadow);
Expand Down
131 changes: 131 additions & 0 deletions examples/demo/Assets/Scripts/UI/LogView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using OneSignalDemo.Services;
using UnityEngine;
using UnityEngine.UIElements;

namespace OneSignalDemo.UI
{
public class LogView
{
public VisualElement Root { get; }

private readonly Label _headerLabel;
private readonly Button _toggleButton;
private readonly ScrollView _body;
private bool _collapsed;

public LogView()
{
Root = new VisualElement();
Root.name = "log_view";
Root.AddToClassList("log-view");

var header = new VisualElement();
header.AddToClassList("log-header");

_headerLabel = new Label();
_headerLabel.AddToClassList("log-header-label");
header.Add(_headerLabel);

var clearButton = new Button(LogService.Clear);
clearButton.name = "log_clear_button";
clearButton.text = MaterialIcons.Delete;
clearButton.AddToClassList("log-header-button");
clearButton.AddToClassList("log-clear-button");
AccessibilityBridge.RegisterE2ETapTarget(clearButton, () => true, LogService.Clear);
header.Add(clearButton);

_toggleButton = new Button(ToggleCollapsed);
_toggleButton.name = "log_toggle_button";
_toggleButton.AddToClassList("log-header-button");
_toggleButton.AddToClassList("log-toggle-button");
AccessibilityBridge.RegisterE2ETapTarget(_toggleButton, () => true, ToggleCollapsed);
header.Add(_toggleButton);

Root.Add(header);

_body = new ScrollView(ScrollViewMode.Vertical);
_body.AddToClassList("log-body");
_body.contentContainer.AddToClassList("log-body-content");
_body.verticalScrollerVisibility = ScrollerVisibility.Hidden;
_body.horizontalScrollerVisibility = ScrollerVisibility.Hidden;
_body.touchScrollBehavior = ScrollView.TouchScrollBehavior.Clamped;
_body.contentContainer.RegisterCallback<GeometryChangedEvent>(_ => ScrollToEnd());
Root.Add(_body);

// Entries is static, so only stay subscribed while attached or rebuilt screens leak.
Root.RegisterCallback<AttachToPanelEvent>(_ =>
{
LogService.Changed += Render;
Render();
});
Root.RegisterCallback<DetachFromPanelEvent>(_ => LogService.Changed -= Render);

ApplyCollapsed();
}

private void ToggleCollapsed()
{
_collapsed = !_collapsed;
ApplyCollapsed();
AccessibilityBridge.RequestResync();
}

private void ApplyCollapsed()
{
_body.style.display = _collapsed ? DisplayStyle.None : DisplayStyle.Flex;
_toggleButton.text = _collapsed ? MaterialIcons.ExpandMore : MaterialIcons.ExpandLess;
if (!_collapsed)
ScrollToEnd();
}

// Rebuilt in full so `log_entry_{index}` stays 0-based after the oldest entries are dropped.
private void Render()
{
var entries = LogService.Entries;
_headerLabel.text = $"LOGS ({entries.Count})";
_body.Clear();

if (entries.Count == 0)
{
var empty = new Label("No logs yet");
empty.AddToClassList("log-empty");
_body.Add(empty);
}

for (var i = 0; i < entries.Count; i++)
{
var row = new VisualElement();
row.AddToClassList("log-entry");

var time = new Label(entries[i].Time);
time.AddToClassList("log-entry-time");
row.Add(time);

// Plain text (no rich-text spans) so the a11y bridge reports the message verbatim.
var message = new Label(entries[i].Message);
message.name = $"log_entry_{i}";
message.enableRichText = false;
message.AddToClassList("log-entry-message");
row.Add(message);

_body.Add(row);
}

AccessibilityBridge.RequestResync();
}

// Deferred a tick so the ScrollView has picked up the new content height.
private void ScrollToEnd()
{
if (_collapsed)
return;
_body.schedule.Execute(() =>
{
var overflow =
_body.contentContainer.layout.height - _body.contentViewport.layout.height;
if (overflow > 0)
_body.scrollOffset = new Vector2(0, overflow);
});
}
}
}
2 changes: 2 additions & 0 deletions examples/demo/Assets/Scripts/UI/LogView.cs.meta

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

Loading
Loading