@digia-engage/core 1.1.0 → 2.0.0-rc.1

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 (89) hide show
  1. package/README.md +147 -177
  2. package/android/build.gradle +2 -2
  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 +301 -3
  10. package/lib/commonjs/Digia.js.map +1 -1
  11. package/lib/commonjs/DigiaGuideController.js +59 -0
  12. package/lib/commonjs/DigiaGuideController.js.map +1 -0
  13. package/lib/commonjs/DigiaHealthReporter.js +45 -0
  14. package/lib/commonjs/DigiaHealthReporter.js.map +1 -0
  15. package/lib/commonjs/DigiaProvider.js +1079 -0
  16. package/lib/commonjs/DigiaProvider.js.map +1 -0
  17. package/lib/commonjs/DigiaSlotView.js +18 -3
  18. package/lib/commonjs/DigiaSlotView.js.map +1 -1
  19. package/lib/commonjs/NativeDigiaEngage.js +14 -8
  20. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  21. package/lib/commonjs/actionHandler.js +316 -0
  22. package/lib/commonjs/actionHandler.js.map +1 -0
  23. package/lib/commonjs/defaultInAppBrowser.js +31 -0
  24. package/lib/commonjs/defaultInAppBrowser.js.map +1 -0
  25. package/lib/commonjs/digiaAnchorRegistry.js +32 -0
  26. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -0
  27. package/lib/commonjs/index.js +7 -0
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/templateTypes.js +2 -0
  30. package/lib/commonjs/templateTypes.js.map +1 -0
  31. package/lib/module/Digia.js +301 -3
  32. package/lib/module/Digia.js.map +1 -1
  33. package/lib/module/DigiaGuideController.js +53 -0
  34. package/lib/module/DigiaGuideController.js.map +1 -0
  35. package/lib/module/DigiaHealthReporter.js +38 -0
  36. package/lib/module/DigiaHealthReporter.js.map +1 -0
  37. package/lib/module/DigiaProvider.js +1072 -0
  38. package/lib/module/DigiaProvider.js.map +1 -0
  39. package/lib/module/DigiaSlotView.js +20 -5
  40. package/lib/module/DigiaSlotView.js.map +1 -1
  41. package/lib/module/NativeDigiaEngage.js +14 -8
  42. package/lib/module/NativeDigiaEngage.js.map +1 -1
  43. package/lib/module/actionHandler.js +311 -0
  44. package/lib/module/actionHandler.js.map +1 -0
  45. package/lib/module/defaultInAppBrowser.js +25 -0
  46. package/lib/module/defaultInAppBrowser.js.map +1 -0
  47. package/lib/module/digiaAnchorRegistry.js +26 -0
  48. package/lib/module/digiaAnchorRegistry.js.map +1 -0
  49. package/lib/module/index.js +1 -0
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/templateTypes.js +2 -0
  52. package/lib/module/templateTypes.js.map +1 -0
  53. package/lib/typescript/Digia.d.ts +29 -2
  54. package/lib/typescript/Digia.d.ts.map +1 -1
  55. package/lib/typescript/DigiaGuideController.d.ts +30 -0
  56. package/lib/typescript/DigiaGuideController.d.ts.map +1 -0
  57. package/lib/typescript/DigiaHealthReporter.d.ts +24 -0
  58. package/lib/typescript/DigiaHealthReporter.d.ts.map +1 -0
  59. package/lib/typescript/DigiaProvider.d.ts +3 -0
  60. package/lib/typescript/DigiaProvider.d.ts.map +1 -0
  61. package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
  62. package/lib/typescript/NativeDigiaEngage.d.ts +10 -6
  63. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  64. package/lib/typescript/actionHandler.d.ts +20 -0
  65. package/lib/typescript/actionHandler.d.ts.map +1 -0
  66. package/lib/typescript/defaultInAppBrowser.d.ts +3 -0
  67. package/lib/typescript/defaultInAppBrowser.d.ts.map +1 -0
  68. package/lib/typescript/digiaAnchorRegistry.d.ts +15 -0
  69. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -0
  70. package/lib/typescript/index.d.ts +1 -0
  71. package/lib/typescript/index.d.ts.map +1 -1
  72. package/lib/typescript/templateTypes.d.ts +140 -0
  73. package/lib/typescript/templateTypes.d.ts.map +1 -0
  74. package/lib/typescript/types.d.ts +140 -3
  75. package/lib/typescript/types.d.ts.map +1 -1
  76. package/package.json +12 -3
  77. package/react-native.config.js +23 -0
  78. package/src/Digia.ts +340 -3
  79. package/src/DigiaGuideController.ts +61 -0
  80. package/src/DigiaHealthReporter.ts +43 -0
  81. package/src/DigiaProvider.tsx +776 -0
  82. package/src/DigiaSlotView.tsx +26 -6
  83. package/src/NativeDigiaEngage.ts +28 -13
  84. package/src/actionHandler.ts +311 -0
  85. package/src/defaultInAppBrowser.ts +31 -0
  86. package/src/digiaAnchorRegistry.ts +27 -0
  87. package/src/index.ts +1 -0
  88. package/src/templateTypes.ts +121 -0
  89. package/src/types.ts +102 -5
package/README.md CHANGED
@@ -1,203 +1,133 @@
1
- # @digia/engage-react-native
1
+ # @digia-engage/core
2
2
 
3
- React Native bridge for the **Digia Engage SDK** – renders native Android
4
- Jetpack Compose UI (dialogs, bottom-sheets) inside React Native applications.
3
+ React Native SDK for **Digia Engage** – renders native Android (Jetpack Compose) and iOS campaign UI (bottom sheets, dialogs, inline banners, tooltips, spotlights) inside React Native applications.
5
4
 
6
5
  > **Platform support**
7
6
  > | Platform | Status |
8
7
  > |---|---|
9
8
  > | Android | ✅ Full support |
10
- > | iOS | 🚧 Stub (no-op coming soon) |
11
-
12
- ---
13
-
14
- ## How it works
15
-
16
- The Digia Engage Android SDK is built entirely with Jetpack Compose. In a
17
- pure-Android app you wrap your content with the `DigiaHost { }` composable; it
18
- then manages campaign-driven overlays (dialogs, bottom sheets) on top of your
19
- content.
20
-
21
- In a React Native app we cannot embed a Composable directly in the JS tree, so
22
- the bridge uses two complementary mechanisms:
23
-
24
- ```
25
- ┌─────────────────────────────────────────┐
26
- │ Android Activity (ReactActivity) │
27
- │ │
28
- │ ┌─────────────────────────────────┐ │
29
- │ │ Android content FrameLayout │ │
30
- │ │ │ │
31
- │ │ ┌─────────────────────────┐ │ │
32
- │ │ │ React Native RootView │ │ │
33
- │ │ │ (your JS UI) │ │ │
34
- │ │ └─────────────────────────┘ │ │
35
- │ │ │ │
36
- │ │ ┌─────────────────────────┐ │ │
37
- │ │ │ DigiaHostComposeView │ │ │
38
- │ │ │ (AbstractComposeView) │ │ │
39
- │ │ │ hosts DigiaHost { } │ │ │
40
- │ │ │ ← transparent, no │ │ │
41
- │ │ │ touch interception │ │ │
42
- │ │ └─────────────────────────┘ │ │
43
- │ └─────────────────────────────────┘ │
44
- │ │
45
- │ ┌──────────────────────────────────┐ │
46
- │ │ Compose Dialog window (overlay) │ │
47
- │ │ Triggered by CEP campaign │ │
48
- │ └──────────────────────────────────┘ │
49
- └─────────────────────────────────────────┘
50
- ```
51
-
52
- 1. **`Digia.initialize()`** initialises the SDK and calls `addContentView()`
53
- to attach a transparent `DigiaHostComposeView` on top of the React Native
54
- view hierarchy. This is the anchor for the Compose composition.
55
-
56
- 2. **`DigiaHost { }`** inside the `ComposeView` manages `DialogManager` and
57
- `BottomSheetManager`. When a CEP plugin triggers a campaign, Compose
58
- renders a `Dialog` or `ModalBottomSheet` – these are **separate Android
59
- windows** that appear on top of everything, including React Native content.
60
-
61
- 3. **`<DigiaHostView>`** is an optional React Native component you can place in
62
- your component tree (e.g. as `StyleSheet.absoluteFill`) if you prefer a
63
- declarative, component-based mount point instead of the auto-mount.
64
-
65
- ---
66
-
67
- ## Installation
9
+ > | iOS | Guide overlays (JS renderer); native bridge (surveys, inline) |
68
10
 
69
11
  ```sh
70
- # npm
71
- npm install @digia/engage-react-native
72
-
73
- # yarn
74
- yarn add @digia/engage-react-native
12
+ npm install @digia-engage/core
75
13
  ```
76
14
 
77
- React Native CLI auto-linking handles the rest. Rebuild the native app:
15
+ React Native CLI auto-linking handles the rest. Rebuild the native app after installing:
78
16
 
79
17
  ```sh
80
- npx react-native build-android
18
+ npx react-native run-android
81
19
  # or
82
20
  cd android && ./gradlew assembleDebug
83
21
  ```
84
22
 
85
- ### Android – host app dependencies
23
+ ### Android – host app dependency
86
24
 
87
- Your app's `android/app/build.gradle` must declare the Digia Engage AAR
88
- (or include it via your local Maven / private registry):
25
+ Add the Digia Engage Android SDK to `android/app/build.gradle.kts`:
89
26
 
90
- ```groovy
27
+ ```kotlin
91
28
  dependencies {
92
- implementation 'com.digia:digia-ui:1.0.0'
29
+ implementation("tech.digia:engage:2.0.0-rc.1")
93
30
  }
94
31
  ```
95
32
 
96
- If you are working inside the monorepo and building locally, add the `:digia-ui`
97
- project instead:
33
+ ---
98
34
 
99
- ```groovy
100
- implementation(project(':digia-ui'))
101
- ```
35
+ ## Usage
102
36
 
103
- ### iOS
37
+ ### 1 — Initialize the SDK
104
38
 
105
- iOS is a no-op stub. All methods resolve immediately without error.
39
+ Call `Digia.initialize()` once, as early as possible (top of `App.tsx`):
106
40
 
107
- ---
41
+ ```tsx
42
+ import { Digia } from '@digia-engage/core';
108
43
 
109
- ## Usage
44
+ await Digia.initialize({
45
+ projectId: 'digia_YOUR_PROJECT_ID',
46
+ environment: 'production', // or 'sandbox'
47
+ logLevel: 'error',
48
+ });
49
+ ```
110
50
 
111
- ### 1 Initialise the SDK
51
+ ### 2 Mount `<DigiaHost />`
112
52
 
113
- Call `Digia.initialize()` once, as early as possible (e.g. the top of
114
- `App.tsx`):
53
+ Place `<DigiaHost />` at the root of your component tree. It renders the JS-side guide/tooltip/spotlight overlays via a `Modal`.
115
54
 
116
55
  ```tsx
117
- import React, { useEffect } from 'react';
118
- import { NavigationContainer } from '@react-navigation/native';
119
- import { Digia } from '@digia/engage-react-native';
120
-
121
- export default function App() {
122
- useEffect(() => {
123
- Digia.initialize({
124
- apiKey: 'YOUR_DIGIA_API_KEY',
125
- environment: 'production', // or 'sandbox'
126
- logLevel: 'error',
127
- });
128
- }, []);
129
-
130
- return <NavigationContainer>{/* … */}</NavigationContainer>;
56
+ // app/_layout.tsx (Expo Router) or App.tsx
57
+ import { DigiaHost } from '@digia-engage/core';
58
+
59
+ export default function RootLayout() {
60
+ return (
61
+ <>
62
+ <Stack />
63
+ <DigiaHost />
64
+ </>
65
+ );
131
66
  }
132
67
  ```
133
68
 
134
- `initialize()` returns a `Promise<void>` and automatically attaches the Compose
135
- overlay host to the Activity. You do **not** need to add `<DigiaHostView>`
136
- unless you want an explicit component-based mount point.
137
-
138
- ### 2 – Track screen changes
139
-
140
- Wire `setCurrentScreen()` to your navigation state so the SDK can trigger
141
- campaigns based on the active screen:
69
+ ### 3 Track screen changes
142
70
 
143
71
  ```tsx
144
- import { useNavigationContainerRef } from '@react-navigation/native';
145
- import { Digia } from '@digia/engage-react-native';
146
-
147
- // Inside your App component:
148
- const navRef = useNavigationContainerRef();
72
+ import { Digia } from '@digia-engage/core';
149
73
 
150
74
  <NavigationContainer
151
- ref={navRef}
152
75
  onStateChange={() => {
153
- const currentRoute = navRef.getCurrentRoute();
154
- if (currentRoute) {
155
- Digia.setCurrentScreen(currentRoute.name);
156
- }
76
+ const route = navRef.getCurrentRoute();
77
+ if (route) Digia.setCurrentScreen(route.name);
157
78
  }}
158
79
  >
159
80
  ```
160
81
 
161
- ### 3 Open the Digia UI navigation flow
82
+ ### 4 Register anchors for guide campaigns
162
83
 
163
- Launch the full-screen native SDUI stack:
84
+ Wrap any UI element you want a tooltip or spotlight to point at:
164
85
 
165
86
  ```tsx
166
- import { Digia } from '@digia/engage-react-native';
87
+ import { DigiaAnchorView } from '@digia-engage/core';
167
88
 
168
- function MyScreen() {
169
- return (
170
- <Button
171
- title="Open Digia Experience"
172
- onPress={() => Digia.createInitialPage()}
173
- />
174
- );
175
- }
89
+ <DigiaAnchorView anchorKey="home_banner_btn">
90
+ <Button title="Banner" />
91
+ </DigiaAnchorView>
176
92
  ```
177
93
 
178
- ### 4 (Optional) Declarative overlay mount via `<DigiaHostView>`
94
+ ### 5 Add slots for inline campaigns
95
+
96
+ ```tsx
97
+ import { DigiaSlotView } from '@digia-engage/core';
98
+
99
+ // Auto-sizes to native content height
100
+ <DigiaSlotView placementKey="home_banner" />
101
+ ```
179
102
 
180
- If you prefer an explicit React component instead of the auto-mount, skip the
181
- `initialize()` auto-mount and place `<DigiaHostView>` at the root of your
182
- component tree:
103
+ ### 6 Register a CEP plugin
183
104
 
184
105
  ```tsx
185
- import React from 'react';
186
- import { StyleSheet, View } from 'react-native';
187
- import { DigiaHostView } from '@digia/engage-react-native';
106
+ import { DigiaCleverTapPlugin, createCleverTapClient } from '@digia-engage/clevertap';
107
+ import CleverTap from 'clevertap-react-native';
188
108
 
189
- export default function App() {
190
- return (
191
- <View style={styles.root}>
192
- {/* DigiaHostView must be above all other content in z-order */}
193
- <DigiaHostView style={StyleSheet.absoluteFill} />
109
+ Digia.register(new DigiaCleverTapPlugin({
110
+ cleverTap: createCleverTapClient(CleverTap),
111
+ }));
112
+ ```
194
113
 
195
- {/* Your navigation / content here */}
196
- </View>
197
- );
198
- }
114
+ ### 7 (Optional) Handle actions
115
+
116
+ Override or observe every action the SDK fires:
199
117
 
200
- const styles = StyleSheet.create({ root: { flex: 1 } });
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
+ });
201
131
  ```
202
132
 
203
133
  ---
@@ -208,57 +138,97 @@ const styles = StyleSheet.create({ root: { flex: 1 } });
208
138
 
209
139
  | Method | Signature | Description |
210
140
  |---|---|---|
211
- | `initialize` | `(config: DigiaConfig) => Promise<void>` | Initialise the SDK and mount the Compose overlay host. |
212
- | `setCurrentScreen` | `(name: string) => void` | Notify the SDK of the current screen. |
213
- | `createInitialPage` | `() => void` | Full-screen Digia SDUI (Android: `DigiaUINavigationActivity`; iOS: modal `DigiaNavigationView`). |
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. |
214
147
 
215
148
  ### `DigiaConfig`
216
149
 
217
150
  | Prop | Type | Default | Description |
218
151
  |---|---|---|---|
219
- | `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. |
220
153
  | `environment` | `'production' \| 'sandbox'` | `'production'` | Target environment. |
221
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. |
222
159
 
223
- ### `CreateInitialPageOptions`
160
+ ### `<DigiaHost />`
224
161
 
225
- Empty interface reserved for future optional arguments.
162
+ No props. Renders the JS guide overlay runtime (`TooltipOverlay`, `SpotlightOverlay`).
163
+ Place it once, anywhere in the root view — `Modal` handles z-ordering.
226
164
 
227
- ### `<DigiaHostView>`
165
+ ### `<DigiaAnchorView>`
228
166
 
229
- A transparent React Native view that hosts the Compose overlay anchor.
167
+ | Prop | Type | Description |
168
+ |---|---|---|
169
+ | `anchorKey` | `string` | **Required.** Must match the step's `anchorKey` in the campaign config. |
170
+ | `...ViewProps` | — | All standard React Native `View` props are forwarded. |
171
+
172
+ ### `<DigiaSlotView>`
173
+
174
+ Renders inline campaign content (banners, cards) at a named placement. Collapses to zero height when no campaign is active for the slot.
230
175
 
231
176
  | Prop | Type | Description |
232
177
  |---|---|---|
233
- | `style` | `ViewStyle?` | Custom style (defaults to `absoluteFill` behaviour). |
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 |
234
197
 
235
198
  ---
236
199
 
237
- ## Architecture overview
200
+ ## Architecture
238
201
 
239
202
  ```
240
- react-native/
241
- ├── src/
242
- │ ├── index.ts Public API exports
243
- │ ├── types.ts ← TypeScript interfaces
244
- │ ├── Digia.ts ← High-level JS SDK wrapper
245
- │ ├── NativeDigiaEngage.ts Low-level native module binding
246
- │ └── DigiaHostView.tsx ← <DigiaHostView> React component
247
-
248
- ├── android/
249
- │ ├── build.gradle ← Android library build config
250
- │ └── src/main/java/com/digia/engage/rn/
251
- │ ├── DigiaPackage.kt ← ReactPackage (registers module + view)
252
- │ ├── DigiaModule.kt ← NativeModule (initialize, setCurrentScreen, createInitialPage)
253
- │ ├── DigiaViewManager.kt ← ViewManager for <DigiaHostView>
254
- │ └── DigiaHostComposeView.kt AbstractComposeView hosting DigiaHost { }
255
-
256
- ├── ios/
257
- │ └── DigiaEngageModule.m ← iOS no-op stub
258
-
259
- ├── DigiaEngageReactNative.podspec
260
- ├── react-native.config.js ← Auto-linking config
261
- └── package.json
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
262
232
  ```
263
233
 
264
234
  ---
@@ -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
 
@@ -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`.