@digia-engage/core 2.0.0 → 2.0.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.
- package/README.md +38 -5
- package/android/build.gradle +2 -1
- package/android/local.properties +1 -1
- package/android/src/main/java/com/digia/engage/rn/DigiaAnchorViewManager.kt +67 -43
- package/ios/DigiaAnchorViewManager.swift +51 -27
- package/ios/DigiaEngageModule.m +7 -11
- package/ios/DigiaModule.swift +57 -90
- package/lib/commonjs/Digia.js +14 -2
- package/lib/commonjs/Digia.js.map +1 -1
- package/lib/commonjs/DigiaAnchorView.js +47 -41
- package/lib/commonjs/DigiaAnchorView.js.map +1 -1
- package/lib/commonjs/DigiaProvider.js +0 -2
- package/lib/commonjs/DigiaProvider.js.map +1 -1
- package/lib/commonjs/NativeDigiaEngage.js +0 -6
- package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
- package/lib/commonjs/actionHandler.js +11 -0
- package/lib/commonjs/actionHandler.js.map +1 -1
- package/lib/commonjs/index.js +26 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/Digia.js +14 -2
- package/lib/module/Digia.js.map +1 -1
- package/lib/module/DigiaAnchorView.js +47 -42
- package/lib/module/DigiaAnchorView.js.map +1 -1
- package/lib/module/DigiaProvider.js +0 -2
- package/lib/module/DigiaProvider.js.map +1 -1
- package/lib/module/NativeDigiaEngage.js +0 -6
- package/lib/module/NativeDigiaEngage.js.map +1 -1
- package/lib/module/actionHandler.js +11 -0
- package/lib/module/actionHandler.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/Digia.d.ts +1 -0
- package/lib/typescript/Digia.d.ts.map +1 -1
- package/lib/typescript/DigiaAnchorView.d.ts +4 -20
- package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
- package/lib/typescript/DigiaProvider.d.ts.map +1 -1
- package/lib/typescript/NativeDigiaEngage.d.ts +7 -4
- package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
- package/lib/typescript/actionHandler.d.ts +1 -0
- package/lib/typescript/actionHandler.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/templateTypes.d.ts +6 -0
- package/lib/typescript/templateTypes.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +4 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/Digia.ts +17 -1
- package/src/DigiaAnchorView.tsx +36 -39
- package/src/DigiaProvider.tsx +0 -2
- package/src/NativeDigiaEngage.ts +4 -14
- package/src/actionHandler.ts +6 -0
- package/src/index.ts +13 -1
- package/src/templateTypes.ts +7 -7
- package/src/types.ts +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @digia-engage/core
|
|
2
2
|
|
|
3
|
-
React Native
|
|
3
|
+
React Native bridge for the **Digia Engage SDK** — renders in-app experiences
|
|
4
|
+
(tooltips, spotlights, carousels, surveys) inside React Native applications.
|
|
4
5
|
|
|
5
6
|
> **Platform support**
|
|
6
7
|
> | Platform | Status |
|
|
@@ -8,11 +9,42 @@ React Native SDK for **Digia Engage** – renders native Android (Jetpack Compos
|
|
|
8
9
|
> | Android | ✅ Full support |
|
|
9
10
|
> | iOS | ✅ Guide overlays (JS renderer); native bridge (surveys, inline) |
|
|
10
11
|
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
Guide campaigns (tooltip / spotlight) are rendered entirely in **JavaScript** by
|
|
17
|
+
`DigiaProvider.tsx` using `@floating-ui/core` for anchor positioning. Surveys
|
|
18
|
+
and inline carousels are forwarded to the **native Android/iOS SDK** via the
|
|
19
|
+
bridge for Compose/SwiftUI rendering.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
CEP plugin (e.g. CleverTap)
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
Digia.onCampaignTriggered(payload)
|
|
26
|
+
│
|
|
27
|
+
├─ campaign_type === 'guide' → DigiaGuideController → DigiaProvider.tsx (JS)
|
|
28
|
+
│ TooltipOverlay / SpotlightOverlay
|
|
29
|
+
│
|
|
30
|
+
├─ campaign_type === 'inline' → nativeDigiaModule.triggerCampaign()
|
|
31
|
+
│ Android: DigiaSlot composable → VWCarousel
|
|
32
|
+
│
|
|
33
|
+
└─ campaign_type === 'survey' → nativeDigiaModule.triggerCampaign()
|
|
34
|
+
Android: SurveyRenderer composable
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
11
41
|
```sh
|
|
12
42
|
npm install @digia-engage/core
|
|
43
|
+
# or
|
|
44
|
+
yarn add @digia-engage/core
|
|
13
45
|
```
|
|
14
46
|
|
|
15
|
-
React Native CLI auto-linking handles the rest. Rebuild the native app
|
|
47
|
+
React Native CLI auto-linking handles the rest. Rebuild the native app:
|
|
16
48
|
|
|
17
49
|
```sh
|
|
18
50
|
npx react-native run-android
|
|
@@ -50,7 +82,8 @@ await Digia.initialize({
|
|
|
50
82
|
|
|
51
83
|
### 2 — Mount `<DigiaHost />`
|
|
52
84
|
|
|
53
|
-
Place `<DigiaHost />` at the root of your component tree. It renders the
|
|
85
|
+
Place `<DigiaHost />` at the root of your component tree. It renders the
|
|
86
|
+
JS-side guide/tooltip/spotlight overlays via a `Modal`.
|
|
54
87
|
|
|
55
88
|
```tsx
|
|
56
89
|
// app/_layout.tsx (Expo Router) or App.tsx
|
|
@@ -211,8 +244,8 @@ react-native/src/
|
|
|
211
244
|
DigiaSlotView.tsx Native slot view wrapper; auto-sizes to content height
|
|
212
245
|
DigiaHostView.tsx Low-level native overlay host (transparent, pointer-events none)
|
|
213
246
|
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
|
|
247
|
+
actionHandler.ts Action execution — deep link, open URL, next/prev/dismiss,
|
|
248
|
+
fire_event; onAction override; cold-start queue
|
|
216
249
|
defaultInAppBrowser.ts Lazily loads react-native-inappbrowser-reborn
|
|
217
250
|
templateTypes.ts TypeScript types for TooltipConfig, SpotlightConfig,
|
|
218
251
|
CarouselConfig, SurveyTemplateConfig
|
package/android/build.gradle
CHANGED
|
@@ -70,7 +70,8 @@ android {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
dependencies {
|
|
73
|
-
|
|
73
|
+
// Digia Engage Android library
|
|
74
|
+
implementation 'tech.digia:engage:2.0.0'
|
|
74
75
|
|
|
75
76
|
// ── React Native ─────────────────────────────────────────────────────────
|
|
76
77
|
// React Native is provided by the host app; mark as compileOnly so it is
|
package/android/local.properties
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
sdk.dir=/Users/
|
|
1
|
+
sdk.dir=/Users/ram/Library/Android/sdk
|
|
@@ -1,65 +1,89 @@
|
|
|
1
1
|
package com.digia.engage.rn
|
|
2
2
|
|
|
3
|
-
import android.
|
|
4
|
-
import android.graphics.Outline
|
|
3
|
+
import android.content.Context
|
|
5
4
|
import android.view.View
|
|
6
|
-
import android.view.
|
|
5
|
+
import android.view.ViewTreeObserver
|
|
7
6
|
import android.widget.FrameLayout
|
|
8
|
-
import
|
|
9
|
-
import androidx.lifecycle.ViewModelStoreOwner
|
|
10
|
-
import androidx.lifecycle.setViewTreeLifecycleOwner
|
|
11
|
-
import androidx.lifecycle.setViewTreeViewModelStoreOwner
|
|
12
|
-
import androidx.savedstate.SavedStateRegistryOwner
|
|
13
|
-
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
|
14
|
-
import com.digia.engage.DigiaAnchorView
|
|
7
|
+
import com.digia.engage.Digia
|
|
15
8
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
16
9
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
17
10
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
18
11
|
|
|
19
|
-
internal class
|
|
12
|
+
internal class DigiaAnchorContainerView(context: Context) : FrameLayout(context) {
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
var anchorKey: String = ""
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
val activityContext = context.currentActivity ?: context
|
|
25
|
-
val view = DigiaAnchorView(activityContext)
|
|
16
|
+
private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener { reportPosition() }
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
override fun onAttachedToWindow() {
|
|
19
|
+
super.onAttachedToWindow()
|
|
20
|
+
viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
|
|
21
|
+
reportPosition()
|
|
22
|
+
}
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
// Prevent the FrameLayout background from leaking white in spotlight corners.
|
|
37
|
-
view.setBackgroundColor(Color.TRANSPARENT)
|
|
38
|
-
return view
|
|
24
|
+
override fun onDetachedFromWindow() {
|
|
25
|
+
viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
|
|
26
|
+
if (anchorKey.isNotBlank()) Digia.unregisterAnchor(anchorKey)
|
|
27
|
+
super.onDetachedFromWindow()
|
|
39
28
|
}
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
private fun reportPosition() {
|
|
31
|
+
val key = anchorKey.takeIf { it.isNotBlank() } ?: return
|
|
32
|
+
// React Native (Fabric) may not call layout() on this FrameLayout wrapper,
|
|
33
|
+
// so this.width/height can be 0. Derive dimensions from the first child instead.
|
|
34
|
+
val targetView: android.view.View = if (childCount > 0) getChildAt(0) else this
|
|
35
|
+
val w = targetView.width
|
|
36
|
+
val h = targetView.height
|
|
37
|
+
if (w == 0 || h == 0) return // not measured yet — wait for next layout pass
|
|
38
|
+
val loc = IntArray(2)
|
|
39
|
+
targetView.getLocationOnScreen(loc)
|
|
40
|
+
android.util.Log.d(
|
|
41
|
+
"Digia",
|
|
42
|
+
"[DigiaAnchorView] registerAnchor key='$key' x=${loc[0]} y=${loc[1]} w=$w h=$h"
|
|
43
|
+
)
|
|
44
|
+
Digia.registerAnchor(key, loc[0], loc[1], w, h)
|
|
45
|
+
Digia.registerAnchorView(key, targetView)
|
|
44
46
|
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ViewGroupManager (not SimpleViewManager) because Fabric calls getViewGroupManager()
|
|
50
|
+
// on any view that hosts RN children — DigiaAnchorView wraps the anchor target element.
|
|
51
|
+
internal class DigiaAnchorViewManager : ViewGroupManager<DigiaAnchorContainerView>() {
|
|
52
|
+
|
|
53
|
+
override fun getName(): String = VIEW_NAME
|
|
54
|
+
|
|
55
|
+
override fun createViewInstance(context: ThemedReactContext) = DigiaAnchorContainerView(context)
|
|
45
56
|
|
|
46
|
-
@ReactProp
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
view.clipToOutline = true
|
|
57
|
-
} else {
|
|
58
|
-
view.clipToOutline = false
|
|
59
|
-
view.outlineProvider = ViewOutlineProvider.BOUNDS
|
|
57
|
+
// Use updateProperties instead of @ReactProp to be compatible with both
|
|
58
|
+
// Old Architecture (Paper) and New Architecture (Fabric) without codegen.
|
|
59
|
+
override fun updateProperties(
|
|
60
|
+
viewToUpdate: DigiaAnchorContainerView,
|
|
61
|
+
props: com.facebook.react.uimanager.ReactStylesDiffMap,
|
|
62
|
+
) {
|
|
63
|
+
super.updateProperties(viewToUpdate, props)
|
|
64
|
+
if (props.hasKey("anchorKey")) {
|
|
65
|
+
viewToUpdate.anchorKey = props.getString("anchorKey") ?: ""
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
@com.facebook.react.uimanager.annotations.ReactProp(name = "anchorKey")
|
|
70
|
+
fun setAnchorKey(view: DigiaAnchorContainerView, key: String?) {
|
|
71
|
+
view.anchorKey = key.orEmpty()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun addView(parent: DigiaAnchorContainerView, child: View, index: Int) {
|
|
75
|
+
parent.addView(child, index)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override fun getChildCount(parent: DigiaAnchorContainerView): Int = parent.childCount
|
|
79
|
+
|
|
80
|
+
override fun getChildAt(parent: DigiaAnchorContainerView, index: Int): View =
|
|
81
|
+
parent.getChildAt(index)
|
|
82
|
+
|
|
83
|
+
override fun removeViewAt(parent: DigiaAnchorContainerView, index: Int) {
|
|
84
|
+
parent.removeViewAt(index)
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
companion object {
|
|
64
88
|
const val VIEW_NAME = "DigiaAnchorView"
|
|
65
89
|
}
|
|
@@ -1,43 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DigiaAnchorViewManager
|
|
3
|
+
*
|
|
4
|
+
* iOS RCTViewManager that vends DigiaAnchorContainerUIView — a UIView wrapper
|
|
5
|
+
* that automatically tracks its screen-coordinate frame and registers it with
|
|
6
|
+
* AnchorRegistry whenever it lays out or moves to a new window.
|
|
7
|
+
*
|
|
8
|
+
* This mirrors Android's DigiaAnchorViewManager / DigiaAnchorContainerView.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import Foundation
|
|
1
12
|
import React
|
|
2
13
|
import UIKit
|
|
3
14
|
import DigiaEngage
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
final class DigiaAnchorViewManager: RCTViewManager {
|
|
7
|
-
|
|
8
|
-
override static func requiresMainQueueSetup() -> Bool { true }
|
|
9
|
-
|
|
10
|
-
override func view() -> UIView! {
|
|
11
|
-
return DigiaAnchorUIView()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
@objc func setAnchorKey(_ anchorKey: String, forView view: DigiaAnchorUIView) {
|
|
15
|
-
view.anchorKey = anchorKey
|
|
16
|
-
}
|
|
17
|
-
}
|
|
16
|
+
// MARK: - DigiaAnchorContainerUIView
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
@objc(DigiaAnchorContainerUIView)
|
|
19
|
+
final class DigiaAnchorContainerUIView: UIView {
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var anchorKey: String = "" {
|
|
21
|
+
@objc var anchorKey: String = "" {
|
|
24
22
|
didSet {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
AnchorRegistry.shared.unregister(key: oldValue)
|
|
28
|
-
}
|
|
29
|
-
if !anchorKey.isEmpty, window != nil {
|
|
30
|
-
AnchorRegistry.shared.register(key: anchorKey, view: self)
|
|
23
|
+
if !oldValue.isEmpty && oldValue != anchorKey {
|
|
24
|
+
Task { @MainActor in AnchorRegistry.shared.unregister(key: oldValue) }
|
|
31
25
|
}
|
|
26
|
+
reportPosition()
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
29
|
|
|
30
|
+
override func layoutSubviews() {
|
|
31
|
+
super.layoutSubviews()
|
|
32
|
+
reportPosition()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
35
|
override func didMoveToWindow() {
|
|
36
36
|
super.didMoveToWindow()
|
|
37
|
-
if window
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
AnchorRegistry.shared.unregister(key:
|
|
37
|
+
if window == nil {
|
|
38
|
+
guard !anchorKey.isEmpty else { return }
|
|
39
|
+
let key = anchorKey
|
|
40
|
+
Task { @MainActor in AnchorRegistry.shared.unregister(key: key) }
|
|
41
|
+
} else {
|
|
42
|
+
reportPosition()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private func reportPosition() {
|
|
47
|
+
guard !anchorKey.isEmpty, window != nil else { return }
|
|
48
|
+
// convert(bounds, to: nil) gives frame in window coordinates (UIKit points)
|
|
49
|
+
let rectInWindow = convert(bounds, to: nil)
|
|
50
|
+
let key = anchorKey
|
|
51
|
+
Task { @MainActor in
|
|
52
|
+
AnchorRegistry.shared.register(key: key, rect: rectInWindow)
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
}
|
|
56
|
+
|
|
57
|
+
// MARK: - DigiaAnchorViewManager
|
|
58
|
+
|
|
59
|
+
@objc(DigiaAnchorView)
|
|
60
|
+
final class DigiaAnchorViewManager: RCTViewManager {
|
|
61
|
+
|
|
62
|
+
override static func requiresMainQueueSetup() -> Bool { true }
|
|
63
|
+
|
|
64
|
+
override func view() -> UIView! {
|
|
65
|
+
DigiaAnchorContainerUIView()
|
|
66
|
+
}
|
|
67
|
+
}
|
package/ios/DigiaEngageModule.m
CHANGED
|
@@ -40,13 +40,13 @@ RCT_EXTERN_METHOD(triggerCampaign:(NSString *)id
|
|
|
40
40
|
|
|
41
41
|
RCT_EXTERN_METHOD(invalidateCampaign:(NSString *)campaignId)
|
|
42
42
|
|
|
43
|
-
RCT_EXTERN_METHOD(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
RCT_EXTERN_METHOD(registerAnchor:(NSString *)key
|
|
44
|
+
x:(double)x
|
|
45
|
+
y:(double)y
|
|
46
|
+
width:(double)width
|
|
47
|
+
height:(double)height)
|
|
48
|
+
|
|
49
|
+
RCT_EXTERN_METHOD(unregisterAnchor:(NSString *)key)
|
|
50
50
|
|
|
51
51
|
@end
|
|
52
52
|
|
|
@@ -64,7 +64,3 @@ RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
|
|
|
64
64
|
@interface RCT_EXTERN_MODULE(DigiaAnchorView, RCTViewManager)
|
|
65
65
|
RCT_EXPORT_VIEW_PROPERTY(anchorKey, NSString)
|
|
66
66
|
@end
|
|
67
|
-
|
|
68
|
-
@interface RCT_EXTERN_MODULE(DigiaAnchorView, RCTViewManager)
|
|
69
|
-
RCT_EXPORT_VIEW_PROPERTY(anchorKey, NSString)
|
|
70
|
-
@end
|
package/ios/DigiaModule.swift
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import DigiaEngage
|
|
1
2
|
/**
|
|
2
3
|
* DigiaModule
|
|
3
4
|
*
|
|
@@ -30,7 +31,6 @@ import Foundation
|
|
|
30
31
|
import React
|
|
31
32
|
import SwiftUI
|
|
32
33
|
import UIKit
|
|
33
|
-
import DigiaEngage
|
|
34
34
|
|
|
35
35
|
@objc(DigiaEngageModule)
|
|
36
36
|
final class DigiaModule: RCTEventEmitter {
|
|
@@ -69,19 +69,22 @@ final class DigiaModule: RCTEventEmitter {
|
|
|
69
69
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
70
70
|
reject: @escaping RCTPromiseRejectBlock
|
|
71
71
|
) {
|
|
72
|
-
let envValue: DigiaEnvironment =
|
|
72
|
+
let envValue: DigiaEnvironment =
|
|
73
|
+
environment.lowercased() == "sandbox" ? .sandbox : .production
|
|
73
74
|
let logLevelValue: DigiaLogLevel
|
|
74
75
|
switch logLevel.lowercased() {
|
|
75
76
|
case "verbose": logLevelValue = .verbose
|
|
76
|
-
case "none":
|
|
77
|
-
default:
|
|
77
|
+
case "none": logLevelValue = .none
|
|
78
|
+
default: logLevelValue = .error
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
let config = DigiaConfig(
|
|
81
82
|
apiKey: projectId,
|
|
82
83
|
logLevel: logLevelValue,
|
|
83
84
|
environment: envValue,
|
|
84
|
-
developerConfig: baseUrl.flatMap {
|
|
85
|
+
developerConfig: baseUrl.flatMap {
|
|
86
|
+
$0.isEmpty ? nil : DigiaDeveloperConfig(baseURL: $0)
|
|
87
|
+
},
|
|
85
88
|
fontFamily: fontFamily.flatMap { $0.isEmpty ? nil : $0 }
|
|
86
89
|
)
|
|
87
90
|
|
|
@@ -156,37 +159,28 @@ final class DigiaModule: RCTEventEmitter {
|
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
// ────────────────────────────────────────────────────────────────────────
|
|
159
|
-
// MARK: -
|
|
162
|
+
// MARK: - registerAnchor / unregisterAnchor
|
|
160
163
|
|
|
161
|
-
///
|
|
162
|
-
///
|
|
164
|
+
/// Registers a UI element as an anchor point for Guide experiences.
|
|
165
|
+
/// JS sends physical pixels; convert to UIKit points using screen scale.
|
|
163
166
|
@objc
|
|
164
|
-
func
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
var argsDict = (mutable["args"] as? [String: Any]) ?? [:]
|
|
177
|
-
argsDict["_anchorX"] = anchorX
|
|
178
|
-
argsDict["_anchorY"] = anchorY
|
|
179
|
-
argsDict["_anchorWidth"] = anchorWidth
|
|
180
|
-
argsDict["_anchorHeight"] = anchorHeight
|
|
181
|
-
mutable["args"] = argsDict
|
|
182
|
-
|
|
183
|
-
let content = buildInAppPayloadContent(from: mutable)
|
|
184
|
-
let cepContext = (cepContextMap as? [String: String]) ?? [:]
|
|
185
|
-
let payload = InAppPayload(id: id, content: content, cepContext: cepContext)
|
|
167
|
+
func registerAnchor(_ key: String, x: Double, y: Double, width: Double, height: Double) {
|
|
168
|
+
let scale = UIScreen.main.scale
|
|
169
|
+
let rect = CGRect(
|
|
170
|
+
x: CGFloat(x) / scale,
|
|
171
|
+
y: CGFloat(y) / scale,
|
|
172
|
+
width: CGFloat(width) / scale,
|
|
173
|
+
height: CGFloat(height) / scale
|
|
174
|
+
)
|
|
175
|
+
Task { @MainActor in
|
|
176
|
+
AnchorRegistry.shared.register(key: key, rect: rect)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
186
179
|
|
|
180
|
+
@objc
|
|
181
|
+
func unregisterAnchor(_ key: String) {
|
|
187
182
|
Task { @MainActor in
|
|
188
|
-
|
|
189
|
-
delegate.onCampaignTriggered(payload)
|
|
183
|
+
AnchorRegistry.shared.unregister(key: key)
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
186
|
|
|
@@ -199,44 +193,34 @@ final class DigiaModule: RCTEventEmitter {
|
|
|
199
193
|
@MainActor
|
|
200
194
|
private func mountDigiaHost() {
|
|
201
195
|
// Locate the key window's root view controller.
|
|
202
|
-
guard
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
guard
|
|
197
|
+
let rootVC = UIApplication.shared
|
|
198
|
+
.connectedScenes
|
|
199
|
+
.compactMap({ ($0 as? UIWindowScene)?.keyWindow })
|
|
200
|
+
.first?
|
|
201
|
+
.rootViewController
|
|
202
|
+
else { return }
|
|
207
203
|
|
|
208
204
|
// Guard against double-mounting (e.g. fast-refresh).
|
|
209
205
|
let mountTag = 0xD19140
|
|
210
206
|
if rootVC.view.viewWithTag(mountTag) != nil { return }
|
|
211
207
|
|
|
212
|
-
// Container with passthrough hitTest — lets tooltip card taps reach SwiftUI
|
|
213
|
-
// while all other touches fall through to React Native content.
|
|
214
|
-
let container = DigiaPassthroughHostView()
|
|
215
|
-
container.tag = mountTag
|
|
216
|
-
container.translatesAutoresizingMaskIntoConstraints = false
|
|
217
|
-
container.backgroundColor = .clear
|
|
218
|
-
|
|
219
208
|
let hc = UIHostingController(rootView: DigiaHostWrapperView())
|
|
209
|
+
hc.view.tag = mountTag
|
|
220
210
|
hc.view.translatesAutoresizingMaskIntoConstraints = false
|
|
221
211
|
hc.view.backgroundColor = .clear
|
|
222
|
-
|
|
223
|
-
|
|
212
|
+
// Pass touches through to React Native content below.
|
|
213
|
+
hc.view.isUserInteractionEnabled = false
|
|
224
214
|
|
|
225
215
|
rootVC.addChild(hc)
|
|
226
|
-
|
|
216
|
+
rootVC.view.addSubview(hc.view)
|
|
227
217
|
hc.didMove(toParent: rootVC)
|
|
228
218
|
|
|
229
|
-
rootVC.view.addSubview(container)
|
|
230
|
-
|
|
231
219
|
NSLayoutConstraint.activate([
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
hc.view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
237
|
-
hc.view.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
|
238
|
-
hc.view.topAnchor.constraint(equalTo: container.topAnchor),
|
|
239
|
-
hc.view.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
220
|
+
hc.view.leadingAnchor.constraint(equalTo: rootVC.view.leadingAnchor),
|
|
221
|
+
hc.view.trailingAnchor.constraint(equalTo: rootVC.view.trailingAnchor),
|
|
222
|
+
hc.view.topAnchor.constraint(equalTo: rootVC.view.topAnchor),
|
|
223
|
+
hc.view.bottomAnchor.constraint(equalTo: rootVC.view.bottomAnchor),
|
|
240
224
|
])
|
|
241
225
|
}
|
|
242
226
|
|
|
@@ -244,23 +228,23 @@ final class DigiaModule: RCTEventEmitter {
|
|
|
244
228
|
// MARK: - Private helpers
|
|
245
229
|
|
|
246
230
|
private func buildInAppPayloadContent(from map: NSDictionary) -> InAppPayloadContent {
|
|
247
|
-
let pk
|
|
248
|
-
let title
|
|
249
|
-
let text
|
|
250
|
-
let viewId
|
|
251
|
-
let command = map["command"]
|
|
252
|
-
let screenId = map["screenId"]
|
|
231
|
+
let pk = map["placementKey"] as? String
|
|
232
|
+
let title = map["title"] as? String
|
|
233
|
+
let text = map["text"] as? String
|
|
234
|
+
let viewId = map["viewId"] as? String
|
|
235
|
+
let command = map["command"] as? String
|
|
236
|
+
let screenId = map["screenId"] as? String
|
|
253
237
|
var type = (map["type"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
254
238
|
if type.isEmpty {
|
|
255
|
-
type =
|
|
239
|
+
type =
|
|
240
|
+
(pk?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "").isEmpty
|
|
241
|
+
? "dialog" : "inline"
|
|
256
242
|
}
|
|
257
243
|
let args: [String: JSONValue] = {
|
|
258
244
|
guard let raw = map["args"] as? [String: Any] else { return [:] }
|
|
259
245
|
return raw.compactMapValues { JSONValue(rawValue: $0) }
|
|
260
246
|
}()
|
|
261
247
|
|
|
262
|
-
let anchorKey = map["anchorKey"] as? String
|
|
263
|
-
|
|
264
248
|
return InAppPayloadContent(
|
|
265
249
|
type: type,
|
|
266
250
|
placementKey: pk,
|
|
@@ -269,36 +253,19 @@ final class DigiaModule: RCTEventEmitter {
|
|
|
269
253
|
viewId: viewId,
|
|
270
254
|
command: command,
|
|
271
255
|
args: args,
|
|
272
|
-
screenId: screenId
|
|
273
|
-
anchorKey: anchorKey
|
|
256
|
+
screenId: screenId
|
|
274
257
|
)
|
|
275
258
|
}
|
|
276
259
|
}
|
|
277
260
|
|
|
278
|
-
// MARK: - DigiaPassthroughHostView
|
|
279
|
-
|
|
280
|
-
/// Container that delegates hitTest to the SwiftUI hosting view.
|
|
281
|
-
/// When SwiftUI renders nothing interactive (no overlay), hitTest returns nil
|
|
282
|
-
/// so touches fall through to React Native. When an overlay is visible,
|
|
283
|
-
/// taps on it are consumed by SwiftUI.
|
|
284
|
-
private final class DigiaPassthroughHostView: UIView {
|
|
285
|
-
weak var hostingView: UIView?
|
|
286
|
-
|
|
287
|
-
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
288
|
-
guard let hv = hostingView else { return nil }
|
|
289
|
-
let converted = convert(point, to: hv)
|
|
290
|
-
return hv.hitTest(converted, with: event)
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
261
|
// MARK: - JSONValue convenience init from Any
|
|
295
|
-
|
|
296
|
-
init?(rawValue: Any) {
|
|
262
|
+
extension JSONValue {
|
|
263
|
+
fileprivate init?(rawValue: Any) {
|
|
297
264
|
switch rawValue {
|
|
298
|
-
case let s as String:
|
|
299
|
-
case let b as Bool:
|
|
300
|
-
case let i as Int:
|
|
301
|
-
case let d as Double:
|
|
265
|
+
case let s as String: self = .string(s)
|
|
266
|
+
case let b as Bool: self = .bool(b)
|
|
267
|
+
case let i as Int: self = .int(i)
|
|
268
|
+
case let d as Double: self = .double(d)
|
|
302
269
|
case let arr as [Any]:
|
|
303
270
|
self = .array(arr.compactMap { JSONValue(rawValue: $0) })
|
|
304
271
|
case let dict as [String: Any]:
|
package/lib/commonjs/Digia.js
CHANGED
|
@@ -67,7 +67,8 @@ class DigiaClass {
|
|
|
67
67
|
_actionHandler.digiaActionHandler.configure({
|
|
68
68
|
onAction: config.onAction,
|
|
69
69
|
routeViaSystemLinking: config.linking?.routeViaSystemLinking ?? true,
|
|
70
|
-
inAppBrowser: config.linking?.inAppBrowser
|
|
70
|
+
inAppBrowser: config.linking?.inAppBrowser,
|
|
71
|
+
onFireEvent: (eventName, properties, context) => this._fireCustomEvent(eventName, properties, context)
|
|
71
72
|
});
|
|
72
73
|
try {
|
|
73
74
|
await _NativeDigiaEngage.nativeDigiaModule.initialize(config.projectId, environment, logLevel, config.baseUrl, config.fontFamily);
|
|
@@ -245,6 +246,17 @@ class DigiaClass {
|
|
|
245
246
|
if (this._engageSubscription) return;
|
|
246
247
|
this._engageSubscription = _reactNative.DeviceEventEmitter.addListener('digiaEngageEvent', data => this._forwardExperienceEvent(data));
|
|
247
248
|
}
|
|
249
|
+
_fireCustomEvent(eventName, properties, context) {
|
|
250
|
+
const payload = context ? this._activePayloads.get(context.campaign_id) : null;
|
|
251
|
+
if (payload) {
|
|
252
|
+
const event = {
|
|
253
|
+
type: 'clicked',
|
|
254
|
+
elementId: eventName
|
|
255
|
+
};
|
|
256
|
+
this._plugins.forEach(plugin => plugin.notifyEvent(event, payload));
|
|
257
|
+
}
|
|
258
|
+
// TODO: record custom event to Digia analytics endpoint when available
|
|
259
|
+
}
|
|
248
260
|
_forwardExperienceEvent(data) {
|
|
249
261
|
const payload = this._activePayloads.get(data.campaignId);
|
|
250
262
|
if (!payload) return;
|
|
@@ -535,7 +547,7 @@ class DigiaClass {
|
|
|
535
547
|
_log(message) {
|
|
536
548
|
if (this._logLevel !== 'verbose') return;
|
|
537
549
|
// eslint-disable-next-line no-console
|
|
538
|
-
|
|
550
|
+
console.log(`[Digia] ${message}`);
|
|
539
551
|
}
|
|
540
552
|
}
|
|
541
553
|
const Digia = exports.Digia = new DigiaClass();
|