@@ -13,71 +13,75 @@ reference OneSignal demo app installed. These screenshots are the source
1313of truth for the UI you are building. Do NOT proceed to Phase 1 without them.
1414
1515Check for connected emulators:
16- adb devices
16+ adb devices
1717
1818If no device is listed, stop and ask the user to start one.
1919
2020Identify which emulator has com.onesignal.sdktest installed by checking each listed device, e.g.:
21- adb -s emulator-5554 shell pm list packages 2>/dev/null | grep -i onesignal
22- adb -s emulator-5556 shell pm list packages 2>/dev/null | grep -i onesignal
21+ adb -s emulator-5554 shell pm list packages 2>/dev/null | grep -i onesignal
22+ adb -s emulator-5556 shell pm list packages 2>/dev/null | grep -i onesignal
2323
2424Use that emulator's serial (e.g. emulator-5556) for all subsequent adb commands via the -s flag.
2525
2626Launch the reference app:
27- adb -s <emulator-serial > shell am start -n com.onesignal.sdktest/.ui.main.MainActivity
27+ adb -s <emulator-serial > shell am start -n com.onesignal.sdktest/.ui.main.MainActivity
2828
2929Dismiss any in-app messages that appear on launch. Tap the X or
3030click-through button on each IAM until the main UI is fully visible
3131with no overlays.
3232
3333Create an output directory:
34- mkdir -p /tmp/onesignal_reference
34+ mkdir -p /tmp/onesignal_reference
3535
3636Capture screenshots by scrolling through the full UI:
37+
37381 . Take a screenshot from the top of the screen:
38- adb shell screencap -p /sdcard/ref_01.png && adb pull /sdcard/ref_01.png /tmp/onesignal_reference/ref_01.png
39+ adb shell screencap -p /sdcard/ref_01.png && adb pull /sdcard/ref_01.png /tmp/onesignal_reference/ref_01.png
39402 . Scroll down by roughly one viewport height:
40- adb shell input swipe 500 1500 500 500
41+ adb shell input swipe 500 1500 500 500
41423 . Take the next screenshot (ref_02.png, ref_03.png, etc.)
42434 . Repeat until you've reached the bottom of the scrollable content
4344
4445You MUST read each captured screenshot image so you can see the actual UI.
4546These images define the visual target for every section you build later.
4647Pay close attention to:
47- - Section header style and casing
48- - Card vs non-card content grouping
49- - Button placement (inside vs outside cards)
50- - List item layout (stacked vs inline key-value)
51- - Icon choices (delete, close, info, etc.)
52- - Typography, spacing, and colors
48+
49+ - Section header style and casing
50+ - Card vs non-card content grouping
51+ - Button placement (inside vs outside cards)
52+ - List item layout (stacked vs inline key-value)
53+ - Icon choices (delete, close, info, etc.)
54+ - Typography, spacing, and colors
5355
5456You can also interact with the reference app to observe specific flows:
5557
5658Dump the UI hierarchy to find elements by resource-id, text, or content-desc:
57- adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml /tmp/onesignal_reference/ui.xml
59+ adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml /tmp/onesignal_reference/ui.xml
5860
5961Parse the XML to find an element's bounds, then tap it:
60- adb shell input tap <centerX > <centerY >
62+ adb shell input tap <centerX > <centerY >
6163
6264Type into a focused text field:
63- adb shell input text "test"
65+ adb shell input text "test"
6466
6567Example flow to observe "Add Tag" behavior:
66- 1 . Dump UI -> find the ADD button bounds -> tap it
67- 2 . Dump UI -> find the Key and Value fields -> tap and type into them
68- 3 . Tap the confirm button -> screenshot the result
69- 4 . Compare the tag list state before and after
68+
69+ 1 . Dump UI -> find the ADD button bounds -> tap it
70+ 2 . Dump UI -> find the Key and Value fields -> tap and type into them
71+ 3 . Tap the confirm button -> screenshot the result
72+ 4 . Compare the tag list state before and after
7073
7174Also capture screenshots of key dialogs to match their layout:
72- - Add Alias (single pair input)
73- - Add Multiple Aliases/Tags (dynamic rows with add/remove)
74- - Remove Selected Tags (checkbox multi-select)
75- - Login User
76- - Send Outcome (radio options)
77- - Track Event (with JSON properties field)
78- - Custom Notification (title + body)
79- These dialog screenshots are important for matching field layout,
80- button placement, spacing, and validation behavior.
75+
76+ - Add Alias (single pair input)
77+ - Add Multiple Aliases/Tags (dynamic rows with add/remove)
78+ - Remove Selected Tags (checkbox multi-select)
79+ - Login User
80+ - Send Outcome (radio options)
81+ - Track Event (with JSON properties field)
82+ - Custom Notification (title + body)
83+ These dialog screenshots are important for matching field layout,
84+ button placement, spacing, and validation behavior.
8185
8286Refer back to these screenshots throughout all remaining phases whenever
8387you need to decide on layout, spacing, section order, dialog flows, or
@@ -696,22 +700,23 @@ interface TooltipOption {
696700### Prompt 4.3 - Tooltip UI Integration
697701
698702For each section, pass an onInfoTap callback to SectionCard:
703+
699704- SectionCard has an optional info icon that calls onInfoTap when tapped
700705- In HomeScreen, wire onInfoTap to show a TooltipModal
701706- TooltipModal displays title, description, and options (if present)
702707
703708Example in HomeScreen:
704709<AliasesSection
705- ...
706- onInfoTap={() => showTooltipModal('aliases')}
710+ ...
711+ onInfoTap={() => showTooltipModal('aliases')}
707712/>
708713
709714function showTooltipModal(key: string) {
710- const tooltip = TooltipHelper.getInstance().getTooltip(key);
711- if (tooltip) {
712- setActiveTooltip(tooltip);
713- setTooltipVisible(true);
714- }
715+ const tooltip = TooltipHelper.getInstance().getTooltip(key);
716+ if (tooltip) {
717+ setActiveTooltip(tooltip);
718+ setTooltipVisible(true);
719+ }
715720}
716721
717722---
@@ -721,6 +726,7 @@ function showTooltipModal(key: string) {
721726### What IS Persisted (AsyncStorage)
722727
723728PreferencesService stores:
729+
724730- OneSignal App ID
725731- Consent required status
726732- Privacy consent status
@@ -736,10 +742,10 @@ On app startup, state is restored in two layers:
736742 - OneSignal.setConsentRequired(cachedConsentRequired)
737743 - OneSignal.setConsentGiven(cachedPrivacyConsent)
738744 - OneSignal.initialize(appId)
739- Then AFTER initialize, restores remaining SDK state:
745+ Then AFTER initialize, restores remaining SDK state:
740746 - OneSignal.InAppMessages.setPaused(cachedPausedStatus)
741747 - OneSignal.Location.setShared(cachedLocationShared)
742- This ensures consent settings are in place before the SDK initializes.
748+ This ensures consent settings are in place before the SDK initializes.
743749
7447502 . AppContextProvider initialization restores UI state:
745751 - consentRequired from cached prefs (no SDK getter)
@@ -750,13 +756,15 @@ On app startup, state is restored in two layers:
750756 - appId from PreferencesService (app-level config)
751757
752758This two-layer approach ensures:
759+
753760- The SDK is configured with the user's last preferences before anything else runs
754761- AppContextProvider exposes one state object and action API for screens
755762- Reducer transitions keep state updates predictable
756763
757764### What is NOT Persisted (In-Memory Only)
758765
759766App state holds in memory:
767+
760768- triggersList: [ string, string] [ ]
761769 - Triggers are session-only
762770 - Cleared on app restart
@@ -814,6 +822,7 @@ Aliases are managed with a hybrid approach:
814822### Notification Permission
815823
816824Notification permission is automatically requested when the home screen loads:
825+
817826- Call appContext.promptPush() in a useEffect with an empty dependency array in HomeScreen
818827- This ensures prompt appears after user sees the app UI
819828- PROMPT PUSH button remains as fallback if user initially denied
@@ -829,11 +838,13 @@ Notification permission is automatically requested when the home screen loads:
829838Use React Context for dependency injection and useReducer for state management.
830839
831840App.tsx:
841+
832842- AppContext.Provider at the root of the component tree
833843- Initialize OneSignal SDK before rendering (outside component or in early useEffect)
834844- Fetch tooltips in the background (non-blocking)
835845
836846AppContextProvider:
847+
837848- Holds all UI state with useReducer
838849- Exposes state and action functions through useAppContext
839850- Uses OneSignalRepository and PreferencesService internally
@@ -844,44 +855,39 @@ AppContextProvider:
844855Create reusable components in src/components/:
845856
846857SectionCard.tsx:
858+
847859- Card View with title Text and optional info TouchableOpacity
848860- Children slot
849861- onInfoTap callback for tooltips
850862- Uses theme styles
851863
852864ToggleRow.tsx:
865+
853866- Label, optional description, Switch
854867- Row layout with justifyContent: 'space-between'
855868
856869ActionButton.tsx:
870+
857871- PrimaryButton (filled, TouchableOpacity)
858872- DestructiveButton (outlined, TouchableOpacity)
859873- Full-width buttons with width: '100%'
860874
861875ListWidgets.tsx:
876+
862877- PairItem (key-value with optional X-icon remove TouchableOpacity)
863878- SingleItem (single value with optional X-icon remove TouchableOpacity)
864879- EmptyState (centered "No items" Text)
865880- CollapsibleList (shows 5 items, expandable)
866881- PairList (simple list of key-value pairs)
867882
868883LoadingOverlay.tsx:
884+
869885- Semi-transparent full-screen overlay using absolute positioned View + StyleSheet
870886- Centered ActivityIndicator
871887- Shown via isLoading state from app context
872888
873- LogView.tsx:
874- - Sticky at the top of the screen (always visible while ScrollView content scrolls below)
875- - Full width, no horizontal margin, no rounded corners, no top margin (touches header)
876- - Single horizontal ScrollView on the entire log list (not per-row), no text truncation
877- - Use onLayout + minWidth so content is at least screen-wide
878- - Vertical ScrollView + mapped entries instead of FlatList (100dp container is small)
879- - Fixed 100dp height
880- - Default expanded
881- - Trash icon button (delete icon from react-native-vector-icons) for clearing logs
882- - Auto-scroll to newest using scrollToEnd on ScrollView ref
883-
884889Modals (src/components/modals/):
890+
885891- All modals use a full-width Modal component with padding: 16 and width: '100%' inner container
886892- SingleInputModal (one TextInput)
887893- PairInputModal (key-value TextInputs on the same row, single pair)
@@ -896,6 +902,7 @@ Tags, Aliases, and Triggers all share a reusable MultiPairInputModal component
896902for adding multiple key-value pairs at once.
897903
898904Behavior:
905+
899906- Modal opens full-width (width: '100%' with horizontal padding 16)
900907- Starts with one empty key-value row (Key and Value TextInputs side by side)
901908- "Add Row" TextButton below the rows adds another empty row
@@ -908,6 +915,7 @@ Behavior:
908915- State is managed with useState inside the modal component
909916
910917Used by:
918+
911919- ADD MULTIPLE button (Aliases section) -> calls viewModel.addAliases(pairs)
912920- ADD MULTIPLE button (Tags section) -> calls viewModel.addTags(pairs)
913921- ADD MULTIPLE button (Triggers section) -> calls viewModel.addTriggers(pairs)
@@ -918,6 +926,7 @@ Tags and Triggers share a reusable MultiSelectRemoveModal component
918926for selectively removing items from the current list.
919927
920928Behavior:
929+
921930- Accepts the current list of items as [ string, string] [ ]
922931- Renders one checkbox per item on the left with just the key as the label (not "key: value")
923932- Use a custom checkbox row with TouchableOpacity + icon since RN has no built-in Checkbox on both platforms
@@ -926,6 +935,7 @@ Behavior:
926935- On confirm, checked items' keys are collected as string[ ] and passed to the callback
927936
928937Used by:
938+
929939- REMOVE SELECTED button (Tags section) -> calls viewModel.removeSelectedTags(keys)
930940- REMOVE SELECTED button (Triggers section) -> calls viewModel.removeSelectedTriggers(keys)
931941
@@ -935,7 +945,7 @@ Create OneSignal theme in src/theme.ts.
935945
936946All colors, spacing, typography, button styles, card styles, and component
937947specs are defined in the shared style reference:
938- https://raw.githubusercontent.com/OneSignal/sdk-shared/refs/heads/main/demo/styles.md
948+ https://raw.githubusercontent.com/OneSignal/sdk-shared/refs/heads/main/demo/styles.md
939949
940950Export AppColors and AppSpacing objects that expose the tokens from styles.md
941951as typed constants. Export an AppTheme object with reusable StyleSheet base
@@ -947,38 +957,37 @@ values for use throughout the app.
947957Add collapsible log view at top of screen for debugging and Appium testing.
948958
949959Files:
960+
950961- src/services/LogManager.ts - Singleton logger with listener callbacks
951962- src/components/LogView.tsx - Log viewer component with testID labels
952963
953964LogManager Features:
965+
954966- Singleton with subscriber callbacks for reactive UI updates
955967- API: LogManager.getInstance().d(tag, message), .i(), .w(), .e() mimics debugPrint levels
956968- Also prints to console via console.log/warn/error for development
969+ - Notifies listeners with new entry only (null on clear)
957970
958971LogView Features:
959- - STICKY at the top of the screen (always visible while ScrollView content scrolls below)
960- - Full width, no horizontal margin, no rounded corners, no top margin (touches header)
961- - Single horizontal ScrollView on the entire log list (not per-row), no text truncation
962- - Use onLayout + minWidth so content is at least screen-wide
963- - Vertical ScrollView + mapped entries instead of FlatList (100dp container is small)
964- - Fixed 100dp height
965- - Default expanded
966- - Trash icon button (delete icon) for clearing logs, not a text button
967- - Auto-scroll to newest using scrollToEnd on ScrollView ref
972+
973+ - Refer to the Logs View section of the shared style reference for layout, colors, and typography
974+ - Header sits above the list; 100dp height applies to the list area only
975+ - Newest entries at the top (prepend to state on each log)
976+ - Trash icon only visible when entries exist
968977
969978Appium testID Labels:
970- | testID | Description |
979+ | testID | Description |
971980| -------------------------| ------------------------------------|
972- | log_view_container | Main container View |
973- | log_view_header | Tappable expand/collapse row |
974- | log_view_count | Shows "(N)" log count Text |
975- | log_view_clear_button | Clear all logs TouchableOpacity |
976- | log_view_list | Scrollable ScrollView |
977- | log_view_empty | "No logs yet" Text |
978- | log_entry _ {N} | Each log row View (N=index) |
979- | log_entry _ {N}_ timestamp | Timestamp Text |
980- | log_entry_ {N}_ level | D/I/W/E indicator Text |
981- | log_entry_ {N}_ message | Log message Text |
981+ | log * view_container | Main container View |
982+ | log_view_header | Tappable expand/collapse row |
983+ | log_view_count | Shows "(N)" log count Text |
984+ | log_view_clear_button | Clear all logs TouchableOpacity |
985+ | log_view_list | Scrollable ScrollView |
986+ | log_view_empty | "No logs yet" Text |
987+ | log_entry * {N} | Each log row View (N=index) |
988+ | log * entry * {N}_ timestamp | Timestamp Text |
989+ | log_entry_ {N}_ level | D/I/W/E indicator Text |
990+ | log_entry_ {N}\ _ message | Log message Text |
982991
983992Use the testID prop for Appium accessibility:
984993<Text testID={` log_entry_${index}_message ` }>{entry.message}</Text >
@@ -1000,6 +1009,7 @@ All user actions should display Toast messages via react-native-toast-message:
10001009- Push: "Push enabled/disabled"
10011010
10021011Implementation:
1012+
10031013- Place <Toast /> at the root of App.tsx (outside NavigationContainer children)
10041014- Show at the bottom of the screen: <Toast position =" bottom " bottomOffset ={20} />
10051015- Call Toast.show({ type: 'info', text1: message }) from action handlers
0 commit comments