Repository navigation
chore: [SDK-5531] add sticky log view to demo app #905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider (Opus+Grok): |
||
| } | ||
| } | ||
|
|
||
| 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(); | ||
| } | ||
| } | ||
| } |
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 |
|---|---|---|
| @@ -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); | ||
| }); | ||
| } | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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
ScrollViewplusAccessibilityBridge.IsVisible(clips tocontentViewport;DisplayStyle.Noneis hidden) drops olderlog_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.