@bigcrunch/react-native-ads 0.3.1 → 0.5.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 +301 -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 +90 -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 +0 -23
- 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 +306 -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 +100 -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 +5 -14
- 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/lib/types/config.d.ts +22 -9
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/events.d.ts +4 -4
- package/lib/types/events.d.ts.map +1 -1
- package/package.json +11 -4
- package/react-native-bigcrunch-ads.podspec +1 -3
- package/scripts/inject-version.js +55 -0
- package/src/index.ts +3 -2
- package/src/types/config.ts +23 -9
- package/src/types/events.ts +4 -4
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import GoogleMobileAds
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Callback protocol for banner ad events
|
|
7
|
+
*/
|
|
8
|
+
public protocol BannerCallback: AnyObject {
|
|
9
|
+
func onAdLoaded()
|
|
10
|
+
func onAdFailedToLoad(error: String)
|
|
11
|
+
func onAdClicked()
|
|
12
|
+
func onAdImpression()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callback protocol for interstitial ad events
|
|
17
|
+
*/
|
|
18
|
+
public protocol InterstitialCallback: AnyObject {
|
|
19
|
+
func onAdLoaded()
|
|
20
|
+
func onAdFailedToLoad(error: String)
|
|
21
|
+
func onAdShowed()
|
|
22
|
+
func onAdDismissed()
|
|
23
|
+
func onAdClicked()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AdOrchestrator - Central orchestration for ad loading lifecycle
|
|
28
|
+
*
|
|
29
|
+
* Coordinates the flow between ConfigManager, BidRequestClient, GoogleAdsAdapter,
|
|
30
|
+
* and AnalyticsClient to load and display ads.
|
|
31
|
+
*
|
|
32
|
+
* Flow:
|
|
33
|
+
* 1. Get PlacementConfig from ConfigManager
|
|
34
|
+
* 2. Track ad request via AnalyticsClient
|
|
35
|
+
* 3. Fetch S2S demand via BidRequestClient
|
|
36
|
+
* 4. Load Google ad via GoogleAdsAdapter (with targeting from S2S)
|
|
37
|
+
* 5. Wire up all callbacks for analytics
|
|
38
|
+
*/
|
|
39
|
+
internal class AdOrchestrator {
|
|
40
|
+
|
|
41
|
+
private static let TAG = "AdOrchestrator"
|
|
42
|
+
|
|
43
|
+
private let configManager: ConfigManager
|
|
44
|
+
private let analyticsClient: AnalyticsClient
|
|
45
|
+
private let bidRequestClient: BidRequestClient
|
|
46
|
+
private let googleAdsAdapter: GoogleAdsAdapter
|
|
47
|
+
|
|
48
|
+
// Cache for preloaded interstitial ads
|
|
49
|
+
private var interstitialCache: [String: GoogleMobileAds.InterstitialAd] = [:]
|
|
50
|
+
private let interstitialLock = NSLock()
|
|
51
|
+
|
|
52
|
+
// Track active banner views for cleanup
|
|
53
|
+
private var activeBanners: [String: GoogleMobileAds.BannerView] = [:]
|
|
54
|
+
private let bannerLock = NSLock()
|
|
55
|
+
|
|
56
|
+
// Store callback wrappers to prevent deallocation
|
|
57
|
+
private var bannerCallbackWrappers: [String: BannerCallbackWrapper] = [:]
|
|
58
|
+
private var interstitialCallbackWrappers: [String: InterstitialCallbackWrapper] = [:]
|
|
59
|
+
|
|
60
|
+
init(
|
|
61
|
+
configManager: ConfigManager,
|
|
62
|
+
analyticsClient: AnalyticsClient,
|
|
63
|
+
bidRequestClient: BidRequestClient,
|
|
64
|
+
googleAdsAdapter: GoogleAdsAdapter
|
|
65
|
+
) {
|
|
66
|
+
self.configManager = configManager
|
|
67
|
+
self.analyticsClient = analyticsClient
|
|
68
|
+
self.bidRequestClient = bidRequestClient
|
|
69
|
+
self.googleAdsAdapter = googleAdsAdapter
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Load a banner ad
|
|
74
|
+
*
|
|
75
|
+
* - Parameters:
|
|
76
|
+
* - placementId: The placement ID from the BigCrunch dashboard
|
|
77
|
+
* - rootViewController: The view controller for presenting ad click actions
|
|
78
|
+
* - callback: Callback for ad events
|
|
79
|
+
* - adSizeOverride: Optional size override (takes precedence over backend config)
|
|
80
|
+
* - bannerViewSetter: Closure to set the banner view on the container
|
|
81
|
+
* - Returns: The GoogleMobileAds.BannerView if loading was initiated, nil if placement not found
|
|
82
|
+
*/
|
|
83
|
+
func loadBannerAd(
|
|
84
|
+
placementId: String,
|
|
85
|
+
rootViewController: UIViewController,
|
|
86
|
+
callback: BannerCallback,
|
|
87
|
+
refreshCount: Int = 0,
|
|
88
|
+
adSizeOverride: AdSize? = nil,
|
|
89
|
+
bannerViewSetter: ((GoogleMobileAds.BannerView) -> Void)? = nil
|
|
90
|
+
) -> GoogleMobileAds.BannerView? {
|
|
91
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Loading banner ad: \(placementId) (refreshCount: \(refreshCount))")
|
|
92
|
+
|
|
93
|
+
// 1. Get placement config
|
|
94
|
+
guard let placement = configManager.getPlacement(placementId) else {
|
|
95
|
+
BCLogger.error("\(AdOrchestrator.TAG): Placement not found: \(placementId)")
|
|
96
|
+
callback.onAdFailedToLoad(error: "Placement not found: \(placementId)")
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
guard placement.format == "banner" else {
|
|
101
|
+
BCLogger.error("\(AdOrchestrator.TAG): Invalid format for banner: \(placement.format)")
|
|
102
|
+
callback.onAdFailedToLoad(error: "Invalid placement format: \(placement.format)")
|
|
103
|
+
return nil
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Track ad request
|
|
107
|
+
analyticsClient.trackAdRequest(placementId: placementId, format: placement.format)
|
|
108
|
+
|
|
109
|
+
// 3. Create callback wrapper and store it
|
|
110
|
+
let callbackWrapper = BannerCallbackWrapper(
|
|
111
|
+
placementId: placementId,
|
|
112
|
+
callback: callback
|
|
113
|
+
)
|
|
114
|
+
bannerCallbackWrappers[placementId] = callbackWrapper
|
|
115
|
+
|
|
116
|
+
// 4. Start async ad loading
|
|
117
|
+
Task {
|
|
118
|
+
await loadBannerAsync(
|
|
119
|
+
placement: placement,
|
|
120
|
+
rootViewController: rootViewController,
|
|
121
|
+
callbackWrapper: callbackWrapper,
|
|
122
|
+
refreshCount: refreshCount,
|
|
123
|
+
adSizeOverride: adSizeOverride,
|
|
124
|
+
bannerViewSetter: bannerViewSetter
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Return nil initially - the banner view will be created by GoogleAdsAdapter
|
|
129
|
+
return nil
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func loadBannerAsync(
|
|
133
|
+
placement: PlacementConfig,
|
|
134
|
+
rootViewController: UIViewController,
|
|
135
|
+
callbackWrapper: BannerCallbackWrapper,
|
|
136
|
+
refreshCount: Int,
|
|
137
|
+
adSizeOverride: AdSize?,
|
|
138
|
+
bannerViewSetter: ((GoogleMobileAds.BannerView) -> Void)?
|
|
139
|
+
) async {
|
|
140
|
+
// Get GAM network code from config
|
|
141
|
+
guard let gamNetworkCode = configManager.getGamNetworkCode() else {
|
|
142
|
+
BCLogger.error("\(AdOrchestrator.TAG): GAM network code not available")
|
|
143
|
+
callbackWrapper.callback?.onAdFailedToLoad(error: "Config not loaded")
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Resolve effective size for S2S bid request (adaptive → concrete dimensions)
|
|
148
|
+
let effectiveSize = adSizeOverride ?? placement.sizes?.first
|
|
149
|
+
let resolvedSize: AdSize?
|
|
150
|
+
if let eff = effectiveSize, eff.isAdaptive {
|
|
151
|
+
let width: CGFloat = eff.width > 0 ? CGFloat(eff.width) : UIScreen.main.bounds.width
|
|
152
|
+
let googleAdSize = GoogleMobileAds.currentOrientationAnchoredAdaptiveBanner(width: width)
|
|
153
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Resolved adaptive size to \(Int(googleAdSize.size.width))x\(Int(googleAdSize.size.height))")
|
|
154
|
+
resolvedSize = AdSize(width: Int(googleAdSize.size.width), height: Int(googleAdSize.size.height))
|
|
155
|
+
} else {
|
|
156
|
+
resolvedSize = effectiveSize
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create ad request
|
|
160
|
+
let adRequest = GoogleMobileAds.Request()
|
|
161
|
+
|
|
162
|
+
// 3. Fetch S2S demand (even if it fails, continue with Google)
|
|
163
|
+
let targeting = await bidRequestClient.fetchDemand(placement: placement)
|
|
164
|
+
if let targeting = targeting, !targeting.isEmpty {
|
|
165
|
+
var customTargeting = adRequest.customTargeting ?? [:]
|
|
166
|
+
for (key, value) in targeting {
|
|
167
|
+
customTargeting[key] = value
|
|
168
|
+
}
|
|
169
|
+
adRequest.customTargeting = customTargeting
|
|
170
|
+
BCLogger.debug("\(AdOrchestrator.TAG): S2S demand fetched for: \(placement.placementId) (\(targeting.count) keys)")
|
|
171
|
+
} else {
|
|
172
|
+
BCLogger.warning("\(AdOrchestrator.TAG): S2S demand fetch returned no targeting, continuing with Google only")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 4. Load Google ad with the (possibly enriched) ad request
|
|
176
|
+
let delegateWrapper = BannerDelegateAdapterWrapper(
|
|
177
|
+
placementId: placement.placementId,
|
|
178
|
+
callbackWrapper: callbackWrapper
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// Check if we should use test ad units
|
|
182
|
+
let useTestAds = configManager.shouldUseTestAds()
|
|
183
|
+
|
|
184
|
+
let bannerView = await googleAdsAdapter.loadBannerAd(
|
|
185
|
+
placementConfig: placement,
|
|
186
|
+
gamNetworkCode: gamNetworkCode,
|
|
187
|
+
request: adRequest,
|
|
188
|
+
rootViewController: rootViewController,
|
|
189
|
+
delegate: delegateWrapper,
|
|
190
|
+
useTestAds: useTestAds,
|
|
191
|
+
adSizeOverride: adSizeOverride,
|
|
192
|
+
refreshCount: refreshCount
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
// Set the banner view on the container (on main thread)
|
|
196
|
+
await MainActor.run {
|
|
197
|
+
bannerViewSetter?(bannerView)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Track the active banner for cleanup
|
|
201
|
+
bannerLock.lock()
|
|
202
|
+
activeBanners[placement.placementId] = bannerView
|
|
203
|
+
bannerLock.unlock()
|
|
204
|
+
|
|
205
|
+
// Store the delegate wrapper to prevent deallocation
|
|
206
|
+
callbackWrapper.delegateWrapper = delegateWrapper
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Destroy a banner ad
|
|
211
|
+
*
|
|
212
|
+
* - Parameter placementId: The placement ID of the banner to destroy
|
|
213
|
+
*/
|
|
214
|
+
func destroyBannerAd(placementId: String) {
|
|
215
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Destroying banner ad: \(placementId)")
|
|
216
|
+
|
|
217
|
+
bannerLock.lock()
|
|
218
|
+
if let bannerView = activeBanners.removeValue(forKey: placementId) {
|
|
219
|
+
googleAdsAdapter.destroyBannerAd(bannerView)
|
|
220
|
+
}
|
|
221
|
+
bannerLock.unlock()
|
|
222
|
+
|
|
223
|
+
// Clean up callback wrapper
|
|
224
|
+
bannerCallbackWrappers.removeValue(forKey: placementId)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Preload an interstitial ad
|
|
229
|
+
*
|
|
230
|
+
* - Parameters:
|
|
231
|
+
* - placementId: The placement ID from the BigCrunch dashboard
|
|
232
|
+
* - callback: Callback for ad events
|
|
233
|
+
*/
|
|
234
|
+
func preloadInterstitialAd(
|
|
235
|
+
placementId: String,
|
|
236
|
+
callback: InterstitialCallback
|
|
237
|
+
) {
|
|
238
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Preloading interstitial ad: \(placementId)")
|
|
239
|
+
|
|
240
|
+
// 1. Get placement config
|
|
241
|
+
guard let placement = configManager.getPlacement(placementId) else {
|
|
242
|
+
BCLogger.error("\(AdOrchestrator.TAG): Placement not found: \(placementId)")
|
|
243
|
+
callback.onAdFailedToLoad(error: "Placement not found: \(placementId)")
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
guard placement.format == "interstitial" else {
|
|
248
|
+
BCLogger.error("\(AdOrchestrator.TAG): Invalid format for interstitial: \(placement.format)")
|
|
249
|
+
callback.onAdFailedToLoad(error: "Invalid placement format: \(placement.format)")
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2. Track ad request
|
|
254
|
+
analyticsClient.trackAdRequest(placementId: placementId, format: placement.format)
|
|
255
|
+
|
|
256
|
+
// 3. Start async ad loading
|
|
257
|
+
Task {
|
|
258
|
+
await preloadInterstitialAsync(placement: placement, callback: callback)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private func preloadInterstitialAsync(
|
|
263
|
+
placement: PlacementConfig,
|
|
264
|
+
callback: InterstitialCallback
|
|
265
|
+
) async {
|
|
266
|
+
interstitialLock.lock()
|
|
267
|
+
// Check if already cached
|
|
268
|
+
if interstitialCache[placement.placementId] != nil {
|
|
269
|
+
interstitialLock.unlock()
|
|
270
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Interstitial already cached: \(placement.placementId)")
|
|
271
|
+
callback.onAdLoaded()
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
interstitialLock.unlock()
|
|
275
|
+
|
|
276
|
+
// Get GAM network code from config
|
|
277
|
+
guard let gamNetworkCode = configManager.getGamNetworkCode() else {
|
|
278
|
+
BCLogger.error("\(AdOrchestrator.TAG): GAM network code not available")
|
|
279
|
+
callback.onAdFailedToLoad(error: "Config not loaded")
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Create ad request
|
|
284
|
+
let adRequest = GoogleMobileAds.Request()
|
|
285
|
+
|
|
286
|
+
// 3. Fetch S2S demand
|
|
287
|
+
let targeting = await bidRequestClient.fetchDemand(placement: placement)
|
|
288
|
+
if let targeting = targeting, !targeting.isEmpty {
|
|
289
|
+
var customTargeting = adRequest.customTargeting ?? [:]
|
|
290
|
+
for (key, value) in targeting {
|
|
291
|
+
customTargeting[key] = value
|
|
292
|
+
}
|
|
293
|
+
adRequest.customTargeting = customTargeting
|
|
294
|
+
BCLogger.debug("\(AdOrchestrator.TAG): S2S demand fetched for: \(placement.placementId) (\(targeting.count) keys)")
|
|
295
|
+
} else {
|
|
296
|
+
BCLogger.warning("\(AdOrchestrator.TAG): S2S demand fetch returned no targeting, continuing with Google only")
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check if we should use test ad units
|
|
300
|
+
let useTestAds = configManager.shouldUseTestAds()
|
|
301
|
+
|
|
302
|
+
// 4. Load Google interstitial ad
|
|
303
|
+
let result = await googleAdsAdapter.loadInterstitialAd(
|
|
304
|
+
placementConfig: placement,
|
|
305
|
+
gamNetworkCode: gamNetworkCode,
|
|
306
|
+
request: adRequest,
|
|
307
|
+
useTestAds: useTestAds
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
switch result {
|
|
311
|
+
case .success(let interstitialAd):
|
|
312
|
+
interstitialLock.lock()
|
|
313
|
+
interstitialCache[placement.placementId] = interstitialAd
|
|
314
|
+
interstitialLock.unlock()
|
|
315
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Interstitial ad preloaded: \(placement.placementId)")
|
|
316
|
+
callback.onAdLoaded()
|
|
317
|
+
|
|
318
|
+
case .failure(let error):
|
|
319
|
+
BCLogger.warning("\(AdOrchestrator.TAG): Interstitial ad failed to load: \(placement.placementId) - \(error.localizedDescription)")
|
|
320
|
+
callback.onAdFailedToLoad(error: error.localizedDescription)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Show an interstitial ad
|
|
326
|
+
*
|
|
327
|
+
* - Parameters:
|
|
328
|
+
* - viewController: The view controller to present the interstitial from
|
|
329
|
+
* - placementId: The placement ID from the BigCrunch dashboard
|
|
330
|
+
* - callback: Callback for ad events
|
|
331
|
+
* - Returns: true if an ad was available and shown, false otherwise
|
|
332
|
+
*/
|
|
333
|
+
func showInterstitialAd(
|
|
334
|
+
from viewController: UIViewController,
|
|
335
|
+
placementId: String,
|
|
336
|
+
callback: InterstitialCallback
|
|
337
|
+
) -> Bool {
|
|
338
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Showing interstitial ad: \(placementId)")
|
|
339
|
+
|
|
340
|
+
// Get placement config
|
|
341
|
+
guard let placement = configManager.getPlacement(placementId) else {
|
|
342
|
+
BCLogger.error("\(AdOrchestrator.TAG): Placement not found: \(placementId)")
|
|
343
|
+
callback.onAdFailedToLoad(error: "Placement not found: \(placementId)")
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get cached interstitial
|
|
348
|
+
interstitialLock.lock()
|
|
349
|
+
guard let interstitialAd = interstitialCache.removeValue(forKey: placementId) else {
|
|
350
|
+
interstitialLock.unlock()
|
|
351
|
+
BCLogger.warning("\(AdOrchestrator.TAG): No preloaded interstitial for: \(placementId)")
|
|
352
|
+
callback.onAdFailedToLoad(error: "No preloaded ad available")
|
|
353
|
+
return false
|
|
354
|
+
}
|
|
355
|
+
interstitialLock.unlock()
|
|
356
|
+
|
|
357
|
+
// Create callback wrapper and store it
|
|
358
|
+
let callbackWrapper = InterstitialCallbackWrapper(
|
|
359
|
+
placementId: placementId,
|
|
360
|
+
callback: callback
|
|
361
|
+
)
|
|
362
|
+
interstitialCallbackWrappers[placementId] = callbackWrapper
|
|
363
|
+
|
|
364
|
+
// Create adapter delegate wrapper
|
|
365
|
+
let delegateWrapper = InterstitialDelegateAdapterWrapper(
|
|
366
|
+
placementId: placementId,
|
|
367
|
+
callbackWrapper: callbackWrapper
|
|
368
|
+
)
|
|
369
|
+
callbackWrapper.delegateWrapper = delegateWrapper
|
|
370
|
+
|
|
371
|
+
// Show the ad
|
|
372
|
+
googleAdsAdapter.showInterstitialAd(
|
|
373
|
+
interstitialAd,
|
|
374
|
+
from: viewController,
|
|
375
|
+
placementConfig: placement,
|
|
376
|
+
delegate: delegateWrapper
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Check if an interstitial ad is ready to show
|
|
384
|
+
*
|
|
385
|
+
* - Parameter placementId: The placement ID to check
|
|
386
|
+
* - Returns: true if an ad is preloaded and ready
|
|
387
|
+
*/
|
|
388
|
+
func isInterstitialReady(placementId: String) -> Bool {
|
|
389
|
+
interstitialLock.lock()
|
|
390
|
+
defer { interstitialLock.unlock() }
|
|
391
|
+
return interstitialCache[placementId] != nil
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Clear all cached ads
|
|
396
|
+
*/
|
|
397
|
+
func clearCache() {
|
|
398
|
+
BCLogger.debug("\(AdOrchestrator.TAG): Clearing ad cache")
|
|
399
|
+
|
|
400
|
+
interstitialLock.lock()
|
|
401
|
+
interstitialCache.removeAll()
|
|
402
|
+
interstitialLock.unlock()
|
|
403
|
+
|
|
404
|
+
// Destroy all active banners
|
|
405
|
+
bannerLock.lock()
|
|
406
|
+
for bannerView in activeBanners.values {
|
|
407
|
+
googleAdsAdapter.destroyBannerAd(bannerView)
|
|
408
|
+
}
|
|
409
|
+
activeBanners.removeAll()
|
|
410
|
+
bannerLock.unlock()
|
|
411
|
+
|
|
412
|
+
// Clear callback wrappers
|
|
413
|
+
bannerCallbackWrappers.removeAll()
|
|
414
|
+
interstitialCallbackWrappers.removeAll()
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// MARK: - Callback Wrapper Classes
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Wrapper to hold banner callback and prevent deallocation
|
|
422
|
+
*/
|
|
423
|
+
private class BannerCallbackWrapper {
|
|
424
|
+
let placementId: String
|
|
425
|
+
weak var callback: BannerCallback?
|
|
426
|
+
var delegateWrapper: BannerDelegateAdapterWrapper?
|
|
427
|
+
|
|
428
|
+
init(placementId: String, callback: BannerCallback) {
|
|
429
|
+
self.placementId = placementId
|
|
430
|
+
self.callback = callback
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Wrapper to hold interstitial callback and prevent deallocation
|
|
436
|
+
*/
|
|
437
|
+
private class InterstitialCallbackWrapper {
|
|
438
|
+
let placementId: String
|
|
439
|
+
weak var callback: InterstitialCallback?
|
|
440
|
+
var delegateWrapper: InterstitialDelegateAdapterWrapper?
|
|
441
|
+
|
|
442
|
+
init(placementId: String, callback: InterstitialCallback) {
|
|
443
|
+
self.placementId = placementId
|
|
444
|
+
self.callback = callback
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// MARK: - Delegate Adapter Wrappers
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Adapts BannerAdDelegate to BannerCallback
|
|
452
|
+
*/
|
|
453
|
+
private class BannerDelegateAdapterWrapper: BannerAdDelegate {
|
|
454
|
+
let placementId: String
|
|
455
|
+
weak var callbackWrapper: BannerCallbackWrapper?
|
|
456
|
+
|
|
457
|
+
init(placementId: String, callbackWrapper: BannerCallbackWrapper) {
|
|
458
|
+
self.placementId = placementId
|
|
459
|
+
self.callbackWrapper = callbackWrapper
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
func bannerAdDidLoad(_ bannerView: GoogleMobileAds.BannerView) {
|
|
463
|
+
callbackWrapper?.callback?.onAdLoaded()
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
func bannerAd(_ bannerView: GoogleMobileAds.BannerView, didFailToLoadWithError error: Error) {
|
|
467
|
+
callbackWrapper?.callback?.onAdFailedToLoad(error: error.localizedDescription)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
func bannerAdDidRecordClick(_ bannerView: GoogleMobileAds.BannerView) {
|
|
471
|
+
callbackWrapper?.callback?.onAdClicked()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
func bannerAdDidRecordImpression(_ bannerView: GoogleMobileAds.BannerView) {
|
|
475
|
+
callbackWrapper?.callback?.onAdImpression()
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Adapts InterstitialAdDelegate to InterstitialCallback
|
|
481
|
+
*/
|
|
482
|
+
private class InterstitialDelegateAdapterWrapper: InterstitialAdDelegate {
|
|
483
|
+
let placementId: String
|
|
484
|
+
weak var callbackWrapper: InterstitialCallbackWrapper?
|
|
485
|
+
|
|
486
|
+
init(placementId: String, callbackWrapper: InterstitialCallbackWrapper) {
|
|
487
|
+
self.placementId = placementId
|
|
488
|
+
self.callbackWrapper = callbackWrapper
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func interstitialAdDidLoad(_ interstitialAd: GoogleMobileAds.InterstitialAd) {
|
|
492
|
+
callbackWrapper?.callback?.onAdLoaded()
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
func interstitialAd(didFailToLoadWithError error: Error) {
|
|
496
|
+
callbackWrapper?.callback?.onAdFailedToLoad(error: error.localizedDescription)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
func interstitialAdDidPresent(_ interstitialAd: GoogleMobileAds.InterstitialAd) {
|
|
500
|
+
callbackWrapper?.callback?.onAdShowed()
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
func interstitialAdDidDismiss(_ interstitialAd: GoogleMobileAds.InterstitialAd) {
|
|
504
|
+
callbackWrapper?.callback?.onAdDismissed()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
func interstitialAdDidRecordClick(_ interstitialAd: GoogleMobileAds.InterstitialAd) {
|
|
508
|
+
callbackWrapper?.callback?.onAdClicked()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
func interstitialAdDidRecordImpression(_ interstitialAd: GoogleMobileAds.InterstitialAd) {
|
|
512
|
+
// Impression tracked by GoogleAdsAdapter
|
|
513
|
+
}
|
|
514
|
+
}
|