From 274a64c85e97effcbcde26330a8b9f9bf6918a21 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 13 Oct 2026 15:23:18 -0700 Subject: [PATCH 1/4] chore: [SDK-5527] add sticky log view to demo app Co-Authored-By: Claude Opus 5.5 --- examples/demo/src/components/LogView.tsx | 110 +++++++++++++++++++++++ examples/demo/src/hooks/useOneSignal.ts | 30 ++++--- examples/demo/src/screens/HomeScreen.tsx | 3 + examples/demo/src/services/LogService.ts | 46 ++++++++++ examples/demo/src/theme.ts | 2 + 5 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 examples/demo/src/components/LogView.tsx create mode 100644 examples/demo/src/services/LogService.ts diff --git a/examples/demo/src/components/LogView.tsx b/examples/demo/src/components/LogView.tsx new file mode 100644 index 00000000..0e052c7a --- /dev/null +++ b/examples/demo/src/components/LogView.tsx @@ -0,0 +1,110 @@ +import React, { useRef, useState } from 'react'; +import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +import { clearLogs, useLogs } from '../services/LogService'; +import { AppColors } from '../theme'; + +const MONOSPACE = Platform.select({ ios: 'Menlo', default: 'monospace' }); + +export default function LogView() { + const logs = useLogs(); + const scrollRef = useRef(null); + const [collapsed, setCollapsed] = useState(false); + + return ( + + + LOGS ({logs.length}) + + + + + setCollapsed((value) => !value)} + hitSlop={8} + accessibilityRole="button" + accessibilityLabel={collapsed ? 'Expand logs' : 'Collapse logs'} + testID="log_toggle_button" + > + + + + + {/* Collapsed via height so entries stay in the view hierarchy. */} + scrollRef.current?.scrollToEnd({ animated: false })} + nestedScrollEnabled + > + {logs.length === 0 ? ( + No logs yet + ) : ( + logs.map((entry, index) => ( + + {entry.time} {entry.message} + + )) + )} + + + ); +} + +const ENTRY_FONT_SIZE = 8; + +const styles = StyleSheet.create({ + container: { + backgroundColor: AppColors.osLogBackground, + }, + header: { + height: 24, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + }, + headerText: { + color: AppColors.osGrey500, + fontFamily: MONOSPACE, + fontSize: 11, + fontWeight: '700', + letterSpacing: 0.5, + }, + actions: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + body: { + height: 100, + }, + bodyCollapsed: { + height: 0, + }, + bodyContent: { + paddingHorizontal: 16, + paddingBottom: 6, + }, + entry: { + color: AppColors.osLogText, + fontFamily: MONOSPACE, + fontSize: ENTRY_FONT_SIZE, + lineHeight: ENTRY_FONT_SIZE * 1.4, + }, + muted: { + color: AppColors.osGrey600, + }, +}); diff --git a/examples/demo/src/hooks/useOneSignal.ts b/examples/demo/src/hooks/useOneSignal.ts index 06177ef6..551c549f 100644 --- a/examples/demo/src/hooks/useOneSignal.ts +++ b/examples/demo/src/hooks/useOneSignal.ts @@ -24,6 +24,7 @@ import { } from 'react-native-onesignal'; import { NotificationType } from '../models/NotificationType'; +import { log } from '../services/LogService'; import OneSignalApiService from '../services/OneSignalApiService'; import PreferencesService from '../services/PreferencesService'; @@ -170,34 +171,37 @@ function useOneSignalState(): UseOneSignalReturn { let userChanged = false; const handleIamWillDisplay = (e: InAppMessageWillDisplayEvent) => { - console.log(`IAM willDisplay: ${e.message.messageId}`); + log(`IAM willDisplay: ${e.message.messageId}`); }; const handleIamDidDisplay = (e: InAppMessageDidDisplayEvent) => { - console.log(`IAM didDisplay: ${e.message.messageId}`); + log(`IAM didDisplay: ${e.message.messageId}`); }; const handleIamWillDismiss = (e: InAppMessageWillDismissEvent) => { - console.log(`IAM willDismiss: ${e.message.messageId}`); + log(`IAM willDismiss: ${e.message.messageId}`); }; const handleIamDidDismiss = (e: InAppMessageDidDismissEvent) => { - console.log(`IAM didDismiss: ${e.message.messageId}`); + log(`IAM didDismiss: ${e.message.messageId}`); }; const handleIamClick = (e: InAppMessageClickEvent) => { - console.log(`IAM click: ${e.message.messageId}`); + log(`IAM click: ${e.message.messageId}`); }; const handleNotificationClick = (e: NotificationClickEvent) => { - console.log(`Notification click: ${e.notification.title ?? ''}`); + log(`Notification click: ${e.notification.notificationId} ${e.notification.title ?? ''}`); // uncomment to see the full event object // console.log('[OneSignal] click event: ', e); }; const handleForegroundWillDisplay = (e: NotificationWillDisplayEvent) => { - console.log(`Notification foregroundWillDisplay: ${e.getNotification().title ?? ''}`); + const notification = e.getNotification(); + log( + `Notification foregroundWillDisplay: ${notification.notificationId} ${notification.title ?? ''}`, + ); // uncomment to see the full event object // console.log('[OneSignal] will display event: ', e.getNotification()); @@ -224,12 +228,18 @@ function useOneSignalState(): UseOneSignalReturn { const pushSubHandler = (event: PushSubscriptionChangedState) => { pushChanged = true; - setPushSubscriptionId(event.current.id ?? undefined); - setIsPushEnabled(event.current.optedIn); + const { previous, current } = event; + const fmtToken = (t: string | undefined) => (t ? `${t.slice(0, 8)}…` : 'null'); + log( + `Push subscription changed: id=${previous.id ?? 'null'} → ${current.id ?? 'null'}, optedIn=${previous.optedIn} → ${current.optedIn}, token=${fmtToken(previous.token)} → ${fmtToken(current.token)}`, + ); + setPushSubscriptionId(current.id ?? undefined); + setIsPushEnabled(current.optedIn); }; const permissionHandler = (granted: boolean) => { permissionChanged = true; + log(`Permission changed: ${granted}`); setHasNotificationPermission(granted); }; @@ -237,7 +247,7 @@ function useOneSignalState(): UseOneSignalReturn { userChanged = true; requestSequenceRef.current++; const nextOnesignalId = event.current.onesignalId ?? null; - console.log( + log( `User changed: onesignalId=${nextOnesignalId ?? 'null'}, externalId=${event.current.externalId ?? 'null'}`, ); diff --git a/examples/demo/src/screens/HomeScreen.tsx b/examples/demo/src/screens/HomeScreen.tsx index 61b95856..d6b7f9e3 100644 --- a/examples/demo/src/screens/HomeScreen.tsx +++ b/examples/demo/src/screens/HomeScreen.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; import ActionButton from '../components/ActionButton'; +import LogView from '../components/LogView'; import TooltipModal from '../components/modals/TooltipModal'; import AliasesSection from '../components/sections/AliasesSection'; import AppSection from '../components/sections/AppSection'; @@ -47,6 +48,8 @@ export default function HomeScreen() { return ( + + void>(); + +function emit(): void { + for (const listener of listeners) listener(); +} + +function formatTime(date: Date): string { + return [date.getHours(), date.getMinutes(), date.getSeconds()] + .map((part) => String(part).padStart(2, '0')) + .join(':'); +} + +/** Write to the on-screen log view and the console. Kept in memory only, so each launch starts empty. */ +export function log(message: string): void { + console.log(`[OneSignal] ${message}`); + entries = [...entries, { id: nextId++, time: formatTime(new Date()), message }].slice( + -MAX_ENTRIES, + ); + emit(); +} + +export function clearLogs(): void { + entries = []; + emit(); +} + +function subscribe(listener: () => void): () => void { + listeners.add(listener); + return () => listeners.delete(listener); +} + +function getSnapshot(): LogEntry[] { + return entries; +} + +export function useLogs(): LogEntry[] { + return useSyncExternalStore(subscribe, getSnapshot); +} diff --git a/examples/demo/src/theme.ts b/examples/demo/src/theme.ts index 163589f2..78a414be 100644 --- a/examples/demo/src/theme.ts +++ b/examples/demo/src/theme.ts @@ -12,6 +12,8 @@ export const AppColors = { osDivider: '#E8EAED', osWarningBackground: '#FFF8E1', osBackdrop: 'rgba(0,0,0,0.54)', + osLogBackground: '#1E1E1E', + osLogText: '#D4D4D4', white: '#FFFFFF', } as const; From ac232135b245c9977610bc993c1414349c51958d Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 13 Oct 2026 16:09:42 -0700 Subject: [PATCH 2/4] chore(demo): update Podfile.lock pod checksums --- examples/demo/ios/Podfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/demo/ios/Podfile.lock b/examples/demo/ios/Podfile.lock index fb3d9bb9..2089e5be 100644 --- a/examples/demo/ios/Podfile.lock +++ b/examples/demo/ios/Podfile.lock @@ -2314,7 +2314,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: c12d2108050e27952983d565a232f6f7b1ad5e69 - hermes-engine: 177322198264d50dc281084c48e1ed80ea14884c + hermes-engine: 3a0f67f9e50aaad69120ec16c582ce0039cdc9f1 OneSignalXCFramework: e916e0283c51b737524cab28f340eeb821b45826 RCTDeprecation: 3280799c14232a56e5a44f92981a8ee33bc69fd9 RCTRequired: 9854a51b0f65ccf43ea0b744df4d70fce339db32 @@ -2324,7 +2324,7 @@ SPEC CHECKSUMS: React: 7ef36630d07638043a134a7dd2ec17e0be10fc3c React-callinvoker: af4e8fe1d60ab63dd8d74c2a68988064c2848954 React-Core: c609976c034ba9556bef9850a571a71bd458d73f - React-Core-prebuilt: f4df078355bf9605a0d0278aa5c63fcd4b764a1d + React-Core-prebuilt: 7b776b3b288057079d0b5e1b91f6fd902d5373f2 React-CoreModules: 0ea85f3b3f4b8cbfb3afacd2ed85458fb878517a React-cxxreact: 6752bab77c0599d6136e2b8b9b64b4a7d316d401 React-debug: 38389b86e3570558ec73dd4cbc0cd2f2eec47a51 @@ -2387,7 +2387,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 6b7e8d8d974ed13fb66698d82c30c5e70c1f7d3a ReactCodegen: c5e5343f6691b0cd76913b9be5e89e5a83ff3315 ReactCommon: 92b53b0bd7f7d86154dc9f512c1ea5dee717cc72 - ReactNativeDependencies: eaebe8c9533ec2c41b08a02844af461bfff10540 + ReactNativeDependencies: a409e83c2181f9bddef2495ca3293b481a646880 RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707 RNSVG: c69f7709226108f5eb89b5aa8833c17a36345468 From 85ae79cd1187c59b14ec2135be150e745eea2b49 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 13 Oct 2026 18:45:14 -0700 Subject: [PATCH 3/4] fix(demo): scroll log view to end after expand Co-Authored-By: Claude Opus 5.5 --- examples/demo/src/components/LogView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/demo/src/components/LogView.tsx b/examples/demo/src/components/LogView.tsx index 0e052c7a..538879d8 100644 --- a/examples/demo/src/components/LogView.tsx +++ b/examples/demo/src/components/LogView.tsx @@ -11,6 +11,7 @@ export default function LogView() { const logs = useLogs(); const scrollRef = useRef(null); const [collapsed, setCollapsed] = useState(false); + const scrollToEnd = () => scrollRef.current?.scrollToEnd({ animated: false }); return ( @@ -46,7 +47,8 @@ export default function LogView() { ref={scrollRef} style={collapsed ? styles.bodyCollapsed : styles.body} contentContainerStyle={styles.bodyContent} - onContentSizeChange={() => scrollRef.current?.scrollToEnd({ animated: false })} + onContentSizeChange={scrollToEnd} + onLayout={scrollToEnd} nestedScrollEnabled > {logs.length === 0 ? ( From bfa4d9126bc64fee6eb3f7fd2f65ef8074ae1240 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Wed, 14 Oct 2026 12:42:37 -0700 Subject: [PATCH 4/4] fix(demo): unmount collapsed log list and drop Podfile.lock churn Co-Authored-By: Claude Opus 5.5 --- examples/demo/ios/Podfile.lock | 6 ++-- examples/demo/src/components/LogView.tsx | 41 +++++++++++------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/demo/ios/Podfile.lock b/examples/demo/ios/Podfile.lock index 2089e5be..fb3d9bb9 100644 --- a/examples/demo/ios/Podfile.lock +++ b/examples/demo/ios/Podfile.lock @@ -2314,7 +2314,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: c12d2108050e27952983d565a232f6f7b1ad5e69 - hermes-engine: 3a0f67f9e50aaad69120ec16c582ce0039cdc9f1 + hermes-engine: 177322198264d50dc281084c48e1ed80ea14884c OneSignalXCFramework: e916e0283c51b737524cab28f340eeb821b45826 RCTDeprecation: 3280799c14232a56e5a44f92981a8ee33bc69fd9 RCTRequired: 9854a51b0f65ccf43ea0b744df4d70fce339db32 @@ -2324,7 +2324,7 @@ SPEC CHECKSUMS: React: 7ef36630d07638043a134a7dd2ec17e0be10fc3c React-callinvoker: af4e8fe1d60ab63dd8d74c2a68988064c2848954 React-Core: c609976c034ba9556bef9850a571a71bd458d73f - React-Core-prebuilt: 7b776b3b288057079d0b5e1b91f6fd902d5373f2 + React-Core-prebuilt: f4df078355bf9605a0d0278aa5c63fcd4b764a1d React-CoreModules: 0ea85f3b3f4b8cbfb3afacd2ed85458fb878517a React-cxxreact: 6752bab77c0599d6136e2b8b9b64b4a7d316d401 React-debug: 38389b86e3570558ec73dd4cbc0cd2f2eec47a51 @@ -2387,7 +2387,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 6b7e8d8d974ed13fb66698d82c30c5e70c1f7d3a ReactCodegen: c5e5343f6691b0cd76913b9be5e89e5a83ff3315 ReactCommon: 92b53b0bd7f7d86154dc9f512c1ea5dee717cc72 - ReactNativeDependencies: a409e83c2181f9bddef2495ca3293b481a646880 + ReactNativeDependencies: eaebe8c9533ec2c41b08a02844af461bfff10540 RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707 RNSVG: c69f7709226108f5eb89b5aa8833c17a36345468 diff --git a/examples/demo/src/components/LogView.tsx b/examples/demo/src/components/LogView.tsx index 538879d8..4b213150 100644 --- a/examples/demo/src/components/LogView.tsx +++ b/examples/demo/src/components/LogView.tsx @@ -42,25 +42,25 @@ export default function LogView() { - {/* Collapsed via height so entries stay in the view hierarchy. */} - - {logs.length === 0 ? ( - No logs yet - ) : ( - logs.map((entry, index) => ( - - {entry.time} {entry.message} - - )) - )} - + {!collapsed && ( + + {logs.length === 0 ? ( + No logs yet + ) : ( + logs.map((entry, index) => ( + + {entry.time} {entry.message} + + )) + )} + + )} ); } @@ -93,9 +93,6 @@ const styles = StyleSheet.create({ body: { height: 100, }, - bodyCollapsed: { - height: 0, - }, bodyContent: { paddingHorizontal: 16, paddingBottom: 6,