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
package/android/build.gradle
CHANGED
|
@@ -158,7 +158,7 @@ repositories {
|
|
|
158
158
|
dependencies {
|
|
159
159
|
implementation "com.facebook.react:react-native:0.76.2"
|
|
160
160
|
|
|
161
|
-
implementation "io.cloudx:sdk:4.
|
|
161
|
+
implementation "io.cloudx:sdk:4.12.0"
|
|
162
162
|
|
|
163
163
|
testImplementation "junit:junit:4.13.2"
|
|
164
164
|
testImplementation "io.mockk:mockk:1.13.10"
|
|
@@ -52,12 +52,21 @@ object CloudXAdEvents {
|
|
|
52
52
|
const val ON_REWARDED_AD_RECEIVED_REWARD_EVENT = "OnRewardedAdReceivedRewardEvent"
|
|
53
53
|
const val ON_REWARDED_AD_REVENUE_PAID = "OnRewardedAdRevenuePaid"
|
|
54
54
|
|
|
55
|
+
// Preloaded Native Ad Module Events
|
|
56
|
+
const val ON_NATIVE_AD_CLICKED_EVENT = "OnNativeAdClickedEvent"
|
|
57
|
+
const val ON_NATIVE_AD_EXPIRED_EVENT = "OnNativeAdExpiredEvent"
|
|
58
|
+
const val ON_NATIVE_AD_CLOSED_EVENT = "OnNativeAdClosedEvent"
|
|
59
|
+
const val ON_NATIVE_AD_REVENUE_PAID = "OnNativeAdRevenuePaid"
|
|
55
60
|
|
|
56
61
|
// In-tree Native Ad View component (CloudXNativeAdView) per-view direct events.
|
|
57
62
|
// These strings are both the event key and the JS prop registrationName.
|
|
58
63
|
const val ON_NATIVE_AD_VIEW_LOADED_EVENT = "onAdLoadedEvent"
|
|
59
64
|
const val ON_NATIVE_AD_VIEW_LOAD_FAILED_EVENT = "onAdLoadFailedEvent"
|
|
65
|
+
const val ON_NATIVE_AD_VIEW_DISPLAYED_EVENT = "onAdDisplayedEvent"
|
|
60
66
|
const val ON_NATIVE_AD_VIEW_DISPLAY_FAILED_EVENT = "onAdDisplayFailedEvent"
|
|
67
|
+
// Internal: native released the view's ad (destroy, expiry, or a newer view
|
|
68
|
+
// took it), so the JS view clears its asset components. Not a publisher callback.
|
|
69
|
+
const val ON_NATIVE_AD_VIEW_BINDING_CLEARED_EVENT = "onAdBindingClearedEvent"
|
|
61
70
|
const val ON_NATIVE_AD_VIEW_CLICKED_EVENT = "onAdClickedEvent"
|
|
62
71
|
const val ON_NATIVE_AD_VIEW_EXPIRED_EVENT = "onAdExpiredEvent"
|
|
63
72
|
const val ON_NATIVE_AD_VIEW_CLOSED_EVENT = "onAdClosedEvent"
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
package com.cloudxreactnative
|
|
2
2
|
|
|
3
3
|
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.drawable.BitmapDrawable
|
|
4
5
|
import android.util.Base64
|
|
5
6
|
import com.facebook.react.bridge.Arguments
|
|
6
7
|
import com.facebook.react.bridge.WritableMap
|
|
7
8
|
import io.cloudx.sdk.CloudXAd
|
|
8
9
|
import io.cloudx.sdk.CloudXError
|
|
10
|
+
import io.cloudx.sdk.CloudXNativeAd
|
|
11
|
+
import org.json.JSONObject
|
|
9
12
|
import java.io.ByteArrayOutputStream
|
|
10
13
|
|
|
11
14
|
fun getAdInfo(ad: CloudXAd): WritableMap {
|
|
@@ -22,6 +25,79 @@ fun getAdInfo(ad: CloudXAd): WritableMap {
|
|
|
22
25
|
return map
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Single source of truth for native ad metadata JSON serialization.
|
|
30
|
+
* Used by both CloudXNativeAdView's nativeAdJson string field and the
|
|
31
|
+
* preloaded module's nativeAd WritableMap dictionary.
|
|
32
|
+
*/
|
|
33
|
+
fun nativeAdMetadataJsonObject(nativeAd: CloudXNativeAd): JSONObject {
|
|
34
|
+
val iconImageSource = nativeAd.icon?.let { icon ->
|
|
35
|
+
if (icon.uri == null) {
|
|
36
|
+
(icon.drawable as? BitmapDrawable)?.bitmap?.let(::bitmapToBase64Png)
|
|
37
|
+
} else {
|
|
38
|
+
null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
val json = JSONObject()
|
|
42
|
+
json.put("title", nativeAd.title ?: JSONObject.NULL)
|
|
43
|
+
json.put("advertiser", nativeAd.advertiser ?: JSONObject.NULL)
|
|
44
|
+
json.put("body", nativeAd.body ?: JSONObject.NULL)
|
|
45
|
+
json.put("callToAction", nativeAd.callToAction ?: JSONObject.NULL)
|
|
46
|
+
nativeAd.starRating?.let { json.put("starRating", it) }
|
|
47
|
+
if (nativeAd.mediaContentAspectRatio > 0f) {
|
|
48
|
+
json.put("mediaContentAspectRatio", nativeAd.mediaContentAspectRatio.toDouble())
|
|
49
|
+
}
|
|
50
|
+
val iconAvailable = nativeAd.icon != null
|
|
51
|
+
json.put("isIconImageAvailable", iconAvailable)
|
|
52
|
+
json.put("isOptionsViewAvailable", nativeAd.optionsView != null)
|
|
53
|
+
json.put("isMediaViewAvailable", nativeAd.mediaView != null)
|
|
54
|
+
nativeAd.icon?.let { icon ->
|
|
55
|
+
icon.uri?.let { json.put("url", it.toString()) }
|
|
56
|
+
if (icon.uri == null) {
|
|
57
|
+
iconImageSource?.let { json.put("imageSource", it) }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
json.put("isVideoContent", nativeAd.isVideoContent)
|
|
61
|
+
json.put("videoDuration", nativeAd.videoDuration)
|
|
62
|
+
json.put("isExpired", nativeAd.isExpired)
|
|
63
|
+
return json
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun nativeAdMetadataJson(nativeAd: CloudXNativeAd): String =
|
|
67
|
+
nativeAdMetadataJsonObject(nativeAd).toString()
|
|
68
|
+
|
|
69
|
+
fun nativeAdMetadataMap(nativeAd: CloudXNativeAd): WritableMap {
|
|
70
|
+
val json = nativeAdMetadataJsonObject(nativeAd)
|
|
71
|
+
val map = Arguments.createMap()
|
|
72
|
+
val keys = json.keys()
|
|
73
|
+
while (keys.hasNext()) {
|
|
74
|
+
val key = keys.next()
|
|
75
|
+
if (json.isNull(key)) {
|
|
76
|
+
map.putNull(key)
|
|
77
|
+
} else {
|
|
78
|
+
when (val value = json.get(key)) {
|
|
79
|
+
is Boolean -> map.putBoolean(key, value)
|
|
80
|
+
is Double -> map.putDouble(key, value)
|
|
81
|
+
is Number -> map.putDouble(key, value.toDouble())
|
|
82
|
+
is String -> map.putString(key, value)
|
|
83
|
+
else -> map.putString(key, value.toString())
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return map
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun getNativeAdInfo(ad: CloudXAd, nativeAdId: String? = null): WritableMap {
|
|
91
|
+
val map = getAdInfo(ad)
|
|
92
|
+
ad.nativeAd?.let { nativeAd ->
|
|
93
|
+
map.putMap("nativeAd", nativeAdMetadataMap(nativeAd))
|
|
94
|
+
}
|
|
95
|
+
if (nativeAdId != null) {
|
|
96
|
+
map.putString("nativeAdId", nativeAdId)
|
|
97
|
+
}
|
|
98
|
+
return map
|
|
99
|
+
}
|
|
100
|
+
|
|
25
101
|
/**
|
|
26
102
|
* Icon encoding for CloudXNativeAdView's codegen-typed `nativeAdJson`
|
|
27
103
|
* direct-event field, which can't carry a nested WritableMap. Capped at 96px
|
|
@@ -30,6 +30,7 @@ import io.cloudx.sdk.CloudXArbiterListener
|
|
|
30
30
|
import io.cloudx.sdk.CloudXArbiterPrecision
|
|
31
31
|
import io.cloudx.sdk.CloudXArbiterResult
|
|
32
32
|
import io.cloudx.sdk.CloudXError
|
|
33
|
+
import io.cloudx.sdk.CloudXErrorCode
|
|
33
34
|
import io.cloudx.sdk.CloudXInitializationConfiguration
|
|
34
35
|
import io.cloudx.sdk.CloudXInitializationListener
|
|
35
36
|
import io.cloudx.sdk.CloudXAppOpenAd
|
|
@@ -44,7 +45,9 @@ import io.cloudx.sdk.CloudXReward
|
|
|
44
45
|
import io.cloudx.sdk.CloudXRewardedAd
|
|
45
46
|
import io.cloudx.sdk.CloudXRewardedListener
|
|
46
47
|
import io.cloudx.sdk.CloudXSdkConfiguration
|
|
48
|
+
import io.cloudx.sdk.CloudXNativeAdLoader
|
|
47
49
|
import java.util.Locale
|
|
50
|
+
import java.util.UUID
|
|
48
51
|
import java.util.concurrent.ConcurrentHashMap
|
|
49
52
|
|
|
50
53
|
class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationContext) : LifecycleEventListener {
|
|
@@ -56,6 +59,7 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
|
|
|
56
59
|
private val interstitials = ConcurrentHashMap<String, CloudXInterstitialAd>()
|
|
57
60
|
private val appOpenAds = ConcurrentHashMap<String, CloudXAppOpenAd>()
|
|
58
61
|
private val rewardedAds = ConcurrentHashMap<String, CloudXRewardedAd>()
|
|
62
|
+
private val nativeAdHolders = ConcurrentHashMap<String, CloudXNativeAdHolder>()
|
|
59
63
|
private val adViews = ConcurrentHashMap<String, SdkAdView>()
|
|
60
64
|
private val adViewAdFormats = ConcurrentHashMap<String, CloudXAdFormat>()
|
|
61
65
|
private val adViewPositions = ConcurrentHashMap<String, String>()
|
|
@@ -448,6 +452,91 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
|
|
|
448
452
|
}
|
|
449
453
|
}
|
|
450
454
|
|
|
455
|
+
// =========================================================================
|
|
456
|
+
// Native (Preloaded)
|
|
457
|
+
// =========================================================================
|
|
458
|
+
|
|
459
|
+
fun getHolder(nativeAdId: String): CloudXNativeAdHolder? = nativeAdHolders[nativeAdId]
|
|
460
|
+
|
|
461
|
+
fun loadNativeAd(adUnitId: String, options: ReadableMap, promise: Promise) {
|
|
462
|
+
onUiThread {
|
|
463
|
+
if (!CloudX.isInitialized()) {
|
|
464
|
+
promise.reject(
|
|
465
|
+
CloudXErrorCode.NOT_INITIALIZED.code.toString(),
|
|
466
|
+
"CloudX SDK is not initialized"
|
|
467
|
+
)
|
|
468
|
+
return@onUiThread
|
|
469
|
+
}
|
|
470
|
+
if (adUnitId.isBlank()) {
|
|
471
|
+
promise.reject(
|
|
472
|
+
CloudXErrorCode.INVALID_AD_UNIT.code.toString(),
|
|
473
|
+
"Invalid or empty adUnitId"
|
|
474
|
+
)
|
|
475
|
+
return@onUiThread
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
val nativeAdId = UUID.randomUUID().toString()
|
|
479
|
+
val loader = CloudX.createNativeAdLoader(reactApplicationContext, adUnitId)
|
|
480
|
+
applyNativeAdOptions(loader, options)
|
|
481
|
+
|
|
482
|
+
val holder = CloudXNativeAdHolder(
|
|
483
|
+
nativeAdId = nativeAdId,
|
|
484
|
+
adUnitId = adUnitId,
|
|
485
|
+
loader = loader,
|
|
486
|
+
onEvent = { eventName, payload -> sendReactNativeEvent(eventName, payload) },
|
|
487
|
+
onLoadSuccess = { adInfo ->
|
|
488
|
+
val result = Arguments.createMap()
|
|
489
|
+
result.putString("nativeAdId", nativeAdId)
|
|
490
|
+
result.putMap("adInfo", adInfo)
|
|
491
|
+
promise.resolve(result)
|
|
492
|
+
},
|
|
493
|
+
onLoadFailure = { error ->
|
|
494
|
+
promise.reject(error.code.code.toString(), error.message)
|
|
495
|
+
nativeAdHolders.remove(nativeAdId)
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
nativeAdHolders[nativeAdId] = holder
|
|
500
|
+
loader.nativeAdListener = holder
|
|
501
|
+
loader.revenueListener = holder
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
loader.loadAd()
|
|
505
|
+
} catch (e: Exception) {
|
|
506
|
+
Log.e(TAG, "loadNativeAd: failed to start load", e)
|
|
507
|
+
promise.reject(CloudXErrorCode.INTERNAL_ERROR.code.toString(), e.message ?: "Internal error")
|
|
508
|
+
nativeAdHolders.remove(nativeAdId)
|
|
509
|
+
holder.destroy()
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fun destroyNativeAd(nativeAdId: String) {
|
|
515
|
+
onUiThread {
|
|
516
|
+
if (nativeAdId.isBlank()) return@onUiThread
|
|
517
|
+
val holder = nativeAdHolders.remove(nativeAdId) ?: return@onUiThread
|
|
518
|
+
holder.destroy()
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private fun applyNativeAdOptions(loader: CloudXNativeAdLoader, options: ReadableMap) {
|
|
523
|
+
fun string(key: String) = if (options.hasKey(key) && !options.isNull(key)) options.getString(key) else null
|
|
524
|
+
fun flag(key: String) = options.hasKey(key) && !options.isNull(key) && options.getBoolean(key)
|
|
525
|
+
// Extras arrive as {key, value} entries, the wire format CloudXNativeAdView also receives.
|
|
526
|
+
fun entries(key: String) =
|
|
527
|
+
if (options.hasKey(key) && !options.isNull(key)) CloudXExtraParameters.entryArrayToMap(options.getArray(key)) else emptyMap()
|
|
528
|
+
|
|
529
|
+
loader.applyLoadOptions(
|
|
530
|
+
placement = string("placement"),
|
|
531
|
+
customData = string("customData"),
|
|
532
|
+
disableVideoFullScreen = flag("disableVideoFullScreen"),
|
|
533
|
+
startVideoUnmuted = flag("startVideoUnmuted"),
|
|
534
|
+
hideVideoMediaControls = flag("hideVideoMediaControls"),
|
|
535
|
+
)
|
|
536
|
+
for ((key, value) in entries("extraParameters")) loader.setExtraParameter(key, value)
|
|
537
|
+
for ((key, value) in entries("localExtraParameters")) loader.setLocalExtraParameter(key, value)
|
|
538
|
+
}
|
|
539
|
+
|
|
451
540
|
// =========================================================================
|
|
452
541
|
// Listener Factories
|
|
453
542
|
// =========================================================================
|
|
@@ -1205,6 +1294,23 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
|
|
|
1205
1294
|
}
|
|
1206
1295
|
rewardedAds.clear()
|
|
1207
1296
|
}
|
|
1297
|
+
// Preloaded native ads are not released here. React Native calls
|
|
1298
|
+
// onHostDestroy whenever the Activity goes away while JS lives on (a
|
|
1299
|
+
// configuration change, or leaving the app and coming back), and a
|
|
1300
|
+
// held ad is the handle's until destroyNativeAd, expiry or module
|
|
1301
|
+
// invalidation. Their loaders use the application context, so they
|
|
1302
|
+
// hold nothing tied to the Activity.
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
fun invalidate() {
|
|
1306
|
+
onHostDestroy()
|
|
1307
|
+
// JS is going away with the module, so no handle remains to own them.
|
|
1308
|
+
onUiThread {
|
|
1309
|
+
for (holder in nativeAdHolders.values) {
|
|
1310
|
+
holder.destroy()
|
|
1311
|
+
}
|
|
1312
|
+
nativeAdHolders.clear()
|
|
1313
|
+
}
|
|
1208
1314
|
}
|
|
1209
1315
|
|
|
1210
1316
|
// =========================================================================
|
|
@@ -1256,5 +1362,9 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
|
|
|
1256
1362
|
"ON_REWARDED_AD_HIDDEN_EVENT" to CloudXAdEvents.ON_REWARDED_AD_HIDDEN_EVENT,
|
|
1257
1363
|
"ON_REWARDED_AD_RECEIVED_REWARD_EVENT" to CloudXAdEvents.ON_REWARDED_AD_RECEIVED_REWARD_EVENT,
|
|
1258
1364
|
"ON_REWARDED_AD_REVENUE_PAID" to CloudXAdEvents.ON_REWARDED_AD_REVENUE_PAID,
|
|
1365
|
+
"ON_NATIVE_AD_CLICKED_EVENT" to CloudXAdEvents.ON_NATIVE_AD_CLICKED_EVENT,
|
|
1366
|
+
"ON_NATIVE_AD_EXPIRED_EVENT" to CloudXAdEvents.ON_NATIVE_AD_EXPIRED_EVENT,
|
|
1367
|
+
"ON_NATIVE_AD_CLOSED_EVENT" to CloudXAdEvents.ON_NATIVE_AD_CLOSED_EVENT,
|
|
1368
|
+
"ON_NATIVE_AD_REVENUE_PAID" to CloudXAdEvents.ON_NATIVE_AD_REVENUE_PAID,
|
|
1259
1369
|
)
|
|
1260
1370
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
package com.cloudxreactnative
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import io.cloudx.sdk.CloudXAd
|
|
6
|
+
import io.cloudx.sdk.CloudXAdRevenueListener
|
|
7
|
+
import io.cloudx.sdk.CloudXError
|
|
8
|
+
import io.cloudx.sdk.CloudXErrorCode
|
|
9
|
+
import io.cloudx.sdk.CloudXNativeAdListener
|
|
10
|
+
import io.cloudx.sdk.CloudXNativeAdLoader
|
|
11
|
+
import io.cloudx.sdk.CloudXNativeAdView as SdkNativeAdView
|
|
12
|
+
import io.cloudx.sdk.toCloudXError
|
|
13
|
+
import java.lang.ref.WeakReference
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Holds a preloaded native ad loaded without a view.
|
|
17
|
+
*
|
|
18
|
+
* Implements [CloudXNativeAdListener] and [CloudXAdRevenueListener] so all lifecycle
|
|
19
|
+
* and revenue events fire on the module handle. Bound views borrow the held ad for
|
|
20
|
+
* display without taking ownership of the loader or ad lifecycle.
|
|
21
|
+
*/
|
|
22
|
+
class CloudXNativeAdHolder(
|
|
23
|
+
val nativeAdId: String,
|
|
24
|
+
val adUnitId: String,
|
|
25
|
+
val loader: CloudXNativeAdLoader,
|
|
26
|
+
private val onEvent: (String, WritableMap) -> Unit,
|
|
27
|
+
private var onLoadSuccess: ((WritableMap) -> Unit)? = null,
|
|
28
|
+
private var onLoadFailure: ((CloudXError) -> Unit)? = null,
|
|
29
|
+
) : CloudXNativeAdListener, CloudXAdRevenueListener {
|
|
30
|
+
|
|
31
|
+
private companion object {
|
|
32
|
+
const val TAG = "CloudXNativeAdHolder"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var ad: CloudXAd? = null
|
|
36
|
+
var expired: Boolean = false
|
|
37
|
+
|
|
38
|
+
// Set by the ad's first revenue, which CloudX reports on its impression.
|
|
39
|
+
// Rendering alone does not count: an ad rendered into a view nobody saw
|
|
40
|
+
// can still move to another view, and its expiry is still reported.
|
|
41
|
+
var impressed: Boolean = false
|
|
42
|
+
private set
|
|
43
|
+
|
|
44
|
+
// Set once the ad has registered with a view for click and impression
|
|
45
|
+
// tracking. The Android one-view rule keys on this, not on the impression:
|
|
46
|
+
// the SDK tracks an impression before its revenue reaches this holder, so
|
|
47
|
+
// an impression-keyed rule leaves a window where a second view could
|
|
48
|
+
// register the ad and a partner could report it twice (CXD-6018).
|
|
49
|
+
var registered: Boolean = false
|
|
50
|
+
private set
|
|
51
|
+
|
|
52
|
+
fun markRegistered() {
|
|
53
|
+
registered = true
|
|
54
|
+
}
|
|
55
|
+
var boundView: WeakReference<CloudXNativeAdView>? = null
|
|
56
|
+
var isDestroyed: Boolean = false
|
|
57
|
+
private set
|
|
58
|
+
|
|
59
|
+
private var expiredEventEmitted: Boolean = false
|
|
60
|
+
|
|
61
|
+
override fun onNativeAdLoaded(adView: SdkNativeAdView?, ad: CloudXAd) {
|
|
62
|
+
if (isDestroyed) return
|
|
63
|
+
this.ad = ad
|
|
64
|
+
val callback = onLoadSuccess
|
|
65
|
+
onLoadSuccess = null
|
|
66
|
+
onLoadFailure = null
|
|
67
|
+
callback?.invoke(getNativeAdInfo(ad, nativeAdId))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
|
|
71
|
+
if (isDestroyed) return
|
|
72
|
+
val callback = onLoadFailure
|
|
73
|
+
onLoadSuccess = null
|
|
74
|
+
onLoadFailure = null
|
|
75
|
+
callback?.invoke(error)
|
|
76
|
+
destroy()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun onNativeAdClicked(ad: CloudXAd) {
|
|
80
|
+
if (isDestroyed) return
|
|
81
|
+
val payload = getNativeAdInfo(ad, nativeAdId)
|
|
82
|
+
onEvent(CloudXAdEvents.ON_NATIVE_AD_CLICKED_EVENT, payload)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
|
|
86
|
+
if (isDestroyed) return
|
|
87
|
+
impressed = true
|
|
88
|
+
val payload = getNativeAdInfo(cloudXAd, nativeAdId)
|
|
89
|
+
onEvent(CloudXAdEvents.ON_NATIVE_AD_REVENUE_PAID, payload)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override fun onNativeAdClosed(ad: CloudXAd) {
|
|
93
|
+
if (isDestroyed) return
|
|
94
|
+
val payload = getNativeAdInfo(ad, nativeAdId)
|
|
95
|
+
onEvent(CloudXAdEvents.ON_NATIVE_AD_CLOSED_EVENT, payload)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun onNativeAdExpired(ad: CloudXAd) {
|
|
99
|
+
expired = true
|
|
100
|
+
if (impressed) {
|
|
101
|
+
Log.d(TAG, "Native ad $nativeAdId expired after its impression; ignoring.")
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!expiredEventEmitted) {
|
|
106
|
+
expiredEventEmitted = true
|
|
107
|
+
val payload = getNativeAdInfo(ad, nativeAdId)
|
|
108
|
+
onEvent(CloudXAdEvents.ON_NATIVE_AD_EXPIRED_EVENT, payload)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// A view may still show the ad (rendered, never seen): clear it there
|
|
112
|
+
// as well, so nothing keeps showing an ad that can no longer pay.
|
|
113
|
+
val view = boundView?.get()
|
|
114
|
+
boundView = null
|
|
115
|
+
view?.onBoundNativeAdExpired()
|
|
116
|
+
|
|
117
|
+
val currentAd = this.ad ?: ad
|
|
118
|
+
loader.destroy(currentAd)
|
|
119
|
+
this.ad = null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Binds a view to this holder.
|
|
124
|
+
* One holder -> at most ONE bound view at a time. Latest bind wins:
|
|
125
|
+
* the previous view clears its rendered ad without emitting an event.
|
|
126
|
+
*/
|
|
127
|
+
fun bindView(newView: CloudXNativeAdView) {
|
|
128
|
+
val previousView = boundView?.get()
|
|
129
|
+
if (previousView != null && previousView !== newView) {
|
|
130
|
+
previousView.clearRenderedAd()
|
|
131
|
+
}
|
|
132
|
+
boundView = WeakReference(newView)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fun unbindView(view: CloudXNativeAdView) {
|
|
136
|
+
if (boundView?.get() === view) {
|
|
137
|
+
boundView = null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clears delegates, notifies any bound view of destruction, destroys the ad
|
|
143
|
+
* and loader, and marks this holder destroyed.
|
|
144
|
+
*/
|
|
145
|
+
fun destroy() {
|
|
146
|
+
if (isDestroyed) return
|
|
147
|
+
isDestroyed = true
|
|
148
|
+
|
|
149
|
+
onLoadFailure?.invoke(CloudXErrorCode.AD_NOT_READY.toCloudXError("native ad destroyed"))
|
|
150
|
+
onLoadSuccess = null
|
|
151
|
+
onLoadFailure = null
|
|
152
|
+
|
|
153
|
+
loader.nativeAdListener = null
|
|
154
|
+
loader.revenueListener = null
|
|
155
|
+
|
|
156
|
+
val view = boundView?.get()
|
|
157
|
+
boundView = null
|
|
158
|
+
view?.onBoundNativeAdDestroyed()
|
|
159
|
+
|
|
160
|
+
ad?.let { loader.destroy(it) }
|
|
161
|
+
ad = null
|
|
162
|
+
|
|
163
|
+
loader.destroy()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Applies load-time options to a native ad loader. Shared by the module's
|
|
169
|
+
* preload path and the view's auto-load path so both configure loaders the
|
|
170
|
+
* same way.
|
|
171
|
+
*/
|
|
172
|
+
internal fun CloudXNativeAdLoader.applyLoadOptions(
|
|
173
|
+
placement: String?,
|
|
174
|
+
customData: String?,
|
|
175
|
+
disableVideoFullScreen: Boolean,
|
|
176
|
+
startVideoUnmuted: Boolean,
|
|
177
|
+
hideVideoMediaControls: Boolean,
|
|
178
|
+
) {
|
|
179
|
+
setPlacement(placement)
|
|
180
|
+
setCustomData(customData)
|
|
181
|
+
isVideoFullScreenDisabled = disableVideoFullScreen
|
|
182
|
+
isStartVideoUnmuted = startVideoUnmuted
|
|
183
|
+
isVideoMediaControlsHidden = hideVideoMediaControls
|
|
184
|
+
}
|