@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.
- package/BillDogReactNative.podspec +30 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/android/build.gradle +99 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
- package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
- package/dist/AdvancedPaywallCache.d.ts +70 -0
- package/dist/AdvancedPaywallCache.js +344 -0
- package/dist/BillDog.d.ts +255 -0
- package/dist/BillDog.js +918 -0
- package/dist/BillDogConfiguration.d.ts +145 -0
- package/dist/BillDogConfiguration.js +229 -0
- package/dist/ComponentRenderer.d.ts +17 -0
- package/dist/ComponentRenderer.js +886 -0
- package/dist/LayoutEngine.d.ts +101 -0
- package/dist/LayoutEngine.js +394 -0
- package/dist/LocalizationResolver.d.ts +99 -0
- package/dist/LocalizationResolver.js +266 -0
- package/dist/NativePaywallView.d.ts +36 -0
- package/dist/NativePaywallView.js +59 -0
- package/dist/PaywallCache.d.ts +74 -0
- package/dist/PaywallCache.js +293 -0
- package/dist/PaywallStateManager.d.ts +62 -0
- package/dist/PaywallStateManager.js +147 -0
- package/dist/PaywallView.d.ts +42 -0
- package/dist/PaywallView.js +275 -0
- package/dist/ProductVariableProvider.d.ts +60 -0
- package/dist/ProductVariableProvider.js +239 -0
- package/dist/VariableResolver.d.ts +120 -0
- package/dist/VariableResolver.js +379 -0
- package/dist/WebViewPaywall.d.ts +75 -0
- package/dist/WebViewPaywall.js +360 -0
- package/dist/animations/presets.d.ts +49 -0
- package/dist/animations/presets.js +367 -0
- package/dist/components/AccoladeCpsComponent.d.ts +13 -0
- package/dist/components/AccoladeCpsComponent.js +248 -0
- package/dist/components/AccordionComponent.d.ts +10 -0
- package/dist/components/AccordionComponent.js +187 -0
- package/dist/components/AnimatedWrapper.d.ts +35 -0
- package/dist/components/AnimatedWrapper.js +277 -0
- package/dist/components/CarouselComponent.d.ts +23 -0
- package/dist/components/CarouselComponent.js +484 -0
- package/dist/components/ChartCurveComponent.d.ts +13 -0
- package/dist/components/ChartCurveComponent.js +333 -0
- package/dist/components/CheckboxComponent.d.ts +9 -0
- package/dist/components/CheckboxComponent.js +172 -0
- package/dist/components/ConfettiComponent.d.ts +9 -0
- package/dist/components/ConfettiComponent.js +172 -0
- package/dist/components/CountdownComponent.d.ts +16 -0
- package/dist/components/CountdownComponent.js +414 -0
- package/dist/components/DelayedXComponent.d.ts +12 -0
- package/dist/components/DelayedXComponent.js +240 -0
- package/dist/components/DrawerModalComponent.d.ts +20 -0
- package/dist/components/DrawerModalComponent.js +286 -0
- package/dist/components/GradientView.d.ts +38 -0
- package/dist/components/GradientView.js +126 -0
- package/dist/components/HapticFeedbackComponent.d.ts +16 -0
- package/dist/components/HapticFeedbackComponent.js +64 -0
- package/dist/components/HorizontalProductsComponent.d.ts +17 -0
- package/dist/components/HorizontalProductsComponent.js +284 -0
- package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
- package/dist/components/MultiChoiceGridComponent.js +234 -0
- package/dist/components/NPSScaleComponent.d.ts +16 -0
- package/dist/components/NPSScaleComponent.js +260 -0
- package/dist/components/NavigationComponent.d.ts +26 -0
- package/dist/components/NavigationComponent.js +302 -0
- package/dist/components/OAuthButtonComponent.d.ts +20 -0
- package/dist/components/OAuthButtonComponent.js +237 -0
- package/dist/components/ProgressLoaderComponent.d.ts +13 -0
- package/dist/components/ProgressLoaderComponent.js +308 -0
- package/dist/components/ProgressStepperComponent.d.ts +18 -0
- package/dist/components/ProgressStepperComponent.js +228 -0
- package/dist/components/SegmentedPickerComponent.d.ts +10 -0
- package/dist/components/SegmentedPickerComponent.js +221 -0
- package/dist/components/ShareButtonsComponent.d.ts +21 -0
- package/dist/components/ShareButtonsComponent.js +265 -0
- package/dist/components/ShareCodeComponent.d.ts +16 -0
- package/dist/components/ShareCodeComponent.js +274 -0
- package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
- package/dist/components/SingleChoiceGridComponent.js +210 -0
- package/dist/components/SliderComponent.d.ts +9 -0
- package/dist/components/SliderComponent.js +263 -0
- package/dist/components/StarRatingInputComponent.d.ts +20 -0
- package/dist/components/StarRatingInputComponent.js +198 -0
- package/dist/components/StickerCompositeComponent.d.ts +13 -0
- package/dist/components/StickerCompositeComponent.js +136 -0
- package/dist/components/TabsComponent.d.ts +19 -0
- package/dist/components/TabsComponent.js +431 -0
- package/dist/components/TextInputComponent.d.ts +18 -0
- package/dist/components/TextInputComponent.js +227 -0
- package/dist/components/TimelineComponent.d.ts +19 -0
- package/dist/components/TimelineComponent.js +104 -0
- package/dist/components/ToggleComponent.d.ts +14 -0
- package/dist/components/ToggleComponent.js +132 -0
- package/dist/components/VerticalProductsComponent.d.ts +17 -0
- package/dist/components/VerticalProductsComponent.js +311 -0
- package/dist/dynamicValues.d.ts +36 -0
- package/dist/dynamicValues.js +142 -0
- package/dist/filters/dateFilters.d.ts +50 -0
- package/dist/filters/dateFilters.js +390 -0
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +253 -0
- package/dist/hooks/useOfferings.d.ts +76 -0
- package/dist/hooks/useOfferings.js +99 -0
- package/dist/hooks/usePaywall.d.ts +27 -0
- package/dist/hooks/usePaywall.js +114 -0
- package/dist/hooks/usePlacement.d.ts +34 -0
- package/dist/hooks/usePlacement.js +89 -0
- package/dist/hooks/useSubscription.d.ts +40 -0
- package/dist/hooks/useSubscription.js +97 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +198 -0
- package/dist/models/BillDogError.d.ts +40 -0
- package/dist/models/BillDogError.js +11 -0
- package/dist/models/CacheFetchPolicy.d.ts +8 -0
- package/dist/models/CacheFetchPolicy.js +4 -0
- package/dist/models/CustomProduct.d.ts +32 -0
- package/dist/models/CustomProduct.js +15 -0
- package/dist/models/CustomerInfo.d.ts +17 -0
- package/dist/models/CustomerInfo.js +11 -0
- package/dist/models/Delegate.d.ts +20 -0
- package/dist/models/Delegate.js +11 -0
- package/dist/models/EntitlementInfo.d.ts +19 -0
- package/dist/models/EntitlementInfo.js +2 -0
- package/dist/models/Placement.d.ts +54 -0
- package/dist/models/Placement.js +15 -0
- package/dist/models/PurchaseParams.d.ts +39 -0
- package/dist/models/PurchaseParams.js +60 -0
- package/dist/models/PurchaseResult.d.ts +30 -0
- package/dist/models/PurchaseResult.js +15 -0
- package/dist/models/RestoreResult.d.ts +30 -0
- package/dist/models/RestoreResult.js +22 -0
- package/dist/models/RestoredItem.d.ts +26 -0
- package/dist/models/RestoredItem.js +13 -0
- package/dist/modules/ABTest.d.ts +65 -0
- package/dist/modules/ABTest.js +113 -0
- package/dist/modules/Analytics.d.ts +91 -0
- package/dist/modules/Analytics.js +182 -0
- package/dist/modules/Survey.d.ts +65 -0
- package/dist/modules/Survey.js +82 -0
- package/dist/modules/VirtualCurrency.d.ts +63 -0
- package/dist/modules/VirtualCurrency.js +147 -0
- package/dist/murmur.d.ts +23 -0
- package/dist/murmur.js +104 -0
- package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
- package/dist/notifications/LiveActivitiesManager.js +106 -0
- package/dist/notifications/NotificationScheduler.d.ts +55 -0
- package/dist/notifications/NotificationScheduler.js +199 -0
- package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
- package/dist/notifications/PurchaseNotificationHandler.js +55 -0
- package/dist/notifications/useDeviceToken.d.ts +36 -0
- package/dist/notifications/useDeviceToken.js +97 -0
- package/dist/notifications/useLiveActivity.d.ts +39 -0
- package/dist/notifications/useLiveActivity.js +104 -0
- package/dist/review/ReviewAnalytics.d.ts +34 -0
- package/dist/review/ReviewAnalytics.js +73 -0
- package/dist/review/ReviewManager.d.ts +83 -0
- package/dist/review/ReviewManager.js +339 -0
- package/dist/review/ReviewTimingManager.d.ts +82 -0
- package/dist/review/ReviewTimingManager.js +224 -0
- package/dist/survey-core/types.d.ts +215 -0
- package/dist/survey-core/types.js +8 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.js +158 -0
- package/dist/utils/ApiClient.d.ts +65 -0
- package/dist/utils/ApiClient.js +132 -0
- package/dist/utils/CSSConverter.d.ts +49 -0
- package/dist/utils/CSSConverter.js +470 -0
- package/dist/utils/animations.d.ts +41 -0
- package/dist/utils/animations.js +194 -0
- package/dist/utils/calendarIntegration.d.ts +36 -0
- package/dist/utils/calendarIntegration.js +89 -0
- package/dist/utils/configurationResolver.d.ts +23 -0
- package/dist/utils/configurationResolver.js +78 -0
- package/dist/utils/deviceDetection.d.ts +38 -0
- package/dist/utils/deviceDetection.js +59 -0
- package/dist/utils/hapticFeedback.d.ts +17 -0
- package/dist/utils/hapticFeedback.js +53 -0
- package/dist/utils/iconMapper.d.ts +26 -0
- package/dist/utils/iconMapper.js +113 -0
- package/dist/utils/notificationPermissions.d.ts +13 -0
- package/dist/utils/notificationPermissions.js +51 -0
- package/dist/utils/priceFormatting.d.ts +19 -0
- package/dist/utils/priceFormatting.js +99 -0
- package/dist/utils/shareSheet.d.ts +17 -0
- package/dist/utils/shareSheet.js +42 -0
- package/dist/utils/subscriptionUpgrade.d.ts +84 -0
- package/dist/utils/subscriptionUpgrade.js +100 -0
- package/dist/valueUtils.d.ts +48 -0
- package/dist/valueUtils.js +129 -0
- package/dist/xFlowTags.d.ts +18 -0
- package/dist/xFlowTags.js +385 -0
- package/ios/BillDogABTest.m +50 -0
- package/ios/BillDogABTest.swift +188 -0
- package/ios/BillDogAnalytics.m +105 -0
- package/ios/BillDogAnalytics.swift +324 -0
- package/ios/BillDogBridge.m +222 -0
- package/ios/BillDogBridge.swift +715 -0
- package/ios/BillDogLiveActivities.m +41 -0
- package/ios/BillDogLiveActivities.swift +104 -0
- package/ios/BillDogPaywallViewManager.m +39 -0
- package/ios/BillDogPaywallViewManager.swift +361 -0
- package/ios/BillDogSurvey.m +34 -0
- package/ios/BillDogSurvey.swift +257 -0
- package/ios/BillDogVirtualCurrencyBridge.m +63 -0
- package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
- package/junit.xml +25 -0
- package/package.json +61 -0
- package/react-native.config.js +13 -0
- package/src/AdvancedPaywallCache.ts +433 -0
- package/src/BillDog.ts +983 -0
- package/src/BillDogConfiguration.ts +318 -0
- package/src/ComponentRenderer.tsx +1325 -0
- package/src/LayoutEngine.ts +459 -0
- package/src/LocalizationResolver.ts +311 -0
- package/src/NativePaywallView.tsx +132 -0
- package/src/PaywallCache.ts +358 -0
- package/src/PaywallStateManager.ts +154 -0
- package/src/PaywallView.tsx +328 -0
- package/src/ProductVariableProvider.ts +282 -0
- package/src/VariableResolver.ts +516 -0
- package/src/WebViewPaywall.tsx +454 -0
- package/src/animations/presets.ts +421 -0
- package/src/components/AccoladeCpsComponent.tsx +259 -0
- package/src/components/AccordionComponent.tsx +213 -0
- package/src/components/AnimatedWrapper.tsx +316 -0
- package/src/components/CarouselComponent.tsx +615 -0
- package/src/components/ChartCurveComponent.tsx +452 -0
- package/src/components/CheckboxComponent.tsx +185 -0
- package/src/components/ConfettiComponent.tsx +178 -0
- package/src/components/CountdownComponent.tsx +454 -0
- package/src/components/DelayedXComponent.tsx +266 -0
- package/src/components/DrawerModalComponent.tsx +324 -0
- package/src/components/GradientView.tsx +129 -0
- package/src/components/HapticFeedbackComponent.tsx +39 -0
- package/src/components/HorizontalProductsComponent.tsx +329 -0
- package/src/components/MultiChoiceGridComponent.tsx +265 -0
- package/src/components/NPSScaleComponent.tsx +294 -0
- package/src/components/NavigationComponent.tsx +329 -0
- package/src/components/OAuthButtonComponent.tsx +266 -0
- package/src/components/ProgressLoaderComponent.tsx +366 -0
- package/src/components/ProgressStepperComponent.tsx +250 -0
- package/src/components/SegmentedPickerComponent.tsx +245 -0
- package/src/components/ShareButtonsComponent.tsx +299 -0
- package/src/components/ShareCodeComponent.tsx +303 -0
- package/src/components/SingleChoiceGridComponent.tsx +229 -0
- package/src/components/SliderComponent.tsx +280 -0
- package/src/components/StarRatingInputComponent.tsx +223 -0
- package/src/components/StickerCompositeComponent.tsx +168 -0
- package/src/components/TabsComponent.tsx +496 -0
- package/src/components/TextInputComponent.tsx +258 -0
- package/src/components/TimelineComponent.tsx +129 -0
- package/src/components/ToggleComponent.tsx +142 -0
- package/src/components/VerticalProductsComponent.tsx +357 -0
- package/src/dynamicValues.ts +180 -0
- package/src/filters/dateFilters.ts +411 -0
- package/src/filters/index.ts +281 -0
- package/src/hooks/useOfferings.ts +192 -0
- package/src/hooks/usePaywall.ts +154 -0
- package/src/hooks/usePlacement.ts +143 -0
- package/src/hooks/useSubscription.ts +154 -0
- package/src/index.ts +245 -0
- package/src/models/BillDogError.ts +24 -0
- package/src/models/CacheFetchPolicy.ts +13 -0
- package/src/models/CustomProduct.ts +33 -0
- package/src/models/CustomerInfo.ts +26 -0
- package/src/models/Delegate.ts +22 -0
- package/src/models/EntitlementInfo.ts +27 -0
- package/src/models/Placement.ts +50 -0
- package/src/models/PurchaseParams.ts +81 -0
- package/src/models/PurchaseResult.ts +32 -0
- package/src/models/RestoreResult.ts +27 -0
- package/src/models/RestoredItem.ts +25 -0
- package/src/modules/ABTest.ts +131 -0
- package/src/modules/Analytics.ts +227 -0
- package/src/modules/Survey.ts +126 -0
- package/src/modules/VirtualCurrency.ts +223 -0
- package/src/murmur.ts +102 -0
- package/src/notifications/LiveActivitiesManager.ts +140 -0
- package/src/notifications/NotificationScheduler.ts +292 -0
- package/src/notifications/PurchaseNotificationHandler.ts +67 -0
- package/src/notifications/useDeviceToken.ts +116 -0
- package/src/notifications/useLiveActivity.ts +125 -0
- package/src/review/ReviewAnalytics.ts +89 -0
- package/src/review/ReviewManager.ts +355 -0
- package/src/review/ReviewTimingManager.ts +265 -0
- package/src/survey-core/types.ts +244 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/types.ts +680 -0
- package/src/utils/ApiClient.ts +163 -0
- package/src/utils/CSSConverter.ts +552 -0
- package/src/utils/animations.ts +232 -0
- package/src/utils/calendarIntegration.ts +113 -0
- package/src/utils/configurationResolver.ts +106 -0
- package/src/utils/deviceDetection.ts +83 -0
- package/src/utils/hapticFeedback.ts +55 -0
- package/src/utils/iconMapper.ts +102 -0
- package/src/utils/notificationPermissions.ts +48 -0
- package/src/utils/priceFormatting.ts +119 -0
- package/src/utils/shareSheet.ts +55 -0
- package/src/utils/subscriptionUpgrade.ts +183 -0
- package/src/valueUtils.ts +108 -0
- 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
|
+
}
|