@bigcrunch/react-native-ads 0.4.0 → 0.6.0
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/README.md +5 -5
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchAds.kt +434 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchBannerView.kt +484 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchInterstitial.kt +403 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchRewarded.kt +409 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/adapters/GoogleAdsAdapter.kt +592 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AdOrchestrator.kt +623 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AnalyticsClient.kt +719 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/BidRequestClient.kt +364 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/ConfigManager.kt +300 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/DeviceContext.kt +385 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/RewardedCallback.kt +42 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/SessionManager.kt +330 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/DeviceHelper.kt +60 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/HttpClient.kt +114 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Logger.kt +71 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/PrivacyStore.kt +125 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Storage.kt +88 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/BannerAdListener.kt +55 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/InterstitialAdListener.kt +55 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/RewardedAdListener.kt +58 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/AdEvent.kt +880 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/AppConfig.kt +87 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/DeviceData.kt +18 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/PlacementConfig.kt +70 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/SessionInfo.kt +21 -0
- package/android/build.gradle +22 -10
- package/android/settings.gradle +2 -6
- package/android/src/main/java/com/bigcrunch/ads/react/BigCrunchAdsModule.kt +8 -2
- package/ios/BigCrunchAds/Sources/Adapters/GoogleAdsAdapter.swift +512 -0
- package/ios/BigCrunchAds/Sources/BigCrunchAds.swift +387 -0
- package/ios/BigCrunchAds/Sources/BigCrunchBannerView.swift +448 -0
- package/ios/BigCrunchAds/Sources/BigCrunchInterstitial.swift +412 -0
- package/ios/BigCrunchAds/Sources/BigCrunchRewarded.swift +523 -0
- package/ios/BigCrunchAds/Sources/Core/AdOrchestrator.swift +514 -0
- package/ios/BigCrunchAds/Sources/Core/AnalyticsClient.swift +874 -0
- package/ios/BigCrunchAds/Sources/Core/BidRequestClient.swift +344 -0
- package/ios/BigCrunchAds/Sources/Core/ConfigManager.swift +305 -0
- package/ios/BigCrunchAds/Sources/Core/DeviceContext.swift +284 -0
- package/ios/BigCrunchAds/Sources/Core/SessionManager.swift +392 -0
- package/ios/BigCrunchAds/Sources/Internal/HTTPClient.swift +146 -0
- package/ios/BigCrunchAds/Sources/Internal/Logger.swift +62 -0
- package/ios/BigCrunchAds/Sources/Internal/PrivacyStore.swift +129 -0
- package/ios/BigCrunchAds/Sources/Internal/Storage.swift +73 -0
- package/ios/BigCrunchAds/Sources/Models/AdEvent.swift +784 -0
- package/ios/BigCrunchAds/Sources/Models/AppConfig.swift +97 -0
- package/ios/BigCrunchAds/Sources/Models/DeviceData.swift +68 -0
- package/ios/BigCrunchAds/Sources/Models/PlacementConfig.swift +137 -0
- package/ios/BigCrunchAds/Sources/Models/SessionInfo.swift +48 -0
- package/ios/BigCrunchAdsModule.swift +37 -9
- package/ios/BigCrunchBannerViewManager.swift +0 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/package.json +7 -1
- package/react-native-bigcrunch-ads.podspec +0 -1
- package/scripts/inject-version.js +55 -0
- package/src/index.ts +3 -2
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
package com.bigcrunch.ads
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import android.util.AttributeSet
|
|
7
|
+
import android.view.View
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
|
+
import com.bigcrunch.ads.adapters.GoogleAdsAdapter
|
|
10
|
+
import com.bigcrunch.ads.core.AdOrchestrator
|
|
11
|
+
import com.bigcrunch.ads.core.BidRequestClient
|
|
12
|
+
import com.bigcrunch.ads.core.BannerCallback
|
|
13
|
+
import com.bigcrunch.ads.internal.BCLogger
|
|
14
|
+
import com.bigcrunch.ads.listeners.BannerAdListener
|
|
15
|
+
import com.bigcrunch.ads.models.AdSize
|
|
16
|
+
import com.bigcrunch.ads.models.RefreshConfig
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* BigCrunch Banner View - UI component for displaying banner ads
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```kotlin
|
|
23
|
+
* val bannerView = BigCrunchBannerView(context)
|
|
24
|
+
* bannerView.configure("banner_placement_id")
|
|
25
|
+
* bannerView.listener = object : BigCrunchBannerView.Listener {
|
|
26
|
+
* override fun onAdLoaded() { /* ad loaded */ }
|
|
27
|
+
* override fun onAdFailedToLoad(error: String) { /* handle error */ }
|
|
28
|
+
* override fun onAdClicked() { /* ad clicked */ }
|
|
29
|
+
* override fun onAdImpression() { /* impression recorded */ }
|
|
30
|
+
* }
|
|
31
|
+
* bannerView.loadAd()
|
|
32
|
+
*
|
|
33
|
+
* // When done:
|
|
34
|
+
* bannerView.destroy()
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Add to layout XML:
|
|
38
|
+
* ```xml
|
|
39
|
+
* <com.bigcrunch.ads.BigCrunchBannerView
|
|
40
|
+
* android:id="@+id/banner_ad"
|
|
41
|
+
* android:layout_width="match_parent"
|
|
42
|
+
* android:layout_height="wrap_content" />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
class BigCrunchBannerView @JvmOverloads constructor(
|
|
46
|
+
context: Context,
|
|
47
|
+
attrs: AttributeSet? = null,
|
|
48
|
+
defStyleAttr: Int = 0
|
|
49
|
+
) : FrameLayout(context, attrs, defStyleAttr) {
|
|
50
|
+
|
|
51
|
+
companion object {
|
|
52
|
+
private const val TAG = "BigCrunchBannerView"
|
|
53
|
+
private const val MIN_REFRESH_INTERVAL_MS = 10000L // 10 seconds minimum
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Safely execute a block, catching exceptions to prevent SDK crashes from affecting the host app.
|
|
57
|
+
*/
|
|
58
|
+
private inline fun safeExecute(block: () -> Unit) {
|
|
59
|
+
try {
|
|
60
|
+
block()
|
|
61
|
+
} catch (e: Exception) {
|
|
62
|
+
BCLogger.e(TAG, "Exception caught in banner view - SDK will not propagate to prevent app crash", e)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Optional listener for ad events
|
|
69
|
+
*/
|
|
70
|
+
var listener: BannerAdListener? = null
|
|
71
|
+
set(value) {
|
|
72
|
+
field = value
|
|
73
|
+
bannerAdListener = value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Keep reference to support both old and new listener interfaces
|
|
77
|
+
private var bannerAdListener: BannerAdListener? = null
|
|
78
|
+
|
|
79
|
+
private var placementId: String? = null
|
|
80
|
+
private var adSize: AdSize? = null
|
|
81
|
+
private var customTargeting: Map<String, String>? = null
|
|
82
|
+
private var isConfigured = false
|
|
83
|
+
private var isDestroyed = false
|
|
84
|
+
private var isPaused = false
|
|
85
|
+
private var adOrchestrator: AdOrchestrator? = null
|
|
86
|
+
|
|
87
|
+
// Refresh properties
|
|
88
|
+
private val refreshHandler = Handler(Looper.getMainLooper())
|
|
89
|
+
private var refreshRunnable: Runnable? = null
|
|
90
|
+
private var refreshCount: Int = 0
|
|
91
|
+
private var effectiveRefreshConfig: RefreshConfig? = null
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Runnable that manually measures and lays out child views.
|
|
95
|
+
* This is needed for React Native compatibility because React Native's UIManagerModule
|
|
96
|
+
* intercepts requestLayout() calls, preventing dynamically added child views
|
|
97
|
+
* (like AdManagerAdView) from being properly measured and laid out.
|
|
98
|
+
* See: https://github.com/facebook/react-native/issues/17968
|
|
99
|
+
*/
|
|
100
|
+
private val measureAndLayout = Runnable {
|
|
101
|
+
BCLogger.d(TAG, "=== measureAndLayout START === childCount=$childCount, parent=${width}x${height}, measured=${measuredWidth}x${measuredHeight}")
|
|
102
|
+
|
|
103
|
+
for (i in 0 until childCount) {
|
|
104
|
+
val child = getChildAt(i)
|
|
105
|
+
|
|
106
|
+
// For AdManagerAdView, use its own ad size for measurement
|
|
107
|
+
val childWidth: Int
|
|
108
|
+
val childHeight: Int
|
|
109
|
+
|
|
110
|
+
if (child is com.google.android.gms.ads.admanager.AdManagerAdView) {
|
|
111
|
+
val adSize = child.adSize
|
|
112
|
+
BCLogger.d(TAG, "Child $i is AdManagerAdView, adSize=$adSize")
|
|
113
|
+
if (adSize != null) {
|
|
114
|
+
val adWidthPx = adSize.getWidthInPixels(context)
|
|
115
|
+
val adHeightPx = adSize.getHeightInPixels(context)
|
|
116
|
+
// Constrain width to parent to prevent overflow (important for adaptive banners)
|
|
117
|
+
val parentWidth = if (width > 0) width else measuredWidth
|
|
118
|
+
childWidth = if (parentWidth > 0) minOf(adWidthPx, parentWidth) else adWidthPx
|
|
119
|
+
childHeight = adHeightPx
|
|
120
|
+
BCLogger.d(TAG, "Using adSize pixels: ${adWidthPx}x${adHeightPx}, constrained width: $childWidth (parent: $parentWidth)")
|
|
121
|
+
} else {
|
|
122
|
+
// Fallback to parent dimensions
|
|
123
|
+
childWidth = if (width > 0) width else measuredWidth
|
|
124
|
+
childHeight = if (height > 0) height else measuredHeight
|
|
125
|
+
BCLogger.d(TAG, "adSize is null, using parent: ${childWidth}x${childHeight}")
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// For other views, use parent dimensions
|
|
129
|
+
childWidth = if (width > 0) width else measuredWidth
|
|
130
|
+
childHeight = if (height > 0) height else measuredHeight
|
|
131
|
+
BCLogger.d(TAG, "Child $i is ${child.javaClass.simpleName}, using parent: ${childWidth}x${childHeight}")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Skip if we don't have valid dimensions
|
|
135
|
+
if (childWidth <= 0 || childHeight <= 0) {
|
|
136
|
+
BCLogger.w(TAG, "Skipping child $i - invalid dimensions: ${childWidth}x${childHeight}")
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
BCLogger.d(TAG, "BEFORE measure/layout - child dimensions: ${child.width}x${child.height}, measured: ${child.measuredWidth}x${child.measuredHeight}, visibility: ${child.visibility}")
|
|
141
|
+
|
|
142
|
+
child.measure(
|
|
143
|
+
View.MeasureSpec.makeMeasureSpec(childWidth, View.MeasureSpec.EXACTLY),
|
|
144
|
+
View.MeasureSpec.makeMeasureSpec(childHeight, View.MeasureSpec.EXACTLY)
|
|
145
|
+
)
|
|
146
|
+
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
|
|
147
|
+
|
|
148
|
+
// Force invalidation to ensure the view is redrawn after layout
|
|
149
|
+
child.invalidate()
|
|
150
|
+
|
|
151
|
+
BCLogger.d(TAG, "AFTER measure/layout - child dimensions: ${child.width}x${child.height}, measured: ${child.measuredWidth}x${child.measuredHeight}, visibility: ${child.visibility}")
|
|
152
|
+
|
|
153
|
+
// Log additional view properties for debugging
|
|
154
|
+
if (child is com.google.android.gms.ads.admanager.AdManagerAdView) {
|
|
155
|
+
BCLogger.d(TAG, """
|
|
156
|
+
=== AD VIEW DEBUG ===
|
|
157
|
+
isShown: ${child.isShown}
|
|
158
|
+
alpha: ${child.alpha}
|
|
159
|
+
isAttachedToWindow: ${child.isAttachedToWindow}
|
|
160
|
+
hasWindowFocus: ${child.hasWindowFocus()}
|
|
161
|
+
parent: ${child.parent}
|
|
162
|
+
clipChildren (this): $clipChildren
|
|
163
|
+
clipToPadding (this): $clipToPadding
|
|
164
|
+
=====================
|
|
165
|
+
""".trimIndent())
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Invalidate this container as well to ensure redraw
|
|
170
|
+
invalidate()
|
|
171
|
+
BCLogger.d(TAG, "=== measureAndLayout END ===")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
init {
|
|
175
|
+
// Disable clipping to ensure child views can be fully drawn
|
|
176
|
+
// This is important for React Native where clipping can cause issues
|
|
177
|
+
clipChildren = false
|
|
178
|
+
clipToPadding = false
|
|
179
|
+
BCLogger.v(TAG, "BigCrunchBannerView created")
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Override requestLayout to ensure child views are properly measured and laid out.
|
|
184
|
+
* React Native suppresses normal Android layout passes, so we manually trigger them.
|
|
185
|
+
*/
|
|
186
|
+
override fun requestLayout() {
|
|
187
|
+
super.requestLayout()
|
|
188
|
+
post(measureAndLayout)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Configure the banner view with a placement ID
|
|
193
|
+
*
|
|
194
|
+
* @param placementId The placement ID from the BigCrunch dashboard
|
|
195
|
+
*/
|
|
196
|
+
fun configure(placementId: String) {
|
|
197
|
+
if (isDestroyed) {
|
|
198
|
+
BCLogger.w(TAG, "Cannot configure destroyed banner view")
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (placementId.isBlank()) {
|
|
203
|
+
BCLogger.e(TAG, "Invalid placementId: cannot be blank")
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.placementId = placementId
|
|
208
|
+
this.isConfigured = true
|
|
209
|
+
BCLogger.d(TAG, "Banner configured with placement: $placementId")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Load a banner ad
|
|
214
|
+
*
|
|
215
|
+
* Must call [configure] before calling this method.
|
|
216
|
+
* The listener will be notified of load success or failure.
|
|
217
|
+
*/
|
|
218
|
+
fun loadAd() = safeExecute {
|
|
219
|
+
if (isDestroyed) {
|
|
220
|
+
BCLogger.w(TAG, "Cannot load ad on destroyed banner view")
|
|
221
|
+
bannerAdListener?.onAdFailedToLoad("DESTROYED", "Banner view has been destroyed")
|
|
222
|
+
return@safeExecute
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!isConfigured || placementId == null) {
|
|
226
|
+
BCLogger.e(TAG, "Banner not configured. Call configure() first.")
|
|
227
|
+
bannerAdListener?.onAdFailedToLoad("NOT_CONFIGURED", "Banner not configured. Call configure() first.")
|
|
228
|
+
return@safeExecute
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
val pid = placementId ?: return@safeExecute
|
|
232
|
+
BCLogger.d(TAG, "Loading banner ad: $pid with adSize override: $adSize (refreshCount: $refreshCount)")
|
|
233
|
+
|
|
234
|
+
if (!BigCrunchAds.isInitialized()) {
|
|
235
|
+
BCLogger.e(TAG, "SDK not initialized")
|
|
236
|
+
bannerAdListener?.onAdFailedToLoad("SDK_NOT_INITIALIZED", "SDK not initialized. Call BigCrunchAds.initialize() first.")
|
|
237
|
+
return@safeExecute
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Resolve effective refresh config on first load
|
|
241
|
+
if (effectiveRefreshConfig == null) {
|
|
242
|
+
val configManager = BigCrunchAds.getConfigManager()
|
|
243
|
+
effectiveRefreshConfig = configManager.getEffectiveRefreshConfig(pid)
|
|
244
|
+
effectiveRefreshConfig?.let { config ->
|
|
245
|
+
BCLogger.d(TAG, "Refresh config resolved - enabled: ${config.enabled}, interval: ${config.intervalMs}ms, max: ${config.maxRefreshes}")
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Create AdOrchestrator lazily
|
|
250
|
+
if (adOrchestrator == null) {
|
|
251
|
+
adOrchestrator = createAdOrchestrator()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Create callback wrapper
|
|
255
|
+
val callback = object : BannerCallback {
|
|
256
|
+
override fun onAdLoaded() {
|
|
257
|
+
BCLogger.d(TAG, "Banner ad loaded: $pid")
|
|
258
|
+
bannerAdListener?.onAdLoaded()
|
|
259
|
+
// Schedule next refresh after successful load
|
|
260
|
+
scheduleRefresh()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun onAdFailedToLoad(error: String) {
|
|
264
|
+
BCLogger.w(TAG, "Banner ad failed to load: $pid - $error")
|
|
265
|
+
bannerAdListener?.onAdFailedToLoad("LOAD_ERROR", error)
|
|
266
|
+
// Note: Do NOT schedule refresh on failure to avoid retry loops
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
override fun onAdClicked() {
|
|
270
|
+
BCLogger.d(TAG, "Banner ad clicked: $pid")
|
|
271
|
+
bannerAdListener?.onAdClicked()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override fun onAdImpression() {
|
|
275
|
+
BCLogger.d(TAG, "Banner ad impression: $pid")
|
|
276
|
+
bannerAdListener?.onAdImpression()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Load the ad through the orchestrator, passing the ad size override if set
|
|
281
|
+
adOrchestrator?.loadBannerAd(pid, this, callback, adSize, refreshCount)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Destroy the banner view and release resources
|
|
286
|
+
*
|
|
287
|
+
* Call this when the banner is no longer needed (e.g., in Activity.onDestroy())
|
|
288
|
+
*/
|
|
289
|
+
fun destroy() = safeExecute {
|
|
290
|
+
if (isDestroyed) {
|
|
291
|
+
BCLogger.v(TAG, "Banner already destroyed")
|
|
292
|
+
return@safeExecute
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
BCLogger.d(TAG, "Destroying banner: $placementId")
|
|
296
|
+
|
|
297
|
+
// Stop refresh timer
|
|
298
|
+
cancelRefreshTimer()
|
|
299
|
+
|
|
300
|
+
placementId?.let { pid ->
|
|
301
|
+
adOrchestrator?.destroyBannerAd(pid)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
removeAllViews()
|
|
305
|
+
listener = null
|
|
306
|
+
adOrchestrator = null
|
|
307
|
+
effectiveRefreshConfig = null
|
|
308
|
+
refreshCount = 0
|
|
309
|
+
isDestroyed = true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Check if the banner view has been destroyed
|
|
314
|
+
*/
|
|
315
|
+
fun isDestroyed(): Boolean = isDestroyed
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get the configured placement ID
|
|
319
|
+
*/
|
|
320
|
+
fun getPlacementId(): String? = placementId
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get the current refresh count
|
|
324
|
+
*/
|
|
325
|
+
fun getRefreshCount(): Int = refreshCount
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Set the placement ID (alternative to configure method)
|
|
329
|
+
*
|
|
330
|
+
* @param placementId The placement ID from the BigCrunch dashboard
|
|
331
|
+
*/
|
|
332
|
+
fun setPlacementId(placementId: String) {
|
|
333
|
+
configure(placementId)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Set the ad size for this banner
|
|
338
|
+
*
|
|
339
|
+
* @param adSize The desired ad size
|
|
340
|
+
*/
|
|
341
|
+
fun setAdSize(adSize: AdSize) {
|
|
342
|
+
this.adSize = adSize
|
|
343
|
+
BCLogger.d(TAG, "Ad size set to: $adSize")
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get the current ad size
|
|
348
|
+
*
|
|
349
|
+
* @return The ad size, or null if not set
|
|
350
|
+
*/
|
|
351
|
+
fun getAdSize(): AdSize? = adSize
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Set custom targeting parameters for ad requests
|
|
355
|
+
*
|
|
356
|
+
* @param targeting Map of key-value pairs for custom targeting
|
|
357
|
+
*/
|
|
358
|
+
fun setCustomTargeting(targeting: Map<String, String>) {
|
|
359
|
+
this.customTargeting = targeting
|
|
360
|
+
BCLogger.d(TAG, "Custom targeting set: ${targeting.size} parameters")
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Set the refresh interval for auto-refreshing ads
|
|
365
|
+
*
|
|
366
|
+
* This overrides the config-driven refresh interval.
|
|
367
|
+
*
|
|
368
|
+
* @param intervalMs Refresh interval in milliseconds (0 to disable)
|
|
369
|
+
*/
|
|
370
|
+
fun setRefreshInterval(intervalMs: Int) {
|
|
371
|
+
if (intervalMs <= 0) {
|
|
372
|
+
effectiveRefreshConfig = RefreshConfig(enabled = false, intervalMs = 0, maxRefreshes = 0)
|
|
373
|
+
cancelRefreshTimer()
|
|
374
|
+
} else {
|
|
375
|
+
effectiveRefreshConfig = RefreshConfig(
|
|
376
|
+
enabled = true,
|
|
377
|
+
intervalMs = intervalMs,
|
|
378
|
+
maxRefreshes = effectiveRefreshConfig?.maxRefreshes ?: 99
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
BCLogger.d(TAG, "Refresh interval set to: ${intervalMs}ms")
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Set the banner ad listener
|
|
386
|
+
*
|
|
387
|
+
* @param listener The listener for ad events
|
|
388
|
+
*/
|
|
389
|
+
fun setBannerAdListener(listener: BannerAdListener) {
|
|
390
|
+
this.listener = listener
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Pause ad refresh (if auto-refresh is enabled)
|
|
395
|
+
*/
|
|
396
|
+
fun pause() {
|
|
397
|
+
isPaused = true
|
|
398
|
+
cancelRefreshTimer()
|
|
399
|
+
BCLogger.d(TAG, "Banner ad refresh paused")
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Resume ad refresh (if auto-refresh is enabled)
|
|
404
|
+
*/
|
|
405
|
+
fun resume() {
|
|
406
|
+
isPaused = false
|
|
407
|
+
BCLogger.d(TAG, "Banner ad refresh resumed")
|
|
408
|
+
scheduleRefresh()
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// MARK: - Refresh Logic
|
|
412
|
+
|
|
413
|
+
private fun scheduleRefresh() {
|
|
414
|
+
// Don't schedule if destroyed or paused
|
|
415
|
+
if (isDestroyed || isPaused) return
|
|
416
|
+
|
|
417
|
+
val config = effectiveRefreshConfig ?: return
|
|
418
|
+
if (!config.enabled) return
|
|
419
|
+
|
|
420
|
+
// Check max refreshes limit
|
|
421
|
+
if (refreshCount >= config.maxRefreshes) {
|
|
422
|
+
BCLogger.d(TAG, "Max refreshes reached (${config.maxRefreshes}) for $placementId")
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Enforce minimum interval
|
|
427
|
+
val intervalMs = maxOf(config.intervalMs.toLong(), MIN_REFRESH_INTERVAL_MS)
|
|
428
|
+
|
|
429
|
+
// Cancel any existing timer
|
|
430
|
+
cancelRefreshTimer()
|
|
431
|
+
|
|
432
|
+
BCLogger.d(TAG, "Scheduling refresh in ${intervalMs}ms (count: $refreshCount/${config.maxRefreshes}) for $placementId")
|
|
433
|
+
|
|
434
|
+
val runnable = Runnable {
|
|
435
|
+
performRefresh()
|
|
436
|
+
}
|
|
437
|
+
refreshRunnable = runnable
|
|
438
|
+
refreshHandler.postDelayed(runnable, intervalMs)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private fun performRefresh() {
|
|
442
|
+
if (isDestroyed || isPaused) return
|
|
443
|
+
|
|
444
|
+
refreshCount++
|
|
445
|
+
BCLogger.d(TAG, "Refreshing banner ad (refresh #$refreshCount) for $placementId")
|
|
446
|
+
|
|
447
|
+
loadAd()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private fun cancelRefreshTimer() {
|
|
451
|
+
refreshRunnable?.let { refreshHandler.removeCallbacks(it) }
|
|
452
|
+
refreshRunnable = null
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private fun createAdOrchestrator(): AdOrchestrator {
|
|
456
|
+
val configManager = BigCrunchAds.getConfigManager()
|
|
457
|
+
val analyticsClient = BigCrunchAds.getAnalyticsClient()
|
|
458
|
+
val googleAdsAdapter = GoogleAdsAdapter(context, analyticsClient)
|
|
459
|
+
|
|
460
|
+
// BidRequestClient is shared across all views for batching
|
|
461
|
+
val bidRequestClient = BigCrunchAds.bidRequestClient ?: BidRequestClient(
|
|
462
|
+
httpClient = com.bigcrunch.ads.internal.HttpClient(),
|
|
463
|
+
configManager = configManager,
|
|
464
|
+
privacyStore = BigCrunchAds.privacyStore,
|
|
465
|
+
s2sConfig = com.bigcrunch.ads.models.S2SConfig(enabled = false, serverUrl = "", timeoutMs = 0)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
return AdOrchestrator(
|
|
469
|
+
context,
|
|
470
|
+
configManager,
|
|
471
|
+
analyticsClient,
|
|
472
|
+
bidRequestClient,
|
|
473
|
+
googleAdsAdapter
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
override fun onDetachedFromWindow() {
|
|
478
|
+
super.onDetachedFromWindow()
|
|
479
|
+
// Note: We don't auto-destroy here for React Native compatibility
|
|
480
|
+
// React Native may temporarily detach views during ScrollView recycling
|
|
481
|
+
// The view manager will call destroy() when appropriate
|
|
482
|
+
BCLogger.v(TAG, "Banner detached from window (not auto-destroying)")
|
|
483
|
+
}
|
|
484
|
+
}
|