package com.bigcrunch.ads.react import android.view.View import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp import com.bigcrunch.ads.BigCrunchAds import com.bigcrunch.ads.BigCrunchBannerView import com.bigcrunch.ads.listeners.BannerAdListener import com.bigcrunch.ads.models.AdSize import kotlinx.coroutines.* /** * View manager for BigCrunchBannerView React Native component */ class BigCrunchBannerViewManager : SimpleViewManager() { companion object { const val REACT_CLASS = "BigCrunchBannerView" // Commands const val COMMAND_LOAD_AD = 1 const val COMMAND_PAUSE_AD = 2 const val COMMAND_RESUME_AD = 3 const val COMMAND_DESTROY = 4 } override fun getName(): String = REACT_CLASS override fun createViewInstance(reactContext: ThemedReactContext): BigCrunchBannerView { android.util.Log.d("BigCrunchBannerViewManager", """ === CREATING VIEW INSTANCE === React Context: $reactContext Activity: ${reactContext.currentActivity} ============================== """.trimIndent()) val view = BigCrunchBannerView(reactContext) // Set default layout params to ensure view is visible view.layoutParams = android.widget.FrameLayout.LayoutParams( android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.WRAP_CONTENT ) android.util.Log.d("BigCrunchBannerViewManager", """ === VIEW CREATED === View: $view Layout params: ${view.layoutParams?.width}x${view.layoutParams?.height} ==================== """.trimIndent()) return view } private val autoLoadMap = mutableMapOf() @ReactProp(name = "placementId") fun setPlacementId(view: BigCrunchBannerView, placementId: String) { view.setPlacementId(placementId) // Check if autoLoad was set to true before placementId if (autoLoadMap[view] == true) { view.loadAd() autoLoadMap.remove(view) } } @ReactProp(name = "size") fun setSize(view: BigCrunchBannerView, size: String) { android.util.Log.d("BigCrunchBannerViewManager", """ === SET SIZE CALLED === Size string: $size Current view dimensions: ${view.width}x${view.height} Current minimums: ${view.minimumWidth}x${view.minimumHeight} ======================= """.trimIndent()) val adSize = when (size) { "BANNER" -> AdSize(320, 50) "LARGE_BANNER" -> AdSize(320, 100) "MEDIUM_RECTANGLE" -> AdSize(300, 250) "FULL_BANNER" -> AdSize(468, 60) "LEADERBOARD" -> AdSize(728, 90) "ADAPTIVE" -> AdSize.adaptive(0) "SMART" -> AdSize.adaptive(0) // Deprecated, use ADAPTIVE else -> AdSize(320, 50) } view.setAdSize(adSize) // For adaptive sizes, resolve concrete dimensions for layout val layoutWidth: Int val layoutHeight: Int if (adSize.isAdaptive) { val displayMetrics = view.resources.displayMetrics val widthDp = (displayMetrics.widthPixels / displayMetrics.density).toInt() val googleAdSize = com.google.android.gms.ads.AdSize .getCurrentOrientationAnchoredAdaptiveBannerAdSize(view.context, widthDp) layoutWidth = googleAdSize.width layoutHeight = googleAdSize.height } else { layoutWidth = adSize.width layoutHeight = adSize.height } // Set minimum height based on ad size to ensure visibility val density = view.resources.displayMetrics.density val minHeightPx = (layoutHeight * density).toInt() val minWidthPx = (layoutWidth * density).toInt() view.minimumHeight = minHeightPx view.minimumWidth = minWidthPx android.util.Log.d("BigCrunchBannerViewManager", """ === SIZE SET === Ad size: ${adSize.width}x${adSize.height} dp (adaptive=${adSize.isAdaptive}) Layout size: ${layoutWidth}x${layoutHeight} dp Density: $density Minimum pixels: ${minWidthPx}x${minHeightPx} View parent: ${view.parent?.javaClass?.simpleName} ================ """.trimIndent()) // Request layout to apply the new dimensions view.requestLayout() } @ReactProp(name = "customWidth") fun setCustomWidth(view: BigCrunchBannerView, width: Int?) { width?.let { val currentHeight = view.getAdSize()?.height ?: 50 view.setAdSize(AdSize(it, currentHeight)) // Apply minimum width in pixels val density = view.resources.displayMetrics.density val minWidthPx = (it * density).toInt() view.minimumWidth = minWidthPx view.requestLayout() } } @ReactProp(name = "customHeight") fun setCustomHeight(view: BigCrunchBannerView, height: Int?) { height?.let { val currentWidth = view.getAdSize()?.width ?: 320 view.setAdSize(AdSize(currentWidth, it)) // Apply minimum height in pixels val density = view.resources.displayMetrics.density val minHeightPx = (it * density).toInt() view.minimumHeight = minHeightPx view.requestLayout() } } @ReactProp(name = "autoLoad") fun setAutoLoad(view: BigCrunchBannerView, autoLoad: Boolean) { android.util.Log.d("BigCrunchBannerViewManager", """ === SET AUTO LOAD === AutoLoad: $autoLoad PlacementId: ${view.getPlacementId()} View dimensions: ${view.width}x${view.height} View parent: ${view.parent?.javaClass?.simpleName} ===================== """.trimIndent()) if (autoLoad) { // Check if placementId is already set if (view.getPlacementId() != null) { android.util.Log.d("BigCrunchBannerViewManager", "AutoLoad triggered immediately - placementId already set") // Add debug about view state before loading view.post { android.util.Log.d("BigCrunchBannerViewManager", """ === BEFORE LOAD AD (autoLoad) === View dimensions: ${view.width}x${view.height} Measured dimensions: ${view.measuredWidth}x${view.measuredHeight} Minimum dimensions: ${view.minimumWidth}x${view.minimumHeight} Parent: ${view.parent?.javaClass?.simpleName} Parent dimensions: ${(view.parent as? android.view.View)?.let { "${it.width}x${it.height}" }} ================================== """.trimIndent()) } view.loadAd() } else { // Store for later when placementId is set android.util.Log.d("BigCrunchBannerViewManager", "AutoLoad deferred - waiting for placementId") autoLoadMap[view] = true } } else { autoLoadMap.remove(view) } } @ReactProp(name = "refreshInterval") fun setRefreshInterval(view: BigCrunchBannerView, interval: Int) { view.setRefreshInterval(interval) } @ReactProp(name = "customTargeting") fun setCustomTargeting(view: BigCrunchBannerView, customTargeting: ReadableMap?) { customTargeting?.let { val targeting = mutableMapOf() val iterator = it.keySetIterator() while (iterator.hasNextKey()) { val key = iterator.nextKey() it.getString(key)?.let { value -> targeting[key] = value } } view.setCustomTargeting(targeting) } } @ReactProp(name = "viewId") fun setViewId(view: BigCrunchBannerView, viewId: String) { view.tag = viewId setupEventListeners(view, viewId) } override fun getCommandsMap(): Map { return mapOf( "loadAd" to COMMAND_LOAD_AD, "pauseAd" to COMMAND_PAUSE_AD, "resumeAd" to COMMAND_RESUME_AD, "destroy" to COMMAND_DESTROY ) } override fun receiveCommand(root: BigCrunchBannerView, commandId: Int, args: ReadableArray?) { when (commandId) { COMMAND_LOAD_AD -> root.loadAd() COMMAND_PAUSE_AD -> root.pause() COMMAND_RESUME_AD -> root.resume() COMMAND_DESTROY -> root.destroy() } } override fun onDropViewInstance(view: BigCrunchBannerView) { super.onDropViewInstance(view) autoLoadMap.remove(view) view.destroy() } private fun setupEventListeners(view: BigCrunchBannerView, viewId: String) { val reactContext = view.context as ThemedReactContext view.setBannerAdListener(object : BannerAdListener { override fun onAdLoaded() { sendEvent(reactContext, "BigCrunchBannerAdLoaded", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) }) } override fun onAdFailedToLoad(errorCode: String, errorMessage: String) { sendEvent(reactContext, "BigCrunchBannerAdFailedToLoad", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) putString("errorCode", errorCode) putString("errorMessage", errorMessage) }) } override fun onAdImpression() { sendEvent(reactContext, "BigCrunchBannerAdImpression", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) }) } override fun onAdClicked() { sendEvent(reactContext, "BigCrunchBannerAdClicked", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) }) } override fun onAdOpened() { sendEvent(reactContext, "BigCrunchBannerAdOpened", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) }) } override fun onAdClosed() { sendEvent(reactContext, "BigCrunchBannerAdClosed", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) }) } override fun onAdRevenue(valueMicros: Long, currencyCode: String, adUnitId: String?, precision: String?) { sendEvent(reactContext, "BigCrunchBannerAdRevenue", Arguments.createMap().apply { putString("viewId", viewId) putString("placementId", view.getPlacementId()) putDouble("valueMicros", valueMicros.toDouble()) putString("currencyCode", currencyCode) adUnitId?.let { putString("adUnitId", it) } precision?.let { putString("precision", it) } }) } }) } private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } }