@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,311 @@
1
+ import { Platform, NativeModules } from 'react-native';
2
+ import { PaywallConfiguration, PaywallComponent } from './types';
3
+ import { BillDogConfiguration } from './BillDogConfiguration';
4
+
5
+ /**
6
+ * Properties that can be localized
7
+ */
8
+ const LOCALIZABLE_PROPERTIES = new Set([
9
+ 'text', 'title', 'subtitle', 'label', 'placeholder',
10
+ 'buttonText', 'ctaText', 'description', 'content',
11
+ 'headerText', 'footerText', 'disclaimer'
12
+ ]);
13
+
14
+ /**
15
+ * Right-to-left languages. CLDR-aligned 11-code set — kept identical to
16
+ * iOS `PaywallRenderer.swift rtlLocales` and Android
17
+ * `PaywallScreen.kt rtlLocales` so layout direction is consistent across
18
+ * every platform.
19
+ */
20
+ const RTL_LOCALES = new Set([
21
+ 'ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi', 'ku', 'ckb', 'dv', 'ug',
22
+ ]);
23
+
24
+ /**
25
+ * Client-side localization resolver for React Native SDK
26
+ * Resolves localized text based on device locale with fallback to baseLocale
27
+ */
28
+ export class LocalizationResolver {
29
+ private localizations?: Record<string, Record<string, string>>;
30
+ private baseLocale: string;
31
+ private selectedLocale: string;
32
+
33
+ constructor(
34
+ localizations?: Record<string, Record<string, string>>,
35
+ baseLocale: string = 'en',
36
+ explicitLocale?: string
37
+ ) {
38
+ this.localizations = localizations;
39
+ this.baseLocale = baseLocale;
40
+ // Priority: explicit locale > global config override > device locale
41
+ this.selectedLocale = explicitLocale ?? this.getEffectiveLocale();
42
+ }
43
+
44
+ /**
45
+ * Get the effective locale. Precedence mirrors Android
46
+ * `BillDogConfig.getEffectiveLocale` (BillDogConfig.kt:150-152):
47
+ * runtime locale override > device locale > "en".
48
+ *
49
+ * Public + static so callers (e.g. WebViewPaywall's locale bridge) can
50
+ * resolve the active locale without constructing a resolver.
51
+ */
52
+ static getEffectiveLocale(): string {
53
+ const configOverride = BillDogConfiguration.getLocaleOverride();
54
+ if (configOverride && configOverride.trim()) {
55
+ return configOverride;
56
+ }
57
+ return LocalizationResolver.detectDeviceLocale();
58
+ }
59
+
60
+ /** Instance accessor — defers to the canonical static resolver. */
61
+ private getEffectiveLocale(): string {
62
+ return LocalizationResolver.getEffectiveLocale();
63
+ }
64
+
65
+ /**
66
+ * Returns true when `locale`'s language belongs to a right-to-left
67
+ * script. Matches against the language portion only (e.g. `ar-EG` → `ar`),
68
+ * mirroring Android `PaywallScreen.kt:431-458`.
69
+ */
70
+ static isRtlLocale(locale: string | null | undefined): boolean {
71
+ if (!locale) return false;
72
+ const language = locale.split(/[-_]/)[0]?.toLowerCase() ?? '';
73
+ return RTL_LOCALES.has(language);
74
+ }
75
+
76
+ /** Detect the device locale from the platform native modules. */
77
+ static detectDeviceLocale(): string {
78
+ try {
79
+ if (Platform.OS === 'ios') {
80
+ return (
81
+ NativeModules.SettingsManager?.settings?.AppleLocale?.split('_')[0] ||
82
+ NativeModules.SettingsManager?.settings?.AppleLanguages?.[0]?.split('-')[0] ||
83
+ 'en'
84
+ );
85
+ } else if (Platform.OS === 'android') {
86
+ return NativeModules.I18nManager?.localeIdentifier?.split('_')[0] || 'en';
87
+ }
88
+ } catch {
89
+ // Fall through to default.
90
+ }
91
+ return 'en';
92
+ }
93
+
94
+ /**
95
+ * Set the active locale for resolution
96
+ */
97
+ setLocale(locale: string): void {
98
+ this.selectedLocale = locale;
99
+ }
100
+
101
+ /**
102
+ * Get the currently selected locale
103
+ */
104
+ getSelectedLocale(): string {
105
+ return this.selectedLocale;
106
+ }
107
+
108
+ /**
109
+ * Get the base locale (source language)
110
+ */
111
+ getBaseLocale(): string {
112
+ return this.baseLocale;
113
+ }
114
+
115
+ /**
116
+ * Find the matching locale key using the canonical 4-step matcher.
117
+ * Mirrors Android `LocalizationResolver.findLocaleKey` (LocalizationResolver.kt:50-86)
118
+ * and iOS `LocalizationResolver.findLocaleKey` exactly:
119
+ * 1. exact match
120
+ * 2. case-insensitive match
121
+ * 3. separator-normalized match (en-US ↔ en_US)
122
+ * 4. language-only fallback (en-US → en, or en → first en-* regional)
123
+ */
124
+ private findLocaleKey(locale: string): string | undefined {
125
+ if (!this.localizations || !locale) return undefined;
126
+ const keys = Object.keys(this.localizations);
127
+
128
+ // Step 1: exact match
129
+ if (this.localizations[locale]) return locale;
130
+
131
+ // Step 2: case-insensitive match
132
+ const lower = locale.toLowerCase();
133
+ const ciMatch = keys.find(k => k.toLowerCase() === lower);
134
+ if (ciMatch) return ciMatch;
135
+
136
+ // Step 3: separator-normalized match
137
+ const alt = locale.includes('-')
138
+ ? locale.replace(/-/g, '_')
139
+ : locale.replace(/_/g, '-');
140
+ if (alt !== locale) {
141
+ if (this.localizations[alt]) return alt;
142
+ const altLower = alt.toLowerCase();
143
+ const altMatch = keys.find(k => k.toLowerCase() === altLower);
144
+ if (altMatch) return altMatch;
145
+ }
146
+
147
+ // Step 4: language-only fallback
148
+ const languageOnly = locale.split(/[-_]/)[0] ?? '';
149
+ if (languageOnly) {
150
+ const langLower = languageOnly.toLowerCase();
151
+ // Prefer an exact-language key (e.g. "en") over a regional variant
152
+ const exactLang = keys.find(k => k.toLowerCase() === langLower);
153
+ if (exactLang) return exactLang;
154
+ // Otherwise any regional variant matching the language
155
+ const regional = keys.find(
156
+ k => (k.split(/[-_]/)[0] ?? '').toLowerCase() === langLower
157
+ );
158
+ if (regional) return regional;
159
+ }
160
+
161
+ return undefined;
162
+ }
163
+
164
+ /**
165
+ * Check if current locale is the base locale
166
+ */
167
+ isBaseLocale(): boolean {
168
+ return this.selectedLocale.toLowerCase() === this.baseLocale.toLowerCase();
169
+ }
170
+
171
+ /**
172
+ * Get available locales from the localization data
173
+ */
174
+ getAvailableLocales(): Set<string> {
175
+ const locales = new Set<string>([this.baseLocale]);
176
+ if (this.localizations) {
177
+ Object.keys(this.localizations).forEach(key => locales.add(key));
178
+ }
179
+ return locales;
180
+ }
181
+
182
+ /**
183
+ * Resolve a localization key to its translated value
184
+ * Falls back to defaultValue if translation not found
185
+ */
186
+ resolve(key: string, defaultValue: string): string {
187
+ // If selected locale is base locale, return default (original) value
188
+ if (this.isBaseLocale()) {
189
+ return defaultValue;
190
+ }
191
+
192
+ // Try to find translation for selected locale (case-insensitive)
193
+ const matchedKey = this.findLocaleKey(this.selectedLocale);
194
+ const translations = matchedKey ? this.localizations![matchedKey] : undefined;
195
+ const translated = translations?.[key];
196
+
197
+ if (translated !== undefined) {
198
+ return translated;
199
+ }
200
+
201
+ // Log missing translation for debugging
202
+ console.debug(
203
+ `[LocalizationResolver] Translation not found for key '${key}' in locale '${this.selectedLocale}', using default`
204
+ );
205
+ return defaultValue;
206
+ }
207
+
208
+ /**
209
+ * Resolve a variable value with localization support
210
+ * Variable keys are prefixed with locale for lookup
211
+ */
212
+ resolveVariable(variableKey: string, defaultValue: string): string {
213
+ if (this.isBaseLocale()) {
214
+ return defaultValue;
215
+ }
216
+
217
+ // Try locale-prefixed key first (e.g., "es.headline" for Spanish)
218
+ const localizedKey = `${this.selectedLocale}.${variableKey}`;
219
+ const matchedKey = this.findLocaleKey(this.selectedLocale);
220
+ const translations = matchedKey ? this.localizations![matchedKey] : undefined;
221
+
222
+ return (
223
+ translations?.[localizedKey] ??
224
+ translations?.[variableKey] ??
225
+ defaultValue
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Resolve all localizable properties in a component.
231
+ *
232
+ * Lookup keys are componentId-prefixed (`<componentId>.<propertyName>`),
233
+ * matching the shape the dashboard `LocalizationPanel` writes and the
234
+ * Android `VariableResolver.resolveComponent` runtime path
235
+ * (VariableResolver.kt:124-156) + iOS `LocalizationResolver.swift:194-198`.
236
+ * Previously this looked up the source string itself as the key, which
237
+ * never matched the dashboard-authored translation maps.
238
+ */
239
+ resolveComponent(component: PaywallComponent): PaywallComponent {
240
+ const resolvedProperties: Record<string, any> = {};
241
+ const componentId = component.id ?? '';
242
+
243
+ for (const [key, value] of Object.entries(component.properties || {})) {
244
+ if (typeof value === 'string' && LOCALIZABLE_PROPERTIES.has(key)) {
245
+ const localizationKey = componentId ? `${componentId}.${key}` : key;
246
+ resolvedProperties[key] = this.resolve(localizationKey, value);
247
+ } else {
248
+ resolvedProperties[key] = value;
249
+ }
250
+ }
251
+
252
+ // Recursively resolve children
253
+ const resolvedChildren = component.children?.map(child =>
254
+ this.resolveComponent(child)
255
+ );
256
+
257
+ return {
258
+ ...component,
259
+ properties: resolvedProperties,
260
+ children: resolvedChildren,
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Resolve all components in a configuration
266
+ */
267
+ resolveConfiguration(config: PaywallConfiguration): PaywallConfiguration {
268
+ return {
269
+ ...config,
270
+ components: config.components.map(c => this.resolveComponent(c)),
271
+ };
272
+ }
273
+
274
+ // Factory Methods
275
+
276
+ /**
277
+ * Create resolver from PaywallConfiguration
278
+ * Respects global locale override from BillDogConfiguration
279
+ */
280
+ static fromConfiguration(config: PaywallConfiguration): LocalizationResolver {
281
+ return new LocalizationResolver(
282
+ config.localizations,
283
+ config.baseLocale ?? 'en'
284
+ );
285
+ }
286
+
287
+ /**
288
+ * Create resolver with device locale auto-detected
289
+ * Respects global locale override from BillDogConfiguration
290
+ */
291
+ static withDeviceLocale(config: PaywallConfiguration): LocalizationResolver {
292
+ return new LocalizationResolver(
293
+ config.localizations,
294
+ config.baseLocale ?? 'en'
295
+ );
296
+ }
297
+
298
+ /**
299
+ * Create resolver with specific locale (overrides global config)
300
+ */
301
+ static withLocale(
302
+ config: PaywallConfiguration,
303
+ locale: string
304
+ ): LocalizationResolver {
305
+ return new LocalizationResolver(
306
+ config.localizations,
307
+ config.baseLocale ?? 'en',
308
+ locale
309
+ );
310
+ }
311
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * NativePaywallView — renders a BillDog paywall by embedding the NATIVE paywall view
3
+ * (SwiftUI/UIKit on iOS, Jetpack Compose on Android) via a legacy (Paper) native
4
+ * component (`BDPaywallView`). This is the thin-bridge "Option 2" inline path: the
5
+ * native BillDog SDK renders the paywall and handles purchases; this component only
6
+ * hosts it and forwards its events.
7
+ *
8
+ * The pure-JS `PaywallView`/`ComponentRenderer` and `WebViewPaywall` remain available
9
+ * as alternative rendering strategies (e.g. for environments without the native module).
10
+ */
11
+ import React from 'react';
12
+ import {
13
+ requireNativeComponent,
14
+ UIManager,
15
+ StyleSheet,
16
+ View,
17
+ Text,
18
+ Platform,
19
+ type NativeSyntheticEvent,
20
+ type StyleProp,
21
+ type ViewStyle,
22
+ } from 'react-native';
23
+
24
+ const COMPONENT_NAME = 'BDPaywallView';
25
+
26
+ /** True only when the native view manager is actually registered on this platform. */
27
+ const isNativeComponentAvailable =
28
+ UIManager.getViewManagerConfig?.(COMPONENT_NAME) != null;
29
+
30
+ type PurchaseEvent = { productId: string; data?: Record<string, unknown> };
31
+ type ErrorEvent = { message: string };
32
+ type OpenUrlEvent = { url: string };
33
+ type TrackEvent = { event: string; data?: Record<string, unknown> };
34
+
35
+ interface NativeProps {
36
+ paywallIdentifier: string;
37
+ style?: StyleProp<ViewStyle>;
38
+ onLoaded?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
39
+ onError?: (e: NativeSyntheticEvent<ErrorEvent>) => void;
40
+ onPurchase?: (e: NativeSyntheticEvent<PurchaseEvent>) => void;
41
+ onDismiss?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
42
+ onRestore?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
43
+ onOpenUrl?: (e: NativeSyntheticEvent<OpenUrlEvent>) => void;
44
+ onTrackEvent?: (e: NativeSyntheticEvent<TrackEvent>) => void;
45
+ }
46
+
47
+ // Only bind the native component when it exists — requireNativeComponent otherwise
48
+ // logs a hard error on some RN versions.
49
+ const BDPaywallViewNative = isNativeComponentAvailable
50
+ ? requireNativeComponent<NativeProps>(COMPONENT_NAME)
51
+ : null;
52
+
53
+ export interface NativePaywallViewProps {
54
+ /** Placement / paywall identifier to load. */
55
+ identifier: string;
56
+ style?: ViewStyle;
57
+ /** Fired after a completed purchase (productId + native result fields). */
58
+ onPurchase?: (productId: string, data?: Record<string, unknown>) => void;
59
+ onDismiss?: () => void;
60
+ onRestore?: () => void;
61
+ onOpenUrl?: (url: string) => void;
62
+ onTrackEvent?: (event: string, data?: Record<string, unknown>) => void;
63
+ onLoaded?: () => void;
64
+ onError?: (message: string) => void;
65
+ /** Rendered when the native paywall component is unavailable on this platform. */
66
+ fallback?: React.ReactNode;
67
+ }
68
+
69
+ /**
70
+ * Inline native paywall. Requires the `BDPaywallView` native component (iOS
71
+ * `BillDogPaywallViewManager`, Android `BillDogPaywallViewManager`). When it is not
72
+ * registered, renders `fallback` (or nothing) instead of crashing.
73
+ */
74
+ export function NativePaywallView(props: NativePaywallViewProps): React.ReactElement | null {
75
+ const {
76
+ identifier,
77
+ style,
78
+ onPurchase,
79
+ onDismiss,
80
+ onRestore,
81
+ onOpenUrl,
82
+ onTrackEvent,
83
+ onLoaded,
84
+ onError,
85
+ fallback,
86
+ } = props;
87
+
88
+ if (BDPaywallViewNative == null) {
89
+ if (fallback != null) return <>{fallback}</>;
90
+ if (__DEV__) {
91
+ return (
92
+ <View style={[styles.fill, styles.center, style]}>
93
+ <Text style={styles.warn}>
94
+ BillDog: native paywall component &quot;{COMPONENT_NAME}&quot; is not registered
95
+ on {Platform.OS}. Ensure the native SDK is linked, or use PaywallView /
96
+ WebViewPaywall.
97
+ </Text>
98
+ </View>
99
+ );
100
+ }
101
+ return null;
102
+ }
103
+
104
+ return (
105
+ <BDPaywallViewNative
106
+ paywallIdentifier={identifier}
107
+ style={[styles.fill, style]}
108
+ onLoaded={() => onLoaded?.()}
109
+ onError={(e) => onError?.(e.nativeEvent?.message ?? 'Unknown paywall error')}
110
+ onPurchase={(e) =>
111
+ onPurchase?.(e.nativeEvent?.productId ?? '', e.nativeEvent?.data)
112
+ }
113
+ onDismiss={() => onDismiss?.()}
114
+ onRestore={() => onRestore?.()}
115
+ onOpenUrl={(e) => onOpenUrl?.(e.nativeEvent?.url ?? '')}
116
+ onTrackEvent={(e) =>
117
+ onTrackEvent?.(e.nativeEvent?.event ?? '', e.nativeEvent?.data)
118
+ }
119
+ />
120
+ );
121
+ }
122
+
123
+ /** Whether the native paywall component is available on the current platform. */
124
+ export const isNativePaywallAvailable = isNativeComponentAvailable;
125
+
126
+ const styles = StyleSheet.create({
127
+ fill: { flex: 1 },
128
+ center: { alignItems: 'center', justifyContent: 'center', padding: 16 },
129
+ warn: { color: '#B00020', textAlign: 'center', fontSize: 13 },
130
+ });
131
+
132
+ export default NativePaywallView;