cloudx-react-native 3.11.0 → 3.11.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.
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/cloudxreactnative/CloudXAdEvents.kt +9 -0
- package/android/src/main/java/com/cloudxreactnative/CloudXAdInfo.kt +76 -0
- package/android/src/main/java/com/cloudxreactnative/CloudXModuleImpl.kt +110 -0
- package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdHolder.kt +184 -0
- package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdView.kt +209 -70
- package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdViewManagerImpl.kt +6 -0
- package/android/src/newarch/com/cloudxreactnative/CloudXModule.kt +9 -0
- package/android/src/newarch/com/cloudxreactnative/CloudXNativeAdViewManager.kt +2 -0
- package/android/src/oldarch/com/cloudxreactnative/CloudXModule.kt +14 -0
- package/android/src/oldarch/com/cloudxreactnative/CloudXNativeAdViewManager.kt +3 -0
- package/android/src/test/java/com/cloudxreactnative/CloudXNativeAdPreloadTest.kt +351 -0
- package/android/src/test/java/com/cloudxreactnative/FakeReadable.kt +2 -2
- package/ios/CloudX.h +12 -0
- package/ios/CloudX.mm +94 -1
- package/ios/CloudXNativeAdHolder.h +66 -0
- package/ios/CloudXNativeAdHolder.mm +247 -0
- package/ios/CloudXNativeAdView.h +9 -0
- package/ios/CloudXNativeAdView.mm +308 -64
- package/ios/CloudXNativeAdViewManager.mm +7 -0
- package/lib/commonjs/CloudXNativeAd.js +178 -0
- package/lib/commonjs/CloudXNativeAd.js.map +1 -0
- package/lib/commonjs/CloudXNativeAdView.js +58 -11
- package/lib/commonjs/CloudXNativeAdView.js.map +1 -1
- package/lib/commonjs/hooks.js +203 -0
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/CloudXNativeAdViewNativeComponent.js +2 -0
- package/lib/commonjs/specs/CloudXNativeAdViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/NativeCloudX.js.map +1 -1
- package/lib/commonjs/types/NativeAd.js +21 -1
- package/lib/commonjs/types/NativeAd.js.map +1 -1
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/CloudXNativeAd.js +170 -0
- package/lib/module/CloudXNativeAd.js.map +1 -0
- package/lib/module/CloudXNativeAdView.js +58 -11
- package/lib/module/CloudXNativeAdView.js.map +1 -1
- package/lib/module/hooks.js +202 -0
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/CloudXNativeAdViewNativeComponent.js +2 -0
- package/lib/module/specs/CloudXNativeAdViewNativeComponent.js.map +1 -1
- package/lib/module/specs/NativeCloudX.js.map +1 -1
- package/lib/module/types/NativeAd.js +26 -1
- package/lib/module/types/NativeAd.js.map +1 -1
- package/lib/module/types/index.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/CloudXNativeAd.d.ts +58 -0
- package/lib/typescript/src/CloudXNativeAd.d.ts.map +1 -0
- package/lib/typescript/src/CloudXNativeAdView.d.ts +6 -13
- package/lib/typescript/src/CloudXNativeAdView.d.ts.map +1 -1
- package/lib/typescript/src/hooks.d.ts +30 -0
- package/lib/typescript/src/hooks.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/CloudXNativeAdViewNativeComponent.d.ts +23 -2
- package/lib/typescript/src/specs/CloudXNativeAdViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeCloudX.d.ts +13 -0
- package/lib/typescript/src/specs/NativeCloudX.d.ts.map +1 -1
- package/lib/typescript/src/types/NativeAd.d.ts +76 -13
- package/lib/typescript/src/types/NativeAd.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +1 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/CloudXNativeAd.ts +177 -0
- package/src/CloudXNativeAdView.tsx +82 -15
- package/src/hooks.ts +236 -0
- package/src/index.ts +14 -1
- package/src/specs/CloudXNativeAdViewNativeComponent.ts +27 -2
- package/src/specs/NativeCloudX.ts +20 -0
- package/src/types/NativeAd.ts +85 -18
- package/src/types/index.ts +4 -0
- package/src/version.ts +1 -1
|
@@ -7,6 +7,7 @@ import android.view.View.MeasureSpec
|
|
|
7
7
|
import android.view.ViewGroup
|
|
8
8
|
import android.widget.ImageView
|
|
9
9
|
import com.facebook.react.bridge.Arguments
|
|
10
|
+
import com.facebook.react.bridge.ReactContext
|
|
10
11
|
import com.facebook.react.bridge.WritableMap
|
|
11
12
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
12
13
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
@@ -66,31 +67,52 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
66
67
|
const val MEDIA_VIEW_RESIZE_RETRY_MS = 500L
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
var nativeAdId: String? = null
|
|
71
|
+
set(value) {
|
|
72
|
+
if (field == value) return
|
|
73
|
+
field = value
|
|
74
|
+
boundHolder?.unbindView(this)
|
|
75
|
+
boundHolder = null
|
|
76
|
+
ownsLoader = (value == null)
|
|
77
|
+
if (value != null) {
|
|
78
|
+
boundHolder = resolveHolder(value)
|
|
79
|
+
}
|
|
80
|
+
if (isAttachedToWindow) {
|
|
81
|
+
if (value != null) {
|
|
82
|
+
bindPreloadedAd()
|
|
83
|
+
} else {
|
|
84
|
+
loadIfPossible()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
internal var holderResolver: ((String) -> CloudXNativeAdHolder?)? = null
|
|
90
|
+
|
|
91
|
+
private var boundHolder: CloudXNativeAdHolder? = null
|
|
92
|
+
|
|
93
|
+
// Set once a preloaded ad has been presented here (template rendered, or
|
|
94
|
+
// the manual bind handed to JS), so a window re-attach (tab switch, screen
|
|
95
|
+
// transition) keeps the existing presentation instead of rendering again.
|
|
96
|
+
private var preloadedAdPresented = false
|
|
97
|
+
private var ownsLoader: Boolean = true
|
|
98
|
+
|
|
99
|
+
private fun resolveHolder(id: String): CloudXNativeAdHolder? {
|
|
100
|
+
holderResolver?.invoke(id)?.let { return it }
|
|
101
|
+
val reactContext = context as? ReactContext ?: return null
|
|
102
|
+
val module = reactContext.getNativeModule(CloudXModule::class.java)
|
|
103
|
+
?: (reactContext as? ThemedReactContext)?.reactApplicationContext?.getNativeModule(CloudXModule::class.java)
|
|
104
|
+
return module?.getHolder(id)
|
|
105
|
+
}
|
|
106
|
+
|
|
69
107
|
var adUnitId: String? = null
|
|
70
108
|
set(value) {
|
|
71
109
|
if (field == value) return
|
|
72
110
|
field = value
|
|
73
|
-
// The @ReactProp setters for `adUnitId` and `manualRender` fire in
|
|
74
|
-
// declaration order, so eagerly loading from this setter would run
|
|
75
|
-
// before `manualRender` has been applied — locking the view into
|
|
76
|
-
// the template load path even when the publisher mounted asset-view
|
|
77
|
-
// children. Defer to `onAttachedToWindow`, which RN guarantees to
|
|
78
|
-
// fire after all props are set.
|
|
79
|
-
//
|
|
80
|
-
// A change AFTER a loader exists is a live re-target: the old
|
|
81
|
-
// loader belongs to the old ad unit and must not keep serving
|
|
82
|
-
// callbacks for this view.
|
|
83
111
|
if (attachedLoader != null) {
|
|
84
112
|
releaseLoaderState()
|
|
85
|
-
// The same ordering trap described above applies to the
|
|
86
|
-
// re-target: `placement`, `customData`, the video flags and the
|
|
87
|
-
// extra parameters are applied AFTER `adUnitId`, so loading
|
|
88
|
-
// synchronously here configured the new loader from the
|
|
89
|
-
// previous unit's values. Post it instead, so the load runs
|
|
90
|
-
// once the whole prop update has been applied.
|
|
91
113
|
val target = value
|
|
92
|
-
if (isAttachedToWindow) {
|
|
93
|
-
post { if (adUnitId == target && isAttachedToWindow) loadIfPossible() }
|
|
114
|
+
if (isAttachedToWindow && nativeAdId == null) {
|
|
115
|
+
post { if (adUnitId == target && isAttachedToWindow && nativeAdId == null) loadIfPossible() }
|
|
94
116
|
}
|
|
95
117
|
}
|
|
96
118
|
}
|
|
@@ -232,11 +254,7 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
232
254
|
attachedLoader = loader
|
|
233
255
|
loader.nativeAdListener = this
|
|
234
256
|
loader.revenueListener = this
|
|
235
|
-
loader.
|
|
236
|
-
loader.setCustomData(customData)
|
|
237
|
-
loader.isVideoFullScreenDisabled = disableVideoFullScreen
|
|
238
|
-
loader.isStartVideoUnmuted = startVideoUnmuted
|
|
239
|
-
loader.isVideoMediaControlsHidden = hideVideoMediaControls
|
|
257
|
+
loader.applyLoadOptions(placement, customData, disableVideoFullScreen, startVideoUnmuted, hideVideoMediaControls)
|
|
240
258
|
appliedExtraParameterKeys = emptySet()
|
|
241
259
|
appliedLocalExtraParameterKeys = emptySet()
|
|
242
260
|
applyExtraParametersIfReady()
|
|
@@ -277,7 +295,14 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
277
295
|
.build()
|
|
278
296
|
}
|
|
279
297
|
|
|
298
|
+
private fun createAndAttachTemplateView(): SdkNativeAdView {
|
|
299
|
+
val adView = SdkNativeAdView(context, buildBinder())
|
|
300
|
+
attachAdView(adView)
|
|
301
|
+
return adView
|
|
302
|
+
}
|
|
303
|
+
|
|
280
304
|
private fun loadIfPossible() {
|
|
305
|
+
if (!ownsLoader || nativeAdId != null) return
|
|
281
306
|
if (loadRequested) return
|
|
282
307
|
val loader = currentLoader() ?: return
|
|
283
308
|
loadRequested = true
|
|
@@ -298,19 +323,129 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
298
323
|
// observer sees a window-backed view when it binds the creative;
|
|
299
324
|
// attaching only from `onNativeAdLoaded` can miss the first
|
|
300
325
|
// impression and its revenue callback.
|
|
301
|
-
val adView =
|
|
302
|
-
attachAdView(adView)
|
|
326
|
+
val adView = createAndAttachTemplateView()
|
|
303
327
|
loader.loadAd(adView)
|
|
304
328
|
}
|
|
305
329
|
}
|
|
306
330
|
|
|
331
|
+
private fun bindPreloadedAd() {
|
|
332
|
+
val id = nativeAdId ?: return
|
|
333
|
+
val holder = boundHolder ?: resolveHolder(id)?.also { boundHolder = it }
|
|
334
|
+
|
|
335
|
+
if (preloadedAdPresented && holder != null && holder.boundView?.get() === this) return
|
|
336
|
+
|
|
337
|
+
if (holder == null || holder.isDestroyed) {
|
|
338
|
+
val error = CloudXErrorCode.AD_NOT_READY.toCloudXError("native ad destroyed")
|
|
339
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(adUnitId ?: "", error))
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (holder.expired) {
|
|
344
|
+
val error = CloudXErrorCode.ADAPTER_AD_EXPIRED.toCloudXError()
|
|
345
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(holder.adUnitId, error))
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
val ad = holder.ad
|
|
350
|
+
if (ad == null) {
|
|
351
|
+
val error = CloudXErrorCode.AD_NOT_READY.toCloudXError("native ad destroyed")
|
|
352
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(holder.adUnitId, error))
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Registering an ad with a second view lets some partners report a
|
|
357
|
+
// second impression, and this Android SDK counts it (CXD-6018). Refuse
|
|
358
|
+
// the re-show once the ad has registered with any view, rather than
|
|
359
|
+
// double-count; remove once the RN pin carries the core fix.
|
|
360
|
+
if (holder.registered && holder.boundView?.get() !== this) {
|
|
361
|
+
val error = CloudXErrorCode.AD_ALREADY_SHOWING.toCloudXError(
|
|
362
|
+
"native ad was already shown in another view; load a new ad to show it again"
|
|
363
|
+
)
|
|
364
|
+
Log.w(TAG, "Refusing to show native ad $id (ad unit ${holder.adUnitId}) in a second view: ${error.formattedMessage}")
|
|
365
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(holder.adUnitId, error))
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (manualRender) {
|
|
370
|
+
pendingManualAd = ad
|
|
371
|
+
loadedCycleAd = ad
|
|
372
|
+
holder.bindView(this)
|
|
373
|
+
preloadedAdPresented = true
|
|
374
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_LOADED_EVENT, infoPayload(ad))
|
|
375
|
+
} else {
|
|
376
|
+
// Take the ad from any previous view first: clearing that view
|
|
377
|
+
// removes the SDK media and options views it inserted, and those
|
|
378
|
+
// are the views render() is about to move into this template.
|
|
379
|
+
holder.bindView(this)
|
|
380
|
+
val adView = createAndAttachTemplateView()
|
|
381
|
+
holder.loader.render(adView, ad)
|
|
382
|
+
if (holder.expired) {
|
|
383
|
+
// An expiry fired by render() already cleared this bound view
|
|
384
|
+
// and reported it; report only if it did not reach us.
|
|
385
|
+
if (boundHolder != null) {
|
|
386
|
+
val error = CloudXErrorCode.ADAPTER_AD_EXPIRED.toCloudXError()
|
|
387
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(ad.adUnitId, error))
|
|
388
|
+
}
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
holder.markRegistered()
|
|
392
|
+
preloadedAdPresented = true
|
|
393
|
+
loadedCycleAd = ad
|
|
394
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAYED_EVENT, infoPayload(ad))
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
307
398
|
override fun onAttachedToWindow() {
|
|
308
399
|
super.onAttachedToWindow()
|
|
309
|
-
|
|
400
|
+
if (nativeAdId != null) {
|
|
401
|
+
bindPreloadedAd()
|
|
402
|
+
} else {
|
|
403
|
+
loadIfPossible()
|
|
404
|
+
}
|
|
310
405
|
}
|
|
311
406
|
|
|
407
|
+
|
|
312
408
|
fun teardown() {
|
|
313
|
-
|
|
409
|
+
if (!ownsLoader) {
|
|
410
|
+
boundHolder?.unbindView(this)
|
|
411
|
+
boundHolder = null
|
|
412
|
+
clearRenderedAd()
|
|
413
|
+
} else {
|
|
414
|
+
releaseLoaderState()
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
fun clearRenderedAd() {
|
|
419
|
+
// A preloaded ad leaving this view also takes its React-rendered text
|
|
420
|
+
// and images with it: tell the JS view so its asset components clear.
|
|
421
|
+
if (nativeAdId != null && (preloadedAdPresented || pendingManualAd != null || loadedCycleAd != null)) {
|
|
422
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_BINDING_CLEARED_EVENT, Arguments.createMap().apply { putString("nativeAdId", nativeAdId) })
|
|
423
|
+
}
|
|
424
|
+
preloadedAdPresented = false
|
|
425
|
+
removeInsertedSdkViews()
|
|
426
|
+
hostedAdView?.let { (it.parent as? ViewGroup)?.removeView(it) }
|
|
427
|
+
hostedAdView = null
|
|
428
|
+
pendingManualAd = null
|
|
429
|
+
loadedCycleAd = null
|
|
430
|
+
clickableViews.clear()
|
|
431
|
+
assetViewsByRole.clear()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fun onBoundNativeAdDestroyed() {
|
|
435
|
+
val unitId = adUnitId ?: boundHolder?.adUnitId ?: ""
|
|
436
|
+
boundHolder = null
|
|
437
|
+
clearRenderedAd()
|
|
438
|
+
val error = CloudXErrorCode.AD_NOT_READY.toCloudXError("native ad destroyed")
|
|
439
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(unitId, error))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** The bound ad expired before its impression; the holder released it. */
|
|
443
|
+
fun onBoundNativeAdExpired() {
|
|
444
|
+
val unitId = boundHolder?.adUnitId ?: adUnitId ?: ""
|
|
445
|
+
boundHolder = null
|
|
446
|
+
clearRenderedAd()
|
|
447
|
+
val error = CloudXErrorCode.ADAPTER_AD_EXPIRED.toCloudXError("native ad expired")
|
|
448
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(unitId, error))
|
|
314
449
|
}
|
|
315
450
|
|
|
316
451
|
/**
|
|
@@ -320,10 +455,12 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
320
455
|
* drops the view instance and when `adUnitId` is re-targeted live.
|
|
321
456
|
*/
|
|
322
457
|
private fun releaseLoaderState() {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
458
|
+
if (ownsLoader) {
|
|
459
|
+
attachedLoader?.let {
|
|
460
|
+
it.nativeAdListener = null
|
|
461
|
+
it.revenueListener = null
|
|
462
|
+
it.destroy()
|
|
463
|
+
}
|
|
327
464
|
}
|
|
328
465
|
attachedLoader = null
|
|
329
466
|
removeInsertedSdkViews()
|
|
@@ -410,6 +547,30 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
410
547
|
child.layout(0, 0, w, h)
|
|
411
548
|
}
|
|
412
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Sizes each child of this host that an adapter inserted to fill it. Some
|
|
552
|
+
* adapters register against a FrameLayout they insert into a
|
|
553
|
+
* non-FrameLayout container. This host never lays out children React did
|
|
554
|
+
* not declare, so that view stays 0x0 and the partner never sees its ad on
|
|
555
|
+
* screen: manual layouts reported no impression.
|
|
556
|
+
*
|
|
557
|
+
* React gives every view it creates its tag as the view id, so a view
|
|
558
|
+
* without an id was added natively. Only views that ask to match the host
|
|
559
|
+
* on both axes are sized: partners also insert their own small tracking
|
|
560
|
+
* views, and stretching one of those over the ad stops its impression.
|
|
561
|
+
*/
|
|
562
|
+
private fun sizeAdapterInsertedChildren() {
|
|
563
|
+
for (i in 0 until childCount) {
|
|
564
|
+
val child = getChildAt(i)
|
|
565
|
+
val params = child.layoutParams ?: continue
|
|
566
|
+
val fillsHost = params.width == ViewGroup.LayoutParams.MATCH_PARENT &&
|
|
567
|
+
params.height == ViewGroup.LayoutParams.MATCH_PARENT
|
|
568
|
+
if (child.id == View.NO_ID && fillsHost && child !in insertedSdkViews) {
|
|
569
|
+
scheduleInsertedViewSizing(child, this, MEDIA_VIEW_RESIZE_RETRY_MS)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
413
574
|
/**
|
|
414
575
|
* Sizes [child] on the next frame, and again after [retryDelayMs] when that
|
|
415
576
|
* is non-zero. A single pass is not enough: the container's own bounds can
|
|
@@ -437,6 +598,7 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
437
598
|
// Commands
|
|
438
599
|
|
|
439
600
|
fun commandLoadAd() {
|
|
601
|
+
if (!ownsLoader) return
|
|
440
602
|
loadRequested = false
|
|
441
603
|
loadIfPossible()
|
|
442
604
|
}
|
|
@@ -523,7 +685,7 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
523
685
|
val ad = pendingManualAd
|
|
524
686
|
if (ad == null) {
|
|
525
687
|
val error = CloudXErrorCode.ADAPTER_INTERNAL_ERROR.toCloudXError("renderNativeAd called before ad loaded")
|
|
526
|
-
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(adUnitId ?: "", error))
|
|
688
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(adUnitId ?: boundHolder?.adUnitId ?: "", error))
|
|
527
689
|
return
|
|
528
690
|
}
|
|
529
691
|
val nativeAd = ad.nativeAd
|
|
@@ -538,15 +700,23 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
538
700
|
return
|
|
539
701
|
}
|
|
540
702
|
val registered = nativeAd.prepareForInteraction(clickableViews.toList(), this)
|
|
703
|
+
if (registered) sizeAdapterInsertedChildren()
|
|
541
704
|
if (!registered) {
|
|
542
705
|
// A load that cannot register click/impression tracking is a
|
|
543
706
|
// display failure: the ad renders but never produces revenue.
|
|
544
707
|
val error = CloudXErrorCode.ADAPTER_INTERNAL_ERROR.toCloudXError("Native ad failed to register click/impression tracking")
|
|
545
708
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT, loadFailedPayload(ad.adUnitId, error))
|
|
709
|
+
} else {
|
|
710
|
+
boundHolder?.let { holder ->
|
|
711
|
+
holder.bindView(this)
|
|
712
|
+
holder.markRegistered()
|
|
713
|
+
}
|
|
714
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAYED_EVENT, infoPayload(ad))
|
|
546
715
|
}
|
|
547
716
|
}
|
|
548
717
|
|
|
549
718
|
fun commandDestroyAd() {
|
|
719
|
+
if (!ownsLoader) return
|
|
550
720
|
// Destroy whichever ad this cycle holds: manual mode stashes it in
|
|
551
721
|
// pendingManualAd, template mode only in loadedCycleAd. Leaving the
|
|
552
722
|
// template-mode ad alive would leak SDK-side resources.
|
|
@@ -594,6 +764,9 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
594
764
|
adView?.let { attachAdView(it) }
|
|
595
765
|
}
|
|
596
766
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_LOADED_EVENT, infoPayload(ad))
|
|
767
|
+
if (!manualRender) {
|
|
768
|
+
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAYED_EVENT, infoPayload(ad))
|
|
769
|
+
}
|
|
597
770
|
}
|
|
598
771
|
|
|
599
772
|
override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
|
|
@@ -616,10 +789,12 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
616
789
|
}
|
|
617
790
|
|
|
618
791
|
override fun onNativeAdClicked(ad: CloudXAd) {
|
|
792
|
+
if (!ownsLoader) return
|
|
619
793
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_CLICKED_EVENT, infoPayload(ad))
|
|
620
794
|
}
|
|
621
795
|
|
|
622
796
|
override fun onNativeAdExpired(ad: CloudXAd) {
|
|
797
|
+
if (!ownsLoader) return
|
|
623
798
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_EXPIRED_EVENT, infoPayload(ad))
|
|
624
799
|
// The ad is invalidated: release SDK-side resources and drop our
|
|
625
800
|
// per-ad refs so the expired state can't leak into the next load
|
|
@@ -629,6 +804,7 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
629
804
|
}
|
|
630
805
|
|
|
631
806
|
override fun onNativeAdClosed(ad: CloudXAd) {
|
|
807
|
+
if (!ownsLoader) return
|
|
632
808
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_CLOSED_EVENT, infoPayload(ad))
|
|
633
809
|
// Mirrors onNativeAdExpired: the ad is done, so detach its view and
|
|
634
810
|
// clear per-ad refs. Loader stays alive for the next load cycle.
|
|
@@ -639,6 +815,7 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
639
815
|
// CloudXAdRevenueListener
|
|
640
816
|
|
|
641
817
|
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
|
|
818
|
+
if (!ownsLoader) return
|
|
642
819
|
dispatch(CloudXAdEvents.ON_NATIVE_AD_VIEW_REVENUE_PAID_EVENT, infoPayload(cloudXAd))
|
|
643
820
|
}
|
|
644
821
|
|
|
@@ -677,42 +854,4 @@ class CloudXNativeAdView(private val reactContext: ThemedReactContext) : ReactVi
|
|
|
677
854
|
map.putString("message", error.message)
|
|
678
855
|
return map
|
|
679
856
|
}
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* JSON-encoded native ad metadata for the codegen-typed `nativeAdJson`
|
|
683
|
-
* string field (codegen cannot express a nested map here). Mirrors the
|
|
684
|
-
* same field set as CloudXAdInfo.kt's imperative-event metadata map.
|
|
685
|
-
*/
|
|
686
|
-
private fun nativeAdMetadataJson(nativeAd: CloudXNativeAd): String {
|
|
687
|
-
val iconImageSource = nativeAd.icon?.let { icon ->
|
|
688
|
-
if (icon.uri == null) {
|
|
689
|
-
(icon.drawable as? BitmapDrawable)?.bitmap?.let(::bitmapToBase64Png)
|
|
690
|
-
} else {
|
|
691
|
-
null
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
val json = JSONObject()
|
|
695
|
-
json.put("title", nativeAd.title ?: JSONObject.NULL)
|
|
696
|
-
json.put("advertiser", nativeAd.advertiser ?: JSONObject.NULL)
|
|
697
|
-
json.put("body", nativeAd.body ?: JSONObject.NULL)
|
|
698
|
-
json.put("callToAction", nativeAd.callToAction ?: JSONObject.NULL)
|
|
699
|
-
nativeAd.starRating?.let { json.put("starRating", it) }
|
|
700
|
-
if (nativeAd.mediaContentAspectRatio > 0f) {
|
|
701
|
-
json.put("mediaContentAspectRatio", nativeAd.mediaContentAspectRatio.toDouble())
|
|
702
|
-
}
|
|
703
|
-
val iconAvailable = nativeAd.icon != null
|
|
704
|
-
json.put("isIconImageAvailable", iconAvailable)
|
|
705
|
-
json.put("isOptionsViewAvailable", nativeAd.optionsView != null)
|
|
706
|
-
json.put("isMediaViewAvailable", nativeAd.mediaView != null)
|
|
707
|
-
nativeAd.icon?.let { icon ->
|
|
708
|
-
icon.uri?.let { json.put("url", it.toString()) }
|
|
709
|
-
if (icon.uri == null) {
|
|
710
|
-
iconImageSource?.let { json.put("imageSource", it) }
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
json.put("isVideoContent", nativeAd.isVideoContent)
|
|
714
|
-
json.put("videoDuration", nativeAd.videoDuration)
|
|
715
|
-
json.put("isExpired", nativeAd.isExpired)
|
|
716
|
-
return json.toString()
|
|
717
|
-
}
|
|
718
857
|
}
|
|
@@ -23,7 +23,9 @@ object CloudXNativeAdViewManagerImpl {
|
|
|
23
23
|
mutableMapOf(
|
|
24
24
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_LOADED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_LOADED_EVENT),
|
|
25
25
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_LOAD_FAILED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_LOAD_FAILED_EVENT),
|
|
26
|
+
CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAYED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAYED_EVENT),
|
|
26
27
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT),
|
|
28
|
+
CloudXAdEvents.ON_NATIVE_AD_VIEW_BINDING_CLEARED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_BINDING_CLEARED_EVENT),
|
|
27
29
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_CLICKED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_CLICKED_EVENT),
|
|
28
30
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_EXPIRED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_EXPIRED_EVENT),
|
|
29
31
|
CloudXAdEvents.ON_NATIVE_AD_VIEW_CLOSED_EVENT to eventRegistration(CloudXAdEvents.ON_NATIVE_AD_VIEW_CLOSED_EVENT),
|
|
@@ -32,6 +34,10 @@ object CloudXNativeAdViewManagerImpl {
|
|
|
32
34
|
|
|
33
35
|
private fun eventRegistration(name: String): Map<String, String> = MapBuilder.of("registrationName", name)
|
|
34
36
|
|
|
37
|
+
fun setNativeAdId(view: CloudXNativeAdView, nativeAdId: String?) {
|
|
38
|
+
view.nativeAdId = nativeAdId
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
fun setAdUnitId(view: CloudXNativeAdView, adUnitId: String?) {
|
|
36
42
|
view.adUnitId = adUnitId
|
|
37
43
|
}
|
|
@@ -80,6 +80,15 @@ class CloudXModule(reactContext: ReactApplicationContext) :
|
|
|
80
80
|
@ReactMethod override fun showRewarded(adUnitId: String, placement: String?, customData: String?) = impl.showRewarded(adUnitId, placement, customData)
|
|
81
81
|
@ReactMethod override fun destroyRewarded(adUnitId: String) = impl.destroyRewarded(adUnitId)
|
|
82
82
|
|
|
83
|
+
@ReactMethod override fun loadNativeAd(adUnitId: String, options: ReadableMap, promise: Promise) = impl.loadNativeAd(adUnitId, options, promise)
|
|
84
|
+
@ReactMethod override fun destroyNativeAd(nativeAdId: String) = impl.destroyNativeAd(nativeAdId)
|
|
85
|
+
|
|
86
|
+
fun getHolder(nativeAdId: String): CloudXNativeAdHolder? = impl.getHolder(nativeAdId)
|
|
87
|
+
|
|
88
|
+
override fun invalidate() {
|
|
89
|
+
impl.invalidate()
|
|
90
|
+
super.invalidate()
|
|
91
|
+
}
|
|
83
92
|
|
|
84
93
|
@ReactMethod override fun addListener(eventName: String) {}
|
|
85
94
|
@ReactMethod override fun removeListeners(count: Double) {}
|
|
@@ -37,6 +37,8 @@ class CloudXNativeAdViewManager :
|
|
|
37
37
|
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
38
38
|
CloudXNativeAdViewManagerImpl.getExportedCustomDirectEventTypeConstants()
|
|
39
39
|
|
|
40
|
+
override fun setNativeAdId(view: CloudXNativeAdView, value: String?) = CloudXNativeAdViewManagerImpl.setNativeAdId(view, value)
|
|
41
|
+
|
|
40
42
|
override fun setAdUnitId(view: CloudXNativeAdView, value: String?) = CloudXNativeAdViewManagerImpl.setAdUnitId(view, value)
|
|
41
43
|
|
|
42
44
|
override fun setTemplateName(view: CloudXNativeAdView, value: String?) = CloudXNativeAdViewManagerImpl.setTemplateName(view, value)
|
|
@@ -6,7 +6,12 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
6
6
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
7
|
import com.facebook.react.bridge.ReactMethod
|
|
8
8
|
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
10
|
|
|
11
|
+
// Preloaded native ad views find their holder through
|
|
12
|
+
// getNativeModule(CloudXModule::class.java), which the legacy bridge resolves
|
|
13
|
+
// from this annotation.
|
|
14
|
+
@ReactModule(name = CloudXModuleImpl.MODULE_NAME)
|
|
10
15
|
class CloudXModule(reactContext: ReactApplicationContext) :
|
|
11
16
|
ReactContextBaseJavaModule(reactContext) {
|
|
12
17
|
|
|
@@ -95,6 +100,15 @@ class CloudXModule(reactContext: ReactApplicationContext) :
|
|
|
95
100
|
@ReactMethod fun showRewarded(adUnitId: String, placement: String?, customData: String?) = impl.showRewarded(adUnitId, placement, customData)
|
|
96
101
|
@ReactMethod fun destroyRewarded(adUnitId: String) = impl.destroyRewarded(adUnitId)
|
|
97
102
|
|
|
103
|
+
@ReactMethod fun loadNativeAd(adUnitId: String, options: ReadableMap, promise: Promise) = impl.loadNativeAd(adUnitId, options, promise)
|
|
104
|
+
@ReactMethod fun destroyNativeAd(nativeAdId: String) = impl.destroyNativeAd(nativeAdId)
|
|
105
|
+
|
|
106
|
+
fun getHolder(nativeAdId: String): CloudXNativeAdHolder? = impl.getHolder(nativeAdId)
|
|
107
|
+
|
|
108
|
+
override fun invalidate() {
|
|
109
|
+
impl.invalidate()
|
|
110
|
+
super.invalidate()
|
|
111
|
+
}
|
|
98
112
|
|
|
99
113
|
@ReactMethod fun addListener(eventName: String) {}
|
|
100
114
|
@ReactMethod fun removeListeners(count: Double) {}
|
|
@@ -29,6 +29,9 @@ class CloudXNativeAdViewManager : ViewGroupManager<CloudXNativeAdView>() {
|
|
|
29
29
|
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
30
30
|
CloudXNativeAdViewManagerImpl.getExportedCustomDirectEventTypeConstants()
|
|
31
31
|
|
|
32
|
+
@ReactProp(name = "nativeAdId")
|
|
33
|
+
fun setNativeAdId(view: CloudXNativeAdView, value: String?) = CloudXNativeAdViewManagerImpl.setNativeAdId(view, value)
|
|
34
|
+
|
|
32
35
|
@ReactProp(name = "adUnitId")
|
|
33
36
|
fun setAdUnitId(view: CloudXNativeAdView, value: String?) = CloudXNativeAdViewManagerImpl.setAdUnitId(view, value)
|
|
34
37
|
|