zixflow-reactnative 1.1.2 → 1.1.4

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 (249) hide show
  1. package/README.md +2224 -322
  2. package/android/build.gradle +18 -1
  3. package/android/consumer-rules.pro +3 -2
  4. package/android/gradle.properties +1 -1
  5. package/android/src/main/java/com/zixflow/reactnative/sdk/NativeZixflowModule.kt +69 -24
  6. package/android/src/main/java/com/zixflow/reactnative/sdk/ZixflowReactNativePackage.kt +25 -1
  7. package/android/src/main/java/com/zixflow/reactnative/sdk/constant/Keys.kt +19 -3
  8. package/android/src/main/java/com/zixflow/reactnative/sdk/geofence/NativeGeofenceModule.kt +65 -0
  9. package/android/src/main/java/com/zixflow/reactnative/sdk/liveactivities/NativeLiveActivitiesModule.kt +310 -0
  10. package/android/src/main/java/com/zixflow/reactnative/sdk/logging/NativeZixflowLoggingModule.kt +2 -2
  11. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +149 -10
  12. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginapp/ReactInboxEventListener.kt +99 -0
  13. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxBellViewManager.kt +38 -0
  14. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxViewManager.kt +30 -0
  15. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactComposeHostView.kt +96 -0
  16. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxBellView.kt +48 -0
  17. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxView.kt +17 -0
  18. package/android/src/main/java/com/zixflow/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +11 -1
  19. package/android/src/main/res/values/zixflow_config.xml +1 -1
  20. package/android/zixflow-core.gradle +17 -7
  21. package/docs/cocoapods-deployment-target-normalization.md +139 -0
  22. package/ios/cocoapods_deployment_target.rb +432 -0
  23. package/ios/wrappers/NativeZixflow.mm +37 -0
  24. package/ios/wrappers/NativeZixflow.swift +135 -1
  25. package/ios/wrappers/ZixflowReactNativeDeepLinkRequestStore.swift +123 -0
  26. package/ios/wrappers/ZixflowReactNativeDeepLinkRouter.swift +312 -0
  27. package/ios/wrappers/geofence/NativeGeofence.mm +46 -0
  28. package/ios/wrappers/geofence/NativeGeofence.swift +27 -0
  29. package/ios/wrappers/inapp/NativeMessagingInApp.mm +22 -0
  30. package/ios/wrappers/inapp/NativeMessagingInApp.swift +90 -0
  31. package/ios/wrappers/inbox/InboxHostContainment.swift +40 -0
  32. package/ios/wrappers/inbox/RCTNotificationInboxBellNative.h +12 -0
  33. package/ios/wrappers/inbox/RCTNotificationInboxBellNative.mm +98 -0
  34. package/ios/wrappers/inbox/RCTNotificationInboxViewNative.h +12 -0
  35. package/ios/wrappers/inbox/RCTNotificationInboxViewNative.mm +78 -0
  36. package/ios/wrappers/inbox/ReactInboxEventListener.swift +76 -0
  37. package/ios/wrappers/inbox/ReactNotificationInboxBellView.h +22 -0
  38. package/ios/wrappers/inbox/ReactNotificationInboxBellView.swift +109 -0
  39. package/ios/wrappers/inbox/ReactNotificationInboxView.h +22 -0
  40. package/ios/wrappers/inbox/ReactNotificationInboxView.swift +69 -0
  41. package/ios/wrappers/liveactivities/NativeLiveActivities.mm +83 -0
  42. package/ios/wrappers/liveactivities/NativeLiveActivities.swift +390 -0
  43. package/ios/wrappers/location/NativeLocation.swift +7 -5
  44. package/ios/wrappers/utils/ZixflowConfigUtils.swift +5 -9
  45. package/ios/wrappers/utils/ZixflowConstants.swift +4 -0
  46. package/ios/wrappers/zixflow_reactnative.h +6 -0
  47. package/lib/commonjs/components/NotificationInboxBellView.js +49 -0
  48. package/lib/commonjs/components/NotificationInboxBellView.js.map +1 -0
  49. package/lib/commonjs/components/NotificationInboxView.js +36 -0
  50. package/lib/commonjs/components/NotificationInboxView.js.map +1 -0
  51. package/lib/commonjs/components/index.js +14 -0
  52. package/lib/commonjs/components/index.js.map +1 -1
  53. package/lib/commonjs/index.js +81 -4
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/notification-inbox.js +1 -0
  56. package/lib/commonjs/notification-inbox.js.map +1 -1
  57. package/lib/commonjs/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  58. package/lib/commonjs/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  59. package/lib/commonjs/specs/modules/NativeZixflow.js.map +1 -1
  60. package/lib/commonjs/specs/modules/NativeZixflowGeofence.js +15 -0
  61. package/lib/commonjs/specs/modules/NativeZixflowGeofence.js.map +1 -0
  62. package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js +23 -0
  63. package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
  64. package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js +3 -1
  65. package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
  66. package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js +3 -1
  67. package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
  68. package/lib/commonjs/types/data-pipelines.js +15 -15
  69. package/lib/commonjs/types/data-pipelines.js.map +1 -1
  70. package/lib/commonjs/types/in-app.js +24 -1
  71. package/lib/commonjs/types/in-app.js.map +1 -1
  72. package/lib/commonjs/types/inbox.js +79 -0
  73. package/lib/commonjs/types/inbox.js.map +1 -0
  74. package/lib/commonjs/types/index.js +22 -0
  75. package/lib/commonjs/types/index.js.map +1 -1
  76. package/lib/commonjs/types/live-activities.js +85 -0
  77. package/lib/commonjs/types/live-activities.js.map +1 -0
  78. package/lib/commonjs/utils/param-validation.js +77 -2
  79. package/lib/commonjs/utils/param-validation.js.map +1 -1
  80. package/lib/commonjs/zixflow-cdp.js +101 -11
  81. package/lib/commonjs/zixflow-cdp.js.map +1 -1
  82. package/lib/commonjs/zixflow-geofence.js +55 -0
  83. package/lib/commonjs/zixflow-geofence.js.map +1 -0
  84. package/lib/commonjs/zixflow-inapp.js +120 -0
  85. package/lib/commonjs/zixflow-inapp.js.map +1 -1
  86. package/lib/commonjs/zixflow-liveactivities.js +85 -0
  87. package/lib/commonjs/zixflow-liveactivities.js.map +1 -0
  88. package/lib/commonjs/zixflow.js.map +1 -1
  89. package/lib/module/components/NotificationInboxBellView.js +44 -0
  90. package/lib/module/components/NotificationInboxBellView.js.map +1 -0
  91. package/lib/module/components/NotificationInboxView.js +31 -0
  92. package/lib/module/components/NotificationInboxView.js.map +1 -0
  93. package/lib/module/components/index.js +5 -0
  94. package/lib/module/components/index.js.map +1 -1
  95. package/lib/module/index.js +8 -3
  96. package/lib/module/index.js.map +1 -1
  97. package/lib/module/notification-inbox.js +1 -1
  98. package/lib/module/notification-inbox.js.map +1 -1
  99. package/lib/module/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  100. package/lib/module/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  101. package/lib/module/specs/modules/NativeZixflow.js.map +1 -1
  102. package/lib/module/specs/modules/NativeZixflowGeofence.js +13 -0
  103. package/lib/module/specs/modules/NativeZixflowGeofence.js.map +1 -0
  104. package/lib/module/specs/modules/NativeZixflowLiveActivities.js +21 -0
  105. package/lib/module/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
  106. package/lib/module/specs/modules/NativeZixflowMessagingInApp.js +3 -1
  107. package/lib/module/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
  108. package/lib/module/specs/modules/NativeZixflowMessagingPush.js +3 -1
  109. package/lib/module/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
  110. package/lib/module/types/data-pipelines.js +15 -15
  111. package/lib/module/types/data-pipelines.js.map +1 -1
  112. package/lib/module/types/in-app.js +24 -0
  113. package/lib/module/types/in-app.js.map +1 -1
  114. package/lib/module/types/inbox.js +75 -0
  115. package/lib/module/types/inbox.js.map +1 -0
  116. package/lib/module/types/index.js +2 -0
  117. package/lib/module/types/index.js.map +1 -1
  118. package/lib/module/types/live-activities.js +88 -0
  119. package/lib/module/types/live-activities.js.map +1 -0
  120. package/lib/module/utils/param-validation.js +77 -2
  121. package/lib/module/utils/param-validation.js.map +1 -1
  122. package/lib/module/zixflow-cdp.js +101 -11
  123. package/lib/module/zixflow-cdp.js.map +1 -1
  124. package/lib/module/zixflow-geofence.js +50 -0
  125. package/lib/module/zixflow-geofence.js.map +1 -0
  126. package/lib/module/zixflow-inapp.js +99 -1
  127. package/lib/module/zixflow-inapp.js.map +1 -1
  128. package/lib/module/zixflow-liveactivities.js +80 -0
  129. package/lib/module/zixflow-liveactivities.js.map +1 -0
  130. package/lib/module/zixflow.js +0 -6
  131. package/lib/module/zixflow.js.map +1 -1
  132. package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts +39 -0
  133. package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/components/NotificationInboxView.d.ts +25 -0
  135. package/lib/typescript/commonjs/components/NotificationInboxView.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/components/index.d.ts +4 -0
  137. package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/index.d.ts +8 -1
  139. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/notification-inbox.d.ts +5 -0
  141. package/lib/typescript/commonjs/notification-inbox.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
  143. package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
  145. package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts +6 -1
  147. package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts +13 -0
  149. package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
  150. package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
  151. package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
  152. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
  153. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
  155. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/types/data-pipelines.d.ts +54 -18
  157. package/lib/typescript/commonjs/types/data-pipelines.d.ts.map +1 -1
  158. package/lib/typescript/commonjs/types/in-app.d.ts +22 -0
  159. package/lib/typescript/commonjs/types/in-app.d.ts.map +1 -1
  160. package/lib/typescript/commonjs/types/inbox.d.ts +97 -0
  161. package/lib/typescript/commonjs/types/inbox.d.ts.map +1 -0
  162. package/lib/typescript/commonjs/types/index.d.ts +2 -0
  163. package/lib/typescript/commonjs/types/index.d.ts.map +1 -1
  164. package/lib/typescript/commonjs/types/live-activities.d.ts +161 -0
  165. package/lib/typescript/commonjs/types/live-activities.d.ts.map +1 -0
  166. package/lib/typescript/commonjs/utils/param-validation.d.ts +7 -0
  167. package/lib/typescript/commonjs/utils/param-validation.d.ts.map +1 -1
  168. package/lib/typescript/commonjs/zixflow-cdp.d.ts +38 -0
  169. package/lib/typescript/commonjs/zixflow-cdp.d.ts.map +1 -1
  170. package/lib/typescript/commonjs/zixflow-geofence.d.ts +36 -0
  171. package/lib/typescript/commonjs/zixflow-geofence.d.ts.map +1 -0
  172. package/lib/typescript/commonjs/zixflow-inapp.d.ts +36 -2
  173. package/lib/typescript/commonjs/zixflow-inapp.d.ts.map +1 -1
  174. package/lib/typescript/commonjs/zixflow-liveactivities.d.ts +66 -0
  175. package/lib/typescript/commonjs/zixflow-liveactivities.d.ts.map +1 -0
  176. package/lib/typescript/commonjs/zixflow.d.ts +0 -5
  177. package/lib/typescript/commonjs/zixflow.d.ts.map +1 -1
  178. package/lib/typescript/module/components/NotificationInboxBellView.d.ts +39 -0
  179. package/lib/typescript/module/components/NotificationInboxBellView.d.ts.map +1 -0
  180. package/lib/typescript/module/components/NotificationInboxView.d.ts +25 -0
  181. package/lib/typescript/module/components/NotificationInboxView.d.ts.map +1 -0
  182. package/lib/typescript/module/components/index.d.ts +4 -0
  183. package/lib/typescript/module/components/index.d.ts.map +1 -1
  184. package/lib/typescript/module/index.d.ts +8 -1
  185. package/lib/typescript/module/index.d.ts.map +1 -1
  186. package/lib/typescript/module/notification-inbox.d.ts +5 -0
  187. package/lib/typescript/module/notification-inbox.d.ts.map +1 -1
  188. package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
  189. package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
  190. package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
  191. package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
  192. package/lib/typescript/module/specs/modules/NativeZixflow.d.ts +6 -1
  193. package/lib/typescript/module/specs/modules/NativeZixflow.d.ts.map +1 -1
  194. package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts +13 -0
  195. package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
  196. package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
  197. package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
  198. package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
  199. package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
  200. package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
  201. package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
  202. package/lib/typescript/module/types/data-pipelines.d.ts +54 -18
  203. package/lib/typescript/module/types/data-pipelines.d.ts.map +1 -1
  204. package/lib/typescript/module/types/in-app.d.ts +22 -0
  205. package/lib/typescript/module/types/in-app.d.ts.map +1 -1
  206. package/lib/typescript/module/types/inbox.d.ts +97 -0
  207. package/lib/typescript/module/types/inbox.d.ts.map +1 -0
  208. package/lib/typescript/module/types/index.d.ts +2 -0
  209. package/lib/typescript/module/types/index.d.ts.map +1 -1
  210. package/lib/typescript/module/types/live-activities.d.ts +161 -0
  211. package/lib/typescript/module/types/live-activities.d.ts.map +1 -0
  212. package/lib/typescript/module/utils/param-validation.d.ts +7 -0
  213. package/lib/typescript/module/utils/param-validation.d.ts.map +1 -1
  214. package/lib/typescript/module/zixflow-cdp.d.ts +38 -0
  215. package/lib/typescript/module/zixflow-cdp.d.ts.map +1 -1
  216. package/lib/typescript/module/zixflow-geofence.d.ts +36 -0
  217. package/lib/typescript/module/zixflow-geofence.d.ts.map +1 -0
  218. package/lib/typescript/module/zixflow-inapp.d.ts +36 -2
  219. package/lib/typescript/module/zixflow-inapp.d.ts.map +1 -1
  220. package/lib/typescript/module/zixflow-liveactivities.d.ts +66 -0
  221. package/lib/typescript/module/zixflow-liveactivities.d.ts.map +1 -0
  222. package/lib/typescript/module/zixflow.d.ts +0 -5
  223. package/lib/typescript/module/zixflow.d.ts.map +1 -1
  224. package/package.json +14 -5
  225. package/src/components/NotificationInboxBellView.tsx +55 -0
  226. package/src/components/NotificationInboxView.tsx +30 -0
  227. package/src/components/index.ts +6 -0
  228. package/src/index.ts +8 -3
  229. package/src/notification-inbox.ts +1 -1
  230. package/src/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  231. package/src/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  232. package/src/specs/modules/NativeZixflow.ts +9 -1
  233. package/src/specs/modules/NativeZixflowGeofence.ts +14 -0
  234. package/src/specs/modules/NativeZixflowLiveActivities.ts +39 -0
  235. package/src/specs/modules/NativeZixflowMessagingInApp.ts +12 -1
  236. package/src/specs/modules/NativeZixflowMessagingPush.ts +3 -1
  237. package/src/types/data-pipelines.ts +55 -19
  238. package/src/types/in-app.ts +23 -0
  239. package/src/types/inbox.ts +110 -0
  240. package/src/types/index.ts +2 -0
  241. package/src/types/live-activities.ts +170 -0
  242. package/src/utils/param-validation.ts +104 -1
  243. package/src/zixflow-cdp.ts +119 -13
  244. package/src/zixflow-geofence.ts +56 -0
  245. package/src/zixflow-inapp.ts +125 -2
  246. package/src/zixflow-liveactivities.ts +86 -0
  247. package/src/zixflow.ts +0 -6
  248. package/zixflow-reactnative-richpush.podspec +6 -3
  249. package/zixflow-reactnative.podspec +28 -2
@@ -0,0 +1,310 @@
1
+ package com.zixflow.reactnative.sdk.liveactivities
2
+
3
+ import android.graphics.Color
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.module.annotations.ReactModule
8
+ import com.zixflow.messagingpush.MessagingPushModuleConfig
9
+ import com.zixflow.messagingpush.ModuleMessagingPushFCM
10
+ import com.zixflow.messagingpush.data.communication.ZixflowLiveNotificationsCallback
11
+ import com.zixflow.messagingpush.di.pushModuleConfig
12
+ import com.zixflow.messagingpush.livenotification.LiveNotificationAsset
13
+ import com.zixflow.messagingpush.livenotification.LiveNotificationBranding
14
+ import com.zixflow.messagingpush.livenotification.LiveNotificationData
15
+ import com.zixflow.messagingpush.livenotification.LiveNotificationType
16
+ import com.zixflow.reactnative.sdk.NativeZixflowLiveActivitiesSpec
17
+ import com.zixflow.reactnative.sdk.extension.getTypedValue
18
+ import com.zixflow.sdk.core.di.SDKComponent
19
+ import com.zixflow.sdk.core.util.Logger
20
+
21
+ /**
22
+ * React Native module implementation for Zixflow Live Activities / Live Notifications
23
+ * using TurboModules with new architecture.
24
+ *
25
+ * Live Notifications live on the FCM push module ([ModuleMessagingPushFCM]); this bridge
26
+ * maps the wrapper's template payloads to [LiveNotificationData] and forwards them.
27
+ */
28
+ @ReactModule(name = NativeLiveActivitiesModule.NAME)
29
+ class NativeLiveActivitiesModule(
30
+ private val reactContext: ReactApplicationContext,
31
+ ) : NativeZixflowLiveActivitiesSpec(reactContext) {
32
+ private val logger: Logger
33
+ get() = SDKComponent.logger
34
+
35
+ // Live Notifications are hosted by the FCM push module. Reach it via the module registry
36
+ // (the wrapper can't reference the SDK-internal MODULE_NAME constant, so use the literal).
37
+ //
38
+ // Deliberately not runCatching: `as?` yields null instead of throwing, so a failure branch keyed
39
+ // on a thrown error would never run and the message below would never be logged.
40
+ private fun getPushModule(): ModuleMessagingPushFCM? {
41
+ val module = SDKComponent.modules[PUSH_FCM_MODULE_NAME] as? ModuleMessagingPushFCM
42
+ if (module == null) {
43
+ logger.error("Live Notifications: push module is not initialized. Ensure the SDK is initialized with live activity templates enabled.")
44
+ }
45
+ return module
46
+ }
47
+
48
+ override fun handleWidgetUrl(url: String?, promise: Promise?) {
49
+ // Zixflow Live Activity widget URLs are an iOS-only contract. Preserve every Android
50
+ // URL so a shared Expo Router redirectSystemPath implementation cannot suppress routing.
51
+ promise?.resolve(url)
52
+ }
53
+
54
+ override fun start(payload: ReadableMap?, promise: Promise?) {
55
+ val module = getPushModule() ?: return promise.rejectNotAvailable()
56
+ try {
57
+ val map = requireNotNull(payload) { "payload is required" }
58
+ // Custom types have no built-in template, so they take the SDK's untyped map path and are
59
+ // rendered by the app's own callback. Built-ins keep the typed path.
60
+ val isCustom = map.getString("type") == CUSTOM_TYPE_DISCRIMINATOR
61
+ val activityType = if (isCustom) {
62
+ requireCustomActivityType()
63
+ } else {
64
+ requireNotNull(map.getString("type")) { "payload.type is required" }
65
+ }
66
+ if (activityType !in enabledActivityTypes()) {
67
+ return promise.rejectNotRegistered(activityType)
68
+ }
69
+ val id = if (isCustom) {
70
+ module.startLiveNotification(activityType, customData(map))
71
+ } else {
72
+ module.startLiveNotification(parseData(map))
73
+ }
74
+ promise?.resolve(id)
75
+ } catch (ex: Throwable) {
76
+ promise?.reject("live_activity_start_failed", ex.message, ex)
77
+ }
78
+ }
79
+
80
+ override fun update(activityId: String?, payload: ReadableMap?, promise: Promise?) {
81
+ val module = getPushModule() ?: return promise.rejectNotAvailable()
82
+ try {
83
+ val id = requireNotNull(activityId) { "activityId is required" }
84
+ val map = requireNotNull(payload) { "payload is required" }
85
+ if (map.getString("type") == CUSTOM_TYPE_DISCRIMINATOR) {
86
+ module.updateLiveNotification(id, requireCustomActivityType(), customData(map))
87
+ } else {
88
+ module.updateLiveNotification(id, parseData(map))
89
+ }
90
+ promise?.resolve(null)
91
+ } catch (ex: Throwable) {
92
+ promise?.reject("live_activity_update_failed", ex.message, ex)
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Ends a live notification. [payload] is accepted for signature parity with iOS, where a final
98
+ * content-state is what makes ActivityKit render a terminal state; Android renders its own
99
+ * terminal state (the notification simply stops being ongoing), so it is ignored here.
100
+ */
101
+ override fun end(activityId: String?, payload: ReadableMap?, promise: Promise?) {
102
+ val module = getPushModule() ?: return promise.rejectNotAvailable()
103
+ try {
104
+ module.endLiveNotification(requireNotNull(activityId) { "activityId is required" })
105
+ promise?.resolve(null)
106
+ } catch (ex: Throwable) {
107
+ promise?.reject("live_activity_end_failed", ex.message, ex)
108
+ }
109
+ }
110
+
111
+ /**
112
+ * The app's own identifier for the custom template. Absent means the app sent a custom payload
113
+ * without configuring `liveNotifications.customType` — say so, rather than starting a
114
+ * notification the allowlist would silently drop.
115
+ */
116
+ /**
117
+ * Identifiers the SDK currently has enabled. Read from the live module config rather than cached
118
+ * at wrapper init: the config can also be built by generated native code that never calls
119
+ * [applyLiveActivitiesConfig] — the Expo config plugin does exactly that — and a cached set would
120
+ * be empty there, rejecting every start.
121
+ */
122
+ private fun enabledActivityTypes(): Set<String> =
123
+ SDKComponent.pushModuleConfig.liveNotificationTypes
124
+
125
+ /**
126
+ * The custom identifier, preferring what the wrapper saw and otherwise deriving it from the live
127
+ * config: exactly one custom type is supported, so it is the single enabled identifier that no
128
+ * built-in [LiveNotificationType] claims.
129
+ */
130
+ private fun customActivityTypeOrNull(): String? = customActivityType
131
+ ?: enabledActivityTypes().firstOrNull { identifier ->
132
+ LiveNotificationType.entries.none { it.identifier == identifier }
133
+ }
134
+
135
+ private fun requireCustomActivityType(): String = requireNotNull(customActivityTypeOrNull()) {
136
+ "No custom Live Activity type is configured. Set `liveNotifications.customType` in your " +
137
+ "Zixflow SDK config to your own reverse-DNS identifier, and render it from your " +
138
+ "ZixflowLiveNotificationsCallback."
139
+ }
140
+
141
+ /** Flattens the payload's `data` map. Android stringifies every value downstream anyway. */
142
+ private fun customData(payload: ReadableMap): Map<String, Any?> =
143
+ payload.getMap("data")?.toHashMap() ?: emptyMap()
144
+
145
+ private fun parseData(payload: ReadableMap): LiveNotificationData {
146
+ return when (val type = payload.getString("type")) {
147
+ LiveNotificationType.SEGMENTS.identifier -> LiveNotificationData.Segments(
148
+ header = payload.requireString("header"),
149
+ status = payload.requireString("status"),
150
+ substatus = payload.optString("substatus"),
151
+ segmentsTotal = payload.requireDouble("segmentsTotal").toInt(),
152
+ segmentsComplete = payload.requireDouble("segmentsComplete").toInt(),
153
+ trailingText = payload.optString("trailingText"),
154
+ )
155
+
156
+ LiveNotificationType.COUNTDOWN_TIMER.identifier -> LiveNotificationData.CountdownTimer(
157
+ header = payload.requireString("header"),
158
+ title = payload.requireString("title"),
159
+ statusMessage = payload.optString("statusMessage"),
160
+ endTime = if (payload.hasKey("endTime") && !payload.isNull("endTime")) {
161
+ payload.getDouble("endTime").toLong()
162
+ } else {
163
+ null
164
+ },
165
+ )
166
+
167
+ // A newer native SDK may know this type even though this wrapper build doesn't.
168
+ // Reject softly (the caller turns this into a rejected promise) rather than crash.
169
+ else -> throw IllegalArgumentException("Unsupported Live Activity template: $type")
170
+ }
171
+ }
172
+
173
+ private fun ReadableMap.requireString(key: String): String =
174
+ requireNotNull(if (hasKey(key)) getString(key) else null) { "$key is required" }
175
+
176
+ private fun ReadableMap.optString(key: String): String? =
177
+ if (hasKey(key) && !isNull(key)) getString(key) else null
178
+
179
+ private fun ReadableMap.requireDouble(key: String): Double {
180
+ require(hasKey(key) && !isNull(key)) { "$key is required" }
181
+ return getDouble(key)
182
+ }
183
+
184
+ private fun Promise?.rejectNotAvailable() {
185
+ this?.reject(
186
+ "live_activity_module_unavailable",
187
+ "Live Notifications are unavailable. Enable live activity templates in the SDK config.",
188
+ )
189
+ }
190
+
191
+ /** Mirrors iOS's `live_activity_type_not_registered` so the same mistake reads the same way. */
192
+ private fun Promise?.rejectNotRegistered(activityType: String?) {
193
+ this?.reject(
194
+ "live_activity_type_not_registered",
195
+ "Live Activity type '$activityType' is not registered. Add it to " +
196
+ "`liveNotifications.types` in your Zixflow SDK config (or set " +
197
+ "`liveNotifications.customType` for a custom type).",
198
+ )
199
+ }
200
+
201
+ companion object {
202
+ const val NAME = "NativeZixflowLiveActivities"
203
+
204
+ // The SDK's ModuleMessagingPushFCM.MODULE_NAME is `internal`, so we mirror its value here.
205
+ private const val PUSH_FCM_MODULE_NAME = "MessagingPushFCM"
206
+
207
+ // Discriminator JavaScript sends for the custom template. Not a wire identifier — the
208
+ // notification is started under the app's own `liveNotifications.customType`.
209
+ private const val CUSTOM_TYPE_DISCRIMINATOR = "custom"
210
+
211
+ // The app's own identifier for the custom template, captured from config at SDK init.
212
+ @Volatile
213
+ private var customActivityType: String? = null
214
+
215
+ // Host-app callback used to render custom (app-defined) live notifications. The native SDK
216
+ // only accepts it at build time, so the app must register it before the SDK initializes
217
+ // (e.g. in Application.onCreate before React Native starts). Stored statically because the
218
+ // SDK config is applied in applyLiveActivitiesConfig, not on this module instance.
219
+ @Volatile
220
+ private var liveNotificationCallback: ZixflowLiveNotificationsCallback? = null
221
+
222
+ /**
223
+ * Register the host app's callback for rendering live notifications itself. Custom
224
+ * (app-defined) activity types have no built-in template, so the app must build the
225
+ * [android.app.Notification] in
226
+ * [ZixflowLiveNotificationsCallback.createLiveNotification]; returning `null` there
227
+ * falls back to the SDK's built-in template.
228
+ * Call this before the Zixflow SDK is initialized.
229
+ */
230
+ @JvmStatic
231
+ fun setLiveNotificationCallback(callback: ZixflowLiveNotificationsCallback) {
232
+ liveNotificationCallback = callback
233
+ }
234
+
235
+ /**
236
+ * Applies live activity configuration onto the FCM push module's config builder. Live
237
+ * Notifications are hosted by [ModuleMessagingPushFCM], so their config (enabled templates,
238
+ * custom types, branding) is set on the same [MessagingPushModuleConfig].
239
+ *
240
+ * @param builder the push module's config builder.
241
+ * @param config the `liveNotifications` config map from the customer app.
242
+ */
243
+ internal fun applyLiveActivitiesConfig(
244
+ builder: MessagingPushModuleConfig.Builder,
245
+ config: Map<String, Any>,
246
+ ) {
247
+ // Wire the host app's live-notification renderer, if one was registered.
248
+ liveNotificationCallback?.let { builder.setLiveNotificationCallback(it) }
249
+
250
+ // Unrecognized identifiers are ignored: a newer native SDK may ship types this
251
+ // wrapper build doesn't know, and that must never break the ones it does know.
252
+ val templateTypes = config.getTypedValue<List<*>>("types")
253
+ ?.mapNotNull { it as? String }
254
+ ?.mapNotNull { identifier ->
255
+ LiveNotificationType.entries.firstOrNull { it.identifier == identifier }
256
+ }
257
+ .orEmpty()
258
+ if (templateTypes.isNotEmpty()) {
259
+ builder.enableLiveNotificationTypes(*templateTypes.toTypedArray())
260
+ }
261
+
262
+ // The custom template. Allowlisting the identifier is both necessary and sufficient:
263
+ // LiveNotificationHandler drops any push whose activityType isn't in this set, and a type
264
+ // with no built-in template falls through to the host app's render callback.
265
+ //
266
+ // Assigned unconditionally: a re-initialize that drops `customType` must clear it, or
267
+ // `start` would keep minting notifications under an identifier no longer allowlisted —
268
+ // which the handler then discards, leaving the caller with an id and nothing on screen.
269
+ val customType = config.getTypedValue<String>("customType")?.trim()?.takeIf { it.isNotEmpty() }
270
+ customActivityType = customType
271
+ if (customType != null) {
272
+ builder.enableCustomLiveNotificationTypes(customType)
273
+ }
274
+
275
+ val branding = config.getTypedValue<Map<String, Any>>("branding")
276
+ if (branding != null) {
277
+ val accentColor = branding.getTypedValue<String>("accentColorHex")
278
+ ?.let { runCatching { Color.parseColor(it) }.getOrNull() }
279
+ ?: Color.TRANSPARENT
280
+ // A bundled drawable is preferred over a remote URL: it renders without a network
281
+ // round-trip, so the logo is present on the very first frame.
282
+ val logo = branding.getTypedValue<String>("logoResource")
283
+ ?.let { name -> drawableResId(name)?.let(LiveNotificationAsset::Drawable) }
284
+ ?: branding.getTypedValue<String>("logoUrl")
285
+ ?.let(LiveNotificationAsset::RemoteUrl)
286
+ val smallIcon = branding.getTypedValue<String>("smallIconResource")
287
+ ?.let { name -> drawableResId(name) }
288
+ builder.setLiveNotificationBranding(
289
+ LiveNotificationBranding(
290
+ companyName = branding.getTypedValue<String>("companyName").orEmpty(),
291
+ accentColor = accentColor,
292
+ smallIcon = smallIcon,
293
+ logo = logo,
294
+ ),
295
+ )
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Resolve a bundled drawable by name, or `null` when the host app doesn't ship one under
301
+ * that name — a missing asset must degrade to "no image", never crash rendering.
302
+ */
303
+ private fun drawableResId(name: String): Int? {
304
+ val context = SDKComponent.android().applicationContext
305
+ return context.resources
306
+ .getIdentifier(name, "drawable", context.packageName)
307
+ .takeIf { it != 0 }
308
+ }
309
+ }
310
+ }
@@ -6,7 +6,7 @@ import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.bridge.ReadableMap
7
7
  import com.zixflow.reactnative.sdk.NativeZixflowLoggingSpec
8
8
  import com.zixflow.sdk.core.di.SDKComponent
9
- import com.zixflow.sdk.core.util.CioLogLevel
9
+ import com.zixflow.sdk.core.util.ZixflowLogLevel
10
10
 
11
11
  /**
12
12
  * React Native module implementation for Zixflow Logging Native SDK
@@ -72,7 +72,7 @@ class NativeZixflowLoggingModule(
72
72
  }
73
73
 
74
74
  // Converts native SDK log events to React Native compatible format and emits them.
75
- private fun emitLogEvent(level: CioLogLevel, message: String) {
75
+ private fun emitLogEvent(level: ZixflowLogLevel, message: String) {
76
76
  // Defensive check: only emit if emitter is available
77
77
  val emitter = logEventEmitter ?: return
78
78
 
@@ -10,6 +10,8 @@ import com.zixflow.messaginginapp.di.inAppMessaging
10
10
  import com.zixflow.messaginginapp.gist.data.model.InboxMessage
11
11
  import com.zixflow.messaginginapp.gist.data.model.response.InboxMessageFactory
12
12
  import com.zixflow.messaginginapp.inbox.NotificationInbox
13
+ import com.zixflow.messaginginapp.type.ColorScheme
14
+ import com.zixflow.messaginginapp.type.NotificationInboxAccessibilityLabels
13
15
  import com.zixflow.reactnative.sdk.NativeZixflowMessagingInAppSpec
14
16
  import com.zixflow.reactnative.sdk.constant.Keys
15
17
  import com.zixflow.reactnative.sdk.extension.getTypedValue
@@ -33,6 +35,7 @@ class NativeMessagingInAppModule(
33
35
 
34
36
  private val inAppEventListener = ReactInAppEventListener.instance
35
37
  private val inboxChangeListener = ReactNotificationInboxChangeListener.instance
38
+ private val inboxEventListener = ReactInboxEventListener.instance
36
39
 
37
40
  // Dedicated lock for inbox listener setup to avoid blocking other operations
38
41
  private val inboxListenerLock = Any()
@@ -63,6 +66,7 @@ class NativeMessagingInAppModule(
63
66
  override fun invalidate() {
64
67
  inAppEventListener.clearEventEmitter()
65
68
  clearInboxChangeListener()
69
+ clearInboxEventListener()
66
70
  super.invalidate()
67
71
  }
68
72
 
@@ -70,10 +74,76 @@ class NativeMessagingInAppModule(
70
74
  inAppMessagingModule?.dismissMessage()
71
75
  }
72
76
 
77
+ // Nullable like every other argument-taking override here, and for the same reason: Codegen
78
+ // declares the parameter non-null, so a non-null Kotlin type would throw
79
+ // `Intrinsics.checkNotNullParameter` before the body runs when JavaScript passes null — which
80
+ // is what an untyped caller reading a stored theme sends on first launch.
81
+ override fun setColorScheme(colorScheme: String?) {
82
+ val resolved = colorSchemeFromRawValue(colorScheme)
83
+ if (resolved == null) {
84
+ // Leave the current scheme alone rather than resetting it to AUTO, so a bad value
85
+ // cannot quietly undo a scheme the app set correctly earlier.
86
+ logger.error(
87
+ "Unrecognized in-app colorScheme '$colorScheme', expected one of auto, light, " +
88
+ "dark. Leaving the color scheme unchanged."
89
+ )
90
+ return
91
+ }
92
+ val module = inAppMessagingModule
93
+ if (module == null) {
94
+ // Reachable when the host calls this before Zixflow.initialize, or without the
95
+ // in-app module configured. Logged rather than ignored: the scheme is silently not
96
+ // applied, and nothing else surfaces that.
97
+ logger.error(
98
+ "In-app messaging is not available, so the color scheme was not applied. " +
99
+ "Ensure Zixflow SDK is initialized with the inApp configuration."
100
+ )
101
+ return
102
+ }
103
+ module.setColorScheme(resolved)
104
+ }
105
+
73
106
  override fun setupInboxListener() {
74
107
  setupInboxChangeListener()
75
108
  }
76
109
 
110
+ /**
111
+ * Registers the native inbox event forwarder with the SDK and wires it to the React Native
112
+ * event emitter. Called when a JS inbox event listener is registered.
113
+ */
114
+ override fun registerInboxEventListener() {
115
+ inboxEventListener.setEventEmitter { data ->
116
+ emitOnInboxEventReceived(data)
117
+ }
118
+ val module = inAppMessagingModule
119
+ if (module == null) {
120
+ // Without this the failure is invisible: the JS subscription reports success while the SDK
121
+ // never forwards anything, so inbox callbacks silently never arrive. Matches the guidance
122
+ // used by getMessages() for the same situation.
123
+ logger.error(
124
+ "Cannot register inbox event listener: in-app messaging is not available. " +
125
+ "Ensure Zixflow SDK is initialized before registering the listener."
126
+ )
127
+ return
128
+ }
129
+ module.setInboxEventListener(inboxEventListener)
130
+ }
131
+
132
+ /**
133
+ * Unregisters the native inbox event forwarder by installing a no-op listener (the native API is
134
+ * non-null), restoring the SDK's default action handling. Called when the JS inbox event
135
+ * listener is removed.
136
+ */
137
+ override fun unregisterInboxEventListener() {
138
+ inAppMessagingModule?.setInboxEventListener(NoOpInboxEventListener)
139
+ inboxEventListener.clearEventEmitter()
140
+ }
141
+
142
+ private fun clearInboxEventListener() {
143
+ inAppMessagingModule?.setInboxEventListener(NoOpInboxEventListener)
144
+ inboxEventListener.clearEventEmitter()
145
+ }
146
+
77
147
  override fun getMessages(topic: String?, promise: Promise?) {
78
148
  try {
79
149
  val inbox = requireInboxInstance() ?: run {
@@ -191,25 +261,94 @@ class NativeMessagingInAppModule(
191
261
  *
192
262
  * @param builder ZixflowBuilder instance to add InAppMessaging module
193
263
  * @param config Configuration provided by customer app for InAppMessaging module
194
- * @param region Region to be used for InAppMessaging module
195
264
  */
196
265
  internal fun addNativeModuleFromConfig(
197
266
  builder: ZixflowBuilder,
198
- config: Map<String, Any>,
199
- region: Region
267
+ config: Map<String, Any>
200
268
  ) {
201
- val siteId = config.getTypedValue<String>(Keys.Config.SITE_ID)
202
- if (siteId.isNullOrBlank()) {
203
- SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module")
204
- return
205
- }
206
-
207
269
  val module = ModuleMessagingInApp(
208
- MessagingInAppModuleConfig.Builder(siteId = siteId, region = region).apply {
270
+ MessagingInAppModuleConfig.Builder().apply {
209
271
  setEventListener(eventListener = ReactInAppEventListener.instance)
272
+ colorSchemeFromConfig(config)?.let { colorScheme ->
273
+ setColorScheme(colorScheme)
274
+ }
275
+ inboxAccessibilityLabelsFromConfig(config)?.let { labels ->
276
+ setNotificationInboxAccessibilityLabels(labels)
277
+ }
210
278
  }.build(),
211
279
  )
212
280
  builder.addZixflowModule(module)
213
281
  }
282
+
283
+ /**
284
+ * Maps the wrapper's `colorScheme` value onto the native [ColorScheme], or null when the
285
+ * value is absent or unrecognized.
286
+ *
287
+ * The accepted values are lowercase because that is the wire contract the JavaScript
288
+ * `ZixflowColorScheme` enum serializes to and the one iOS already matches. Kept free of
289
+ * logging so each caller can report a bad value in its own terms: at initialization an
290
+ * unrecognized value falls back to AUTO, while the runtime setter leaves the scheme the
291
+ * app already chose untouched.
292
+ */
293
+ internal fun colorSchemeFromRawValue(rawValue: String?): ColorScheme? = when (rawValue) {
294
+ "auto" -> ColorScheme.AUTO
295
+ "light" -> ColorScheme.LIGHT
296
+ "dark" -> ColorScheme.DARK
297
+ else -> null
298
+ }
299
+
300
+ private fun colorSchemeFromConfig(config: Map<String, Any>): ColorScheme? {
301
+ // Absent is not a mistake — the SDK's own AUTO default stands — so only a value the
302
+ // host actually provided is worth reporting.
303
+ val rawValue = config.getTypedValue<String>(Keys.Config.COLOR_SCHEME) ?: return null
304
+
305
+ return colorSchemeFromRawValue(rawValue).also { resolved ->
306
+ if (resolved == null) {
307
+ SDKComponent.logger.error(
308
+ "Unrecognized in-app colorScheme '$rawValue', expected one of auto, " +
309
+ "light, dark. Falling back to the device appearance."
310
+ )
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Builds the host's inbox accessibility labels from the wrapper configuration, or null when
317
+ * the app provided none — in which case the SDK keeps its default of emitting no labels at
318
+ * all rather than falling back to English.
319
+ *
320
+ * `bellWithUnreadCount` arrives as a template string because the bridge carries data but not
321
+ * functions; it is converted here into the `(Int) -> String` the native SDK expects. A
322
+ * template without the placeholder is returned verbatim for every count; the JavaScript
323
+ * layer warns about that case, where the developer can actually see it.
324
+ */
325
+ private fun inboxAccessibilityLabelsFromConfig(
326
+ config: Map<String, Any>
327
+ ): NotificationInboxAccessibilityLabels? {
328
+ val labels = config.getTypedValue<Map<String, Any>>(
329
+ Keys.Config.NOTIFICATION_INBOX_ACCESSIBILITY_LABELS
330
+ ) ?: return null
331
+
332
+ val unreadCountTemplate = labels.getTypedValue<String>(
333
+ Keys.InboxAccessibilityLabels.BELL_WITH_UNREAD_COUNT
334
+ )
335
+ return NotificationInboxAccessibilityLabels(
336
+ bell = labels.getTypedValue<String>(Keys.InboxAccessibilityLabels.BELL),
337
+ bellWithUnreadCount = unreadCountTemplate?.let { template ->
338
+ { count: Int ->
339
+ template.replace(
340
+ Keys.InboxAccessibilityLabels.COUNT_PLACEHOLDER,
341
+ count.toString()
342
+ )
343
+ }
344
+ },
345
+ loadingIndicator = labels.getTypedValue<String>(
346
+ Keys.InboxAccessibilityLabels.LOADING_INDICATOR
347
+ ),
348
+ emptyState = labels.getTypedValue<String>(
349
+ Keys.InboxAccessibilityLabels.EMPTY_STATE
350
+ )
351
+ )
352
+ }
214
353
  }
215
354
  }
@@ -0,0 +1,99 @@
1
+ package com.zixflow.reactnative.sdk.messaginginapp
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.zixflow.messaginginapp.gist.data.model.InboxMessage
6
+ import com.zixflow.messaginginapp.type.InboxEventListener
7
+
8
+ /**
9
+ * React Native bridge for Zixflow Visual Notification Inbox events.
10
+ *
11
+ * Registered on the SDK (via [com.zixflow.messaginginapp.ModuleMessagingInApp.setInboxEventListener])
12
+ * only while a JS listener is registered. Because the New-Arch event emitter is one-way we cannot
13
+ * round-trip a bool back from JS, so [messageActionTaken] forwards the event fire-and-forget and
14
+ * RETURNS `true` — telling the SDK the host handled the action so the SDK suppresses its default
15
+ * navigation. The JS host owns action handling while a listener is registered. Observational
16
+ * callbacks forward fire-and-forget.
17
+ */
18
+ class ReactInboxEventListener private constructor() : InboxEventListener {
19
+ // Event emitter function to send events to React Native layer
20
+ private var eventEmitter: ((ReadableMap) -> Unit)? = null
21
+
22
+ // Sets the event emitter function
23
+ internal fun setEventEmitter(emitter: ((ReadableMap) -> Unit)?) {
24
+ this.eventEmitter = emitter
25
+ }
26
+
27
+ // Clears the event emitter to prevent memory leaks
28
+ internal fun clearEventEmitter() {
29
+ this.eventEmitter = null
30
+ }
31
+
32
+ // Emits an inbox message event to React Native with a serialized message payload
33
+ private fun emitInboxEvent(
34
+ eventType: String,
35
+ message: InboxMessage,
36
+ actionName: String? = null,
37
+ actionValue: String? = null,
38
+ ) {
39
+ // Get the emitter, return early if not set
40
+ val emitter = eventEmitter ?: return
41
+
42
+ val data = Arguments.createMap().apply {
43
+ putString("eventType", eventType)
44
+ putMap("message", message.toWritableMap())
45
+ actionName?.let { putString("actionName", it) }
46
+ actionValue?.let { putString("actionValue", it) }
47
+ }
48
+
49
+ emitter.invoke(data)
50
+ }
51
+
52
+ override fun messageActionTaken(
53
+ message: InboxMessage,
54
+ actionName: String,
55
+ actionValue: String,
56
+ ): Boolean {
57
+ emitInboxEvent(
58
+ eventType = "messageActionTaken",
59
+ message = message,
60
+ actionName = actionName,
61
+ actionValue = actionValue,
62
+ )
63
+ // Host (React Native) owns action handling while a listener is registered.
64
+ return true
65
+ }
66
+
67
+ override fun messageShown(message: InboxMessage) = emitInboxEvent(
68
+ eventType = "messageShown",
69
+ message = message,
70
+ )
71
+
72
+ override fun messageOpened(message: InboxMessage) = emitInboxEvent(
73
+ eventType = "messageOpened",
74
+ message = message,
75
+ )
76
+
77
+ override fun messageDismissed(message: InboxMessage) = emitInboxEvent(
78
+ eventType = "messageDismissed",
79
+ message = message,
80
+ )
81
+
82
+ companion object {
83
+ // Singleton instance with public visibility for direct access by Expo plugin
84
+ val instance: ReactInboxEventListener by lazy { ReactInboxEventListener() }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * No-op inbox listener used to clear the forwarder from the SDK. Returning `false` from
90
+ * [messageActionTaken] restores the SDK's default action handling. The native
91
+ * `setInboxEventListener` API is non-null, so a no-op is installed rather than clearing.
92
+ */
93
+ internal object NoOpInboxEventListener : InboxEventListener {
94
+ override fun messageActionTaken(
95
+ message: InboxMessage,
96
+ actionName: String,
97
+ actionValue: String,
98
+ ): Boolean = false
99
+ }
@@ -0,0 +1,38 @@
1
+ package com.zixflow.reactnative.sdk.messaginginbox
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
+ import com.facebook.react.viewmanagers.NotificationInboxBellNativeManagerDelegate
8
+ import com.facebook.react.viewmanagers.NotificationInboxBellNativeManagerInterface
9
+
10
+ /**
11
+ * View manager for the Visual Notification Inbox bell (just the bell; host opens its own UI).
12
+ *
13
+ * Registers the `onTap` direct event so taps reach the JS wrapper.
14
+ */
15
+ @ReactModule(name = NotificationInboxBellViewManager.NAME)
16
+ class NotificationInboxBellViewManager :
17
+ NotificationInboxBellNativeManagerInterface<ReactNotificationInboxBellView>,
18
+ SimpleViewManager<ReactNotificationInboxBellView>() {
19
+ private val delegate = NotificationInboxBellNativeManagerDelegate(this)
20
+
21
+ override fun getName() = NAME
22
+ override fun getDelegate(): ViewManagerDelegate<ReactNotificationInboxBellView> = delegate
23
+
24
+ override fun createViewInstance(
25
+ reactContext: ThemedReactContext
26
+ ): ReactNotificationInboxBellView = ReactNotificationInboxBellView(reactContext)
27
+
28
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
29
+ val customEvents = super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()
30
+ customEvents[ReactNotificationInboxBellView.ON_TAP] =
31
+ mapOf("registrationName" to ReactNotificationInboxBellView.ON_TAP)
32
+ return customEvents
33
+ }
34
+
35
+ companion object {
36
+ internal const val NAME = "NotificationInboxBellNative"
37
+ }
38
+ }