@billdog.io/react-native 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/BillDogReactNative.podspec +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/android/build.gradle +99 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
  7. package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
  8. package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
  9. package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
  10. package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
  11. package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
  12. package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
  13. package/dist/AdvancedPaywallCache.d.ts +70 -0
  14. package/dist/AdvancedPaywallCache.js +344 -0
  15. package/dist/BillDog.d.ts +255 -0
  16. package/dist/BillDog.js +918 -0
  17. package/dist/BillDogConfiguration.d.ts +145 -0
  18. package/dist/BillDogConfiguration.js +229 -0
  19. package/dist/ComponentRenderer.d.ts +17 -0
  20. package/dist/ComponentRenderer.js +886 -0
  21. package/dist/LayoutEngine.d.ts +101 -0
  22. package/dist/LayoutEngine.js +394 -0
  23. package/dist/LocalizationResolver.d.ts +99 -0
  24. package/dist/LocalizationResolver.js +266 -0
  25. package/dist/NativePaywallView.d.ts +36 -0
  26. package/dist/NativePaywallView.js +59 -0
  27. package/dist/PaywallCache.d.ts +74 -0
  28. package/dist/PaywallCache.js +293 -0
  29. package/dist/PaywallStateManager.d.ts +62 -0
  30. package/dist/PaywallStateManager.js +147 -0
  31. package/dist/PaywallView.d.ts +42 -0
  32. package/dist/PaywallView.js +275 -0
  33. package/dist/ProductVariableProvider.d.ts +60 -0
  34. package/dist/ProductVariableProvider.js +239 -0
  35. package/dist/VariableResolver.d.ts +120 -0
  36. package/dist/VariableResolver.js +379 -0
  37. package/dist/WebViewPaywall.d.ts +75 -0
  38. package/dist/WebViewPaywall.js +360 -0
  39. package/dist/animations/presets.d.ts +49 -0
  40. package/dist/animations/presets.js +367 -0
  41. package/dist/components/AccoladeCpsComponent.d.ts +13 -0
  42. package/dist/components/AccoladeCpsComponent.js +248 -0
  43. package/dist/components/AccordionComponent.d.ts +10 -0
  44. package/dist/components/AccordionComponent.js +187 -0
  45. package/dist/components/AnimatedWrapper.d.ts +35 -0
  46. package/dist/components/AnimatedWrapper.js +277 -0
  47. package/dist/components/CarouselComponent.d.ts +23 -0
  48. package/dist/components/CarouselComponent.js +484 -0
  49. package/dist/components/ChartCurveComponent.d.ts +13 -0
  50. package/dist/components/ChartCurveComponent.js +333 -0
  51. package/dist/components/CheckboxComponent.d.ts +9 -0
  52. package/dist/components/CheckboxComponent.js +172 -0
  53. package/dist/components/ConfettiComponent.d.ts +9 -0
  54. package/dist/components/ConfettiComponent.js +172 -0
  55. package/dist/components/CountdownComponent.d.ts +16 -0
  56. package/dist/components/CountdownComponent.js +414 -0
  57. package/dist/components/DelayedXComponent.d.ts +12 -0
  58. package/dist/components/DelayedXComponent.js +240 -0
  59. package/dist/components/DrawerModalComponent.d.ts +20 -0
  60. package/dist/components/DrawerModalComponent.js +286 -0
  61. package/dist/components/GradientView.d.ts +38 -0
  62. package/dist/components/GradientView.js +126 -0
  63. package/dist/components/HapticFeedbackComponent.d.ts +16 -0
  64. package/dist/components/HapticFeedbackComponent.js +64 -0
  65. package/dist/components/HorizontalProductsComponent.d.ts +17 -0
  66. package/dist/components/HorizontalProductsComponent.js +284 -0
  67. package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
  68. package/dist/components/MultiChoiceGridComponent.js +234 -0
  69. package/dist/components/NPSScaleComponent.d.ts +16 -0
  70. package/dist/components/NPSScaleComponent.js +260 -0
  71. package/dist/components/NavigationComponent.d.ts +26 -0
  72. package/dist/components/NavigationComponent.js +302 -0
  73. package/dist/components/OAuthButtonComponent.d.ts +20 -0
  74. package/dist/components/OAuthButtonComponent.js +237 -0
  75. package/dist/components/ProgressLoaderComponent.d.ts +13 -0
  76. package/dist/components/ProgressLoaderComponent.js +308 -0
  77. package/dist/components/ProgressStepperComponent.d.ts +18 -0
  78. package/dist/components/ProgressStepperComponent.js +228 -0
  79. package/dist/components/SegmentedPickerComponent.d.ts +10 -0
  80. package/dist/components/SegmentedPickerComponent.js +221 -0
  81. package/dist/components/ShareButtonsComponent.d.ts +21 -0
  82. package/dist/components/ShareButtonsComponent.js +265 -0
  83. package/dist/components/ShareCodeComponent.d.ts +16 -0
  84. package/dist/components/ShareCodeComponent.js +274 -0
  85. package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
  86. package/dist/components/SingleChoiceGridComponent.js +210 -0
  87. package/dist/components/SliderComponent.d.ts +9 -0
  88. package/dist/components/SliderComponent.js +263 -0
  89. package/dist/components/StarRatingInputComponent.d.ts +20 -0
  90. package/dist/components/StarRatingInputComponent.js +198 -0
  91. package/dist/components/StickerCompositeComponent.d.ts +13 -0
  92. package/dist/components/StickerCompositeComponent.js +136 -0
  93. package/dist/components/TabsComponent.d.ts +19 -0
  94. package/dist/components/TabsComponent.js +431 -0
  95. package/dist/components/TextInputComponent.d.ts +18 -0
  96. package/dist/components/TextInputComponent.js +227 -0
  97. package/dist/components/TimelineComponent.d.ts +19 -0
  98. package/dist/components/TimelineComponent.js +104 -0
  99. package/dist/components/ToggleComponent.d.ts +14 -0
  100. package/dist/components/ToggleComponent.js +132 -0
  101. package/dist/components/VerticalProductsComponent.d.ts +17 -0
  102. package/dist/components/VerticalProductsComponent.js +311 -0
  103. package/dist/dynamicValues.d.ts +36 -0
  104. package/dist/dynamicValues.js +142 -0
  105. package/dist/filters/dateFilters.d.ts +50 -0
  106. package/dist/filters/dateFilters.js +390 -0
  107. package/dist/filters/index.d.ts +18 -0
  108. package/dist/filters/index.js +253 -0
  109. package/dist/hooks/useOfferings.d.ts +76 -0
  110. package/dist/hooks/useOfferings.js +99 -0
  111. package/dist/hooks/usePaywall.d.ts +27 -0
  112. package/dist/hooks/usePaywall.js +114 -0
  113. package/dist/hooks/usePlacement.d.ts +34 -0
  114. package/dist/hooks/usePlacement.js +89 -0
  115. package/dist/hooks/useSubscription.d.ts +40 -0
  116. package/dist/hooks/useSubscription.js +97 -0
  117. package/dist/index.d.ts +98 -0
  118. package/dist/index.js +198 -0
  119. package/dist/models/BillDogError.d.ts +40 -0
  120. package/dist/models/BillDogError.js +11 -0
  121. package/dist/models/CacheFetchPolicy.d.ts +8 -0
  122. package/dist/models/CacheFetchPolicy.js +4 -0
  123. package/dist/models/CustomProduct.d.ts +32 -0
  124. package/dist/models/CustomProduct.js +15 -0
  125. package/dist/models/CustomerInfo.d.ts +17 -0
  126. package/dist/models/CustomerInfo.js +11 -0
  127. package/dist/models/Delegate.d.ts +20 -0
  128. package/dist/models/Delegate.js +11 -0
  129. package/dist/models/EntitlementInfo.d.ts +19 -0
  130. package/dist/models/EntitlementInfo.js +2 -0
  131. package/dist/models/Placement.d.ts +54 -0
  132. package/dist/models/Placement.js +15 -0
  133. package/dist/models/PurchaseParams.d.ts +39 -0
  134. package/dist/models/PurchaseParams.js +60 -0
  135. package/dist/models/PurchaseResult.d.ts +30 -0
  136. package/dist/models/PurchaseResult.js +15 -0
  137. package/dist/models/RestoreResult.d.ts +30 -0
  138. package/dist/models/RestoreResult.js +22 -0
  139. package/dist/models/RestoredItem.d.ts +26 -0
  140. package/dist/models/RestoredItem.js +13 -0
  141. package/dist/modules/ABTest.d.ts +65 -0
  142. package/dist/modules/ABTest.js +113 -0
  143. package/dist/modules/Analytics.d.ts +91 -0
  144. package/dist/modules/Analytics.js +182 -0
  145. package/dist/modules/Survey.d.ts +65 -0
  146. package/dist/modules/Survey.js +82 -0
  147. package/dist/modules/VirtualCurrency.d.ts +63 -0
  148. package/dist/modules/VirtualCurrency.js +147 -0
  149. package/dist/murmur.d.ts +23 -0
  150. package/dist/murmur.js +104 -0
  151. package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
  152. package/dist/notifications/LiveActivitiesManager.js +106 -0
  153. package/dist/notifications/NotificationScheduler.d.ts +55 -0
  154. package/dist/notifications/NotificationScheduler.js +199 -0
  155. package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
  156. package/dist/notifications/PurchaseNotificationHandler.js +55 -0
  157. package/dist/notifications/useDeviceToken.d.ts +36 -0
  158. package/dist/notifications/useDeviceToken.js +97 -0
  159. package/dist/notifications/useLiveActivity.d.ts +39 -0
  160. package/dist/notifications/useLiveActivity.js +104 -0
  161. package/dist/review/ReviewAnalytics.d.ts +34 -0
  162. package/dist/review/ReviewAnalytics.js +73 -0
  163. package/dist/review/ReviewManager.d.ts +83 -0
  164. package/dist/review/ReviewManager.js +339 -0
  165. package/dist/review/ReviewTimingManager.d.ts +82 -0
  166. package/dist/review/ReviewTimingManager.js +224 -0
  167. package/dist/survey-core/types.d.ts +215 -0
  168. package/dist/survey-core/types.js +8 -0
  169. package/dist/types.d.ts +438 -0
  170. package/dist/types.js +158 -0
  171. package/dist/utils/ApiClient.d.ts +65 -0
  172. package/dist/utils/ApiClient.js +132 -0
  173. package/dist/utils/CSSConverter.d.ts +49 -0
  174. package/dist/utils/CSSConverter.js +470 -0
  175. package/dist/utils/animations.d.ts +41 -0
  176. package/dist/utils/animations.js +194 -0
  177. package/dist/utils/calendarIntegration.d.ts +36 -0
  178. package/dist/utils/calendarIntegration.js +89 -0
  179. package/dist/utils/configurationResolver.d.ts +23 -0
  180. package/dist/utils/configurationResolver.js +78 -0
  181. package/dist/utils/deviceDetection.d.ts +38 -0
  182. package/dist/utils/deviceDetection.js +59 -0
  183. package/dist/utils/hapticFeedback.d.ts +17 -0
  184. package/dist/utils/hapticFeedback.js +53 -0
  185. package/dist/utils/iconMapper.d.ts +26 -0
  186. package/dist/utils/iconMapper.js +113 -0
  187. package/dist/utils/notificationPermissions.d.ts +13 -0
  188. package/dist/utils/notificationPermissions.js +51 -0
  189. package/dist/utils/priceFormatting.d.ts +19 -0
  190. package/dist/utils/priceFormatting.js +99 -0
  191. package/dist/utils/shareSheet.d.ts +17 -0
  192. package/dist/utils/shareSheet.js +42 -0
  193. package/dist/utils/subscriptionUpgrade.d.ts +84 -0
  194. package/dist/utils/subscriptionUpgrade.js +100 -0
  195. package/dist/valueUtils.d.ts +48 -0
  196. package/dist/valueUtils.js +129 -0
  197. package/dist/xFlowTags.d.ts +18 -0
  198. package/dist/xFlowTags.js +385 -0
  199. package/ios/BillDogABTest.m +50 -0
  200. package/ios/BillDogABTest.swift +188 -0
  201. package/ios/BillDogAnalytics.m +105 -0
  202. package/ios/BillDogAnalytics.swift +324 -0
  203. package/ios/BillDogBridge.m +222 -0
  204. package/ios/BillDogBridge.swift +715 -0
  205. package/ios/BillDogLiveActivities.m +41 -0
  206. package/ios/BillDogLiveActivities.swift +104 -0
  207. package/ios/BillDogPaywallViewManager.m +39 -0
  208. package/ios/BillDogPaywallViewManager.swift +361 -0
  209. package/ios/BillDogSurvey.m +34 -0
  210. package/ios/BillDogSurvey.swift +257 -0
  211. package/ios/BillDogVirtualCurrencyBridge.m +63 -0
  212. package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
  213. package/junit.xml +25 -0
  214. package/package.json +61 -0
  215. package/react-native.config.js +13 -0
  216. package/src/AdvancedPaywallCache.ts +433 -0
  217. package/src/BillDog.ts +983 -0
  218. package/src/BillDogConfiguration.ts +318 -0
  219. package/src/ComponentRenderer.tsx +1325 -0
  220. package/src/LayoutEngine.ts +459 -0
  221. package/src/LocalizationResolver.ts +311 -0
  222. package/src/NativePaywallView.tsx +132 -0
  223. package/src/PaywallCache.ts +358 -0
  224. package/src/PaywallStateManager.ts +154 -0
  225. package/src/PaywallView.tsx +328 -0
  226. package/src/ProductVariableProvider.ts +282 -0
  227. package/src/VariableResolver.ts +516 -0
  228. package/src/WebViewPaywall.tsx +454 -0
  229. package/src/animations/presets.ts +421 -0
  230. package/src/components/AccoladeCpsComponent.tsx +259 -0
  231. package/src/components/AccordionComponent.tsx +213 -0
  232. package/src/components/AnimatedWrapper.tsx +316 -0
  233. package/src/components/CarouselComponent.tsx +615 -0
  234. package/src/components/ChartCurveComponent.tsx +452 -0
  235. package/src/components/CheckboxComponent.tsx +185 -0
  236. package/src/components/ConfettiComponent.tsx +178 -0
  237. package/src/components/CountdownComponent.tsx +454 -0
  238. package/src/components/DelayedXComponent.tsx +266 -0
  239. package/src/components/DrawerModalComponent.tsx +324 -0
  240. package/src/components/GradientView.tsx +129 -0
  241. package/src/components/HapticFeedbackComponent.tsx +39 -0
  242. package/src/components/HorizontalProductsComponent.tsx +329 -0
  243. package/src/components/MultiChoiceGridComponent.tsx +265 -0
  244. package/src/components/NPSScaleComponent.tsx +294 -0
  245. package/src/components/NavigationComponent.tsx +329 -0
  246. package/src/components/OAuthButtonComponent.tsx +266 -0
  247. package/src/components/ProgressLoaderComponent.tsx +366 -0
  248. package/src/components/ProgressStepperComponent.tsx +250 -0
  249. package/src/components/SegmentedPickerComponent.tsx +245 -0
  250. package/src/components/ShareButtonsComponent.tsx +299 -0
  251. package/src/components/ShareCodeComponent.tsx +303 -0
  252. package/src/components/SingleChoiceGridComponent.tsx +229 -0
  253. package/src/components/SliderComponent.tsx +280 -0
  254. package/src/components/StarRatingInputComponent.tsx +223 -0
  255. package/src/components/StickerCompositeComponent.tsx +168 -0
  256. package/src/components/TabsComponent.tsx +496 -0
  257. package/src/components/TextInputComponent.tsx +258 -0
  258. package/src/components/TimelineComponent.tsx +129 -0
  259. package/src/components/ToggleComponent.tsx +142 -0
  260. package/src/components/VerticalProductsComponent.tsx +357 -0
  261. package/src/dynamicValues.ts +180 -0
  262. package/src/filters/dateFilters.ts +411 -0
  263. package/src/filters/index.ts +281 -0
  264. package/src/hooks/useOfferings.ts +192 -0
  265. package/src/hooks/usePaywall.ts +154 -0
  266. package/src/hooks/usePlacement.ts +143 -0
  267. package/src/hooks/useSubscription.ts +154 -0
  268. package/src/index.ts +245 -0
  269. package/src/models/BillDogError.ts +24 -0
  270. package/src/models/CacheFetchPolicy.ts +13 -0
  271. package/src/models/CustomProduct.ts +33 -0
  272. package/src/models/CustomerInfo.ts +26 -0
  273. package/src/models/Delegate.ts +22 -0
  274. package/src/models/EntitlementInfo.ts +27 -0
  275. package/src/models/Placement.ts +50 -0
  276. package/src/models/PurchaseParams.ts +81 -0
  277. package/src/models/PurchaseResult.ts +32 -0
  278. package/src/models/RestoreResult.ts +27 -0
  279. package/src/models/RestoredItem.ts +25 -0
  280. package/src/modules/ABTest.ts +131 -0
  281. package/src/modules/Analytics.ts +227 -0
  282. package/src/modules/Survey.ts +126 -0
  283. package/src/modules/VirtualCurrency.ts +223 -0
  284. package/src/murmur.ts +102 -0
  285. package/src/notifications/LiveActivitiesManager.ts +140 -0
  286. package/src/notifications/NotificationScheduler.ts +292 -0
  287. package/src/notifications/PurchaseNotificationHandler.ts +67 -0
  288. package/src/notifications/useDeviceToken.ts +116 -0
  289. package/src/notifications/useLiveActivity.ts +125 -0
  290. package/src/review/ReviewAnalytics.ts +89 -0
  291. package/src/review/ReviewManager.ts +355 -0
  292. package/src/review/ReviewTimingManager.ts +265 -0
  293. package/src/survey-core/types.ts +244 -0
  294. package/src/types/ambient.d.ts +9 -0
  295. package/src/types.ts +680 -0
  296. package/src/utils/ApiClient.ts +163 -0
  297. package/src/utils/CSSConverter.ts +552 -0
  298. package/src/utils/animations.ts +232 -0
  299. package/src/utils/calendarIntegration.ts +113 -0
  300. package/src/utils/configurationResolver.ts +106 -0
  301. package/src/utils/deviceDetection.ts +83 -0
  302. package/src/utils/hapticFeedback.ts +55 -0
  303. package/src/utils/iconMapper.ts +102 -0
  304. package/src/utils/notificationPermissions.ts +48 -0
  305. package/src/utils/priceFormatting.ts +119 -0
  306. package/src/utils/shareSheet.ts +55 -0
  307. package/src/utils/subscriptionUpgrade.ts +183 -0
  308. package/src/valueUtils.ts +108 -0
  309. package/src/xFlowTags.ts +463 -0
@@ -0,0 +1,383 @@
1
+ package io.billdog.reactnative
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // NOTE ON VERIFICATION
5
+ // This file is compile-ready and internally consistent, but there is NO Android
6
+ // toolchain in the authoring environment. It MUST be built via the react-native
7
+ // example app's Gradle/Android build (`./gradlew :billdog_reactnative:assembleDebug`
8
+ // / a full example RN Android build) to confirm it compiles and that the Compose
9
+ // ViewTree owner wiring behaves correctly inside a React Native (Paper) host view.
10
+ //
11
+ // This is the Paper / legacy (SimpleViewManager, NON-Fabric) implementation of the
12
+ // inline `BDPaywallView`. It is a direct port of the Flutter inline-paywall factory:
13
+ // packages/flutter/android/src/main/kotlin/io/billdog/flutter/BillDogPaywallViewFactory.kt
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ import android.app.Activity
17
+ import android.util.Log
18
+ import android.widget.FrameLayout
19
+ import androidx.compose.ui.platform.ComposeView
20
+ import androidx.compose.ui.platform.ViewCompositionStrategy
21
+ import androidx.lifecycle.LifecycleOwner
22
+ import androidx.lifecycle.ViewModelStoreOwner
23
+ import androidx.lifecycle.setViewTreeLifecycleOwner
24
+ import androidx.lifecycle.setViewTreeViewModelStoreOwner
25
+ import androidx.savedstate.SavedStateRegistryOwner
26
+ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
27
+ import com.facebook.react.bridge.Arguments
28
+ import com.facebook.react.bridge.WritableArray
29
+ import com.facebook.react.bridge.WritableMap
30
+ import com.facebook.react.uimanager.SimpleViewManager
31
+ import com.facebook.react.uimanager.ThemedReactContext
32
+ import com.facebook.react.uimanager.annotations.ReactProp
33
+ import com.facebook.react.uimanager.events.RCTEventEmitter
34
+ import io.billdog.compose.ui.PaywallScreen
35
+ import io.billdog.core.BillDog
36
+ import io.billdog.core.PurchaseResult
37
+ import io.billdog.core.models.PaywallConfiguration
38
+ import io.billdog.core.oauth.OAuthResult
39
+ import kotlinx.coroutines.CoroutineScope
40
+ import kotlinx.coroutines.Dispatchers
41
+ import kotlinx.coroutines.SupervisorJob
42
+ import kotlinx.coroutines.cancel
43
+ import kotlinx.coroutines.launch
44
+
45
+ /**
46
+ * React Native (Paper / legacy) view manager for the inline BillDog paywall.
47
+ *
48
+ * Exposed to JS as `requireNativeComponent('BDPaywallView')`. Each view instance is
49
+ * a [FrameLayout] hosting a Jetpack Compose [ComposeView] that renders
50
+ * [PaywallScreen]. A single manager instance serves every mounted view, so all
51
+ * per-view state (the [ThemedReactContext], the [ComposeView], and the fetch/
52
+ * purchase [CoroutineScope]) is keyed off the view in [states].
53
+ *
54
+ * Native → JS events are delivered as custom *bubbling* events via
55
+ * `RCTEventEmitter.receiveEvent(viewId, name, payload)`. The payload shapes match
56
+ * the Flutter factory (onPurchase {productId, data}, onError {message},
57
+ * onTrackEvent {event, data}).
58
+ */
59
+ class BillDogPaywallViewManager : SimpleViewManager<FrameLayout>() {
60
+
61
+ override fun getName(): String = REACT_CLASS
62
+
63
+ // ── Per-view state ───────────────────────────────────────────────────────
64
+ // One manager, many views: hold each view's context / ComposeView / scope here
65
+ // and tear it down in onDropViewInstance.
66
+ private class ViewState(
67
+ val reactContext: ThemedReactContext,
68
+ val composeView: ComposeView,
69
+ ) {
70
+ // Scope for the paywall fetch + all subsequent purchase coroutines
71
+ // (Flutter parity: BillDogPaywallPlatformView.scope, factory:96). Cancelled
72
+ // in onDropViewInstance.
73
+ val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
74
+
75
+ // Guard so a repeated prop update with the same identifier loads once.
76
+ var loadedIdentifier: String? = null
77
+ }
78
+
79
+ private val states = mutableMapOf<FrameLayout, ViewState>()
80
+
81
+ // ── View lifecycle ────────────────────────────────────────────────────────
82
+
83
+ override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
84
+ val frame = FrameLayout(reactContext)
85
+ val composeView = ComposeView(reactContext).apply {
86
+ // Flutter parity: dispose when detached (factory:101-103).
87
+ setViewCompositionStrategy(
88
+ ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool
89
+ )
90
+ layoutParams = FrameLayout.LayoutParams(
91
+ FrameLayout.LayoutParams.MATCH_PARENT,
92
+ FrameLayout.LayoutParams.MATCH_PARENT
93
+ )
94
+ }
95
+ frame.addView(composeView)
96
+ states[frame] = ViewState(reactContext, composeView)
97
+ return frame
98
+ }
99
+
100
+ override fun onDropViewInstance(view: FrameLayout) {
101
+ super.onDropViewInstance(view)
102
+ // Flutter parity: dispose() → scope.cancel() + composeView.disposeComposition()
103
+ // (factory:275-278).
104
+ states.remove(view)?.let { state ->
105
+ state.scope.cancel()
106
+ state.composeView.disposeComposition()
107
+ }
108
+ }
109
+
110
+ // ── Props ──────────────────────────────────────────────────────────────────
111
+
112
+ /**
113
+ * `paywallIdentifier` prop. Once non-empty (and not already loading the same id),
114
+ * install the Compose ViewTree owners from the host Activity and fetch + render
115
+ * the paywall. Mirrors BillDogPaywallPlatformView.init + loadPaywall (factory:100-160).
116
+ */
117
+ @ReactProp(name = "paywallIdentifier")
118
+ fun setPaywallIdentifier(view: FrameLayout, identifier: String?) {
119
+ val id = identifier?.takeIf { it.isNotEmpty() } ?: return
120
+ val state = states[view] ?: return
121
+ if (state.loadedIdentifier == id) return
122
+ state.loadedIdentifier = id
123
+
124
+ // A ComposeView hosted inside a RN Paper view hierarchy is NOT attached under
125
+ // a ComponentActivity's content view, so it lacks the ViewTree lifecycle /
126
+ // savedState / viewModelStore owners Compose requires. Borrow them from the
127
+ // host Activity (a ComponentActivity implements all three) — Flutter parity
128
+ // (factory:105-129). We also need the Activity for BillDog.purchase(...).
129
+ val activity: Activity? = state.reactContext.currentActivity
130
+ if (activity == null) {
131
+ emit(view, "onError", mapOf("message" to "No Activity available to host the paywall"))
132
+ return
133
+ }
134
+ if (activity is LifecycleOwner &&
135
+ activity is SavedStateRegistryOwner &&
136
+ activity is ViewModelStoreOwner
137
+ ) {
138
+ state.composeView.setViewTreeLifecycleOwner(activity)
139
+ state.composeView.setViewTreeSavedStateRegistryOwner(activity)
140
+ state.composeView.setViewTreeViewModelStoreOwner(activity)
141
+ } else {
142
+ Log.w(
143
+ TAG,
144
+ "Host Activity is not a ComponentActivity (LifecycleOwner/" +
145
+ "SavedStateRegistryOwner/ViewModelStoreOwner); Compose may fail to attach."
146
+ )
147
+ }
148
+
149
+ state.scope.launch {
150
+ try {
151
+ // Fetch the paywall config WITHOUT presenting it, so we can host it in
152
+ // this inline container. BillDog.getPaywall is `suspend` and returns a
153
+ // PaywallConfiguration (BillDog.kt:655) — Flutter parity (factory:140).
154
+ val config: PaywallConfiguration = BillDog.getPaywall(id)
155
+
156
+ // PaywallScreen composable — signature at
157
+ // billdog-compose/src/main/java/io/billdog/compose/ui/PaywallScreen.kt:335.
158
+ // onPurchase: (String) -> Unit yields a product id that may be a raw
159
+ // productId or a component ("comp-") id. Flutter parity (factory:146-153).
160
+ state.composeView.setContent {
161
+ PaywallScreen(
162
+ configuration = config,
163
+ onDismiss = { emit(view, "onDismiss", null) },
164
+ onPurchase = { productId -> handlePurchase(view, state, activity, productId) },
165
+ onOAuthResult = { result -> handleOAuth(view, result) }
166
+ )
167
+ }
168
+ emit(view, "onLoaded", null)
169
+ } catch (e: Exception) {
170
+ Log.e(TAG, "getPaywall('$id') failed", e)
171
+ emit(view, "onError", mapOf("message" to (e.message ?: "Failed to load paywall")))
172
+ }
173
+ }
174
+ }
175
+
176
+ // ── Purchase routing ─────────────────────────────────────────────────────
177
+
178
+ /**
179
+ * Resolve `rawId` (possibly a `comp-` component id or blank) to a real store
180
+ * product id, mirroring PaywallActivity.resolveProductIdentifier (PaywallActivity.kt:571)
181
+ * and the Flutter factory (factory:169-178). No Offering exists on this inline
182
+ * path (getPaywall returns config only), so we rely on the static resolution
183
+ * bridges the renderer populates during selection.
184
+ */
185
+ private fun resolveProductIdentifier(rawId: String): String? {
186
+ // 1. componentId → productIdentifier registry (PaywallActivity.kt:573).
187
+ io.billdog.core.ProductSelectionBridge.resolveProductId(rawId)?.let { return it }
188
+ // 2. Live selection recorded by the renderer (PaywallActivity.kt:580).
189
+ io.billdog.compose.ui.PaywallStateManager
190
+ .getState<String>("selected_product_id")
191
+ ?.takeIf { it.isNotEmpty() }
192
+ ?.let { return it }
193
+ return null
194
+ }
195
+
196
+ /**
197
+ * Purchase routing — mirrors PaywallActivity.handlePurchase (PaywallActivity.kt:627)
198
+ * and the Flutter factory (factory:188-243). A `comp-`-prefixed or blank id is
199
+ * resolved to a real store product id first; a concrete id is used as-is. The
200
+ * purchase runs through the Activity-aware entry point
201
+ * BillDog.purchase(activity, productId) (BillDog.kt:700, suspend), and the outcome
202
+ * is forwarded to JS as an `onPurchase {productId, data}` event.
203
+ */
204
+ private fun handlePurchase(
205
+ view: FrameLayout,
206
+ state: ViewState,
207
+ activity: Activity,
208
+ productId: String
209
+ ) {
210
+ val resolvedProductId = if (productId.startsWith("comp-") || productId.isBlank()) {
211
+ val resolved = resolveProductIdentifier(productId)
212
+ if (resolved == null) {
213
+ Log.e(TAG, "Cannot resolve product identifier from '$productId'")
214
+ emit(view, "onError", mapOf("message" to "Unable to identify product for purchase"))
215
+ return
216
+ }
217
+ resolved
218
+ } else {
219
+ productId
220
+ }
221
+
222
+ state.scope.launch {
223
+ try {
224
+ // BillDog.purchase(activity, productId) — BillDog.kt:700 (suspend).
225
+ val result = BillDog.purchase(activity, resolvedProductId)
226
+ val data: Map<String, Any?> = when (result) {
227
+ is PurchaseResult.Purchased -> mapOf(
228
+ "status" to "purchased",
229
+ "transactionId" to result.transactionId,
230
+ "originalTransactionId" to result.originalTransactionId
231
+ )
232
+ is PurchaseResult.Restored -> mapOf(
233
+ "status" to "restored",
234
+ "transactionId" to result.transactionId,
235
+ "originalTransactionId" to result.originalTransactionId
236
+ )
237
+ is PurchaseResult.Cancelled -> mapOf("status" to "cancelled")
238
+ is PurchaseResult.Pending -> mapOf("status" to "pending")
239
+ is PurchaseResult.Failed -> mapOf(
240
+ "status" to "failed",
241
+ "error" to result.error.message
242
+ )
243
+ }
244
+ val effectiveProductId = when (result) {
245
+ is PurchaseResult.Purchased -> result.productId
246
+ is PurchaseResult.Restored -> result.productId
247
+ else -> resolvedProductId
248
+ }
249
+ emit(
250
+ view,
251
+ "onPurchase",
252
+ mapOf("productId" to effectiveProductId, "data" to data)
253
+ )
254
+ } catch (e: Exception) {
255
+ Log.e(TAG, "purchase('$resolvedProductId') threw", e)
256
+ emit(view, "onError", mapOf("message" to (e.message ?: "Purchase error")))
257
+ }
258
+ }
259
+ }
260
+
261
+ /**
262
+ * OAuth result routing. PaywallScreen's `onOAuthResult` (PaywallScreen.kt:341)
263
+ * delivers an [OAuthResult] (fields at OAuthManager.kt). We surface a successful
264
+ * sign-in as a tracked event onto the JS side; failures are logged. Flutter
265
+ * parity (factory:250-266).
266
+ */
267
+ private fun handleOAuth(view: FrameLayout, result: OAuthResult) {
268
+ Log.d(TAG, "OAuth result: provider=${result.provider}, success=${result.success}")
269
+ if (result.success) {
270
+ emit(
271
+ view,
272
+ "onTrackEvent",
273
+ mapOf(
274
+ "event" to "oauth_sign_in",
275
+ "data" to mapOf(
276
+ "provider" to result.provider.name,
277
+ "userId" to result.userId,
278
+ "email" to result.email,
279
+ "name" to result.name
280
+ )
281
+ )
282
+ )
283
+ }
284
+ }
285
+
286
+ // ── Events ───────────────────────────────────────────────────────────────
287
+
288
+ /**
289
+ * Declare the custom bubbling events so RN's UIManager will forward
290
+ * `on<Event>` props from JSX onto the native `receiveEvent` name.
291
+ */
292
+ override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
293
+ val constants = mutableMapOf<String, Any>()
294
+ for (event in EVENTS) {
295
+ constants[event] = mapOf(
296
+ "phasedRegistrationNames" to mapOf("bubbled" to event)
297
+ )
298
+ }
299
+ return constants
300
+ }
301
+
302
+ /**
303
+ * Dispatch a native → JS bubbling event for [view]. The [payload] map (or null)
304
+ * is converted to a [WritableMap] and delivered via
305
+ * `RCTEventEmitter.receiveEvent(viewId, name, payload)`.
306
+ */
307
+ private fun emit(view: FrameLayout, name: String, payload: Map<String, Any?>?) {
308
+ val reactContext = states[view]?.reactContext ?: return
309
+ val writable: WritableMap = payload?.let { toWritableMap(it) } ?: Arguments.createMap()
310
+ reactContext.getJSModule(RCTEventEmitter::class.java)
311
+ .receiveEvent(view.id, name, writable)
312
+ }
313
+
314
+ // ── Map<String, Any?> → WritableMap converter ────────────────────────────
315
+ // Copied from BillDogModule.kt:729 — RN's Writable* types are the only values
316
+ // the bridge can send to JS. Recursively converts plain Kotlin maps/lists/scalars.
317
+ // Longs/Floats are widened to Double (JS number).
318
+
319
+ private fun toWritableMap(map: Map<String, Any?>): WritableMap {
320
+ val out = Arguments.createMap()
321
+ for ((key, value) in map) {
322
+ when (value) {
323
+ null -> out.putNull(key)
324
+ is Boolean -> out.putBoolean(key, value)
325
+ is Int -> out.putInt(key, value)
326
+ is Long -> out.putDouble(key, value.toDouble())
327
+ is Float -> out.putDouble(key, value.toDouble())
328
+ is Double -> out.putDouble(key, value)
329
+ is String -> out.putString(key, value)
330
+ is WritableMap -> out.putMap(key, value)
331
+ is WritableArray -> out.putArray(key, value)
332
+ is Map<*, *> -> out.putMap(key, toWritableMap(coerceStringKeys(value)))
333
+ is List<*> -> out.putArray(key, toWritableArray(value))
334
+ else -> out.putString(key, value.toString())
335
+ }
336
+ }
337
+ return out
338
+ }
339
+
340
+ private fun toWritableArray(list: List<*>): WritableArray {
341
+ val out = Arguments.createArray()
342
+ for (value in list) {
343
+ when (value) {
344
+ null -> out.pushNull()
345
+ is Boolean -> out.pushBoolean(value)
346
+ is Int -> out.pushInt(value)
347
+ is Long -> out.pushDouble(value.toDouble())
348
+ is Float -> out.pushDouble(value.toDouble())
349
+ is Double -> out.pushDouble(value)
350
+ is String -> out.pushString(value)
351
+ is WritableMap -> out.pushMap(value)
352
+ is WritableArray -> out.pushArray(value)
353
+ is Map<*, *> -> out.pushMap(toWritableMap(coerceStringKeys(value)))
354
+ is List<*> -> out.pushArray(toWritableArray(value))
355
+ else -> out.pushString(value.toString())
356
+ }
357
+ }
358
+ return out
359
+ }
360
+
361
+ private fun coerceStringKeys(map: Map<*, *>): Map<String, Any?> =
362
+ map.entries.associate { (k, v) -> k.toString() to v }
363
+
364
+ companion object {
365
+ private const val TAG = "BillDogPaywallView"
366
+ private const val REACT_CLASS = "BDPaywallView"
367
+
368
+ // Custom bubbling events surfaced to JS. onDismiss/onPurchase/onLoaded/onError/
369
+ // onTrackEvent are emitted by this port (matching the Flutter factory).
370
+ // onRestore/onOpenUrl are declared for JS-prop parity; PaywallScreen surfaces a
371
+ // restore as an `onPurchase` with a "restored"/"__restore__" outcome, so no
372
+ // separate native callback exists for them on this inline path.
373
+ private val EVENTS = listOf(
374
+ "onLoaded",
375
+ "onError",
376
+ "onPurchase",
377
+ "onDismiss",
378
+ "onRestore",
379
+ "onOpenUrl",
380
+ "onTrackEvent"
381
+ )
382
+ }
383
+ }