@digia-engage/core 1.1.1 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +134 -51
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +52 -8
  4. package/android/src/main/java/com/digia/engage/rn/DigiaSlotViewManager.kt +6 -2
  5. package/android/src/main/java/com/digia/engage/rn/DigiaViewManager.kt +1 -0
  6. package/ios/DigiaEngageModule.m +7 -1
  7. package/ios/DigiaHostViewManager.swift +20 -20
  8. package/ios/DigiaModule.swift +8 -4
  9. package/lib/commonjs/Digia.js +390 -4
  10. package/lib/commonjs/Digia.js.map +1 -1
  11. package/lib/commonjs/DigiaAnchorView.js +35 -3
  12. package/lib/commonjs/DigiaAnchorView.js.map +1 -1
  13. package/lib/commonjs/DigiaGuideController.js +59 -0
  14. package/lib/commonjs/DigiaGuideController.js.map +1 -0
  15. package/lib/commonjs/DigiaHealthReporter.js +45 -0
  16. package/lib/commonjs/DigiaHealthReporter.js.map +1 -0
  17. package/lib/commonjs/DigiaProvider.js +1081 -0
  18. package/lib/commonjs/DigiaProvider.js.map +1 -0
  19. package/lib/commonjs/DigiaSlotView.js +18 -3
  20. package/lib/commonjs/DigiaSlotView.js.map +1 -1
  21. package/lib/commonjs/NativeDigiaEngage.js +14 -8
  22. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  23. package/lib/commonjs/actionHandler.js +316 -0
  24. package/lib/commonjs/actionHandler.js.map +1 -0
  25. package/lib/commonjs/defaultInAppBrowser.js +31 -0
  26. package/lib/commonjs/defaultInAppBrowser.js.map +1 -0
  27. package/lib/commonjs/digiaAnchorRegistry.js +32 -0
  28. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -0
  29. package/lib/commonjs/frequencyEvaluator.js +70 -0
  30. package/lib/commonjs/frequencyEvaluator.js.map +1 -0
  31. package/lib/commonjs/frequencyStore.js +70 -0
  32. package/lib/commonjs/frequencyStore.js.map +1 -0
  33. package/lib/commonjs/index.js +7 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/templateTypes.js +2 -0
  36. package/lib/commonjs/templateTypes.js.map +1 -0
  37. package/lib/module/Digia.js +389 -4
  38. package/lib/module/Digia.js.map +1 -1
  39. package/lib/module/DigiaAnchorView.js +33 -1
  40. package/lib/module/DigiaAnchorView.js.map +1 -1
  41. package/lib/module/DigiaGuideController.js +53 -0
  42. package/lib/module/DigiaGuideController.js.map +1 -0
  43. package/lib/module/DigiaHealthReporter.js +38 -0
  44. package/lib/module/DigiaHealthReporter.js.map +1 -0
  45. package/lib/module/DigiaProvider.js +1074 -0
  46. package/lib/module/DigiaProvider.js.map +1 -0
  47. package/lib/module/DigiaSlotView.js +20 -5
  48. package/lib/module/DigiaSlotView.js.map +1 -1
  49. package/lib/module/NativeDigiaEngage.js +14 -8
  50. package/lib/module/NativeDigiaEngage.js.map +1 -1
  51. package/lib/module/actionHandler.js +311 -0
  52. package/lib/module/actionHandler.js.map +1 -0
  53. package/lib/module/defaultInAppBrowser.js +25 -0
  54. package/lib/module/defaultInAppBrowser.js.map +1 -0
  55. package/lib/module/digiaAnchorRegistry.js +26 -0
  56. package/lib/module/digiaAnchorRegistry.js.map +1 -0
  57. package/lib/module/frequencyEvaluator.js +61 -0
  58. package/lib/module/frequencyEvaluator.js.map +1 -0
  59. package/lib/module/frequencyStore.js +64 -0
  60. package/lib/module/frequencyStore.js.map +1 -0
  61. package/lib/module/index.js +1 -0
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/templateTypes.js +2 -0
  64. package/lib/module/templateTypes.js.map +1 -0
  65. package/lib/typescript/Digia.d.ts +35 -3
  66. package/lib/typescript/Digia.d.ts.map +1 -1
  67. package/lib/typescript/DigiaAnchorView.d.ts +5 -1
  68. package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
  69. package/lib/typescript/DigiaGuideController.d.ts +30 -0
  70. package/lib/typescript/DigiaGuideController.d.ts.map +1 -0
  71. package/lib/typescript/DigiaHealthReporter.d.ts +24 -0
  72. package/lib/typescript/DigiaHealthReporter.d.ts.map +1 -0
  73. package/lib/typescript/DigiaProvider.d.ts +3 -0
  74. package/lib/typescript/DigiaProvider.d.ts.map +1 -0
  75. package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
  76. package/lib/typescript/NativeDigiaEngage.d.ts +10 -6
  77. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  78. package/lib/typescript/actionHandler.d.ts +20 -0
  79. package/lib/typescript/actionHandler.d.ts.map +1 -0
  80. package/lib/typescript/defaultInAppBrowser.d.ts +3 -0
  81. package/lib/typescript/defaultInAppBrowser.d.ts.map +1 -0
  82. package/lib/typescript/digiaAnchorRegistry.d.ts +15 -0
  83. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -0
  84. package/lib/typescript/frequencyEvaluator.d.ts +14 -0
  85. package/lib/typescript/frequencyEvaluator.d.ts.map +1 -0
  86. package/lib/typescript/frequencyStore.d.ts +7 -0
  87. package/lib/typescript/frequencyStore.d.ts.map +1 -0
  88. package/lib/typescript/index.d.ts +1 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/templateTypes.d.ts +140 -0
  91. package/lib/typescript/templateTypes.d.ts.map +1 -0
  92. package/lib/typescript/types.d.ts +163 -4
  93. package/lib/typescript/types.d.ts.map +1 -1
  94. package/package.json +15 -3
  95. package/src/Digia.ts +439 -4
  96. package/src/DigiaAnchorView.tsx +30 -2
  97. package/src/DigiaGuideController.ts +61 -0
  98. package/src/DigiaHealthReporter.ts +43 -0
  99. package/src/DigiaProvider.tsx +778 -0
  100. package/src/DigiaSlotView.tsx +26 -6
  101. package/src/NativeDigiaEngage.ts +28 -13
  102. package/src/actionHandler.ts +311 -0
  103. package/src/defaultInAppBrowser.ts +31 -0
  104. package/src/digiaAnchorRegistry.ts +27 -0
  105. package/src/frequencyEvaluator.ts +57 -0
  106. package/src/frequencyStore.ts +79 -0
  107. package/src/index.ts +1 -0
  108. package/src/templateTypes.ts +121 -0
  109. package/src/types.ts +132 -6
package/README.md CHANGED
@@ -6,11 +6,7 @@ React Native SDK for **Digia Engage** – renders native Android (Jetpack Compos
6
6
  > | Platform | Status |
7
7
  > |---|---|
8
8
  > | Android | ✅ Full support |
9
- > | iOS | ✅ Full support |
10
-
11
- ---
12
-
13
- ## Installation
9
+ > | iOS | ✅ Guide overlays (JS renderer); native bridge (surveys, inline) |
14
10
 
15
11
  ```sh
16
12
  npm install @digia-engage/core
@@ -30,7 +26,7 @@ Add the Digia Engage Android SDK to `android/app/build.gradle.kts`:
30
26
 
31
27
  ```kotlin
32
28
  dependencies {
33
- implementation("tech.digia:engage:1.1.0")
29
+ implementation("tech.digia:engage:2.0.0-rc.1")
34
30
  }
35
31
  ```
36
32
 
@@ -38,57 +34,43 @@ dependencies {
38
34
 
39
35
  ## Usage
40
36
 
41
- ### 1 Initialise the SDK
37
+ ### 1 Initialize the SDK
42
38
 
43
- Call `Digia.initialize()` once at app startup, before registering any CEP plugin:
39
+ Call `Digia.initialize()` once, as early as possible (top of `App.tsx`):
44
40
 
45
41
  ```tsx
46
- import { useEffect } from 'react';
47
42
  import { Digia } from '@digia-engage/core';
48
43
 
49
- export function RootApp() {
50
- useEffect(() => {
51
- (async () => {
52
- await Digia.initialize({ apiKey: 'YOUR_ACCESS_KEY' });
53
- // Register your CEP plugin here (e.g. DigiaCleverTapPlugin)
54
- })().catch(console.error);
55
- }, []);
56
-
57
- return <AppNavigator />;
58
- }
44
+ await Digia.initialize({
45
+ projectId: 'digia_YOUR_PROJECT_ID',
46
+ environment: 'production', // or 'sandbox'
47
+ logLevel: 'error',
48
+ });
59
49
  ```
60
50
 
61
- ### 2 Mount the overlay host
51
+ ### 2 Mount `<DigiaHost />`
62
52
 
63
- Place `<DigiaHostView>` at the app root so nudge campaigns (bottom sheets, dialogs, tooltips, spotlights) render above all content:
53
+ Place `<DigiaHost />` at the root of your component tree. It renders the JS-side guide/tooltip/spotlight overlays via a `Modal`.
64
54
 
65
55
  ```tsx
66
- import { StyleSheet, View } from 'react-native';
67
- import { DigiaHostView } from '@digia-engage/core';
68
- import { Stack } from 'expo-router';
56
+ // app/_layout.tsx (Expo Router) or App.tsx
57
+ import { DigiaHost } from '@digia-engage/core';
69
58
 
70
59
  export default function RootLayout() {
71
60
  return (
72
- <View style={styles.root}>
73
- <DigiaHostView style={StyleSheet.absoluteFill} />
61
+ <>
74
62
  <Stack />
75
- </View>
63
+ <DigiaHost />
64
+ </>
76
65
  );
77
66
  }
78
-
79
- const styles = StyleSheet.create({
80
- root: { flex: 1 },
81
- });
82
67
  ```
83
68
 
84
- ### 3 Track screen changes
85
-
86
- Wire `setCurrentScreen()` to your navigation library so the SDK can trigger screen-scoped campaigns:
69
+ ### 3 Track screen changes
87
70
 
88
71
  ```tsx
89
72
  import { Digia } from '@digia-engage/core';
90
73
 
91
- // React Navigation example:
92
74
  <NavigationContainer
93
75
  onStateChange={() => {
94
76
  const route = navRef.getCurrentRoute();
@@ -97,17 +79,55 @@ import { Digia } from '@digia-engage/core';
97
79
  >
98
80
  ```
99
81
 
100
- ### 4 Add inline slots
82
+ ### 4 Register anchors for guide campaigns
101
83
 
102
- Place `<DigiaSlotView>` wherever you want inline campaign content (banners, cards):
84
+ Wrap any UI element you want a tooltip or spotlight to point at:
85
+
86
+ ```tsx
87
+ import { DigiaAnchorView } from '@digia-engage/core';
88
+
89
+ <DigiaAnchorView anchorKey="home_banner_btn">
90
+ <Button title="Banner" />
91
+ </DigiaAnchorView>
92
+ ```
93
+
94
+ ### 5 — Add slots for inline campaigns
103
95
 
104
96
  ```tsx
105
97
  import { DigiaSlotView } from '@digia-engage/core';
106
98
 
107
- <DigiaSlotView
108
- placementKey="home_hero_banner"
109
- style={{ width: '100%', height: 180 }}
110
- />
99
+ // Auto-sizes to native content height
100
+ <DigiaSlotView placementKey="home_banner" />
101
+ ```
102
+
103
+ ### 6 — Register a CEP plugin
104
+
105
+ ```tsx
106
+ import { DigiaCleverTapPlugin, createCleverTapClient } from '@digia-engage/clevertap';
107
+ import CleverTap from 'clevertap-react-native';
108
+
109
+ Digia.register(new DigiaCleverTapPlugin({
110
+ cleverTap: createCleverTapClient(CleverTap),
111
+ }));
112
+ ```
113
+
114
+ ### 7 — (Optional) Handle actions
115
+
116
+ Override or observe every action the SDK fires:
117
+
118
+ ```tsx
119
+ await Digia.initialize({
120
+ projectId: '...',
121
+ onAction: (action, context) => {
122
+ if (action.type === 'deep_link') {
123
+ // return true to suppress SDK default; false/void to let SDK handle it
124
+ }
125
+ },
126
+ linking: {
127
+ routeViaSystemLinking: true,
128
+ inAppBrowser: defaultInAppBrowser, // requires react-native-inappbrowser-reborn
129
+ },
130
+ });
111
131
  ```
112
132
 
113
133
  ---
@@ -118,26 +138,36 @@ import { DigiaSlotView } from '@digia-engage/core';
118
138
 
119
139
  | Method | Signature | Description |
120
140
  |---|---|---|
121
- | `initialize` | `(config: DigiaConfig) => Promise<void>` | Initialise the SDK. Call once at app startup. |
122
- | `register` | `(plugin: DigiaPlugin) => void` | Register a CEP plugin (e.g. `DigiaCleverTapPlugin`). |
123
- | `unregister` | `(plugin: DigiaPlugin \| string) => void` | Unregister a plugin and call its `teardown()`. |
124
- | `setCurrentScreen` | `(name: string) => void` | Notify the SDK of the current screen name. |
141
+ | `initialize` | `(config: DigiaConfig) => Promise<void>` | Initialize the SDK. Call once before anything else. |
142
+ | `register` | `(plugin: DigiaPlugin) => void` | Register a CEP plugin (CleverTap, MoEngage, etc.). |
143
+ | `unregister` | `(plugin: DigiaPlugin \| string) => void` | Remove a previously registered plugin. |
144
+ | `setCurrentScreen` | `(name: string) => void` | Notify the SDK of the active screen. |
145
+ | `registerAnchor` | `(key: string, screen?: string) => void` | Manually register an anchor key. |
146
+ | `unregisterAnchor` | `(key: string) => void` | Remove an anchor registration. |
125
147
 
126
148
  ### `DigiaConfig`
127
149
 
128
150
  | Prop | Type | Default | Description |
129
151
  |---|---|---|---|
130
- | `apiKey` | `string` | — | **Required.** Your Digia project API key. |
152
+ | `projectId` | `string` | — | **Required.** Your Digia project ID (format: `digia_…`). Sent as `x-digia-project-id` on all SDK requests. |
131
153
  | `environment` | `'production' \| 'sandbox'` | `'production'` | Target environment. |
132
154
  | `logLevel` | `'none' \| 'error' \| 'verbose'` | `'error'` | Log verbosity. |
155
+ | `onAction` | `OnAction` | — | Override hook for all actions. Return `true` to suppress SDK default. |
156
+ | `linking.routeViaSystemLinking` | `boolean` | `true` | Use `Linking.openURL` for URL actions. |
157
+ | `linking.inAppBrowser` | `InAppBrowserAdapter` | — | Required for `open_url` with `presentation: 'in_app'`. |
158
+ | `baseUrl` | `string` | — | Override the Digia API base URL. |
133
159
 
134
- ### `<DigiaHostView>`
160
+ ### `<DigiaHost />`
135
161
 
136
- Transparent overlay that hosts Digia-rendered campaign UI (dialogs, bottom sheets, tooltips, spotlights) above app content. Mount once at the app root.
162
+ No props. Renders the JS guide overlay runtime (`TooltipOverlay`, `SpotlightOverlay`).
163
+ Place it once, anywhere in the root view — `Modal` handles z-ordering.
164
+
165
+ ### `<DigiaAnchorView>`
137
166
 
138
167
  | Prop | Type | Description |
139
168
  |---|---|---|
140
- | `style` | `ViewStyle?` | Style for the overlay view. Typically `StyleSheet.absoluteFill`. |
169
+ | `anchorKey` | `string` | **Required.** Must match the step's `anchorKey` in the campaign config. |
170
+ | `...ViewProps` | — | All standard React Native `View` props are forwarded. |
141
171
 
142
172
  ### `<DigiaSlotView>`
143
173
 
@@ -145,8 +175,61 @@ Renders inline campaign content (banners, cards) at a named placement. Collapses
145
175
 
146
176
  | Prop | Type | Description |
147
177
  |---|---|---|
148
- | `placementKey` | `string` | Must match the placement key in your CEP campaign. |
149
- | `style` | `ViewStyle?` | An explicit height is required the slot is not visible without one. |
178
+ | `placementKey` | `string` | **Required.** Must match the campaign's `slotKey`. |
179
+ | `style` | `ViewStyle?` | Pass an explicit `height` to fix size; otherwise auto-sizes. |
180
+
181
+ ### `<DigiaHostView>`
182
+
183
+ Low-level transparent native overlay view (Android/iOS). Use `<DigiaHost />` instead
184
+ unless you need the native Compose/SwiftUI overlay host explicitly.
185
+
186
+ ---
187
+
188
+ ## Campaign types
189
+
190
+ | Type | Trigger | JS or Native |
191
+ |---|---|---|
192
+ | `guide` (tooltip) | `DigiaGuideController` → `DigiaHost` | **JS** — `TooltipOverlay` via `Modal` |
193
+ | `guide` (spotlight) | `DigiaGuideController` → `DigiaHost` | **JS** — `SpotlightOverlay` via `Modal` |
194
+ | `inline` | `nativeDigiaModule.triggerCampaign()` | **Native** — Android Compose `VWCarousel` |
195
+ | `survey` | `nativeDigiaModule.triggerCampaign()` | **Native** — Android Compose `SurveyRenderer` |
196
+ | `nudge` | `nativeDigiaModule.triggerCampaign()` | **Native** — Android Compose dialog / bottom-sheet |
197
+
198
+ ---
199
+
200
+ ## Architecture
201
+
202
+ ```
203
+ react-native/src/
204
+ index.ts Public API exports
205
+ Digia.ts SDK singleton — initialize, register, setCurrentScreen,
206
+ campaign routing, campaign store (fetched from backend)
207
+ DigiaProvider.tsx JS guide renderer — TooltipOverlay + SpotlightOverlay + DigiaHost
208
+ DigiaGuideController.ts Event bus for guide start/cancel; queues if DigiaHost not mounted
209
+ digiaAnchorRegistry.ts In-memory anchor position store with subscriber pattern
210
+ DigiaAnchorView.tsx Wraps UI elements; measures position via ref.measure
211
+ DigiaSlotView.tsx Native slot view wrapper; auto-sizes to content height
212
+ DigiaHostView.tsx Low-level native overlay host (transparent, pointer-events none)
213
+ NativeDigiaEngage.ts Codegen native module spec (TurboModule)
214
+ actionHandler.ts Action execution — deep link, open URL, next/prev/dismiss;
215
+ onAction override; cold-start queue
216
+ defaultInAppBrowser.ts Lazily loads react-native-inappbrowser-reborn
217
+ templateTypes.ts TypeScript types for TooltipConfig, SpotlightConfig,
218
+ CarouselConfig, SurveyTemplateConfig
219
+ types.ts DigiaConfig, DigiaPlugin, DigiaDelegate, InAppPayload,
220
+ DigiaAction, ActionContext, DigiaExperienceEvent
221
+
222
+ react-native/android/ Android bridge
223
+ DigiaModule.kt initialize, registerBridge, triggerCampaign, registerAnchor, …
224
+ DigiaSlotViewManager.kt DigiaSlotView native view manager
225
+ DigiaAnchorViewManager.kt DigiaAnchorView native view manager
226
+ DigiaViewManager.kt DigiaHostView native view manager
227
+
228
+ react-native/ios/ iOS bridge
229
+ DigiaModule.swift initialize, registerBridge, triggerCampaign, …
230
+ DigiaHostViewManager.swift
231
+ DigiaAnchorViewManager.swift
232
+ ```
150
233
 
151
234
  ---
152
235
 
@@ -2,13 +2,13 @@
2
2
  * Android Gradle build file for the @digia/engage-react-native library module.
3
3
  *
4
4
  * This module is a thin bridge layer that:
5
- * • Depends on the Digia Engage Android AAR (digia-ui)
5
+ * • Depends on the Digia Engage Android AAR
6
6
  * • Depends on the React Native SDK
7
7
  * • Exposes a ReactPackage (DigiaPackage) that registers the native module
8
8
  * and native view to React Native's bridge
9
9
  *
10
10
  * The Compose runtime/tooling dependencies are inherited transitively from
11
- * the digia-ui AAR via `api` declarations; we only add what is needed for
11
+ * the Digia Engage AAR via `api` declarations; we only add what is needed for
12
12
  * the bridge code itself.
13
13
  */
14
14
 
@@ -70,7 +70,7 @@ android {
70
70
  }
71
71
 
72
72
  dependencies {
73
- implementation 'tech.digia:engage:1.1.0'
73
+ implementation 'tech.digia:engage:2.0.0-rc.2'
74
74
 
75
75
  // ── React Native ─────────────────────────────────────────────────────────
76
76
  // React Native is provided by the host app; mark as compileOnly so it is
@@ -22,7 +22,6 @@
22
22
  */
23
23
  package com.digia.engage.rn
24
24
 
25
- import android.widget.FrameLayout
26
25
  import androidx.lifecycle.LifecycleOwner
27
26
  import androidx.lifecycle.ViewModelStoreOwner
28
27
  import androidx.lifecycle.setViewTreeLifecycleOwner
@@ -59,7 +58,14 @@ internal class DigiaModule(
59
58
  // ─── initialize ───────────────────────────────────────────────────────────
60
59
 
61
60
  @ReactMethod
62
- fun initialize(apiKey: String, environment: String, logLevel: String, promise: Promise) {
61
+ fun initialize(
62
+ apiKey: String,
63
+ environment: String,
64
+ logLevel: String,
65
+ baseUrl: String?,
66
+ fontFamily: String?,
67
+ promise: Promise
68
+ ) {
63
69
  try {
64
70
  val config =
65
71
  DigiaConfig(
@@ -75,10 +81,18 @@ internal class DigiaModule(
75
81
  "none" -> DigiaLogLevel.NONE
76
82
  else -> DigiaLogLevel.ERROR
77
83
  },
84
+ baseUrl = baseUrl?.takeIf { it.isNotBlank() },
85
+ fontFamily = fontFamily?.takeIf { it.isNotBlank() },
78
86
  )
79
87
  Digia.initialize(reactContext.applicationContext, config)
80
88
 
81
89
  UiThreadUtil.runOnUiThread {
90
+ // Mount the Compose overlay ABOVE the ReactRootView via addContentView().
91
+ // This keeps it outside Fabric's shadow tree entirely so Fabric hit-testing
92
+ // never sees it. Touch pass-through is handled by DigiaHostView.dispatchTouchEvent
93
+ // returning false at the native Android level — which works correctly at this
94
+ // level of the hierarchy, unlike inside Fabric where pointerEvents="none" on
95
+ // non-ReactViewGroup views is not respected during shadow-tree hit-testing.
82
96
  mountDigiaHost()
83
97
  promise.resolve(null)
84
98
  }
@@ -138,13 +152,42 @@ internal class DigiaModule(
138
152
  rnPlugin.delegate?.onCampaignInvalidated(campaignId)
139
153
  }
140
154
 
155
+ // ─── Anchor registration ──────────────────────────────────────────────────
156
+
157
+ @ReactMethod
158
+ fun registerAnchor(key: String, x: Int, y: Int, width: Int, height: Int) {
159
+ Digia.registerAnchor(key, x, y, width, height)
160
+ }
161
+
162
+ @ReactMethod
163
+ fun unregisterAnchor(key: String) {
164
+ Digia.unregisterAnchor(key)
165
+ }
166
+
141
167
  // ─── Internal: mount the Compose overlay host ─────────────────────────────
142
168
 
143
169
  private fun mountDigiaHost() {
144
- val activity = reactContext.currentActivity ?: return
170
+ val activity = reactContext.currentActivity ?: run {
171
+ android.util.Log.w("DigiaHost", "[mountDigiaHost] no current activity — skipping")
172
+ return
173
+ }
145
174
 
146
175
  val contentRoot = activity.window.decorView.findViewWithTag<DigiaHostView>(DIGIA_HOST_TAG)
147
- if (contentRoot != null) return
176
+ if (contentRoot != null) {
177
+ android.util.Log.d("DigiaHost", "[mountDigiaHost] already mounted (tag found) — skipping")
178
+ return
179
+ }
180
+
181
+ android.util.Log.d("DigiaHost", "[mountDigiaHost] mounting native overlay on DecorView")
182
+
183
+ // Mount on the DecorView (not content area) so the overlay covers the full screen
184
+ // including status bar and navigation bar. This also ensures the Compose Popup's
185
+ // canvas y=0 aligns with the absolute screen y=0, matching getLocationOnScreen()
186
+ // coordinates used by DigiaAnchorView.
187
+ val decorView = activity.window.decorView as? android.view.ViewGroup ?: run {
188
+ android.util.Log.w("DigiaHost", "[mountDigiaHost] decorView not a ViewGroup — skipping")
189
+ return
190
+ }
148
191
 
149
192
  val composeView =
150
193
  DigiaHostView(activity).apply {
@@ -155,13 +198,14 @@ internal class DigiaModule(
155
198
  setViewTreeSavedStateRegistryOwner(activity)
156
199
  }
157
200
 
158
- activity.addContentView(
201
+ decorView.addView(
159
202
  composeView,
160
- FrameLayout.LayoutParams(
161
- FrameLayout.LayoutParams.MATCH_PARENT,
162
- FrameLayout.LayoutParams.MATCH_PARENT,
203
+ android.view.ViewGroup.LayoutParams(
204
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
205
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
163
206
  ),
164
207
  )
208
+ android.util.Log.d("DigiaHost", "[mountDigiaHost] done — DecorView child count: ${decorView.childCount}")
165
209
  }
166
210
 
167
211
  companion object {
@@ -26,10 +26,14 @@ private class ContentSizeChangeEvent(
26
26
  surfaceId: Int,
27
27
  viewTag: Int,
28
28
  private val heightDp: Double,
29
+ private val widthDp: Double,
29
30
  ) : Event<ContentSizeChangeEvent>(surfaceId, viewTag) {
30
31
  override fun getEventName(): String = "onContentSizeChange"
31
32
  override fun getEventData(): WritableMap =
32
- Arguments.createMap().apply { putDouble("height", heightDp) }
33
+ Arguments.createMap().apply {
34
+ putDouble("height", heightDp)
35
+ putDouble("width", widthDp)
36
+ }
33
37
  }
34
38
 
35
39
  // ── DigiaSlotContainerView ────────────────────────────────────────────────────
@@ -141,7 +145,7 @@ internal class DigiaSlotContainerView(context: Context) : FrameLayout(context) {
141
145
 
142
146
  val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(ctx, id) ?: return
143
147
  val surfaceId = UIManagerHelper.getSurfaceId(this)
144
- dispatcher.dispatchEvent(ContentSizeChangeEvent(surfaceId, id, heightDp.toDouble()))
148
+ dispatcher.dispatchEvent(ContentSizeChangeEvent(surfaceId, id, heightDp.toDouble(), 0.0))
145
149
  }
146
150
 
147
151
  companion object {
@@ -54,6 +54,7 @@ internal class DigiaViewManager : SimpleViewManager<DigiaHostView>() {
54
54
  FrameLayout.LayoutParams.MATCH_PARENT,
55
55
  )
56
56
 
57
+ android.util.Log.d("DigiaHost", "[DigiaViewManager] RN-side NativeDigiaHostView instance created (id=${view.id})")
57
58
  return view
58
59
  }
59
60
 
@@ -22,9 +22,11 @@
22
22
 
23
23
  @interface RCT_EXTERN_MODULE(DigiaEngageModule, RCTEventEmitter)
24
24
 
25
- RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
25
+ RCT_EXTERN_METHOD(initialize:(NSString *)projectId
26
26
  environment:(NSString *)environment
27
27
  logLevel:(NSString *)logLevel
28
+ baseUrl:(NSString *)baseUrl
29
+ fontFamily:(NSString *)fontFamily
28
30
  resolve:(RCTPromiseResolveBlock)resolve
29
31
  reject:(RCTPromiseRejectBlock)reject)
30
32
 
@@ -62,3 +64,7 @@ RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
62
64
  @interface RCT_EXTERN_MODULE(DigiaAnchorView, RCTViewManager)
63
65
  RCT_EXPORT_VIEW_PROPERTY(anchorKey, NSString)
64
66
  @end
67
+
68
+ @interface RCT_EXTERN_MODULE(DigiaAnchorView, RCTViewManager)
69
+ RCT_EXPORT_VIEW_PROPERTY(anchorKey, NSString)
70
+ @end
@@ -48,30 +48,27 @@ final class DigiaHostUIView: UIView {
48
48
  }
49
49
 
50
50
  private func mountHostingController() {
51
- guard let parentVC = parentViewController() else { return}
51
+ let parentVC = parentViewController()
52
52
 
53
53
  let swiftUIView = DigiaHostWrapperView()
54
54
  let hc = UIHostingController(rootView: swiftUIView)
55
55
  hc.view.translatesAutoresizingMaskIntoConstraints = false
56
56
  hc.view.backgroundColor = .clear
57
57
 
58
- parentVC.addChild(hc)
59
- // Mount onto parentVC.view (full-screen) rather than self, because
60
- // DigiaHostView is intentionally sized 0×0 in React Native (it takes no
61
- // screen space). A zero-size UIHostingController frame prevents SwiftUI
62
- // from rendering its body, which means @ObservedObject subscriptions and
63
- // .onChange(of:) modifiers are never established so activePayload
64
- // changes are silently dropped and no overlay is ever shown.
65
- // Anchoring to parentVC.view guarantees a non-zero frame so SwiftUI's
66
- // rendering loop runs and reacts to SDK state changes.
67
- parentVC.view.addSubview(hc.view)
68
- hc.didMove(toParent: parentVC)
58
+ if let parentVC { parentVC.addChild(hc) }
59
+ // Mount onto self rather than parentVC.view. DigiaHostView is given
60
+ // absoluteFillObject style from JS so self IS full-screen, and SwiftUI
61
+ // will render normally. Mounting here means hitTest below is the single
62
+ // control point for touch dispatch parentVC.view never has a rogue
63
+ // full-screen sibling that intercepts all RN touches.
64
+ addSubview(hc.view)
65
+ if let parentVC { hc.didMove(toParent: parentVC) }
69
66
 
70
67
  NSLayoutConstraint.activate([
71
- hc.view.leadingAnchor.constraint(equalTo: parentVC.view.leadingAnchor),
72
- hc.view.trailingAnchor.constraint(equalTo: parentVC.view.trailingAnchor),
73
- hc.view.topAnchor.constraint(equalTo: parentVC.view.topAnchor),
74
- hc.view.bottomAnchor.constraint(equalTo: parentVC.view.bottomAnchor),
68
+ hc.view.leadingAnchor.constraint(equalTo: leadingAnchor),
69
+ hc.view.trailingAnchor.constraint(equalTo: trailingAnchor),
70
+ hc.view.topAnchor.constraint(equalTo: topAnchor),
71
+ hc.view.bottomAnchor.constraint(equalTo: bottomAnchor),
75
72
  ])
76
73
 
77
74
  hostingController = hc
@@ -79,11 +76,14 @@ final class DigiaHostUIView: UIView {
79
76
 
80
77
  // Pass touches through to RN when no overlay is active.
81
78
  // When an overlay renders in-host (bottom sheet / dialog inside DigiaHost's ZStack),
82
- // SwiftUI's hit test returns the overlay view and we forward that — making the
83
- // overlay fully interactive. When nothing is rendered (EmptyView), SwiftUI returns
84
- // nil and we return nil, so UIKit falls through to RN content below.
79
+ // SwiftUI's hit test returns the overlay view — making the overlay interactive.
80
+ // When nothing is rendered, _UIHostingView.hitTest returns nil or itself; either
81
+ // way we return nil so UIKit falls through to RN content below.
85
82
  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
86
- return hostingController?.view.hitTest(point, with: event)
83
+ guard let hcView = hostingController?.view else { return nil }
84
+ let hit = hcView.hitTest(point, with: event)
85
+ if hit == nil || hit === hcView { return nil }
86
+ return hit
87
87
  }
88
88
 
89
89
  /// Prefer React Native’s `UIView.reactViewController`, then walk `next` from each view’s `.next`.
@@ -4,7 +4,7 @@
4
4
  * React Native NativeModule that bridges the Digia Engage iOS SDK.
5
5
  *
6
6
  * Exposed methods (callable from JS via NativeModules.DigiaEngageModule):
7
- * initialize(apiKey, environment, logLevel): Promise<void>
7
+ * initialize(projectId, environment, logLevel): Promise<void>
8
8
  * registerBridge(): void
9
9
  * setCurrentScreen(name): void
10
10
  * triggerCampaign(id, content, cepContext): void
@@ -61,9 +61,11 @@ final class DigiaModule: RCTEventEmitter {
61
61
 
62
62
  @objc
63
63
  func initialize(
64
- _ apiKey: String,
64
+ _ projectId: String,
65
65
  environment: String,
66
66
  logLevel: String,
67
+ baseUrl: String?,
68
+ fontFamily: String?,
67
69
  resolve: @escaping RCTPromiseResolveBlock,
68
70
  reject: @escaping RCTPromiseRejectBlock
69
71
  ) {
@@ -76,9 +78,11 @@ final class DigiaModule: RCTEventEmitter {
76
78
  }
77
79
 
78
80
  let config = DigiaConfig(
79
- apiKey: apiKey,
81
+ apiKey: projectId,
80
82
  logLevel: logLevelValue,
81
- environment: envValue
83
+ environment: envValue,
84
+ developerConfig: baseUrl.flatMap { $0.isEmpty ? nil : DigiaDeveloperConfig(baseURL: $0) },
85
+ fontFamily: fontFamily.flatMap { $0.isEmpty ? nil : $0 }
82
86
  )
83
87
 
84
88
  Task { @MainActor in