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
@@ -0,0 +1,390 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import android.app.Activity
4
+ import android.view.ViewGroup
5
+ import ai.novvy.android.sdk.*
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.Promise
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import com.facebook.react.bridge.ReadableArray
10
+ import com.facebook.react.bridge.ReadableMap
11
+ import com.facebook.react.bridge.UiThreadUtil
12
+ import com.facebook.react.bridge.WritableMap
13
+ import java.util.concurrent.ConcurrentHashMap
14
+
15
+ /**
16
+ * TurboModule bridging the Novvy Android ad SDK to React Native.
17
+ *
18
+ * Mirrors the Flutter plugin's `NovvyAdsApiImpl.kt` one-to-one:
19
+ * - SDK init + user/content context
20
+ * - Interstitial / Rewarded / Mid / End full-screen ads (load → show)
21
+ * - Scroll (inline feed) ads (load → attach → setPlaying → detach)
22
+ * - Pause (realtime pause card) ads (attach → detach, no load)
23
+ *
24
+ * A single `onAdEvent` emitter multiplexes every ad's lifecycle events to JS;
25
+ * each JS ad object filters by its own `adObjectId`.
26
+ */
27
+ class NovvyAdsModule(reactContext: ReactApplicationContext) :
28
+ NativeNovvyAdsSpec(reactContext) {
29
+
30
+ private val appContext = reactContext.applicationContext
31
+ // Accessed from both the native-modules thread (load*) and the main thread
32
+ // (attach/show/dispose, marshaled via runOnMain) — must be concurrency-safe.
33
+ private val adMap = ConcurrentHashMap<String, Any>()
34
+ private val scrollContainerMap = ConcurrentHashMap<String, ViewGroup>()
35
+ private val pauseContainerMap = ConcurrentHashMap<String, ViewGroup>()
36
+ private val pauseAdapterMap = ConcurrentHashMap<String, RnPlayerAdapter>()
37
+
38
+ init {
39
+ instance = this
40
+ }
41
+
42
+ override fun invalidate() {
43
+ super.invalidate()
44
+ if (instance === this) {
45
+ instance = null
46
+ }
47
+ }
48
+
49
+ // ── Event emission ──────────────────────────────────────────────────────
50
+
51
+ private fun runOnMain(block: () -> Unit) {
52
+ if (UiThreadUtil.isOnUiThread()) block() else UiThreadUtil.runOnUiThread(block)
53
+ }
54
+
55
+ private fun emit(
56
+ adObjectId: String,
57
+ adUnitId: String,
58
+ event: String,
59
+ error: String? = null,
60
+ remainingSeconds: Int? = null,
61
+ ) {
62
+ val map: WritableMap = Arguments.createMap().apply {
63
+ putString("adObjectId", adObjectId)
64
+ putString("adUnitId", adUnitId)
65
+ putString("event", event)
66
+ if (error != null) putString("error", error)
67
+ if (remainingSeconds != null) putInt("remainingSeconds", remainingSeconds)
68
+ }
69
+ runOnMain { emitOnAdEvent(map) }
70
+ }
71
+
72
+ private fun errorText(error: NovvyError): String = when (error) {
73
+ is NovvyError.InternalError -> "InternalError: ${error.details}"
74
+ else -> error.message ?: error::class.simpleName ?: "UnknownError"
75
+ }
76
+
77
+ // ── Initialization & context ──────────────────────────────────────────────
78
+
79
+ override fun initialize(
80
+ appId: String,
81
+ endpoint: String,
82
+ apiKey: String,
83
+ userContext: ReadableMap?,
84
+ promise: Promise,
85
+ ) {
86
+ NovvySDK.initialize(
87
+ context = appContext,
88
+ appId = appId,
89
+ endpoint = endpoint,
90
+ apiKey = apiKey,
91
+ userContext = userContext?.toSdkUserContext(),
92
+ onComplete = { success -> runOnMain { promise.resolve(success) } },
93
+ )
94
+ }
95
+
96
+ override fun setUserContext(context: ReadableMap) {
97
+ NovvySDK.setUserContext(context.toSdkUserContext())
98
+ }
99
+
100
+ override fun updateUserContext(partial: ReadableMap) {
101
+ NovvySDK.updateUserContext(
102
+ userId = partial.optString("userId"),
103
+ hashedEmail = partial.optString("hashedEmail"),
104
+ isPaidUser = partial.optBoolean("isPaidUser"),
105
+ age = partial.optInt("age"),
106
+ gender = partial.optString("gender"),
107
+ dramaWatchHistory = partial.optArray("dramaWatchHistory")?.toSdkRecords(),
108
+ maximumAdsInOneDrama = partial.optInt("maximumAdsInOneDrama"),
109
+ )
110
+ }
111
+
112
+ override fun setContentContext(context: ReadableMap) {
113
+ NovvySDK.setContentContext(
114
+ NovvyContentContext(
115
+ seriesName = context.optString("seriesName"),
116
+ episodeNumber = context.optInt("episodeNumber"),
117
+ url = context.optString("url"),
118
+ )
119
+ )
120
+ }
121
+
122
+ override fun updateContentContext(partial: ReadableMap) {
123
+ NovvySDK.updateContentContext(
124
+ seriesName = partial.optString("seriesName"),
125
+ episodeNumber = partial.optInt("episodeNumber"),
126
+ url = partial.optString("url"),
127
+ )
128
+ }
129
+
130
+ private fun ReadableMap.toSdkUserContext(): NovvyUserContext = NovvyUserContext(
131
+ userId = optString("userId"),
132
+ hashedEmail = optString("hashedEmail"),
133
+ isPaidUser = optBoolean("isPaidUser"),
134
+ age = optInt("age"),
135
+ gender = optString("gender"),
136
+ dramaWatchHistory = optArray("dramaWatchHistory")?.toSdkRecords(),
137
+ maximumAdsInOneDrama = optInt("maximumAdsInOneDrama"),
138
+ )
139
+
140
+ private fun ReadableArray.toSdkRecords(): List<NovvyDramaRecord> {
141
+ val out = mutableListOf<NovvyDramaRecord>()
142
+ for (i in 0 until size()) {
143
+ val record = getMap(i) ?: continue
144
+ out.add(
145
+ NovvyDramaRecord(
146
+ seriesId = record.optString("seriesId") ?: "",
147
+ lastWatchedEpisode = record.optInt("lastWatchedEpisode") ?: 0,
148
+ unlockedEpisode = record.optInt("unlockedEpisode") ?: 0,
149
+ dramaTitle = record.optString("dramaTitle"),
150
+ )
151
+ )
152
+ }
153
+ return out
154
+ }
155
+
156
+ // ── Interstitial ───────────────────────────────────────────────────────────
157
+
158
+ override fun loadInterstitialAd(adUnitId: String, adObjectId: String) {
159
+ val ad = NovvyInterstitialAd(adUnitId)
160
+ adMap[adObjectId] = ad
161
+ ad.load(object : NovvyInterstitialAdListener {
162
+ override fun onAdLoaded(ad: NovvyInterstitialAd) = emit(adObjectId, adUnitId, EV_LOADED)
163
+ override fun onAdFailedToLoad(ad: NovvyInterstitialAd, error: NovvyError) =
164
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
165
+ override fun onAdShown(ad: NovvyInterstitialAd) = emit(adObjectId, adUnitId, EV_SHOWED)
166
+ override fun onAdClosed(ad: NovvyInterstitialAd) = emit(adObjectId, adUnitId, EV_DISMISSED)
167
+ })
168
+ }
169
+
170
+ override fun showInterstitialAd(adObjectId: String) = runOnMain {
171
+ val ad = adMap[adObjectId] as? NovvyInterstitialAd ?: return@runOnMain
172
+ val activity = requireActivity(adObjectId) ?: return@runOnMain
173
+ ad.show(activity)
174
+ }
175
+
176
+ // ── Rewarded ─────────────────────────────────────────────────────────────
177
+
178
+ override fun loadRewardedAd(adUnitId: String, adObjectId: String) {
179
+ val ad = NovvyRewardedAd(adUnitId)
180
+ adMap[adObjectId] = ad
181
+ ad.load(object : NovvyRewardedAdListener {
182
+ override fun onAdLoaded(ad: NovvyRewardedAd) = emit(adObjectId, adUnitId, EV_LOADED)
183
+ override fun onAdFailedToLoad(ad: NovvyRewardedAd, error: NovvyError) =
184
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
185
+ override fun onAdShown(ad: NovvyRewardedAd) = emit(adObjectId, adUnitId, EV_SHOWED)
186
+ override fun onUserEarnedReward(ad: NovvyRewardedAd) =
187
+ emit(adObjectId, adUnitId, EV_EARNED_REWARD)
188
+ override fun onAdClosed(ad: NovvyRewardedAd) = emit(adObjectId, adUnitId, EV_DISMISSED)
189
+ })
190
+ }
191
+
192
+ override fun showRewardedAd(adObjectId: String) = runOnMain {
193
+ val ad = adMap[adObjectId] as? NovvyRewardedAd ?: return@runOnMain
194
+ val activity = requireActivity(adObjectId) ?: return@runOnMain
195
+ ad.show(activity)
196
+ }
197
+
198
+ // ── Mid-roll ───────────────────────────────────────────────────────────────
199
+
200
+ override fun loadMidAd(adUnitId: String, adObjectId: String) {
201
+ val ad = NovvyMidAd(adUnitId)
202
+ adMap[adObjectId] = ad
203
+ ad.load(object : NovvyMidAdListener {
204
+ override fun onAdLoaded(ad: NovvyMidAd) = emit(adObjectId, adUnitId, EV_LOADED)
205
+ override fun onAdFailedToLoad(ad: NovvyMidAd, error: NovvyError) =
206
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
207
+ override fun onAdShown(ad: NovvyMidAd) = emit(adObjectId, adUnitId, EV_SHOWED)
208
+ override fun onAdClosed(ad: NovvyMidAd) = emit(adObjectId, adUnitId, EV_DISMISSED)
209
+ })
210
+ }
211
+
212
+ override fun showMidAd(adObjectId: String) = runOnMain {
213
+ val ad = adMap[adObjectId] as? NovvyMidAd ?: return@runOnMain
214
+ val activity = requireActivity(adObjectId) ?: return@runOnMain
215
+ ad.show(activity)
216
+ }
217
+
218
+ // ── End-roll ─────────────────────────────────────────────────────────────
219
+
220
+ override fun loadEndAd(adUnitId: String, adObjectId: String) {
221
+ val ad = NovvyEndAd(adUnitId)
222
+ adMap[adObjectId] = ad
223
+ ad.load(object : NovvyEndAdListener {
224
+ override fun onAdLoaded(ad: NovvyEndAd) = emit(adObjectId, adUnitId, EV_LOADED)
225
+ override fun onAdFailedToLoad(ad: NovvyEndAd, error: NovvyError) =
226
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
227
+ override fun onAdShown(ad: NovvyEndAd) = emit(adObjectId, adUnitId, EV_SHOWED)
228
+ override fun onAdClosed(ad: NovvyEndAd) = emit(adObjectId, adUnitId, EV_DISMISSED)
229
+ })
230
+ }
231
+
232
+ override fun showEndAd(adObjectId: String) = runOnMain {
233
+ val ad = adMap[adObjectId] as? NovvyEndAd ?: return@runOnMain
234
+ val activity = requireActivity(adObjectId) ?: return@runOnMain
235
+ ad.show(activity)
236
+ }
237
+
238
+ // ── Scroll (inline feed) ─────────────────────────────────────────────────
239
+
240
+ override fun loadScrollAd(adUnitId: String, adObjectId: String, episodeNumber: Double?) {
241
+ val ad = NovvyScrollAd(adUnitId = adUnitId, episode = episodeNumber?.toInt())
242
+ adMap[adObjectId] = ad
243
+ ad.load(object : NovvyScrollAdListener {
244
+ override fun onAdLoaded(ad: NovvyScrollAd) = emit(adObjectId, adUnitId, EV_LOADED)
245
+ override fun onAdFailedToLoad(ad: NovvyScrollAd, error: NovvyError) =
246
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
247
+ override fun onAdShown(ad: NovvyScrollAd) = emit(adObjectId, adUnitId, EV_SHOWED)
248
+ override fun onAdHidden(ad: NovvyScrollAd) = emit(adObjectId, adUnitId, EV_HIDDEN)
249
+ })
250
+ }
251
+
252
+ override fun attachScrollAd(
253
+ adObjectId: String,
254
+ durationSeconds: Double?,
255
+ useDefaultSwipeOverlay: Boolean,
256
+ ) = runOnMain {
257
+ val ad = adMap[adObjectId] as? NovvyScrollAd ?: return@runOnMain
258
+ val container = scrollContainerMap[adObjectId] ?: return@runOnMain
259
+ ad.attach(
260
+ container = container,
261
+ durationSeconds = durationSeconds?.toInt(),
262
+ useDefaultSwipeOverlay = useDefaultSwipeOverlay,
263
+ onAdPlaybackTick = { remaining -> emit(adObjectId, "", EV_PLAYBACK_TICK, remainingSeconds = remaining) },
264
+ )
265
+ }
266
+
267
+ override fun setScrollAdPlaying(adObjectId: String, playing: Boolean) = runOnMain {
268
+ (adMap[adObjectId] as? NovvyScrollAd)?.setPlaying(playing)
269
+ }
270
+
271
+ override fun detachScrollAd(adObjectId: String) = runOnMain {
272
+ (adMap[adObjectId] as? NovvyScrollAd)?.detach()
273
+ scrollContainerMap.remove(adObjectId)
274
+ }
275
+
276
+ // ── Pause (realtime pause card) ─────────────────────────────────────────
277
+
278
+ override fun attachPauseAd(
279
+ adUnitId: String,
280
+ adObjectId: String,
281
+ positionMs: Double,
282
+ isPlaying: Boolean,
283
+ ) = runOnMain {
284
+ val container = pauseContainerMap[adObjectId] ?: return@runOnMain
285
+ val adapter = RnPlayerAdapter().apply {
286
+ currentPositionMs = positionMs.toLong()
287
+ this.isPlaying = isPlaying
288
+ }
289
+ pauseAdapterMap[adObjectId] = adapter
290
+ val ad = NovvyPauseAd(adUnitId)
291
+ adMap[adObjectId] = ad
292
+ ad.attach(container, adapter, object : NovvyPauseAdListener {
293
+ override fun onAdFailedToLoad(ad: NovvyPauseAd, error: NovvyError) =
294
+ emit(adObjectId, adUnitId, EV_FAILED, errorText(error))
295
+ override fun onAdShown(ad: NovvyPauseAd) = emit(adObjectId, adUnitId, EV_SHOWED)
296
+ override fun onAdHidden(ad: NovvyPauseAd) = emit(adObjectId, adUnitId, EV_HIDDEN)
297
+ })
298
+ }
299
+
300
+ override fun updatePauseAdPlayerState(
301
+ adObjectId: String,
302
+ positionMs: Double,
303
+ isPlaying: Boolean,
304
+ ) {
305
+ val adapter = pauseAdapterMap[adObjectId] ?: return
306
+ adapter.currentPositionMs = positionMs.toLong()
307
+ adapter.isPlaying = isPlaying
308
+ }
309
+
310
+ override fun detachPauseAd(adObjectId: String) = runOnMain {
311
+ (adMap[adObjectId] as? NovvyPauseAd)?.detach()
312
+ pauseContainerMap.remove(adObjectId)
313
+ pauseAdapterMap.remove(adObjectId)
314
+ }
315
+
316
+ // ── Common ────────────────────────────────────────────────────────────────
317
+
318
+ override fun disposeAd(adObjectId: String) = runOnMain {
319
+ when (val ad = adMap[adObjectId]) {
320
+ is NovvyPauseAd -> {
321
+ ad.detach(); ad.destroy()
322
+ pauseContainerMap.remove(adObjectId)
323
+ pauseAdapterMap.remove(adObjectId)
324
+ }
325
+ is NovvyScrollAd -> {
326
+ ad.detach(); ad.destroy()
327
+ scrollContainerMap.remove(adObjectId)
328
+ }
329
+ is NovvyRewardedAd -> ad.destroy()
330
+ is NovvyInterstitialAd -> ad.destroy()
331
+ is NovvyMidAd -> ad.destroy()
332
+ is NovvyEndAd -> ad.destroy()
333
+ }
334
+ adMap.remove(adObjectId)
335
+ }
336
+
337
+ // ── Platform view helpers (called by view managers) ───────────────────────
338
+
339
+ fun registerScrollContainer(adObjectId: String, container: ViewGroup) {
340
+ scrollContainerMap[adObjectId] = container
341
+ }
342
+
343
+ fun registerPauseContainer(adObjectId: String, container: ViewGroup) {
344
+ pauseContainerMap[adObjectId] = container
345
+ }
346
+
347
+ fun onScrollViewDisposed(adObjectId: String) = detachScrollAd(adObjectId)
348
+ fun onPauseViewDisposed(adObjectId: String) = detachPauseAd(adObjectId)
349
+
350
+ private fun requireActivity(adObjectId: String): Activity? {
351
+ val activity = currentActivity
352
+ if (activity == null) {
353
+ emit(adObjectId, "", EV_FAILED, "No active Activity available")
354
+ }
355
+ return activity
356
+ }
357
+
358
+ companion object {
359
+ const val NAME = NativeNovvyAdsSpec.NAME
360
+
361
+ /** The live module instance, used by the view managers to register containers. */
362
+ @Volatile
363
+ @JvmStatic
364
+ var instance: NovvyAdsModule? = null
365
+ private set
366
+
367
+ // Event names — must match the NovvyAdEvent string union in the TS layer.
368
+ private const val EV_LOADED = "loaded"
369
+ private const val EV_FAILED = "failedToLoad"
370
+ private const val EV_SHOWED = "showed"
371
+ private const val EV_DISMISSED = "dismissed"
372
+ private const val EV_HIDDEN = "hidden"
373
+ private const val EV_EARNED_REWARD = "earnedReward"
374
+ private const val EV_PLAYBACK_TICK = "playbackTick"
375
+ }
376
+ }
377
+
378
+ // ── ReadableMap helpers ───────────────────────────────────────────────────────
379
+
380
+ private fun ReadableMap.optString(key: String): String? =
381
+ if (hasKey(key) && !isNull(key)) getString(key) else null
382
+
383
+ private fun ReadableMap.optBoolean(key: String): Boolean? =
384
+ if (hasKey(key) && !isNull(key)) getBoolean(key) else null
385
+
386
+ private fun ReadableMap.optInt(key: String): Int? =
387
+ if (hasKey(key) && !isNull(key)) getDouble(key).toInt() else null
388
+
389
+ private fun ReadableMap.optArray(key: String): ReadableArray? =
390
+ if (hasKey(key) && !isNull(key)) getArray(key) else null
@@ -0,0 +1,46 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+
10
+ /**
11
+ * React Native package exposing the Novvy Ads TurboModule and the two inline
12
+ * ad view managers (scroll + pause). Mirrors the Flutter plugin's
13
+ * `NovvyAdsPlugin` registration.
14
+ */
15
+ class NovvyAdsPackage : BaseReactPackage() {
16
+
17
+ override fun getModule(
18
+ name: String,
19
+ reactContext: ReactApplicationContext,
20
+ ): NativeModule? = if (name == NovvyAdsModule.NAME) {
21
+ NovvyAdsModule(reactContext)
22
+ } else {
23
+ null
24
+ }
25
+
26
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
27
+ ReactModuleInfoProvider {
28
+ mapOf(
29
+ NovvyAdsModule.NAME to ReactModuleInfo(
30
+ NovvyAdsModule.NAME,
31
+ NovvyAdsModule.NAME,
32
+ false, // canOverrideExistingModule
33
+ false, // needsEagerInit
34
+ false, // isCxxModule
35
+ true, // isTurboModule
36
+ ),
37
+ )
38
+ }
39
+
40
+ override fun createViewManagers(
41
+ reactContext: ReactApplicationContext,
42
+ ): List<ViewManager<*, *>> = listOf(
43
+ NovvyScrollAdViewManager(),
44
+ NovvyPauseAdViewManager(),
45
+ )
46
+ }
@@ -0,0 +1,68 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import android.view.ViewGroup
4
+ import android.widget.FrameLayout
5
+ import com.facebook.react.module.annotations.ReactModule
6
+ import com.facebook.react.uimanager.SimpleViewManager
7
+ import com.facebook.react.uimanager.ThemedReactContext
8
+ import com.facebook.react.uimanager.UIManagerHelper
9
+ import com.facebook.react.uimanager.ViewManagerDelegate
10
+ import com.facebook.react.viewmanagers.NovvyPauseAdViewManagerDelegate
11
+ import com.facebook.react.viewmanagers.NovvyPauseAdViewManagerInterface
12
+
13
+ /**
14
+ * Fabric ViewManager for the inline realtime pause-card ad container.
15
+ *
16
+ * Registers a transparent [FrameLayout] with [NovvyAdsModule] keyed by
17
+ * `adObjectId`, then fires `onViewReady` so JS issues `attachPauseAd` (there is
18
+ * no load step for pause ads). Mirrors the Flutter plugin's `NovvyPauseViewFactory`.
19
+ */
20
+ @ReactModule(name = NovvyPauseAdViewManager.NAME)
21
+ class NovvyPauseAdViewManager :
22
+ SimpleViewManager<FrameLayout>(),
23
+ NovvyPauseAdViewManagerInterface<FrameLayout> {
24
+
25
+ private val delegate = NovvyPauseAdViewManagerDelegate(this)
26
+ private val adObjectIds = HashMap<FrameLayout, String>()
27
+
28
+ override fun getName(): String = NAME
29
+
30
+ override fun getDelegate(): ViewManagerDelegate<FrameLayout> = delegate
31
+
32
+ override fun createViewInstance(context: ThemedReactContext): FrameLayout =
33
+ NovvyAdContainerView(context).apply {
34
+ layoutParams = ViewGroup.LayoutParams(
35
+ ViewGroup.LayoutParams.MATCH_PARENT,
36
+ ViewGroup.LayoutParams.MATCH_PARENT,
37
+ )
38
+ }
39
+
40
+ override fun setAdObjectId(view: FrameLayout, value: String?) {
41
+ if (value.isNullOrEmpty()) return
42
+ adObjectIds[view] = value
43
+ NovvyAdsModule.instance?.registerPauseContainer(value, view)
44
+ dispatchViewReady(view, value)
45
+ }
46
+
47
+ private fun dispatchViewReady(view: FrameLayout, adObjectId: String) {
48
+ val context = (view as? NovvyAdContainerView)?.reactContext ?: return
49
+ val dispatcher =
50
+ UIManagerHelper.getEventDispatcherForReactTag(context, view.id) ?: return
51
+ val surfaceId = UIManagerHelper.getSurfaceId(view)
52
+ dispatcher.dispatchEvent(ViewReadyEvent(surfaceId, view.id, adObjectId))
53
+ }
54
+
55
+ override fun onDropViewInstance(view: FrameLayout) {
56
+ super.onDropViewInstance(view)
57
+ adObjectIds.remove(view)?.let { NovvyAdsModule.instance?.onPauseViewDisposed(it) }
58
+ }
59
+
60
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
61
+ mutableMapOf(
62
+ ViewReadyEvent.EVENT_NAME to mapOf("registrationName" to "onViewReady"),
63
+ )
64
+
65
+ companion object {
66
+ const val NAME = "NovvyPauseAdView"
67
+ }
68
+ }
@@ -0,0 +1,72 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import android.graphics.Color
4
+ import android.view.ViewGroup
5
+ import android.widget.FrameLayout
6
+ import com.facebook.react.module.annotations.ReactModule
7
+ import com.facebook.react.uimanager.SimpleViewManager
8
+ import com.facebook.react.uimanager.ThemedReactContext
9
+ import com.facebook.react.uimanager.UIManagerHelper
10
+ import com.facebook.react.uimanager.ViewManagerDelegate
11
+ import com.facebook.react.viewmanagers.NovvyScrollAdViewManagerDelegate
12
+ import com.facebook.react.viewmanagers.NovvyScrollAdViewManagerInterface
13
+
14
+ /**
15
+ * Fabric ViewManager for the inline scroll (feed) ad container.
16
+ *
17
+ * Creates a black-backed [FrameLayout], registers it with [NovvyAdsModule]
18
+ * keyed by `adObjectId`, then fires `onViewReady` so JS issues `attachScrollAd`.
19
+ * Mirrors the Flutter plugin's `NovvyScrollViewFactory` / `NovvyScrollPlatformView`.
20
+ */
21
+ @ReactModule(name = NovvyScrollAdViewManager.NAME)
22
+ class NovvyScrollAdViewManager :
23
+ SimpleViewManager<FrameLayout>(),
24
+ NovvyScrollAdViewManagerInterface<FrameLayout> {
25
+
26
+ private val delegate = NovvyScrollAdViewManagerDelegate(this)
27
+ private val adObjectIds = HashMap<FrameLayout, String>()
28
+
29
+ override fun getName(): String = NAME
30
+
31
+ override fun getDelegate(): ViewManagerDelegate<FrameLayout> = delegate
32
+
33
+ override fun createViewInstance(context: ThemedReactContext): FrameLayout =
34
+ NovvyAdContainerView(context).apply {
35
+ layoutParams = ViewGroup.LayoutParams(
36
+ ViewGroup.LayoutParams.MATCH_PARENT,
37
+ ViewGroup.LayoutParams.MATCH_PARENT,
38
+ )
39
+ // Opaque black backdrop: the ad view is transparent until the video
40
+ // decodes its first frame; black reads as a clean loading state.
41
+ setBackgroundColor(Color.BLACK)
42
+ }
43
+
44
+ override fun setAdObjectId(view: FrameLayout, value: String?) {
45
+ if (value.isNullOrEmpty()) return
46
+ adObjectIds[view] = value
47
+ NovvyAdsModule.instance?.registerScrollContainer(value, view)
48
+ dispatchViewReady(view, value)
49
+ }
50
+
51
+ private fun dispatchViewReady(view: FrameLayout, adObjectId: String) {
52
+ val context = (view as? NovvyAdContainerView)?.reactContext ?: return
53
+ val dispatcher =
54
+ UIManagerHelper.getEventDispatcherForReactTag(context, view.id) ?: return
55
+ val surfaceId = UIManagerHelper.getSurfaceId(view)
56
+ dispatcher.dispatchEvent(ViewReadyEvent(surfaceId, view.id, adObjectId))
57
+ }
58
+
59
+ override fun onDropViewInstance(view: FrameLayout) {
60
+ super.onDropViewInstance(view)
61
+ adObjectIds.remove(view)?.let { NovvyAdsModule.instance?.onScrollViewDisposed(it) }
62
+ }
63
+
64
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
65
+ mutableMapOf(
66
+ ViewReadyEvent.EVENT_NAME to mapOf("registrationName" to "onViewReady"),
67
+ )
68
+
69
+ companion object {
70
+ const val NAME = "NovvyScrollAdView"
71
+ }
72
+ }
@@ -0,0 +1,18 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import ai.novvy.android.sdk.NovvyPlayerAdapter
4
+
5
+ /**
6
+ * A [NovvyPlayerAdapter] whose state is pushed from JS via TurboModule calls.
7
+ *
8
+ * The SDK's PlaybackMonitor polls this adapter; JS pushes updated values at
9
+ * ~500ms cadence via `updatePauseAdPlayerState`. Mirrors the Flutter plugin's
10
+ * `FlutterPlayerAdapter`.
11
+ */
12
+ internal class RnPlayerAdapter : NovvyPlayerAdapter {
13
+ @Volatile
14
+ override var currentPositionMs: Long = 0L
15
+
16
+ @Volatile
17
+ override var isPlaying: Boolean = false
18
+ }
@@ -0,0 +1,26 @@
1
+ package ai.novvy.reactnative
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ /**
8
+ * Direct event fired once the native ad container is registered with
9
+ * [NovvyAdsModule], signalling JS to issue attach (mirrors the Flutter
10
+ * plugin's `onPlatformViewCreated`).
11
+ */
12
+ internal class ViewReadyEvent(
13
+ surfaceId: Int,
14
+ viewTag: Int,
15
+ private val adObjectId: String,
16
+ ) : Event<ViewReadyEvent>(surfaceId, viewTag) {
17
+
18
+ override fun getEventName(): String = EVENT_NAME
19
+
20
+ override fun getEventData(): WritableMap =
21
+ Arguments.createMap().apply { putString("adObjectId", adObjectId) }
22
+
23
+ companion object {
24
+ const val EVENT_NAME = "topViewReady"
25
+ }
26
+ }
package/app.plugin.js ADDED
@@ -0,0 +1,4 @@
1
+ // Entry point Expo looks for when the plugin is referenced as
2
+ // "novvy-ads-react-native" in an app's `plugins` array. Points at the compiled
3
+ // config plugin (build-time only, no runtime dependency).
4
+ module.exports = require('./plugin/build');