@digia-engage/core 1.0.0-beta.4 → 1.0.0-beta.6
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.
- package/DigiaEngageReactNative.podspec +24 -8
- package/README.md +8 -17
- package/android/.project +28 -0
- package/android/build.gradle +1 -1
- package/android/settings.gradle +1 -3
- package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +1 -1
- package/android/src/main/java/com/digia/engage/rn/DigiaSlotViewManager.kt +146 -31
- package/ios/DigiaEngageModule.m +25 -44
- package/ios/DigiaHostViewManager.swift +128 -0
- package/ios/DigiaModule.swift +241 -0
- package/ios/DigiaSlotViewManager.swift +275 -0
- package/ios/RNEventBridgePlugin.swift +71 -0
- package/lib/commonjs/Digia.js +50 -0
- package/lib/commonjs/Digia.js.map +1 -1
- package/lib/commonjs/DigiaHostView.js +6 -50
- package/lib/commonjs/DigiaHostView.js.map +1 -1
- package/lib/commonjs/DigiaSlotView.js +37 -54
- package/lib/commonjs/DigiaSlotView.js.map +1 -1
- package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
- package/lib/module/Digia.js +50 -0
- package/lib/module/Digia.js.map +1 -1
- package/lib/module/DigiaHostView.js +6 -51
- package/lib/module/DigiaHostView.js.map +1 -1
- package/lib/module/DigiaSlotView.js +37 -52
- package/lib/module/DigiaSlotView.js.map +1 -1
- package/lib/module/NativeDigiaEngage.js.map +1 -1
- package/lib/typescript/Digia.d.ts +12 -0
- package/lib/typescript/Digia.d.ts.map +1 -1
- package/lib/typescript/DigiaHostView.d.ts +2 -28
- package/lib/typescript/DigiaHostView.d.ts.map +1 -1
- package/lib/typescript/DigiaSlotView.d.ts +3 -39
- package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
- package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +21 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +8 -18
- package/src/Digia.ts +60 -1
- package/src/DigiaHostView.tsx +7 -48
- package/src/DigiaSlotView.tsx +42 -49
- package/src/NativeDigiaEngage.ts +1 -0
- package/src/index.ts +1 -1
- package/src/types.ts +30 -0
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'DigiaEngageReactNative'
|
|
3
3
|
s.version = '0.1.0'
|
|
4
|
-
s.summary = 'React Native bridge for the Digia Engage SDK (
|
|
4
|
+
s.summary = 'React Native bridge for the Digia Engage SDK (iOS & Android).'
|
|
5
5
|
s.description = <<-DESC
|
|
6
|
-
Provides a React Native bridge that surfaces the Digia Engage
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Provides a React Native bridge that surfaces the Digia Engage SDK inside
|
|
7
|
+
React Native applications. Supports both iOS (SwiftUI) and Android
|
|
8
|
+
(Jetpack Compose) using the New Architecture (TurboModules / Fabric).
|
|
9
9
|
DESC
|
|
10
10
|
|
|
11
|
-
s.homepage = 'https://github.com/Digia-Technology-Private-Limited/digia_engage'
|
|
12
11
|
s.license = { :type => 'MIT', :file => '../LICENSE' }
|
|
13
|
-
s.author = { 'Digia Technology Private Limited' => '' }
|
|
14
|
-
s.source = { :git => 'https://github.com/Digia-Technology-Private-Limited/digia_engage.git', :tag => s.version.to_s }
|
|
15
12
|
|
|
16
|
-
s.
|
|
13
|
+
s.authors = { 'Digia Technology Private Limited' => 'https://digia.tech' }
|
|
14
|
+
s.homepage = 'https://github.com/Digia-Technology-Private-Limited/digia_engage'
|
|
15
|
+
s.source = {
|
|
16
|
+
:git => 'https://github.com/Digia-Technology-Private-Limited/digia_engage.git',
|
|
17
|
+
:tag => "react-native-v#{s.version}"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# DigiaEngage iOS SDK requires iOS 17+ (SwiftUI features used internally).
|
|
21
|
+
s.ios.deployment_target = '17.0'
|
|
17
22
|
|
|
18
23
|
s.source_files = 'ios/**/*.{h,m,mm,swift}'
|
|
19
24
|
|
|
25
|
+
# Swift version must match the Digia iOS SDK.
|
|
26
|
+
s.swift_version = '5.9'
|
|
27
|
+
|
|
20
28
|
s.dependency 'React-Core'
|
|
29
|
+
|
|
30
|
+
# ── Digia Engage iOS SDK ──────────────────────────────────────────────────
|
|
31
|
+
# Available on SPM: https://swiftpackageindex.com/Digia-Technology-Private-Limited/digia_engage_iOS
|
|
32
|
+
# CocoaPods: host app Podfile must declare the git source (see README).
|
|
33
|
+
s.dependency 'DigiaEngage'
|
|
34
|
+
|
|
35
|
+
# ── New Architecture (Fabric / TurboModules) support ─────────────────────
|
|
36
|
+
install_modules_dependencies(s)
|
|
21
37
|
end
|
package/README.md
CHANGED
|
@@ -158,10 +158,9 @@ const navRef = useNavigationContainerRef();
|
|
|
158
158
|
>
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
### 3 – Open the Digia UI navigation flow
|
|
161
|
+
### 3 – Open the Digia UI navigation flow
|
|
162
162
|
|
|
163
|
-
Launch the full-screen
|
|
164
|
-
configuration:
|
|
163
|
+
Launch the full-screen native SDUI stack:
|
|
165
164
|
|
|
166
165
|
```tsx
|
|
167
166
|
import { Digia } from '@digia/engage-react-native';
|
|
@@ -170,12 +169,7 @@ function MyScreen() {
|
|
|
170
169
|
return (
|
|
171
170
|
<Button
|
|
172
171
|
title="Open Digia Experience"
|
|
173
|
-
onPress={() =>
|
|
174
|
-
Digia.openNavigation({
|
|
175
|
-
startPageId: 'onboarding',
|
|
176
|
-
pageArgs: { userId: '123' },
|
|
177
|
-
})
|
|
178
|
-
}
|
|
172
|
+
onPress={() => Digia.createInitialPage()}
|
|
179
173
|
/>
|
|
180
174
|
);
|
|
181
175
|
}
|
|
@@ -216,7 +210,7 @@ const styles = StyleSheet.create({ root: { flex: 1 } });
|
|
|
216
210
|
|---|---|---|
|
|
217
211
|
| `initialize` | `(config: DigiaConfig) => Promise<void>` | Initialise the SDK and mount the Compose overlay host. |
|
|
218
212
|
| `setCurrentScreen` | `(name: string) => void` | Notify the SDK of the current screen. |
|
|
219
|
-
| `
|
|
213
|
+
| `createInitialPage` | `() => void` | Full-screen Digia SDUI (Android: `DigiaUINavigationActivity`; iOS: modal `DigiaNavigationView`). |
|
|
220
214
|
|
|
221
215
|
### `DigiaConfig`
|
|
222
216
|
|
|
@@ -226,12 +220,9 @@ const styles = StyleSheet.create({ root: { flex: 1 } });
|
|
|
226
220
|
| `environment` | `'production' \| 'sandbox'` | `'production'` | Target environment. |
|
|
227
221
|
| `logLevel` | `'none' \| 'error' \| 'verbose'` | `'error'` | Log verbosity. |
|
|
228
222
|
|
|
229
|
-
### `
|
|
223
|
+
### `CreateInitialPageOptions`
|
|
230
224
|
|
|
231
|
-
|
|
232
|
-
|---|---|---|
|
|
233
|
-
| `startPageId` | `string?` | DSL page ID to start from. |
|
|
234
|
-
| `pageArgs` | `Record<string, string>?` | Key/value args forwarded to the start page. |
|
|
225
|
+
Empty interface — reserved for future optional arguments.
|
|
235
226
|
|
|
236
227
|
### `<DigiaHostView>`
|
|
237
228
|
|
|
@@ -251,14 +242,14 @@ react-native/
|
|
|
251
242
|
│ ├── index.ts ← Public API exports
|
|
252
243
|
│ ├── types.ts ← TypeScript interfaces
|
|
253
244
|
│ ├── Digia.ts ← High-level JS SDK wrapper
|
|
254
|
-
│ ├──
|
|
245
|
+
│ ├── NativeDigiaEngage.ts ← Low-level native module binding
|
|
255
246
|
│ └── DigiaHostView.tsx ← <DigiaHostView> React component
|
|
256
247
|
│
|
|
257
248
|
├── android/
|
|
258
249
|
│ ├── build.gradle ← Android library build config
|
|
259
250
|
│ └── src/main/java/com/digia/engage/rn/
|
|
260
251
|
│ ├── DigiaPackage.kt ← ReactPackage (registers module + view)
|
|
261
|
-
│ ├── DigiaModule.kt ← NativeModule (initialize, setCurrentScreen,
|
|
252
|
+
│ ├── DigiaModule.kt ← NativeModule (initialize, setCurrentScreen, createInitialPage)
|
|
262
253
|
│ ├── DigiaViewManager.kt ← ViewManager for <DigiaHostView>
|
|
263
254
|
│ └── DigiaHostComposeView.kt ← AbstractComposeView hosting DigiaHost { }
|
|
264
255
|
│
|
package/android/.project
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<projectDescription>
|
|
3
|
+
<name>digia-engage_core</name>
|
|
4
|
+
<comment>Project digia-engage_core created by Buildship.</comment>
|
|
5
|
+
<projects>
|
|
6
|
+
</projects>
|
|
7
|
+
<buildSpec>
|
|
8
|
+
<buildCommand>
|
|
9
|
+
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
10
|
+
<arguments>
|
|
11
|
+
</arguments>
|
|
12
|
+
</buildCommand>
|
|
13
|
+
</buildSpec>
|
|
14
|
+
<natures>
|
|
15
|
+
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
16
|
+
</natures>
|
|
17
|
+
<filteredResources>
|
|
18
|
+
<filter>
|
|
19
|
+
<id>1775559486270</id>
|
|
20
|
+
<name></name>
|
|
21
|
+
<type>30</type>
|
|
22
|
+
<matcher>
|
|
23
|
+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
|
24
|
+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
|
25
|
+
</matcher>
|
|
26
|
+
</filter>
|
|
27
|
+
</filteredResources>
|
|
28
|
+
</projectDescription>
|
package/android/build.gradle
CHANGED
|
@@ -71,7 +71,7 @@ android {
|
|
|
71
71
|
|
|
72
72
|
dependencies {
|
|
73
73
|
// Digia Engage Android library
|
|
74
|
-
implementation 'tech.digia:engage:1.0.0-beta.
|
|
74
|
+
implementation 'tech.digia:engage:1.0.0-beta.04'
|
|
75
75
|
|
|
76
76
|
// ── React Native ─────────────────────────────────────────────────────────
|
|
77
77
|
// React Native is provided by the host app; mark as compileOnly so it is
|
package/android/settings.gradle
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DigiaSlotViewManager
|
|
3
|
-
*
|
|
4
|
-
* React Native ViewManager that exposes [DigiaSlotView] (from the Digia Android SDK) as the native
|
|
5
|
-
* view behind the JS `<DigiaSlotView>` component.
|
|
6
|
-
*
|
|
7
|
-
* Supported JS props:
|
|
8
|
-
* - `placementKey` (String) — matches the placement key set in the Digia dashboard.
|
|
9
|
-
*/
|
|
10
1
|
package com.digia.engage.rn
|
|
11
2
|
|
|
12
3
|
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewTreeObserver
|
|
13
6
|
import android.widget.FrameLayout
|
|
14
7
|
import androidx.lifecycle.LifecycleOwner
|
|
15
8
|
import androidx.lifecycle.ViewModelStoreOwner
|
|
@@ -18,43 +11,165 @@ import androidx.lifecycle.setViewTreeViewModelStoreOwner
|
|
|
18
11
|
import androidx.savedstate.SavedStateRegistryOwner
|
|
19
12
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
|
20
13
|
import com.digia.engage.DigiaSlotView
|
|
14
|
+
import com.facebook.react.bridge.Arguments
|
|
15
|
+
import com.facebook.react.bridge.WritableMap
|
|
21
16
|
import com.facebook.react.uimanager.SimpleViewManager
|
|
22
17
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
18
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
23
19
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
20
|
+
import com.facebook.react.uimanager.events.Event
|
|
21
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
// ── ContentSizeChangeEvent ────────────────────────────────────────────────────
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
private class ContentSizeChangeEvent(
|
|
26
|
+
surfaceId: Int,
|
|
27
|
+
viewTag: Int,
|
|
28
|
+
private val heightDp: Double,
|
|
29
|
+
) : Event<ContentSizeChangeEvent>(surfaceId, viewTag) {
|
|
30
|
+
override fun getEventName(): String = "onContentSizeChange"
|
|
31
|
+
override fun getEventData(): WritableMap =
|
|
32
|
+
Arguments.createMap().apply { putDouble("height", heightDp) }
|
|
33
|
+
}
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
val activityContext: Context = context.currentActivity ?: context
|
|
35
|
+
// ── DigiaSlotContainerView ────────────────────────────────────────────────────
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
// Plain FrameLayout wrapper: Fabric can measure it before window attachment.
|
|
38
|
+
// The inner DigiaSlotView (ComposeView) is created lazily in onAttachedToWindow.
|
|
39
|
+
internal class DigiaSlotContainerView(context: Context) : FrameLayout(context) {
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
var rnContext: ThemedReactContext? = null
|
|
42
|
+
|
|
43
|
+
private var _slotView: DigiaSlotView? = null
|
|
44
|
+
private val lastReportedHeightPx = AtomicInteger(-1)
|
|
45
|
+
|
|
46
|
+
var placementKey: String = ""
|
|
47
|
+
set(value) {
|
|
48
|
+
field = value
|
|
49
|
+
val slot = _slotView ?: return
|
|
50
|
+
slot.placementKey = value
|
|
51
|
+
lastReportedHeightPx.set(-1)
|
|
52
|
+
// Defer measure so the slot has a non-zero width. Do NOT call requestLayout() here —
|
|
53
|
+
// it sets PFLAG_FORCE_LAYOUT on the container and RN bypasses a full traversal,
|
|
54
|
+
// causing subsequent Compose requestLayout() calls to be swallowed.
|
|
55
|
+
post { measureAndDispatch() }
|
|
56
|
+
postDelayed({
|
|
57
|
+
lastReportedHeightPx.set(-1)
|
|
58
|
+
measureAndDispatch()
|
|
59
|
+
}, DELAYED_MEASURE_MS)
|
|
41
60
|
}
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
|
|
62
|
+
private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
|
|
63
|
+
measureAndDispatch()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// preDrawListener catches Compose content changes that globalLayoutListener misses in RN's
|
|
67
|
+
// layout model: Compose's invalidate() propagates to ViewRootImpl even when requestLayout()
|
|
68
|
+
// is blocked by PFLAG_FORCE_LAYOUT.
|
|
69
|
+
private val preDrawListener = ViewTreeObserver.OnPreDrawListener {
|
|
70
|
+
measureAndDispatch()
|
|
71
|
+
true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun onAttachedToWindow() {
|
|
75
|
+
super.onAttachedToWindow()
|
|
76
|
+
if (_slotView == null) createSlotView()
|
|
77
|
+
viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
|
|
78
|
+
viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
|
79
|
+
post { measureAndDispatch() }
|
|
80
|
+
// Retry: catches campaigns that arrive slightly after mount.
|
|
81
|
+
postDelayed({
|
|
82
|
+
if (lastReportedHeightPx.get() <= 0) {
|
|
83
|
+
lastReportedHeightPx.set(-1)
|
|
84
|
+
measureAndDispatch()
|
|
85
|
+
}
|
|
86
|
+
}, DELAYED_MEASURE_MS)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun onDetachedFromWindow() {
|
|
90
|
+
viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
|
|
91
|
+
viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
92
|
+
super.onDetachedFromWindow()
|
|
93
|
+
lastReportedHeightPx.set(-1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Defer measureAndDispatch to avoid re-measuring during Compose's composition phase,
|
|
97
|
+
// which crashes with "pending composition has not been applied".
|
|
98
|
+
override fun requestLayout() {
|
|
99
|
+
super.requestLayout()
|
|
100
|
+
post { measureAndDispatch() }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun createSlotView() {
|
|
104
|
+
val themedCtx = context as? ThemedReactContext
|
|
105
|
+
val activityCtx: Context = themedCtx?.currentActivity ?: context
|
|
106
|
+
|
|
107
|
+
val slot = DigiaSlotView(activityCtx)
|
|
108
|
+
|
|
109
|
+
val activity = themedCtx?.currentActivity
|
|
110
|
+
if (activity is LifecycleOwner) slot.setViewTreeLifecycleOwner(activity)
|
|
111
|
+
if (activity is ViewModelStoreOwner) slot.setViewTreeViewModelStoreOwner(activity)
|
|
112
|
+
if (activity is SavedStateRegistryOwner) slot.setViewTreeSavedStateRegistryOwner(activity)
|
|
113
|
+
|
|
114
|
+
slot.placementKey = placementKey
|
|
115
|
+
slot.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
116
|
+
slot.addOnLayoutChangeListener { _: View, l: Int, _: Int, r: Int, _: Int,
|
|
117
|
+
_: Int, _: Int, _: Int, _: Int ->
|
|
118
|
+
if (r - l > 0) measureAndDispatch()
|
|
44
119
|
}
|
|
45
120
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
121
|
+
addView(slot)
|
|
122
|
+
_slotView = slot
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fun measureAndDispatch() {
|
|
126
|
+
val slot = _slotView ?: return
|
|
127
|
+
val ctx = rnContext ?: return
|
|
128
|
+
val viewWidth = width
|
|
129
|
+
if (viewWidth <= 0) return
|
|
130
|
+
|
|
131
|
+
val widthSpec = MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY)
|
|
132
|
+
val heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
133
|
+
slot.measure(widthSpec, heightSpec)
|
|
134
|
+
val intrinsicHeightPx = slot.measuredHeight
|
|
135
|
+
|
|
136
|
+
if (intrinsicHeightPx == lastReportedHeightPx.get()) return
|
|
137
|
+
lastReportedHeightPx.set(intrinsicHeightPx)
|
|
138
|
+
|
|
139
|
+
val density = resources.displayMetrics.density
|
|
140
|
+
val heightDp = intrinsicHeightPx / density
|
|
141
|
+
|
|
142
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(ctx, id) ?: return
|
|
143
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
144
|
+
dispatcher.dispatchEvent(ContentSizeChangeEvent(surfaceId, id, heightDp.toDouble()))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
companion object {
|
|
148
|
+
private const val DELAYED_MEASURE_MS = 300L
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── DigiaSlotViewManager ──────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
internal class DigiaSlotViewManager : SimpleViewManager<DigiaSlotContainerView>() {
|
|
155
|
+
|
|
156
|
+
override fun getName(): String = VIEW_NAME
|
|
157
|
+
|
|
158
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
|
|
159
|
+
mapOf("onContentSizeChange" to mapOf("registrationName" to "onContentSizeChange"))
|
|
52
160
|
|
|
53
|
-
|
|
161
|
+
override fun createViewInstance(context: ThemedReactContext): DigiaSlotContainerView {
|
|
162
|
+
val container = DigiaSlotContainerView(context)
|
|
163
|
+
container.rnContext = context
|
|
164
|
+
container.layoutParams = FrameLayout.LayoutParams(
|
|
165
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
166
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
167
|
+
)
|
|
168
|
+
return container
|
|
54
169
|
}
|
|
55
170
|
|
|
56
171
|
@ReactProp(name = "placementKey")
|
|
57
|
-
fun setPlacementKey(view:
|
|
172
|
+
fun setPlacementKey(view: DigiaSlotContainerView, placementKey: String?) {
|
|
58
173
|
view.placementKey = placementKey.orEmpty()
|
|
59
174
|
}
|
|
60
175
|
|
package/ios/DigiaEngageModule.m
CHANGED
|
@@ -1,71 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DigiaEngageModule
|
|
2
|
+
* DigiaEngageModule.m
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* ObjC bridge file that exports Swift implementations to the React Native
|
|
5
|
+
* runtime (both Old Architecture bridge and New Architecture TurboModules).
|
|
6
|
+
*
|
|
7
|
+
* All real logic lives in the Swift files alongside this one:
|
|
8
|
+
* DigiaModule.swift — NativeModule (RCTEventEmitter subclass)
|
|
9
|
+
* RNEventBridgePlugin.swift — DigiaCEPPlugin bridge
|
|
10
|
+
* DigiaHostViewManager.swift — ViewManager for <DigiaHostView>
|
|
11
|
+
* DigiaSlotViewManager.swift — ViewManager for <DigiaSlotView>
|
|
6
12
|
*/
|
|
13
|
+
|
|
7
14
|
#import <React/RCTBridgeModule.h>
|
|
15
|
+
#import <React/RCTEventEmitter.h>
|
|
8
16
|
#import <React/RCTViewManager.h>
|
|
9
17
|
|
|
10
|
-
// ── NativeModule
|
|
11
|
-
|
|
12
|
-
@interface DigiaEngageModule : NSObject <RCTBridgeModule>
|
|
13
|
-
@end
|
|
18
|
+
// ── NativeModule ──────────────────────────────────────────────────────────────
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
// RCT_EXTERN_MODULE wires the Swift class DigiaModule (which inherits
|
|
21
|
+
// RCTEventEmitter) to the React Native bridge under the name "DigiaEngageModule".
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
@interface RCT_EXTERN_MODULE(DigiaEngageModule, RCTEventEmitter)
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
|
|
20
26
|
environment:(NSString *)environment
|
|
21
27
|
logLevel:(NSString *)logLevel
|
|
22
28
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
23
29
|
reject:(RCTPromiseRejectBlock)reject)
|
|
24
|
-
{
|
|
25
|
-
// iOS not yet implemented – resolve immediately so JS doesn't hang.
|
|
26
|
-
resolve(nil);
|
|
27
|
-
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
// no-op
|
|
32
|
-
}
|
|
31
|
+
RCT_EXTERN_METHOD(registerBridge)
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
pageArgs:(NSDictionary *)pageArgs)
|
|
36
|
-
{
|
|
37
|
-
// no-op
|
|
38
|
-
}
|
|
33
|
+
RCT_EXTERN_METHOD(setCurrentScreen:(NSString *)name)
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
RCT_EXTERN_METHOD(triggerCampaign:(NSString *)id
|
|
41
36
|
content:(NSDictionary *)content
|
|
42
37
|
cepContext:(NSDictionary *)cepContext)
|
|
43
|
-
{
|
|
44
|
-
// no-op — iOS Digia SDK not yet available
|
|
45
|
-
}
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
{
|
|
49
|
-
// no-op
|
|
50
|
-
}
|
|
39
|
+
RCT_EXTERN_METHOD(invalidateCampaign:(NSString *)campaignId)
|
|
51
40
|
|
|
52
41
|
@end
|
|
53
42
|
|
|
54
43
|
|
|
55
|
-
// ──
|
|
44
|
+
// ── ViewManagers ──────────────────────────────────────────────────────────────
|
|
56
45
|
|
|
57
|
-
@interface
|
|
46
|
+
@interface RCT_EXTERN_MODULE(DigiaHostView, RCTViewManager)
|
|
58
47
|
@end
|
|
59
48
|
|
|
60
|
-
@
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- (UIView *)view {
|
|
65
|
-
// Return a transparent placeholder view
|
|
66
|
-
UIView *v = [[UIView alloc] init];
|
|
67
|
-
v.userInteractionEnabled = NO;
|
|
68
|
-
return v;
|
|
69
|
-
}
|
|
70
|
-
|
|
49
|
+
@interface RCT_EXTERN_MODULE(DigiaSlotView, RCTViewManager)
|
|
50
|
+
RCT_EXPORT_VIEW_PROPERTY(placementKey, NSString)
|
|
51
|
+
RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
|
|
71
52
|
@end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DigiaHostViewManager
|
|
3
|
+
*
|
|
4
|
+
* React Native ViewManager that exposes a UIView wrapping DigiaHost (from the
|
|
5
|
+
* Digia iOS SDK) as the native view behind the JS <DigiaHostView> component.
|
|
6
|
+
*
|
|
7
|
+
* DigiaHost is a SwiftUI view, so we bridge it into UIKit using a
|
|
8
|
+
* UIHostingController embedded as a child view controller. The host view
|
|
9
|
+
* manages dialog and bottom-sheet overlays driven by Digia CEP plugins.
|
|
10
|
+
*
|
|
11
|
+
* Place <DigiaHostView> once at the root of your RN component tree.
|
|
12
|
+
*/
|
|
13
|
+
import SwiftUI
|
|
14
|
+
import React
|
|
15
|
+
import DigiaEngage
|
|
16
|
+
|
|
17
|
+
@objc(DigiaHostView)
|
|
18
|
+
final class DigiaHostViewManager: RCTViewManager {
|
|
19
|
+
|
|
20
|
+
override static func requiresMainQueueSetup() -> Bool { true }
|
|
21
|
+
|
|
22
|
+
override func view() -> UIView! {
|
|
23
|
+
return DigiaHostUIView()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// MARK: - DigiaHostUIView
|
|
28
|
+
|
|
29
|
+
/// Lightweight UIView container that embeds a UIHostingController<DigiaHost>
|
|
30
|
+
/// as a child so SwiftUI's DigiaHost composable renders overlays above all
|
|
31
|
+
/// React Native content.
|
|
32
|
+
final class DigiaHostUIView: UIView {
|
|
33
|
+
|
|
34
|
+
private var hostingController: UIHostingController<DigiaHostWrapperView>?
|
|
35
|
+
|
|
36
|
+
override func didMoveToWindow() {
|
|
37
|
+
super.didMoveToWindow()
|
|
38
|
+
guard window != nil else {
|
|
39
|
+
// View removed from hierarchy — tear down the hosting controller.
|
|
40
|
+
hostingController?.willMove(toParent: nil)
|
|
41
|
+
hostingController?.view.removeFromSuperview()
|
|
42
|
+
hostingController?.removeFromParent()
|
|
43
|
+
hostingController = nil
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
guard hostingController == nil else { return }
|
|
47
|
+
mountHostingController()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func mountHostingController() {
|
|
51
|
+
guard let parentVC = parentViewController() else { return}
|
|
52
|
+
|
|
53
|
+
let swiftUIView = DigiaHostWrapperView()
|
|
54
|
+
let hc = UIHostingController(rootView: swiftUIView)
|
|
55
|
+
hc.view.translatesAutoresizingMaskIntoConstraints = false
|
|
56
|
+
hc.view.backgroundColor = .clear
|
|
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)
|
|
69
|
+
|
|
70
|
+
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),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
hostingController = hc
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Pass touches through to RN when no overlay is active.
|
|
81
|
+
// 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.
|
|
85
|
+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
86
|
+
return hostingController?.view.hitTest(point, with: event)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Prefer React Native’s `UIView.reactViewController`, then walk `next` from each view’s `.next`.
|
|
90
|
+
private func parentViewController() -> UIViewController? {
|
|
91
|
+
let reactSel = NSSelectorFromString("reactViewController")
|
|
92
|
+
var view: UIView? = self
|
|
93
|
+
while let v = view {
|
|
94
|
+
if v.responds(to: reactSel), let raw = v.perform(reactSel)?.takeUnretainedValue() {
|
|
95
|
+
if let vc = raw as? UIViewController { return vc }
|
|
96
|
+
}
|
|
97
|
+
view = v.superview
|
|
98
|
+
}
|
|
99
|
+
view = self
|
|
100
|
+
while let v = view {
|
|
101
|
+
var r: UIResponder? = v.next
|
|
102
|
+
while let responder = r {
|
|
103
|
+
if let vc = responder as? UIViewController { return vc }
|
|
104
|
+
r = responder.next
|
|
105
|
+
}
|
|
106
|
+
view = v.superview
|
|
107
|
+
}
|
|
108
|
+
return nil
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// MARK: - SwiftUI wrapper
|
|
113
|
+
|
|
114
|
+
/// A SwiftUI view that acts as the DigiaHost root. An EmptyView is used as
|
|
115
|
+
/// content because React Native's own navigation already manages the app's
|
|
116
|
+
/// view hierarchy — DigiaHost only needs to be mounted to activate the overlay
|
|
117
|
+
/// layer.
|
|
118
|
+
struct DigiaHostWrapperView: View {
|
|
119
|
+
var body: some View {
|
|
120
|
+
DigiaHost {
|
|
121
|
+
EmptyView()
|
|
122
|
+
}
|
|
123
|
+
// No frame constraint here — the view fills its parent (absoluteFill
|
|
124
|
+
// from JS). A non-zero frame is required so UIKit calls viewDidAppear
|
|
125
|
+
// on the UIHostingController, which in turn triggers SwiftUI onAppear
|
|
126
|
+
// and establishes the onChange(activePayload) subscription.
|
|
127
|
+
}
|
|
128
|
+
}
|