package com.topon import android.view.View import android.view.ViewGroup import com.facebook.react.bridge.Arguments import com.facebook.react.uimanager.UIManagerHelper import com.secmtp.sdk.core.api.ATAdConst import com.secmtp.sdk.core.api.ATAdInfo import com.secmtp.sdk.core.api.ATAdStatusInfo import com.secmtp.sdk.core.api.AdError import com.secmtp.sdk.nativead.api.ATNative import com.secmtp.sdk.nativead.api.ATNativeAdView import com.secmtp.sdk.nativead.api.ATNativeDislikeListener import com.secmtp.sdk.nativead.api.ATNativeEventExListener import com.secmtp.sdk.nativead.api.ATNativeMaterial import com.secmtp.sdk.nativead.api.ATNativeNetworkListener import com.secmtp.sdk.nativead.api.ATNativePrepareExInfo import com.secmtp.sdk.nativead.api.ATNativePrepareInfo import com.secmtp.sdk.nativead.api.NativeAd import org.json.JSONObject class NativeAdHelper(module: ToponModule) : BaseHelper(module) { private var nativeAdHandler: ATNative? = null private var nativeAd: NativeAd? = null private var placementId: String = "" private var isReady: Boolean = false private var lastNativeView: ToponNativeAdView? = null private fun ensureNative(initPlacementId: String) { if (nativeAdHandler != null && placementId == initPlacementId) { return } placementId = initPlacementId val activity = currentActivity() ?: reactContext() nativeAdHandler = ATNative(activity, placementId, object : ATNativeNetworkListener { override fun onNativeAdLoaded() { MsgTools.printMsg("native onNativeAdLoaded: $placementId") isReady = true val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) sendEvent(Const.NativeCallback.LoadedCallbackKey, map) } override fun onNativeAdLoadFail(adError: AdError) { MsgTools.printMsg("native onNativeAdLoadFail: $placementId, ${adError.fullErrorInfo}") isReady = false val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putString(Const.CallbackKey.ErrorMsg, adError.fullErrorInfo) sendEvent(Const.NativeCallback.LoadFailCallbackKey, map) } }) } fun loadNative(placementId: String, settingsJson: String) { MsgTools.printMsg("loadNative: $placementId, settings: $settingsJson") runOnUiThread { ensureNative(placementId) val handler = nativeAdHandler ?: return@runOnUiThread val localExtra = CommonUtil.jsonStringToMap(settingsJson).toMutableMap() if (settingsJson.isNotEmpty()) { try { val jsonObject = JSONObject(settingsJson) val usesPixel = jsonObject.optBoolean("usesPixel", false) val width = jsonObject.optInt(Const.WIDTH, 0) val height = jsonObject.optInt(Const.HEIGHT, 0) if (width > 0 && height > 0) { val widthPx = if (usesPixel) width else CommonUtil.dip2px(reactContext(), width.toFloat()) val heightPx = if (usesPixel) height else CommonUtil.dip2px(reactContext(), height.toFloat()) localExtra[ATAdConst.KEY.AD_WIDTH] = widthPx localExtra[ATAdConst.KEY.AD_HEIGHT] = heightPx } } catch (t: Throwable) { MsgTools.printMsg("loadNative parse settings failed: ${t.message}") } } handler.setLocalExtra(localExtra) handler.makeAdRequest() } } fun showNative(viewTagsJson: String, scenario: String) { val safeScenario = scenario.take(1024) MsgTools.printMsg("showNative: $placementId, scenario: $safeScenario, viewTags: $viewTagsJson") runOnUiThread { val handler = nativeAdHandler if (handler == null) { MsgTools.printMsg("showNative error, you must call loadNative first $placementId") sendNativeFail("you must call loadNative first") return@runOnUiThread } val nativeView = resolveNativeView(viewTagsJson) ?: return@runOnUiThread lastNativeView = nativeView val showConfig = com.secmtp.sdk.core.api.ATShowConfig.Builder() .scenarioId(safeScenario) .build() if (safeScenario.isNotEmpty()) { ATNative.entryAdScenario(placementId, safeScenario) } nativeAd = handler.getNativeAd(showConfig) val ad = nativeAd if (ad == null) { MsgTools.printMsg("showNative error, no cached ad $placementId") sendNativeFail("no cached ad") return@runOnUiThread } setNativeListeners(ad) val prepareInfo = buildPrepareInfo(viewTagsJson) val container = nativeView.adContainer() val contentView = nativeView.contentView() try { ad.renderAdContainer(container, contentView) ad.prepare(container, prepareInfo) } catch (t: Throwable) { MsgTools.printMsg("showNative error: ${t.message}") sendNativeFail(t.message ?: "show failed") } } } fun removeNative() { runOnUiThread { nativeAd?.destory() nativeAd = null val nativeView = lastNativeView ?: return@runOnUiThread val container = nativeView.adContainer() container.removeAllViews() container.addView( nativeView.contentView(), ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) ) } } fun isAdReady(): Boolean { MsgTools.printMsg("native isAdReady: $placementId") return try { nativeAdHandler?.checkAdStatus()?.isReady ?: isReady } catch (t: Throwable) { MsgTools.printMsg("native isAdReady Throwable: ${t.message}") isReady } } fun checkAdStatus(): String { MsgTools.printMsg("native checkAdStatus: $placementId") return try { val statusInfo: ATAdStatusInfo? = nativeAdHandler?.checkAdStatus() if (statusInfo != null) { val jsonObject = JSONObject() jsonObject.put("isLoading", statusInfo.isLoading) jsonObject.put("isReady", statusInfo.isReady) jsonObject.put("adInfo", statusInfo.getATTopAdInfo()) val result = jsonObject.toString() MsgTools.printMsg("native checkAdStatus: $placementId, $result") result } else { MsgTools.printMsg("native checkAdStatus error, you must call loadNative first $placementId") "" } } catch (t: Throwable) { MsgTools.printMsg("native checkAdStatus Throwable: ${t.message}") "" } } fun getAdMaterial(): String { val adMaterial = nativeAd?.adMaterial ?: return "" return try { buildAdMaterialJson(adMaterial).toString() } catch (t: Throwable) { MsgTools.printMsg("native getAdMaterial error: ${t.message}") "" } } private fun buildAdMaterialJson(material: ATNativeMaterial): JSONObject { val json = JSONObject() json.put("title", material.title) json.put("desc", material.descriptionText) json.put("cta", material.callToActionText) json.put("iconUrl", material.iconImageUrl) json.put("mainImageUrl", material.mainImageUrl) json.put("adFrom", material.adFrom) json.put("videoUrl", material.videoUrl) readMainImageUrlList(material)?.let { imageUrls -> json.put("imageUrls", imageUrls) } return json } private fun readMainImageUrlList(material: ATNativeMaterial): Any? { return try { val getter = material.javaClass.methods.firstOrNull { it.name == "getMainImageUrlList" && it.parameterCount == 0 } if (getter != null) { getter.invoke(material) } else { val field = material.javaClass.fields.firstOrNull { it.name == "mainImageUrlList" } field?.get(material) } } catch (_: Throwable) { null } } private fun resolveNativeView(viewTagsJson: String): ToponNativeAdView? { val tags = CommonUtil.jsonStringToMap(viewTagsJson) val parentTag = tags[Const.parent]?.let { toIntTag(it) } if (parentTag == null) { MsgTools.printMsg("showNative error, parent view tag missing $placementId") sendNativeFail("parent view tag missing") return null } val view = resolveViewByTag(parentTag) if (view !is ToponNativeAdView) { MsgTools.printMsg("showNative error, parent view is not ToponNativeAdView $placementId") sendNativeFail("parent view is not ToponNativeAdView") return null } return view } private fun buildPrepareInfo(viewTagsJson: String): ATNativePrepareInfo { val tags = CommonUtil.jsonStringToMap(viewTagsJson) val prepareInfo = ATNativePrepareExInfo() val clickViews = mutableListOf() fun resolveView(key: String): View? { val tagValue = tags[key] ?: return null val tag = toIntTag(tagValue) ?: return null return resolveViewByTag(tag) } resolveView(Const.title)?.let { prepareInfo.setTitleView(it) clickViews.add(it) } resolveView(Const.desc)?.let { prepareInfo.setDescView(it) clickViews.add(it) } resolveView(Const.cta)?.let { prepareInfo.setCtaView(it) clickViews.add(it) } resolveView(Const.icon)?.let { prepareInfo.setIconView(it) clickViews.add(it) } resolveView(Const.mainImage)?.let { prepareInfo.setMainImageView(it) clickViews.add(it) } resolveView(Const.adLogo)?.let { logoView -> try { prepareInfo.setAdLogoView(logoView) } catch (_: Throwable) { // ignore if method not available } } if (clickViews.isNotEmpty()) { prepareInfo.setClickViewList(clickViews) } return prepareInfo } private fun resolveViewByTag(tag: Int): View? { val uiManager = UIManagerHelper.getUIManagerForReactTag(reactContext(), tag) return try { uiManager?.resolveView(tag) } catch (t: Throwable) { MsgTools.printMsg("resolveViewByTag failed: ${t.message}") null } } private fun toIntTag(value: Any): Int? { return when (value) { is Int -> value is Double -> value.toInt() is Float -> value.toInt() is String -> value.toIntOrNull() else -> null } } private fun setNativeListeners(ad: NativeAd) { ad.setNativeEventListener(object : ATNativeEventExListener { override fun onAdImpressed(view: ATNativeAdView, adInfo: ATAdInfo) { MsgTools.printMsg("native onAdImpressed: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putString(Const.CallbackKey.AdInfo, adInfo.toString()) map.putString(Const.CallbackKey.AdMaterial, getAdMaterial()) sendEvent(Const.NativeCallback.ShowCallbackKey, map) } override fun onAdClicked(view: ATNativeAdView, adInfo: ATAdInfo) { MsgTools.printMsg("native onAdClicked: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putString(Const.CallbackKey.AdInfo, adInfo.toString()) sendEvent(Const.NativeCallback.ClickCallbackKey, map) } override fun onAdVideoStart(view: ATNativeAdView) { MsgTools.printMsg("native onAdVideoStart: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) sendEvent(Const.NativeCallback.VideoStartKey, map) } override fun onAdVideoEnd(view: ATNativeAdView) { MsgTools.printMsg("native onAdVideoEnd: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) sendEvent(Const.NativeCallback.VideoEndKey, map) } override fun onAdVideoProgress(view: ATNativeAdView, progress: Int) { MsgTools.printMsg("native onAdVideoProgress: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putInt("progress", progress) sendEvent(Const.NativeCallback.VideoProgressKey, map) } override fun onDeeplinkCallback(view: ATNativeAdView, adInfo: ATAdInfo, isSuccess: Boolean) { MsgTools.printMsg("native onDeeplinkCallback: $placementId, success: $isSuccess") } }) ad.setDislikeCallbackListener(object : ATNativeDislikeListener() { override fun onAdCloseButtonClick(view: ATNativeAdView, adInfo: ATAdInfo) { MsgTools.printMsg("native onAdCloseButtonClick: $placementId") val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putString(Const.CallbackKey.AdInfo, adInfo.toString()) sendEvent(Const.NativeCallback.CloseCallbackKey, map) } }) } private fun sendNativeFail(message: String) { val map = Arguments.createMap() map.putString(Const.CallbackKey.PlacementId, placementId) map.putString(Const.CallbackKey.ErrorMsg, message) sendEvent(Const.NativeCallback.LoadFailCallbackKey, map) } }