novvy-ads-react-native 1.0.0-beta.1

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 (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/android/build.gradle +84 -0
  4. package/android/gradle.properties +4 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/ai/novvy/reactnative/NovvyAdContainerView.kt +63 -0
  7. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsModule.kt +390 -0
  8. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsPackage.kt +46 -0
  9. package/android/src/main/java/ai/novvy/reactnative/NovvyPauseAdViewManager.kt +68 -0
  10. package/android/src/main/java/ai/novvy/reactnative/NovvyScrollAdViewManager.kt +72 -0
  11. package/android/src/main/java/ai/novvy/reactnative/RnPlayerAdapter.kt +18 -0
  12. package/android/src/main/java/ai/novvy/reactnative/ViewReadyEvent.kt +26 -0
  13. package/app.plugin.js +4 -0
  14. package/ios/NovvyAds.mm +168 -0
  15. package/ios/NovvyAdsImpl.swift +409 -0
  16. package/ios/NovvyPauseAdView.mm +79 -0
  17. package/ios/NovvyScrollAdView.mm +80 -0
  18. package/ios/RnPlayerAdapter.swift +14 -0
  19. package/lib/commonjs/NovvyAds.js +87 -0
  20. package/lib/commonjs/NovvyAds.js.map +1 -0
  21. package/lib/commonjs/ads/NovvyAd.js +121 -0
  22. package/lib/commonjs/ads/NovvyAd.js.map +1 -0
  23. package/lib/commonjs/ads/NovvyEndAd.js +19 -0
  24. package/lib/commonjs/ads/NovvyEndAd.js.map +1 -0
  25. package/lib/commonjs/ads/NovvyFullScreenAd.js +49 -0
  26. package/lib/commonjs/ads/NovvyFullScreenAd.js.map +1 -0
  27. package/lib/commonjs/ads/NovvyInterstitialAd.js +19 -0
  28. package/lib/commonjs/ads/NovvyInterstitialAd.js.map +1 -0
  29. package/lib/commonjs/ads/NovvyMidAd.js +19 -0
  30. package/lib/commonjs/ads/NovvyMidAd.js.map +1 -0
  31. package/lib/commonjs/ads/NovvyPauseAd.js +116 -0
  32. package/lib/commonjs/ads/NovvyPauseAd.js.map +1 -0
  33. package/lib/commonjs/ads/NovvyRewardedAd.js +53 -0
  34. package/lib/commonjs/ads/NovvyRewardedAd.js.map +1 -0
  35. package/lib/commonjs/ads/NovvyScrollAd.js +136 -0
  36. package/lib/commonjs/ads/NovvyScrollAd.js.map +1 -0
  37. package/lib/commonjs/eventBus.js +60 -0
  38. package/lib/commonjs/eventBus.js.map +1 -0
  39. package/lib/commonjs/index.js +90 -0
  40. package/lib/commonjs/index.js.map +1 -0
  41. package/lib/commonjs/package.json +1 -0
  42. package/lib/commonjs/spec/NativeNovvyAds.js +27 -0
  43. package/lib/commonjs/spec/NativeNovvyAds.js.map +1 -0
  44. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js +16 -0
  45. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  46. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js +17 -0
  47. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  48. package/lib/commonjs/spec/codegen-event-emitter.d.js +2 -0
  49. package/lib/commonjs/spec/codegen-event-emitter.d.js.map +1 -0
  50. package/lib/commonjs/types.js +54 -0
  51. package/lib/commonjs/types.js.map +1 -0
  52. package/lib/commonjs/views/NovvyPauseAdView.js +76 -0
  53. package/lib/commonjs/views/NovvyPauseAdView.js.map +1 -0
  54. package/lib/commonjs/views/NovvyScrollAdView.js +63 -0
  55. package/lib/commonjs/views/NovvyScrollAdView.js.map +1 -0
  56. package/lib/module/NovvyAds.js +83 -0
  57. package/lib/module/NovvyAds.js.map +1 -0
  58. package/lib/module/ads/NovvyAd.js +117 -0
  59. package/lib/module/ads/NovvyAd.js.map +1 -0
  60. package/lib/module/ads/NovvyEndAd.js +15 -0
  61. package/lib/module/ads/NovvyEndAd.js.map +1 -0
  62. package/lib/module/ads/NovvyFullScreenAd.js +45 -0
  63. package/lib/module/ads/NovvyFullScreenAd.js.map +1 -0
  64. package/lib/module/ads/NovvyInterstitialAd.js +15 -0
  65. package/lib/module/ads/NovvyInterstitialAd.js.map +1 -0
  66. package/lib/module/ads/NovvyMidAd.js +15 -0
  67. package/lib/module/ads/NovvyMidAd.js.map +1 -0
  68. package/lib/module/ads/NovvyPauseAd.js +112 -0
  69. package/lib/module/ads/NovvyPauseAd.js.map +1 -0
  70. package/lib/module/ads/NovvyRewardedAd.js +49 -0
  71. package/lib/module/ads/NovvyRewardedAd.js.map +1 -0
  72. package/lib/module/ads/NovvyScrollAd.js +132 -0
  73. package/lib/module/ads/NovvyScrollAd.js.map +1 -0
  74. package/lib/module/eventBus.js +55 -0
  75. package/lib/module/eventBus.js.map +1 -0
  76. package/lib/module/index.js +22 -0
  77. package/lib/module/index.js.map +1 -0
  78. package/lib/module/package.json +1 -0
  79. package/lib/module/spec/NativeNovvyAds.js +28 -0
  80. package/lib/module/spec/NativeNovvyAds.js.map +1 -0
  81. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js +13 -0
  82. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  83. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js +14 -0
  84. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  85. package/lib/module/spec/codegen-event-emitter.d.js +2 -0
  86. package/lib/module/spec/codegen-event-emitter.d.js.map +1 -0
  87. package/lib/module/types.js +56 -0
  88. package/lib/module/types.js.map +1 -0
  89. package/lib/module/views/NovvyPauseAdView.js +70 -0
  90. package/lib/module/views/NovvyPauseAdView.js.map +1 -0
  91. package/lib/module/views/NovvyScrollAdView.js +57 -0
  92. package/lib/module/views/NovvyScrollAdView.js.map +1 -0
  93. package/lib/typescript/NovvyAds.d.ts +69 -0
  94. package/lib/typescript/NovvyAds.d.ts.map +1 -0
  95. package/lib/typescript/ads/NovvyAd.d.ts +57 -0
  96. package/lib/typescript/ads/NovvyAd.d.ts.map +1 -0
  97. package/lib/typescript/ads/NovvyEndAd.d.ts +7 -0
  98. package/lib/typescript/ads/NovvyEndAd.d.ts.map +1 -0
  99. package/lib/typescript/ads/NovvyFullScreenAd.d.ts +19 -0
  100. package/lib/typescript/ads/NovvyFullScreenAd.d.ts.map +1 -0
  101. package/lib/typescript/ads/NovvyInterstitialAd.d.ts +7 -0
  102. package/lib/typescript/ads/NovvyInterstitialAd.d.ts.map +1 -0
  103. package/lib/typescript/ads/NovvyMidAd.d.ts +7 -0
  104. package/lib/typescript/ads/NovvyMidAd.d.ts.map +1 -0
  105. package/lib/typescript/ads/NovvyPauseAd.d.ts +45 -0
  106. package/lib/typescript/ads/NovvyPauseAd.d.ts.map +1 -0
  107. package/lib/typescript/ads/NovvyRewardedAd.d.ts +19 -0
  108. package/lib/typescript/ads/NovvyRewardedAd.d.ts.map +1 -0
  109. package/lib/typescript/ads/NovvyScrollAd.d.ts +57 -0
  110. package/lib/typescript/ads/NovvyScrollAd.d.ts.map +1 -0
  111. package/lib/typescript/eventBus.d.ts +15 -0
  112. package/lib/typescript/eventBus.d.ts.map +1 -0
  113. package/lib/typescript/index.d.ts +15 -0
  114. package/lib/typescript/index.d.ts.map +1 -0
  115. package/lib/typescript/spec/NativeNovvyAds.d.ts +73 -0
  116. package/lib/typescript/spec/NativeNovvyAds.d.ts.map +1 -0
  117. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts +18 -0
  118. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts.map +1 -0
  119. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts +19 -0
  120. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts.map +1 -0
  121. package/lib/typescript/types.d.ts +77 -0
  122. package/lib/typescript/types.d.ts.map +1 -0
  123. package/lib/typescript/views/NovvyPauseAdView.d.ts +21 -0
  124. package/lib/typescript/views/NovvyPauseAdView.d.ts.map +1 -0
  125. package/lib/typescript/views/NovvyScrollAdView.d.ts +22 -0
  126. package/lib/typescript/views/NovvyScrollAdView.d.ts.map +1 -0
  127. package/novvy-ads-react-native.podspec +71 -0
  128. package/package.json +84 -0
  129. package/plugin/build/index.d.ts +11 -0
  130. package/plugin/build/index.js +53 -0
  131. package/react-native.config.js +7 -0
  132. package/src/NovvyAds.ts +98 -0
  133. package/src/ads/NovvyAd.ts +125 -0
  134. package/src/ads/NovvyEndAd.ts +13 -0
  135. package/src/ads/NovvyFullScreenAd.ts +46 -0
  136. package/src/ads/NovvyInterstitialAd.ts +13 -0
  137. package/src/ads/NovvyMidAd.ts +13 -0
  138. package/src/ads/NovvyPauseAd.ts +125 -0
  139. package/src/ads/NovvyRewardedAd.ts +49 -0
  140. package/src/ads/NovvyScrollAd.ts +138 -0
  141. package/src/eventBus.ts +59 -0
  142. package/src/index.tsx +29 -0
  143. package/src/spec/NativeNovvyAds.ts +134 -0
  144. package/src/spec/NovvyPauseAdViewNativeComponent.ts +20 -0
  145. package/src/spec/NovvyScrollAdViewNativeComponent.ts +21 -0
  146. package/src/spec/codegen-event-emitter.d.ts +10 -0
  147. package/src/types.ts +82 -0
  148. package/src/views/NovvyPauseAdView.tsx +80 -0
  149. package/src/views/NovvyScrollAdView.tsx +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) NovvyAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # novvy-ads-react-native
2
+
3
+ React Native SDK for **Novvy Ads** — short-drama vertical-video ad formats
4
+ (interstitial, rewarded, mid, end, scroll, pause). It wraps the native Novvy
5
+ Android & iOS SDKs behind a single TypeScript API, mirroring the design of the
6
+ official Flutter plugin.
7
+
8
+ - **New Architecture only** (TurboModule + Fabric). Requires **React Native ≥ 0.76**.
9
+ - **Framework-agnostic** — plain RN native module, **zero Expo runtime dependency**.
10
+ - Ships an **optional Expo config plugin** (build-time only) for Expo hosts.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm install novvy-ads-react-native
18
+ # or
19
+ yarn add novvy-ads-react-native
20
+ ```
21
+
22
+ ### iOS
23
+
24
+ ```sh
25
+ cd ios && pod install
26
+ ```
27
+
28
+ - The podspec downloads `NovvyAds.xcframework` (currently pinned to **1.1.0**) from the [NovvyAds cocoapods release](https://github.com/NovvyAI/novvy-ads-cocoapods/releases) automatically — no manual binary integration.
29
+ - Minimum deployment target **iOS 13.0**. Raise your Podfile's `platform :ios` to 13.0+ if it's lower.
30
+ - **No AdMob / Google-Mobile-Ads-SDK dependency** (removed in 1.1.0).
31
+ - The vendored framework ships **arm64 simulator only** (x86_64 simulator is excluded), so simulator builds require an **Apple Silicon Mac**. Intel Macs can only build for physical devices.
32
+ - Local development against an unreleased NovvyAds iOS SDK: set `NOVVY_ADS_LOCAL_XCFRAMEWORK=<path>` before `pod install` and the podspec will skip the download and use that path instead.
33
+
34
+ ### Android
35
+
36
+ No extra steps — autolinking wires it up.
37
+
38
+ - The native SDK **`ai.novvy.android:sdk:1.1.0`** is pulled from **Maven Central**. Make sure `mavenCentral()` is in your root `build.gradle`'s `allprojects.repositories` (default in RN templates).
39
+ - Minimum **`minSdkVersion 24`** (Android 7.0). Raise `android/build.gradle`'s `minSdkVersion` if it's lower.
40
+ - Compiles with **JDK 17**; `compileSdkVersion` 34+ recommended.
41
+
42
+ ### Expo (optional)
43
+
44
+ For managed / CNG (prebuild) Expo apps, add the config plugin. It only runs at
45
+ build time and adds **no** runtime dependency. Non-Expo hosts do **not** need it.
46
+
47
+ ```json
48
+ {
49
+ "expo": {
50
+ "plugins": [
51
+ ["novvy-ads-react-native", {
52
+ "iosDeploymentTarget": "13.0",
53
+ "androidMinSdkVersion": 24,
54
+ "userTrackingUsageDescription": "We use your data to show relevant ads."
55
+ }]
56
+ ]
57
+ }
58
+ }
59
+ ```
60
+
61
+ Then run `npx expo prebuild`. All options are optional:
62
+
63
+ | Option | Type | Default | Effect |
64
+ | ------ | ------ | ------ | ------ |
65
+ | `iosDeploymentTarget` | `string` | `"13.0"` | Raises `Podfile.properties`'s `ios.deploymentTarget` to at least this value |
66
+ | `androidMinSdkVersion` | `number` | `24` | Raises `gradle.properties`'s `android.minSdkVersion` to at least this value |
67
+ | `userTrackingUsageDescription` | `string` | — | If set, injects `NSUserTrackingUsageDescription` into `Info.plist` (iOS ATT copy) |
68
+
69
+ > New Architecture must be enabled (default on RN 0.76+). For Expo, ensure
70
+ > `newArchEnabled` is true.
71
+
72
+ ---
73
+
74
+ ## Quick start
75
+
76
+ ```ts
77
+ import {
78
+ NovvyAds,
79
+ NovvyRewardedAd,
80
+ } from 'novvy-ads-react-native';
81
+
82
+ // 1. Initialize once at app startup.
83
+ const ok = await NovvyAds.initialize({
84
+ appId: 'YOUR_APP_ID',
85
+ endpoint: 'https://bid.novvy.ai',
86
+ apiKey: 'YOUR_API_KEY',
87
+ userContext: { userId: 'u_123', isPaidUser: false },
88
+ });
89
+
90
+ // 2. Set content context before requesting ads (required for drama fill).
91
+ NovvyAds.setContentContext({ seriesName: 'My Drama', episodeNumber: 3 });
92
+
93
+ // 3. Create an ad, wire callbacks, load & show.
94
+ const ad = new NovvyRewardedAd('YOUR_AD_UNIT_ID');
95
+ let earned = false;
96
+ ad.onAdLoaded = () => ad.show();
97
+ ad.onUserEarnedReward = () => { earned = true; };
98
+ ad.onAdDismissed = () => { if (earned) grantReward(); };
99
+ ad.onAdFailedToLoad = (e) => console.warn('load failed', e);
100
+ ad.load();
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Ad formats
106
+
107
+ | Class | Kind | Lifecycle |
108
+ | --- | --- | --- |
109
+ | `NovvyInterstitialAd` | full-screen | `load()` → `onAdLoaded` → `show()` → `onAdDismissed` |
110
+ | `NovvyRewardedAd` | rewarded video | `load()` → `show()` → `onUserEarnedReward` → `onAdDismissed` |
111
+ | `NovvyMidAd` | full-screen (mid-roll) | `load()` → `show()` → `onAdDismissed` |
112
+ | `NovvyEndAd` | full-screen (end-roll) | `load()` → `show()` → `onAdDismissed` |
113
+ | `NovvyScrollAd` | inline feed video | `load()` → `<NovvyScrollAdView>` → `setPlaying()` |
114
+ | `NovvyPauseAd` | realtime pause card (260×90) | `<NovvyPauseAdView>` (no `load()`) |
115
+
116
+ > There is **no banner / native / app-open** format, and **no click callback**.
117
+ > `NovvyRewardedAd` grants a parameterless reward (no amount/type payload).
118
+
119
+ ### Full-screen (interstitial / mid / end)
120
+
121
+ ```ts
122
+ import { NovvyInterstitialAd } from 'novvy-ads-react-native';
123
+
124
+ const ad = new NovvyInterstitialAd('AD_UNIT_ID');
125
+ ad.onAdLoaded = () => ad.show();
126
+ ad.onAdShowed = () => {};
127
+ ad.onAdDismissed = () => {}; // ad auto-destroys after dismiss
128
+ ad.onAdFailedToLoad = (e) => {};
129
+ ad.load();
130
+ ```
131
+
132
+ ### Scroll (inline feed) ad
133
+
134
+ ```tsx
135
+ import { NovvyScrollAd, NovvyScrollAdView } from 'novvy-ads-react-native';
136
+
137
+ const ad = useMemo(() => new NovvyScrollAd('AD_UNIT_ID', /* episodeNumber */ 3), []);
138
+
139
+ useEffect(() => {
140
+ ad.onAdLoaded = () => ad.setPlaying(true);
141
+ ad.onAdPlaybackTick = (remaining) => {/* update your skip UI */};
142
+ ad.load();
143
+ return () => ad.destroy();
144
+ }, [ad]);
145
+
146
+ // Give the view a fixed height in your feed item:
147
+ <View style={{ height: 640 }}>
148
+ <NovvyScrollAdView ad={ad} durationSeconds={5} useDefaultSwipeOverlay />
149
+ </View>
150
+
151
+ // Toggle playback from viewport visibility:
152
+ // ad.setPlaying(true) // scrolled into view
153
+ // ad.setPlaying(false) // scrolled out
154
+ ```
155
+
156
+ ### Pause card ad
157
+
158
+ `NovvyPauseAd` has **no load step** — mounting `<NovvyPauseAdView>` issues the
159
+ request. You must supply a `NovvyPlayerAdapter` that reports your video player's
160
+ position; the SDK uses it to detect resume and hide the card.
161
+
162
+ ```tsx
163
+ import { NovvyPauseAd, NovvyPauseAdView, type NovvyPlayerAdapter } from 'novvy-ads-react-native';
164
+
165
+ // Adapter backed by your player's current state (read live each access):
166
+ const player: NovvyPlayerAdapter = {
167
+ get currentPositionMs() { return videoRef.current?.positionMs ?? 0; },
168
+ get isPlaying() { return videoRef.current?.isPlaying ?? false; },
169
+ };
170
+
171
+ const ad = useMemo(() => new NovvyPauseAd('AD_UNIT_ID'), []);
172
+ useEffect(() => {
173
+ ad.onAdShowed = () => {};
174
+ ad.onAdHidden = () => ad.destroy(); // resumed
175
+ return () => ad.destroy();
176
+ }, [ad]);
177
+
178
+ // Position within your player overlay (fixed 260×90):
179
+ <NovvyPauseAdView ad={ad} player={player} />
180
+ ```
181
+
182
+ ---
183
+
184
+ ## SDK API (`NovvyAds`)
185
+
186
+ | Method | Description |
187
+ | --- | --- |
188
+ | `initialize(opts)` | `Promise<boolean>` — init with `appId` / `endpoint` / `apiKey` / optional `userContext`. |
189
+ | `setUserContext(ctx)` / `updateUserContext(partial)` | Replace / merge user targeting context. |
190
+ | `setContentContext(ctx)` / `updateContentContext(partial)` | Replace / merge series/episode context. Call before requesting drama ads. |
191
+ | `helperHashEmail(email)` | SHA-256 of the trimmed/lowercased email (pure JS). |
192
+
193
+ ### Privacy / consent
194
+
195
+ There is no explicit GDPR/CCPA consent API. Privacy targeting is expressed
196
+ through `NovvyUserContext` / `NovvyContentContext`. On iOS, present the ATT
197
+ prompt yourself (the config plugin can add `NSUserTrackingUsageDescription`).
198
+
199
+ ---
200
+
201
+ ## How it works
202
+
203
+ A single native → JS event (`onAdEvent`, carrying `adObjectId`) multiplexes
204
+ every ad's lifecycle; each ad object filters the stream by its own id, exactly
205
+ like the Flutter plugin. `adObjectId`s are generated JS-side. Inline formats use
206
+ Fabric view components that register a native container keyed by `adObjectId`.
207
+
208
+ ## License
209
+
210
+ MIT
@@ -0,0 +1,84 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = { name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NovvyAds_" + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.6.0"
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
14
+ }
15
+ }
16
+
17
+ def reactNativeArchitectures() {
18
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
19
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
20
+ }
21
+
22
+ def isNewArchitectureEnabled() {
23
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
24
+ }
25
+
26
+ apply plugin: "com.android.library"
27
+ apply plugin: "kotlin-android"
28
+
29
+ if (isNewArchitectureEnabled()) {
30
+ apply plugin: "com.facebook.react"
31
+ }
32
+
33
+ def getExtOrIntegerDefault = { name ->
34
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NovvyAds_" + name]).toInteger()
35
+ }
36
+
37
+ android {
38
+ namespace "ai.novvy.reactnative"
39
+
40
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
41
+
42
+ defaultConfig {
43
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
44
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
45
+ }
46
+
47
+ buildFeatures {
48
+ buildConfig true
49
+ }
50
+
51
+ compileOptions {
52
+ sourceCompatibility JavaVersion.VERSION_17
53
+ targetCompatibility JavaVersion.VERSION_17
54
+ }
55
+
56
+ kotlinOptions {
57
+ jvmTarget = "17"
58
+ }
59
+
60
+ sourceSets {
61
+ main {
62
+ if (isNewArchitectureEnabled()) {
63
+ java.srcDirs += ["generated/java", "generated/jni"]
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ repositories {
70
+ google()
71
+ mavenCentral()
72
+ }
73
+
74
+ dependencies {
75
+ implementation "com.facebook.react:react-android"
76
+
77
+ // Novvy native Android ad SDK (published to Maven Central).
78
+ implementation "ai.novvy.android:sdk:1.1.0"
79
+ // For publishing the host Activity as a ViewTreeLifecycleOwner on the ad
80
+ // container so the SDK's inline host can observe background/foreground (pauses
81
+ // a VIDEO creative on click-through). Provided at runtime by RN + the Novvy
82
+ // SDK, so compileOnly avoids forcing a version.
83
+ compileOnly "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
84
+ }
@@ -0,0 +1,4 @@
1
+ NovvyAds_kotlinVersion=1.9.24
2
+ NovvyAds_minSdkVersion=24
3
+ NovvyAds_targetSdkVersion=34
4
+ NovvyAds_compileSdkVersion=35
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- Permissions (INTERNET, ACCESS_NETWORK_STATE, AD_ID) are declared by the
3
+ underlying ai.novvy.android:sdk manifest and merged automatically. -->
4
+ </manifest>
@@ -0,0 +1,63 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import android.widget.FrameLayout
4
+ import androidx.lifecycle.LifecycleOwner
5
+ import androidx.lifecycle.setViewTreeLifecycleOwner
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+
8
+ /**
9
+ * Container [FrameLayout] that hosts an SDK-rendered ad creative.
10
+ *
11
+ * Two RN/New-Arch integration gaps are patched here:
12
+ *
13
+ * 1. **Layout of imperatively-added children.** Fabric only measures/lays out
14
+ * views in the JS shadow tree, and RN-managed ViewGroups swallow normal
15
+ * `requestLayout()` propagation. The Novvy SDK mounts its creative (video /
16
+ * image / native) via `addView` at runtime — that child is NOT in the shadow
17
+ * tree, so it never gets measured and stays 0×0 (blank/black slot; a VIDEO's
18
+ * ExoPlayer still decodes audio). [requestLayout] is overridden to post a
19
+ * manual measure + layout of our own subtree (the standard react-native-video
20
+ * / -webview / google-mobile-ads workaround). Converges rather than
21
+ * busy-looping because the SDK only re-fits on bounds *change*.
22
+ *
23
+ * 2. **Activity for click-through.** The SDK resolves the Activity used to open
24
+ * a creative's `cta_url` via `container.context as? Activity` (see
25
+ * `NovvyPlacementAd.attach` / `ClickOpenPipeline`). Under RN the view's
26
+ * context is a [ThemedReactContext], never an Activity, so that cast returns
27
+ * null and every click silently no-ops. `View.getContext()` is final and
28
+ * cannot be overridden, so we construct the view with the current Activity as
29
+ * its base context — `getContext()` then returns the Activity and the cast
30
+ * (and CTA open) works. RN-side event dispatch must read [reactContext]
31
+ * instead of `getContext()`, which is no longer the ReactContext.
32
+ */
33
+ class NovvyAdContainerView(
34
+ /** The RN context. Kept separately because getContext() now returns the Activity. */
35
+ val reactContext: ThemedReactContext,
36
+ ) : FrameLayout(reactContext.currentActivity ?: reactContext) {
37
+
38
+ init {
39
+ // RN does not reliably expose a ViewTreeLifecycleOwner on its view tree, so
40
+ // the SDK's inline host's `container.findViewTreeLifecycleOwner()` returns
41
+ // null and it never registers its onStop/onStart observer — meaning a VIDEO
42
+ // creative keeps playing (audio included) when a click-through opens the
43
+ // browser on top. Publish the host Activity (a LifecycleOwner) on this
44
+ // container so the SDK can observe background/foreground and pause/resume.
45
+ (reactContext.currentActivity as? LifecycleOwner)?.let { setViewTreeLifecycleOwner(it) }
46
+ }
47
+
48
+ private val measureAndLayout = Runnable {
49
+ measure(
50
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
51
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
52
+ )
53
+ layout(left, top, right, bottom)
54
+ }
55
+
56
+ override fun requestLayout() {
57
+ super.requestLayout()
58
+ // React Native won't lay out children that aren't in the shadow tree, so
59
+ // force a measure + layout pass; without it the SDK's imperatively-added
60
+ // creative stays 0×0 and the slot renders as a blank (black) container.
61
+ post(measureAndLayout)
62
+ }
63
+ }