package com.bigcrunch.ads import android.app.Activity import android.content.Context import com.bigcrunch.ads.adapters.GoogleAdsAdapter import com.bigcrunch.ads.core.AdOrchestrator import com.bigcrunch.ads.core.InterstitialCallback import com.bigcrunch.ads.internal.BCLogger /** * BigCrunch Interstitial Ads - Static API for full-screen interstitial ads * * Interstitial ads are full-screen ads that cover the interface of an app until * closed by the user. They're best used at natural transition points in the app. * * Usage: * ```kotlin * // 1. Preload the ad (do this early, e.g., when entering a screen) * BigCrunchInterstitial.preload( * context = applicationContext, * placementId = "article_interstitial", * callback = object : BigCrunchInterstitial.PreloadCallback { * override fun onAdLoaded() { /* ad ready to show */ } * override fun onAdFailedToLoad(error: String) { /* handle error */ } * } * ) * * // 2. Check if ready (optional) * if (BigCrunchInterstitial.isReady("article_interstitial")) { * // Ad is ready to show * } * * // 3. Show when appropriate (e.g., after completing an action) * BigCrunchInterstitial.show( * activity = this, * placementId = "article_interstitial", * callback = object : BigCrunchInterstitial.ShowCallback { * override fun onAdShowed() { /* ad displayed */ } * override fun onAdDismissed() { /* ad closed, resume app */ } * override fun onAdClicked() { /* ad clicked */ } * override fun onAdFailedToShow(error: String) { /* handle error */ } * } * ) * ``` */ object BigCrunchInterstitial { private const val TAG = "BigCrunchInterstitial" private var adOrchestrator: AdOrchestrator? = null private val orchestratorLock = Any() /** * Safely execute a block, catching exceptions to prevent SDK crashes from affecting the host app. */ private inline fun safeExecute(default: T, block: () -> T): T { return try { block() } catch (e: Exception) { BCLogger.e(TAG, "Exception caught in interstitial - SDK will not propagate to prevent app crash", e) default } } /** * Safely execute a Unit-returning block. */ private inline fun safeExecute(block: () -> Unit) { try { block() } catch (e: Exception) { BCLogger.e(TAG, "Exception caught in interstitial - SDK will not propagate to prevent app crash", e) } } /** * Callback interface for preload events */ interface PreloadCallback { /** * Called when the ad has been successfully preloaded and is ready to show */ fun onAdLoaded() /** * Called when the ad fails to load * @param error Description of the error */ fun onAdFailedToLoad(error: String) } /** * Callback interface for show events */ interface ShowCallback { /** * Called when the ad is displayed on screen */ fun onAdShowed() /** * Called when the ad is dismissed by the user */ fun onAdDismissed() /** * Called when the user clicks on the ad */ fun onAdClicked() /** * Called when the ad fails to show * @param error Description of the error */ fun onAdFailedToShow(error: String) } /** * Legacy callback interface (for backwards compatibility) */ interface Callback { fun onAdShown() fun onAdDismissed() fun onAdFailed(error: String) } /** * Preload an interstitial ad for the given placement * * Call this early (e.g., when entering a screen) to give the ad time to load. * When the ad is ready, the callback's onAdLoaded() will be called. * * @param context Application context * @param placementId The placement ID from BigCrunch dashboard * @param callback Optional callback for load events */ fun preload( context: Context, placementId: String, callback: PreloadCallback? = null ) = safeExecute { BCLogger.d(TAG, "Preloading interstitial: $placementId") if (!BigCrunchAds.isInitialized()) { BCLogger.e(TAG, "SDK not initialized") callback?.onAdFailedToLoad("SDK not initialized. Call BigCrunchAds.initialize() first.") return@safeExecute } if (placementId.isBlank()) { BCLogger.e(TAG, "Invalid placementId: cannot be blank") callback?.onAdFailedToLoad("Invalid placementId: cannot be blank") return@safeExecute } val orchestrator = getOrCreateOrchestrator(context) val internalCallback = object : InterstitialCallback { override fun onAdLoaded() { BCLogger.d(TAG, "Interstitial preloaded: $placementId") callback?.onAdLoaded() } override fun onAdFailedToLoad(error: String) { BCLogger.w(TAG, "Interstitial failed to preload: $placementId - $error") callback?.onAdFailedToLoad(error) } override fun onAdShowed() { // Not used during preload } override fun onAdDismissed() { // Not used during preload } override fun onAdClicked() { // Not used during preload } } orchestrator.preloadInterstitialAd(placementId, internalCallback) } /** * Preload an interstitial ad (simplified version without callback) * * @param placementId The placement ID from BigCrunch dashboard */ fun preload(placementId: String) = safeExecute { if (!BigCrunchAds.isInitialized()) { BCLogger.e(TAG, "SDK not initialized") return@safeExecute } // Use application context from BigCrunchAds val orchestrator = getOrCreateOrchestrator(null) orchestrator.preloadInterstitialAd(placementId, object : InterstitialCallback { override fun onAdLoaded() { BCLogger.d(TAG, "Interstitial preloaded: $placementId") } override fun onAdFailedToLoad(error: String) { BCLogger.w(TAG, "Interstitial failed to preload: $placementId - $error") } override fun onAdShowed() {} override fun onAdDismissed() {} override fun onAdClicked() {} }) } /** * Show a preloaded interstitial ad * * The ad must be preloaded first using [preload]. Check [isReady] before calling * this to ensure an ad is available. * * @param activity The activity to show the ad from * @param placementId The placement ID * @param callback Optional callback for show events * @return true if a preloaded ad was available and presentation was initiated; * presentation failures are delivered asynchronously via the callback */ fun show( activity: Activity, placementId: String, callback: ShowCallback? = null ): Boolean = safeExecute(false) { BCLogger.d(TAG, "Showing interstitial: $placementId") if (!BigCrunchAds.isInitialized()) { BCLogger.e(TAG, "SDK not initialized") callback?.onAdFailedToShow("SDK not initialized. Call BigCrunchAds.initialize() first.") return@safeExecute false } if (placementId.isBlank()) { BCLogger.e(TAG, "Invalid placementId: cannot be blank") callback?.onAdFailedToShow("Invalid placementId: cannot be blank") return@safeExecute false } val orchestrator = getOrCreateOrchestrator(activity) val internalCallback = object : InterstitialCallback { override fun onAdLoaded() { // Not used during show } override fun onAdFailedToLoad(error: String) { BCLogger.w(TAG, "Interstitial failed to show: $placementId - $error") callback?.onAdFailedToShow(error) } override fun onAdShowed() { BCLogger.d(TAG, "Interstitial showed: $placementId") callback?.onAdShowed() } override fun onAdDismissed() { BCLogger.d(TAG, "Interstitial dismissed: $placementId") callback?.onAdDismissed() } override fun onAdClicked() { BCLogger.d(TAG, "Interstitial clicked: $placementId") callback?.onAdClicked() } } orchestrator.showInterstitialAd(activity, placementId, internalCallback) } /** * Show a preloaded interstitial ad (legacy API) * * @param activity The activity to show the ad in * @param placementId The placement ID * @param callback Legacy callback for ad events */ fun show( activity: Activity, placementId: String, callback: Callback? = null ) { show( activity = activity, placementId = placementId, callback = callback?.let { legacyCallback -> object : ShowCallback { override fun onAdShowed() = legacyCallback.onAdShown() override fun onAdDismissed() = legacyCallback.onAdDismissed() override fun onAdClicked() {} override fun onAdFailedToShow(error: String) = legacyCallback.onAdFailed(error) } } ) } /** * Check if an interstitial ad is ready to show * * @param placementId The placement ID * @return true if an ad is preloaded and ready to show */ fun isReady(placementId: String): Boolean = safeExecute(false) { if (!BigCrunchAds.isInitialized()) { BCLogger.w(TAG, "isReady called before SDK initialized") return@safeExecute false } adOrchestrator?.isInterstitialReady(placementId) ?: false } /** * Load an interstitial ad with custom targeting * * @param placementId The placement ID for the ad * @param customTargeting Optional custom targeting parameters */ fun load(placementId: String, customTargeting: Map? = null) { // For now, just call preload - custom targeting will be added in AdOrchestrator preload(placementId) if (customTargeting != null) { BCLogger.d(TAG, "Custom targeting requested: ${customTargeting.size} parameters") } } /** * Check if an interstitial ad is loaded and ready to show * * @param placementId The placement ID to check * @return true if the ad is loaded and ready, false otherwise */ fun isLoaded(placementId: String): Boolean = isReady(placementId) /** * Destroy a specific interstitial ad * * @param placementId The placement ID to destroy */ fun destroy(placementId: String) = safeExecute { BCLogger.d(TAG, "Destroying interstitial: $placementId") synchronized(orchestratorLock) { adOrchestrator?.destroyInterstitialAd(placementId) } } /** * Destroy all cached interstitial ads */ fun destroyAll() = safeExecute { clearCache() } /** * Clear all cached interstitial ads * * Call this to free up memory when ads are no longer needed. */ fun clearCache() = safeExecute { BCLogger.d(TAG, "Clearing interstitial cache") synchronized(orchestratorLock) { adOrchestrator?.clearCache() } } private fun getOrCreateOrchestrator(context: Context?): AdOrchestrator { synchronized(orchestratorLock) { if (adOrchestrator == null) { val ctx = context?.applicationContext ?: throw IllegalStateException("Context required to create AdOrchestrator") val configManager = BigCrunchAds.getConfigManager() val analyticsClient = BigCrunchAds.getAnalyticsClient() val googleAdsAdapter = GoogleAdsAdapter(ctx, analyticsClient) val bidRequestClient = BigCrunchAds.bidRequestClient ?: com.bigcrunch.ads.core.BidRequestClient( httpClient = com.bigcrunch.ads.internal.HttpClient(), configManager = configManager, privacyStore = BigCrunchAds.privacyStore, s2sConfig = com.bigcrunch.ads.models.S2SConfig(enabled = false, serverUrl = "", timeoutMs = 0) ) adOrchestrator = AdOrchestrator( ctx, configManager, analyticsClient, bidRequestClient, googleAdsAdapter ) } return adOrchestrator!! } } /** * Reset for testing purposes only */ internal fun resetForTesting() { synchronized(orchestratorLock) { adOrchestrator?.clearCache() adOrchestrator = null } } }