zixflow-reactnative 1.1.3 → 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 (239) hide show
  1. package/README.md +631 -261
  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 +40 -5
  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 -0
  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/messaginginapp/NativeMessagingInAppModule.kt +150 -0
  11. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginapp/ReactInboxEventListener.kt +99 -0
  12. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxBellViewManager.kt +38 -0
  13. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxViewManager.kt +30 -0
  14. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactComposeHostView.kt +96 -0
  15. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxBellView.kt +48 -0
  16. package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxView.kt +17 -0
  17. package/android/src/main/java/com/zixflow/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +11 -1
  18. package/android/src/main/res/values/zixflow_config.xml +1 -1
  19. package/android/zixflow-core.gradle +17 -7
  20. package/docs/cocoapods-deployment-target-normalization.md +139 -0
  21. package/ios/cocoapods_deployment_target.rb +432 -0
  22. package/ios/wrappers/NativeZixflow.mm +37 -0
  23. package/ios/wrappers/NativeZixflow.swift +135 -1
  24. package/ios/wrappers/ZixflowReactNativeDeepLinkRequestStore.swift +123 -0
  25. package/ios/wrappers/ZixflowReactNativeDeepLinkRouter.swift +312 -0
  26. package/ios/wrappers/geofence/NativeGeofence.mm +46 -0
  27. package/ios/wrappers/geofence/NativeGeofence.swift +27 -0
  28. package/ios/wrappers/inapp/NativeMessagingInApp.mm +22 -0
  29. package/ios/wrappers/inapp/NativeMessagingInApp.swift +90 -0
  30. package/ios/wrappers/inbox/InboxHostContainment.swift +40 -0
  31. package/ios/wrappers/inbox/RCTNotificationInboxBellNative.h +12 -0
  32. package/ios/wrappers/inbox/RCTNotificationInboxBellNative.mm +98 -0
  33. package/ios/wrappers/inbox/RCTNotificationInboxViewNative.h +12 -0
  34. package/ios/wrappers/inbox/RCTNotificationInboxViewNative.mm +78 -0
  35. package/ios/wrappers/inbox/ReactInboxEventListener.swift +76 -0
  36. package/ios/wrappers/inbox/ReactNotificationInboxBellView.h +22 -0
  37. package/ios/wrappers/inbox/ReactNotificationInboxBellView.swift +109 -0
  38. package/ios/wrappers/inbox/ReactNotificationInboxView.h +22 -0
  39. package/ios/wrappers/inbox/ReactNotificationInboxView.swift +69 -0
  40. package/ios/wrappers/liveactivities/NativeLiveActivities.mm +83 -0
  41. package/ios/wrappers/liveactivities/NativeLiveActivities.swift +390 -0
  42. package/ios/wrappers/location/NativeLocation.swift +7 -5
  43. package/ios/wrappers/utils/ZixflowConstants.swift +4 -0
  44. package/ios/wrappers/zixflow_reactnative.h +6 -0
  45. package/lib/commonjs/components/NotificationInboxBellView.js +49 -0
  46. package/lib/commonjs/components/NotificationInboxBellView.js.map +1 -0
  47. package/lib/commonjs/components/NotificationInboxView.js +36 -0
  48. package/lib/commonjs/components/NotificationInboxView.js.map +1 -0
  49. package/lib/commonjs/components/index.js +14 -0
  50. package/lib/commonjs/components/index.js.map +1 -1
  51. package/lib/commonjs/index.js +81 -4
  52. package/lib/commonjs/index.js.map +1 -1
  53. package/lib/commonjs/notification-inbox.js +1 -0
  54. package/lib/commonjs/notification-inbox.js.map +1 -1
  55. package/lib/commonjs/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  56. package/lib/commonjs/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  57. package/lib/commonjs/specs/modules/NativeZixflow.js.map +1 -1
  58. package/lib/commonjs/specs/modules/NativeZixflowGeofence.js +15 -0
  59. package/lib/commonjs/specs/modules/NativeZixflowGeofence.js.map +1 -0
  60. package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js +23 -0
  61. package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
  62. package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js +3 -1
  63. package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
  64. package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js +3 -1
  65. package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
  66. package/lib/commonjs/types/data-pipelines.js +13 -1
  67. package/lib/commonjs/types/data-pipelines.js.map +1 -1
  68. package/lib/commonjs/types/in-app.js +24 -1
  69. package/lib/commonjs/types/in-app.js.map +1 -1
  70. package/lib/commonjs/types/inbox.js +79 -0
  71. package/lib/commonjs/types/inbox.js.map +1 -0
  72. package/lib/commonjs/types/index.js +22 -0
  73. package/lib/commonjs/types/index.js.map +1 -1
  74. package/lib/commonjs/types/live-activities.js +85 -0
  75. package/lib/commonjs/types/live-activities.js.map +1 -0
  76. package/lib/commonjs/utils/param-validation.js +76 -1
  77. package/lib/commonjs/utils/param-validation.js.map +1 -1
  78. package/lib/commonjs/zixflow-cdp.js +99 -1
  79. package/lib/commonjs/zixflow-cdp.js.map +1 -1
  80. package/lib/commonjs/zixflow-geofence.js +55 -0
  81. package/lib/commonjs/zixflow-geofence.js.map +1 -0
  82. package/lib/commonjs/zixflow-inapp.js +120 -0
  83. package/lib/commonjs/zixflow-inapp.js.map +1 -1
  84. package/lib/commonjs/zixflow-liveactivities.js +85 -0
  85. package/lib/commonjs/zixflow-liveactivities.js.map +1 -0
  86. package/lib/module/components/NotificationInboxBellView.js +44 -0
  87. package/lib/module/components/NotificationInboxBellView.js.map +1 -0
  88. package/lib/module/components/NotificationInboxView.js +31 -0
  89. package/lib/module/components/NotificationInboxView.js.map +1 -0
  90. package/lib/module/components/index.js +5 -0
  91. package/lib/module/components/index.js.map +1 -1
  92. package/lib/module/index.js +8 -3
  93. package/lib/module/index.js.map +1 -1
  94. package/lib/module/notification-inbox.js +1 -1
  95. package/lib/module/notification-inbox.js.map +1 -1
  96. package/lib/module/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  97. package/lib/module/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  98. package/lib/module/specs/modules/NativeZixflow.js.map +1 -1
  99. package/lib/module/specs/modules/NativeZixflowGeofence.js +13 -0
  100. package/lib/module/specs/modules/NativeZixflowGeofence.js.map +1 -0
  101. package/lib/module/specs/modules/NativeZixflowLiveActivities.js +21 -0
  102. package/lib/module/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
  103. package/lib/module/specs/modules/NativeZixflowMessagingInApp.js +3 -1
  104. package/lib/module/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
  105. package/lib/module/specs/modules/NativeZixflowMessagingPush.js +3 -1
  106. package/lib/module/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
  107. package/lib/module/types/data-pipelines.js +13 -0
  108. package/lib/module/types/data-pipelines.js.map +1 -1
  109. package/lib/module/types/in-app.js +24 -0
  110. package/lib/module/types/in-app.js.map +1 -1
  111. package/lib/module/types/inbox.js +75 -0
  112. package/lib/module/types/inbox.js.map +1 -0
  113. package/lib/module/types/index.js +2 -0
  114. package/lib/module/types/index.js.map +1 -1
  115. package/lib/module/types/live-activities.js +88 -0
  116. package/lib/module/types/live-activities.js.map +1 -0
  117. package/lib/module/utils/param-validation.js +76 -1
  118. package/lib/module/utils/param-validation.js.map +1 -1
  119. package/lib/module/zixflow-cdp.js +99 -1
  120. package/lib/module/zixflow-cdp.js.map +1 -1
  121. package/lib/module/zixflow-geofence.js +50 -0
  122. package/lib/module/zixflow-geofence.js.map +1 -0
  123. package/lib/module/zixflow-inapp.js +99 -1
  124. package/lib/module/zixflow-inapp.js.map +1 -1
  125. package/lib/module/zixflow-liveactivities.js +80 -0
  126. package/lib/module/zixflow-liveactivities.js.map +1 -0
  127. package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts +39 -0
  128. package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/components/NotificationInboxView.d.ts +25 -0
  130. package/lib/typescript/commonjs/components/NotificationInboxView.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/components/index.d.ts +4 -0
  132. package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/index.d.ts +8 -1
  134. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/notification-inbox.d.ts +5 -0
  136. package/lib/typescript/commonjs/notification-inbox.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
  138. package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
  140. package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts +6 -1
  142. package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts +13 -0
  144. package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
  146. package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
  148. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
  150. package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
  151. package/lib/typescript/commonjs/types/data-pipelines.d.ts +53 -1
  152. package/lib/typescript/commonjs/types/data-pipelines.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/types/in-app.d.ts +22 -0
  154. package/lib/typescript/commonjs/types/in-app.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/types/inbox.d.ts +97 -0
  156. package/lib/typescript/commonjs/types/inbox.d.ts.map +1 -0
  157. package/lib/typescript/commonjs/types/index.d.ts +2 -0
  158. package/lib/typescript/commonjs/types/index.d.ts.map +1 -1
  159. package/lib/typescript/commonjs/types/live-activities.d.ts +161 -0
  160. package/lib/typescript/commonjs/types/live-activities.d.ts.map +1 -0
  161. package/lib/typescript/commonjs/utils/param-validation.d.ts +7 -0
  162. package/lib/typescript/commonjs/utils/param-validation.d.ts.map +1 -1
  163. package/lib/typescript/commonjs/zixflow-cdp.d.ts +38 -0
  164. package/lib/typescript/commonjs/zixflow-cdp.d.ts.map +1 -1
  165. package/lib/typescript/commonjs/zixflow-geofence.d.ts +36 -0
  166. package/lib/typescript/commonjs/zixflow-geofence.d.ts.map +1 -0
  167. package/lib/typescript/commonjs/zixflow-inapp.d.ts +36 -2
  168. package/lib/typescript/commonjs/zixflow-inapp.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/zixflow-liveactivities.d.ts +66 -0
  170. package/lib/typescript/commonjs/zixflow-liveactivities.d.ts.map +1 -0
  171. package/lib/typescript/module/components/NotificationInboxBellView.d.ts +39 -0
  172. package/lib/typescript/module/components/NotificationInboxBellView.d.ts.map +1 -0
  173. package/lib/typescript/module/components/NotificationInboxView.d.ts +25 -0
  174. package/lib/typescript/module/components/NotificationInboxView.d.ts.map +1 -0
  175. package/lib/typescript/module/components/index.d.ts +4 -0
  176. package/lib/typescript/module/components/index.d.ts.map +1 -1
  177. package/lib/typescript/module/index.d.ts +8 -1
  178. package/lib/typescript/module/index.d.ts.map +1 -1
  179. package/lib/typescript/module/notification-inbox.d.ts +5 -0
  180. package/lib/typescript/module/notification-inbox.d.ts.map +1 -1
  181. package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
  182. package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
  183. package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
  184. package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
  185. package/lib/typescript/module/specs/modules/NativeZixflow.d.ts +6 -1
  186. package/lib/typescript/module/specs/modules/NativeZixflow.d.ts.map +1 -1
  187. package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts +13 -0
  188. package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
  189. package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
  190. package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
  191. package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
  192. package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
  193. package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
  194. package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
  195. package/lib/typescript/module/types/data-pipelines.d.ts +53 -1
  196. package/lib/typescript/module/types/data-pipelines.d.ts.map +1 -1
  197. package/lib/typescript/module/types/in-app.d.ts +22 -0
  198. package/lib/typescript/module/types/in-app.d.ts.map +1 -1
  199. package/lib/typescript/module/types/inbox.d.ts +97 -0
  200. package/lib/typescript/module/types/inbox.d.ts.map +1 -0
  201. package/lib/typescript/module/types/index.d.ts +2 -0
  202. package/lib/typescript/module/types/index.d.ts.map +1 -1
  203. package/lib/typescript/module/types/live-activities.d.ts +161 -0
  204. package/lib/typescript/module/types/live-activities.d.ts.map +1 -0
  205. package/lib/typescript/module/utils/param-validation.d.ts +7 -0
  206. package/lib/typescript/module/utils/param-validation.d.ts.map +1 -1
  207. package/lib/typescript/module/zixflow-cdp.d.ts +38 -0
  208. package/lib/typescript/module/zixflow-cdp.d.ts.map +1 -1
  209. package/lib/typescript/module/zixflow-geofence.d.ts +36 -0
  210. package/lib/typescript/module/zixflow-geofence.d.ts.map +1 -0
  211. package/lib/typescript/module/zixflow-inapp.d.ts +36 -2
  212. package/lib/typescript/module/zixflow-inapp.d.ts.map +1 -1
  213. package/lib/typescript/module/zixflow-liveactivities.d.ts +66 -0
  214. package/lib/typescript/module/zixflow-liveactivities.d.ts.map +1 -0
  215. package/package.json +11 -2
  216. package/src/components/NotificationInboxBellView.tsx +55 -0
  217. package/src/components/NotificationInboxView.tsx +30 -0
  218. package/src/components/index.ts +6 -0
  219. package/src/index.ts +8 -3
  220. package/src/notification-inbox.ts +1 -1
  221. package/src/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
  222. package/src/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
  223. package/src/specs/modules/NativeZixflow.ts +9 -1
  224. package/src/specs/modules/NativeZixflowGeofence.ts +14 -0
  225. package/src/specs/modules/NativeZixflowLiveActivities.ts +39 -0
  226. package/src/specs/modules/NativeZixflowMessagingInApp.ts +12 -1
  227. package/src/specs/modules/NativeZixflowMessagingPush.ts +3 -1
  228. package/src/types/data-pipelines.ts +54 -1
  229. package/src/types/in-app.ts +23 -0
  230. package/src/types/inbox.ts +110 -0
  231. package/src/types/index.ts +2 -0
  232. package/src/types/live-activities.ts +170 -0
  233. package/src/utils/param-validation.ts +103 -0
  234. package/src/zixflow-cdp.ts +117 -1
  235. package/src/zixflow-geofence.ts +56 -0
  236. package/src/zixflow-inapp.ts +125 -2
  237. package/src/zixflow-liveactivities.ts +86 -0
  238. package/zixflow-reactnative-richpush.podspec +4 -1
  239. package/zixflow-reactnative.podspec +26 -0
@@ -10,11 +10,14 @@ buildscript {
10
10
  dependencies {
11
11
  // noinspection DifferentKotlinGradleVersion
12
12
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13
+ // Compose compiler plugin (Kotlin 2.x) for hosting the Visual Notification Inbox composables.
14
+ classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlin_version"
13
15
  }
14
16
  }
15
17
 
16
18
  apply plugin: 'com.android.library'
17
19
  apply plugin: 'kotlin-android'
20
+ apply plugin: 'org.jetbrains.kotlin.plugin.compose'
18
21
  apply plugin: "com.facebook.react"
19
22
 
20
23
  def getExtOrDefault(name) {
@@ -26,6 +29,7 @@ def getExtOrIntegerDefault(name) {
26
29
  }
27
30
 
28
31
  ext.zixflowLocationEnabled = rootProject.findProperty("zixflow_location_enabled")?.toBoolean() ?: false
32
+ ext.zixflowGeofenceEnabled = rootProject.findProperty("zixflow_geofence_enabled")?.toBoolean() ?: false
29
33
 
30
34
  android {
31
35
  namespace = 'com.zixflow.reactnative.sdk'
@@ -33,12 +37,17 @@ android {
33
37
  defaultConfig {
34
38
  minSdkVersion getExtOrIntegerDefault('minSdkVersion')
35
39
  targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
36
- buildConfigField "boolean", "ZIXFLOW_LOCATION_ENABLED", "${project.zixflowLocationEnabled}"
40
+ // Geofence implies Location, so the Location flag is on whenever geofence is enabled
41
+ // (mirrors the iOS geofence subspec, which also defines ZIXFLOW_LOCATION_ENABLED).
42
+ buildConfigField "boolean", "ZIXFLOW_LOCATION_ENABLED", "${project.zixflowLocationEnabled || project.zixflowGeofenceEnabled}"
43
+ buildConfigField "boolean", "ZIXFLOW_GEOFENCE_ENABLED", "${project.zixflowGeofenceEnabled}"
37
44
  consumerProguardFiles 'consumer-rules.pro'
38
45
  }
39
46
 
40
47
  buildFeatures {
41
48
  buildConfig true
49
+ // Required to host the Visual Notification Inbox Compose UI components.
50
+ compose true
42
51
  }
43
52
 
44
53
  buildTypes {
@@ -144,4 +153,12 @@ dependencies {
144
153
  // noinspection GradleDynamicVersion
145
154
  api 'com.facebook.react:react-native:+'
146
155
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
156
+
157
+ // Jetpack Compose — needed because the Visual Notification Inbox view managers host
158
+ // the native `@Composable` inbox components inside a ComposeView.
159
+ def composeBom = platform('androidx.compose:compose-bom:2025.10.00')
160
+ implementation composeBom
161
+ implementation 'androidx.compose.ui:ui'
162
+ implementation 'androidx.compose.foundation:foundation'
163
+ implementation 'androidx.compose.runtime:runtime'
147
164
  }
@@ -1,3 +1,4 @@
1
- # Location module is an optional compileOnly dependency.
2
- # When not enabled, R8 must not fail on missing location classes.
1
+ # Location and geofence modules are optional compileOnly dependencies.
2
+ # When not enabled, R8 must not fail on their missing classes.
3
3
  -dontwarn io.customer.location.**
4
+ -dontwarn io.customer.geofence.**
@@ -2,4 +2,4 @@ zixflow.reactnative.kotlinVersion=2.1.20
2
2
  zixflow.reactnative.compileSdkVersion=36
3
3
  zixflow.reactnative.targetSdkVersion=36
4
4
  zixflow.reactnative.minSdkVersion=21
5
- zixflow.reactnative.sdkVersionAndroid=1.1.3
5
+ zixflow.reactnative.sdkVersionAndroid=4.23.2
@@ -101,11 +101,14 @@ class NativeZixflowModule(
101
101
  cdnHost(it)
102
102
  }
103
103
 
104
- // Configure push messaging module based on config provided by customer app
104
+ // Configure push messaging module based on config provided by customer app.
105
+ // Live Activities are hosted by the FCM push module, so the `liveNotifications`
106
+ // config is applied to the same module.
105
107
  packageConfig.getTypedValue<Map<String, Any>>(key = "push").let { pushConfig ->
106
108
  NativeMessagingPushModule.addNativeModuleFromConfig(
107
109
  builder = this,
108
- config = pushConfig ?: emptyMap()
110
+ config = pushConfig ?: emptyMap(),
111
+ liveNotificationsConfig = packageConfig.getTypedValue<Map<String, Any>>(key = "liveNotifications")
109
112
  )
110
113
  }
111
114
  packageConfig.getTypedValue<Map<String, Any>>(key = "inApp")?.let { inAppConfig ->
@@ -114,12 +117,28 @@ class NativeZixflowModule(
114
117
  config = inAppConfig
115
118
  )
116
119
  }
117
- // Configure location module if enabled via gradle property
120
+ // Configure location module. Geofence implies location, so register location
121
+ // (with the app's config if given, otherwise defaults) whenever location or
122
+ // geofence is enabled, since geofence relies on the location module's fixes.
123
+ // ZIXFLOW_LOCATION_ENABLED is already true for geofence-enabled builds.
118
124
  if (BuildConfig.ZIXFLOW_LOCATION_ENABLED) {
119
- packageConfig.getTypedValue<Map<String, Any>>(key = "location")?.let { locationConfig ->
125
+ val locationConfig = packageConfig.getTypedValue<Map<String, Any>>(key = "location")
126
+ val geofenceConfigured = BuildConfig.ZIXFLOW_GEOFENCE_ENABLED &&
127
+ packageConfig.getTypedValue<Map<String, Any>>(key = "geofence") != null
128
+ if (locationConfig != null || geofenceConfigured) {
120
129
  NativeLocationModule.addNativeModuleFromConfig(
121
130
  builder = this,
122
- config = locationConfig
131
+ config = locationConfig ?: emptyMap()
132
+ )
133
+ }
134
+ }
135
+ // Configure geofence module if enabled via gradle property; it runs
136
+ // automatically once registered and relies on the location module above.
137
+ if (BuildConfig.ZIXFLOW_GEOFENCE_ENABLED) {
138
+ packageConfig.getTypedValue<Map<String, Any>>(key = "geofence")?.let { geofenceConfig ->
139
+ NativeGeofenceModule.addNativeModuleFromConfig(
140
+ builder = this,
141
+ config = geofenceConfig
123
142
  )
124
143
  }
125
144
  }
@@ -133,6 +152,22 @@ class NativeZixflowModule(
133
152
  }
134
153
  }
135
154
 
155
+ override fun setDeepLinkRoutingReady() {
156
+ // UIScene and React Native Linking readiness are iOS-only concerns.
157
+ }
158
+
159
+ override fun registerDeepLinkHandler() {
160
+ // UIScene deep-link acknowledgement is an iOS-only concern.
161
+ }
162
+
163
+ override fun unregisterDeepLinkHandler() {
164
+ // UIScene deep-link acknowledgement is an iOS-only concern.
165
+ }
166
+
167
+ override fun acknowledgeDeepLink(id: String?, handled: Boolean) {
168
+ // UIScene deep-link acknowledgement is an iOS-only concern.
169
+ }
170
+
136
171
  override fun identify(params: ReadableMap?) {
137
172
  val userId = params?.getString("userId")
138
173
  val traits = params?.getMap("traits")
@@ -6,10 +6,14 @@ import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.module.model.ReactModuleInfo
7
7
  import com.facebook.react.module.model.ReactModuleInfoProvider
8
8
  import com.facebook.react.uimanager.ViewManager
9
+ import com.zixflow.reactnative.sdk.geofence.NativeGeofenceModule
10
+ import com.zixflow.reactnative.sdk.liveactivities.NativeLiveActivitiesModule
9
11
  import com.zixflow.reactnative.sdk.location.NativeLocationModule
10
12
  import com.zixflow.reactnative.sdk.logging.NativeZixflowLoggingModule
11
13
  import com.zixflow.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager
12
14
  import com.zixflow.reactnative.sdk.messaginginapp.NativeMessagingInAppModule
15
+ import com.zixflow.reactnative.sdk.messaginginbox.NotificationInboxBellViewManager
16
+ import com.zixflow.reactnative.sdk.messaginginbox.NotificationInboxViewManager
13
17
  import com.zixflow.reactnative.sdk.messagingpush.NativeMessagingPushModule
14
18
  import com.zixflow.reactnative.sdk.util.assertNotNull
15
19
 
@@ -22,7 +26,11 @@ class ZixflowReactNativePackage : BaseReactPackage() {
22
26
  * Creates the list of view managers for the Zixflow React Native SDK.
23
27
  */
24
28
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
25
- return listOf(InlineInAppMessageViewManager())
29
+ return listOf(
30
+ InlineInAppMessageViewManager(),
31
+ NotificationInboxBellViewManager(),
32
+ NotificationInboxViewManager()
33
+ )
26
34
  }
27
35
 
28
36
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
@@ -31,11 +39,23 @@ class ZixflowReactNativePackage : BaseReactPackage() {
31
39
  // See: https://reactnative.dev/docs/fabric-native-components-introduction?platforms=android#4-write-the-reactwebviewpackage
32
40
  return when (name) {
33
41
  InlineInAppMessageViewManager.NAME -> InlineInAppMessageViewManager()
42
+ NotificationInboxBellViewManager.NAME -> NotificationInboxBellViewManager()
43
+ NotificationInboxViewManager.NAME -> NotificationInboxViewManager()
34
44
  NativeZixflowLoggingModule.NAME -> NativeZixflowLoggingModule(reactContext)
35
45
  NativeZixflowModule.NAME -> NativeZixflowModule(reactContext = reactContext)
46
+ // Unconditional on purpose, unlike Location below. Location has its own artifact
47
+ // (com.zixflow.android:location) that the flag switches between `api` and `compileOnly`,
48
+ // so gating it genuinely keeps a dependency off the consumer's classpath. Live
49
+ // Notifications ship inside messaging-push-fcm, already a hard dependency here, so a flag
50
+ // would remove nothing — and the module is inert until `liveNotifications` config enables
51
+ // a type. iOS gates its equivalent only because the subspec pulls in extra pods.
52
+ NativeLiveActivitiesModule.NAME -> NativeLiveActivitiesModule(reactContext)
36
53
  NativeLocationModule.NAME -> if (BuildConfig.ZIXFLOW_LOCATION_ENABLED) {
37
54
  NativeLocationModule(reactContext)
38
55
  } else null
56
+ NativeGeofenceModule.NAME -> if (BuildConfig.ZIXFLOW_GEOFENCE_ENABLED) {
57
+ NativeGeofenceModule(reactContext)
58
+ } else null
39
59
  NativeMessagingInAppModule.NAME -> NativeMessagingInAppModule(reactContext)
40
60
  NativeMessagingPushModule.NAME -> NativeMessagingPushModule(reactContext)
41
61
  else -> assertNotNull<NativeModule>(value = null) { "Unknown module name: $name" }
@@ -69,9 +89,13 @@ class ZixflowReactNativePackage : BaseReactPackage() {
69
89
  // doesn't crash at import time. getModule() returns null when disabled.
70
90
  val moduleNames: List<String> = listOf(
71
91
  InlineInAppMessageViewManager.NAME,
92
+ NotificationInboxBellViewManager.NAME,
93
+ NotificationInboxViewManager.NAME,
72
94
  NativeZixflowLoggingModule.NAME,
73
95
  NativeZixflowModule.NAME,
96
+ NativeLiveActivitiesModule.NAME,
74
97
  NativeLocationModule.NAME,
98
+ NativeGeofenceModule.NAME,
75
99
  NativeMessagingInAppModule.NAME,
76
100
  NativeMessagingPushModule.NAME,
77
101
  )
@@ -12,6 +12,25 @@ internal object Keys {
12
12
  const val SCREEN_VIEW_USE = "screenViewUse"
13
13
  const val API_HOST = "apiHost"
14
14
  const val CDN_HOST = "cdnHost"
15
+ const val NOTIFICATION_INBOX_ACCESSIBILITY_LABELS = "notificationInboxAccessibilityLabels"
16
+ const val COLOR_SCHEME = "colorScheme"
17
+ // Push messaging
15
18
  const val PUSH_CLICK_BEHAVIOR = "pushClickBehavior"
16
19
  }
20
+
21
+ /**
22
+ * Keys of the `notificationInboxAccessibilityLabels` sub-map.
23
+ *
24
+ * [BELL_WITH_UNREAD_COUNT] carries a template string containing [COUNT_PLACEHOLDER] rather than
25
+ * a callback, because wrapper configuration crosses a bridge that carries data but not
26
+ * functions. The native SDK takes a `(Int) -> String`, so the template is converted to one when
27
+ * the module is configured.
28
+ */
29
+ object InboxAccessibilityLabels {
30
+ const val BELL = "bell"
31
+ const val BELL_WITH_UNREAD_COUNT = "bellWithUnreadCount"
32
+ const val LOADING_INDICATOR = "loadingIndicator"
33
+ const val EMPTY_STATE = "emptyState"
34
+ const val COUNT_PLACEHOLDER = "{count}"
35
+ }
17
36
  }
@@ -0,0 +1,65 @@
1
+ package com.zixflow.reactnative.sdk.geofence
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.module.annotations.ReactModule
5
+ import com.zixflow.geofence.GeofenceLocationMode
6
+ import com.zixflow.geofence.GeofenceModuleConfig
7
+ import com.zixflow.geofence.ModuleGeofence
8
+ import com.zixflow.reactnative.sdk.NativeZixflowGeofenceSpec
9
+ import com.zixflow.reactnative.sdk.extension.getTypedValue
10
+ import com.zixflow.sdk.ZixflowBuilder
11
+ import com.zixflow.sdk.core.di.SDKComponent
12
+ import com.zixflow.sdk.core.util.Logger
13
+
14
+ /**
15
+ * React Native module implementation for Zixflow Geofence Native SDK
16
+ * using TurboModules with new architecture.
17
+ *
18
+ * The reference to [ModuleGeofence] is isolated in this file so the geofence dependency is
19
+ * only loaded when the module is enabled and bundled. Geofence depends on the location
20
+ * module, which the caller registers alongside it.
21
+ */
22
+ @ReactModule(name = NativeGeofenceModule.NAME)
23
+ class NativeGeofenceModule(
24
+ private val reactContext: ReactApplicationContext,
25
+ ) : NativeZixflowGeofenceSpec(reactContext) {
26
+ private val logger: Logger
27
+ get() = SDKComponent.logger
28
+
29
+ private fun getModuleGeofence() = runCatching {
30
+ ModuleGeofence.instance()
31
+ }.onFailure {
32
+ logger.error("Geofence module is not initialized. Ensure geofence config is provided during SDK initialization.")
33
+ }.getOrNull()
34
+
35
+ override fun refreshFromCurrentLocation() {
36
+ getModuleGeofence()?.refreshFromCurrentLocation()
37
+ }
38
+
39
+ companion object {
40
+ const val NAME = "NativeZixflowGeofence"
41
+
42
+ /**
43
+ * Adds the geofence module to the native Android SDK based on the configuration
44
+ * provided by the customer app.
45
+ *
46
+ * @param builder instance of ZixflowBuilder to add the geofence module.
47
+ * @param config configuration provided by the customer app for the geofence module.
48
+ */
49
+ internal fun addNativeModuleFromConfig(
50
+ builder: ZixflowBuilder,
51
+ config: Map<String, Any>
52
+ ) {
53
+ val locationModeValue = config.getTypedValue<String>("locationMode")
54
+ // Uppercase before matching so casing can't diverge from iOS (which uppercases too);
55
+ // enumValueOf is case-sensitive. Unknown values fall back to the SDK default.
56
+ val locationMode = locationModeValue?.let { value ->
57
+ runCatching { enumValueOf<GeofenceLocationMode>(value.uppercase()) }.getOrNull()
58
+ }
59
+
60
+ val configBuilder = GeofenceModuleConfig.Builder()
61
+ locationMode?.let { configBuilder.setLocationMode(it) }
62
+ builder.addZixflowModule(ModuleGeofence(configBuilder.build()))
63
+ }
64
+ }
65
+ }
@@ -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
+ }