@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,448 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import GoogleMobileAds
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BigCrunch Banner View - UI component for displaying banner ads
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```swift
|
|
9
|
+
* let bannerView = BigCrunchBannerView()
|
|
10
|
+
* bannerView.configure(placementId: "home_banner")
|
|
11
|
+
* bannerView.delegate = self
|
|
12
|
+
* bannerView.loadAd(rootViewController: self)
|
|
13
|
+
*
|
|
14
|
+
* // Add to view hierarchy
|
|
15
|
+
* view.addSubview(bannerView)
|
|
16
|
+
*
|
|
17
|
+
* // When done:
|
|
18
|
+
* bannerView.destroy()
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Implement BigCrunchBannerViewDelegate for ad events:
|
|
22
|
+
* ```swift
|
|
23
|
+
* extension ViewController: BigCrunchBannerViewDelegate {
|
|
24
|
+
* func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView) { }
|
|
25
|
+
* func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String) { }
|
|
26
|
+
* func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView) { }
|
|
27
|
+
* func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView) { }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
public class BigCrunchBannerView: UIView {
|
|
32
|
+
|
|
33
|
+
// MARK: - Public Properties
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Delegate for receiving banner ad events
|
|
37
|
+
*/
|
|
38
|
+
public weak var delegate: BigCrunchBannerViewDelegate?
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The configured placement ID
|
|
42
|
+
*/
|
|
43
|
+
public private(set) var placementId: String?
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Override ad size (optional)
|
|
47
|
+
*
|
|
48
|
+
* If set, this size will be used instead of the size from backend config.
|
|
49
|
+
* Set this before calling loadAd().
|
|
50
|
+
* Use `AdSize.adaptive()` for adaptive banners that fill the screen width.
|
|
51
|
+
*/
|
|
52
|
+
public var adSizeOverride: AdSize?
|
|
53
|
+
|
|
54
|
+
// MARK: - Private Properties
|
|
55
|
+
|
|
56
|
+
private static let TAG = "BigCrunchBannerView"
|
|
57
|
+
private static let MIN_REFRESH_INTERVAL_MS = 10000 // 10 seconds minimum
|
|
58
|
+
private var isConfigured = false
|
|
59
|
+
private var isDestroyed = false
|
|
60
|
+
private var isPaused = false
|
|
61
|
+
private var adOrchestrator: AdOrchestrator?
|
|
62
|
+
private var internalBannerView: GoogleMobileAds.BannerView?
|
|
63
|
+
|
|
64
|
+
// Refresh properties
|
|
65
|
+
private var refreshTimer: Timer?
|
|
66
|
+
private var refreshCount: Int = 0
|
|
67
|
+
private var effectiveRefreshConfig: RefreshConfig?
|
|
68
|
+
private weak var rootViewControllerForRefresh: UIViewController?
|
|
69
|
+
|
|
70
|
+
// MARK: - Initialization
|
|
71
|
+
|
|
72
|
+
public override init(frame: CGRect) {
|
|
73
|
+
super.init(frame: frame)
|
|
74
|
+
BCLogger.verbose("\(BigCrunchBannerView.TAG): BigCrunchBannerView created")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public required init?(coder: NSCoder) {
|
|
78
|
+
super.init(coder: coder)
|
|
79
|
+
BCLogger.verbose("\(BigCrunchBannerView.TAG): BigCrunchBannerView created from coder")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
deinit {
|
|
83
|
+
if !isDestroyed {
|
|
84
|
+
BCLogger.verbose("\(BigCrunchBannerView.TAG): Auto-destroying banner in deinit")
|
|
85
|
+
destroy()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Public Methods
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Configure the banner view with a placement ID
|
|
93
|
+
*
|
|
94
|
+
* - Parameter placementId: The placement ID from the BigCrunch dashboard
|
|
95
|
+
*/
|
|
96
|
+
public func configure(placementId: String) {
|
|
97
|
+
if isDestroyed {
|
|
98
|
+
BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot configure destroyed banner view")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
guard !placementId.isEmpty else {
|
|
103
|
+
BCLogger.error("\(BigCrunchBannerView.TAG): Invalid placementId: cannot be empty")
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
self.placementId = placementId
|
|
108
|
+
self.isConfigured = true
|
|
109
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Banner configured with placement: \(placementId)")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Load a banner ad
|
|
114
|
+
*
|
|
115
|
+
* Must call `configure(placementId:)` before calling this method.
|
|
116
|
+
* The delegate will be notified of load success or failure.
|
|
117
|
+
*
|
|
118
|
+
* - Parameter rootViewController: The view controller to use for presenting ad click actions
|
|
119
|
+
*/
|
|
120
|
+
public func loadAd(rootViewController: UIViewController) {
|
|
121
|
+
if isDestroyed {
|
|
122
|
+
BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot load ad on destroyed banner view")
|
|
123
|
+
delegate?.bannerView(self, didFailToLoadWithError: "Banner view has been destroyed")
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
guard isConfigured, let pid = placementId else {
|
|
128
|
+
BCLogger.error("\(BigCrunchBannerView.TAG): Banner not configured. Call configure() first.")
|
|
129
|
+
delegate?.bannerView(self, didFailToLoadWithError: "Banner not configured. Call configure() first.")
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Loading banner ad: \(pid) (refreshCount: \(refreshCount))")
|
|
134
|
+
|
|
135
|
+
guard BigCrunchAds.isInitialized() else {
|
|
136
|
+
BCLogger.error("\(BigCrunchBannerView.TAG): SDK not initialized")
|
|
137
|
+
delegate?.bannerView(self, didFailToLoadWithError: "SDK not initialized. Call BigCrunchAds.initialize() first.")
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Store root view controller for refresh
|
|
142
|
+
rootViewControllerForRefresh = rootViewController
|
|
143
|
+
|
|
144
|
+
// Resolve effective refresh config on first load
|
|
145
|
+
if effectiveRefreshConfig == nil {
|
|
146
|
+
let configManager = BigCrunchAds.getConfigManager()
|
|
147
|
+
effectiveRefreshConfig = configManager.getEffectiveRefreshConfig(placementId: pid)
|
|
148
|
+
if let config = effectiveRefreshConfig {
|
|
149
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Refresh config resolved - enabled: \(config.enabled), interval: \(config.intervalMs)ms, max: \(config.maxRefreshes)")
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create AdOrchestrator lazily
|
|
154
|
+
if adOrchestrator == nil {
|
|
155
|
+
adOrchestrator = createAdOrchestrator()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Create callback wrapper
|
|
159
|
+
let callback = BannerCallbackImpl(bannerView: self)
|
|
160
|
+
|
|
161
|
+
// Load the ad through the orchestrator
|
|
162
|
+
_ = adOrchestrator?.loadBannerAd(
|
|
163
|
+
placementId: pid,
|
|
164
|
+
rootViewController: rootViewController,
|
|
165
|
+
callback: callback,
|
|
166
|
+
refreshCount: refreshCount,
|
|
167
|
+
adSizeOverride: adSizeOverride,
|
|
168
|
+
bannerViewSetter: { [weak self] bannerView in
|
|
169
|
+
self?.setInternalBannerView(bannerView)
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Load and display the banner ad (legacy method without root view controller)
|
|
176
|
+
*
|
|
177
|
+
* Note: This method attempts to find the root view controller automatically.
|
|
178
|
+
* For better control, use loadAd(rootViewController:) instead.
|
|
179
|
+
*/
|
|
180
|
+
public func loadAd() {
|
|
181
|
+
guard BigCrunchAds.isInitialized() else {
|
|
182
|
+
BCLogger.error("\(BigCrunchBannerView.TAG): SDK not initialized")
|
|
183
|
+
delegate?.bannerView(self, didFailToLoadWithError: "SDK not initialized. Call BigCrunchAds.initialize() first.")
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Try to find the root view controller
|
|
188
|
+
guard let rootVC = findRootViewController() else {
|
|
189
|
+
BCLogger.error("\(BigCrunchBannerView.TAG): Could not find root view controller")
|
|
190
|
+
delegate?.bannerView(self, didFailToLoadWithError: "Could not find root view controller. Use loadAd(rootViewController:) instead.")
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
loadAd(rootViewController: rootVC)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Pause ad refresh
|
|
199
|
+
*
|
|
200
|
+
* Stops the refresh timer. Call `resume()` to restart.
|
|
201
|
+
*/
|
|
202
|
+
public func pause() {
|
|
203
|
+
isPaused = true
|
|
204
|
+
cancelRefreshTimer()
|
|
205
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Banner ad refresh paused")
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resume ad refresh
|
|
210
|
+
*
|
|
211
|
+
* Restarts the refresh timer if refresh is enabled and max refreshes not reached.
|
|
212
|
+
*/
|
|
213
|
+
public func resume() {
|
|
214
|
+
isPaused = false
|
|
215
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Banner ad refresh resumed")
|
|
216
|
+
scheduleRefresh()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Destroy the banner view and release resources
|
|
221
|
+
*
|
|
222
|
+
* Call this when the banner is no longer needed.
|
|
223
|
+
*/
|
|
224
|
+
public func destroy() {
|
|
225
|
+
if isDestroyed {
|
|
226
|
+
BCLogger.verbose("\(BigCrunchBannerView.TAG): Banner already destroyed")
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Destroying banner: \(placementId ?? "nil")")
|
|
231
|
+
|
|
232
|
+
// Stop refresh timer
|
|
233
|
+
cancelRefreshTimer()
|
|
234
|
+
|
|
235
|
+
if let pid = placementId {
|
|
236
|
+
adOrchestrator?.destroyBannerAd(placementId: pid)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Remove internal banner view
|
|
240
|
+
internalBannerView?.removeFromSuperview()
|
|
241
|
+
internalBannerView = nil
|
|
242
|
+
|
|
243
|
+
// Clear references
|
|
244
|
+
delegate = nil
|
|
245
|
+
adOrchestrator = nil
|
|
246
|
+
rootViewControllerForRefresh = nil
|
|
247
|
+
effectiveRefreshConfig = nil
|
|
248
|
+
refreshCount = 0
|
|
249
|
+
isDestroyed = true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if the banner view has been destroyed
|
|
254
|
+
*/
|
|
255
|
+
public func isDestroyedState() -> Bool {
|
|
256
|
+
return isDestroyed
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get the current refresh count
|
|
261
|
+
*/
|
|
262
|
+
public func getRefreshCount() -> Int {
|
|
263
|
+
return refreshCount
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// MARK: - Internal Methods
|
|
267
|
+
|
|
268
|
+
internal func setInternalBannerView(_ bannerView: GoogleMobileAds.BannerView) {
|
|
269
|
+
internalBannerView?.removeFromSuperview()
|
|
270
|
+
internalBannerView = bannerView
|
|
271
|
+
|
|
272
|
+
// Add to view hierarchy
|
|
273
|
+
bannerView.translatesAutoresizingMaskIntoConstraints = false
|
|
274
|
+
addSubview(bannerView)
|
|
275
|
+
|
|
276
|
+
NSLayoutConstraint.activate([
|
|
277
|
+
bannerView.topAnchor.constraint(equalTo: topAnchor),
|
|
278
|
+
bannerView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
279
|
+
bannerView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
280
|
+
bannerView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
|
281
|
+
])
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Called by the callback when an ad loads successfully. Schedules the next refresh.
|
|
285
|
+
internal func onAdLoadedForRefresh() {
|
|
286
|
+
scheduleRefresh()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// MARK: - Refresh Logic
|
|
290
|
+
|
|
291
|
+
private func scheduleRefresh() {
|
|
292
|
+
// Don't schedule if destroyed or paused
|
|
293
|
+
guard !isDestroyed, !isPaused else { return }
|
|
294
|
+
|
|
295
|
+
guard let config = effectiveRefreshConfig, config.enabled else { return }
|
|
296
|
+
|
|
297
|
+
// Check max refreshes limit
|
|
298
|
+
guard refreshCount < config.maxRefreshes else {
|
|
299
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Max refreshes reached (\(config.maxRefreshes)) for \(placementId ?? "")")
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Enforce minimum interval
|
|
304
|
+
let intervalMs = max(config.intervalMs, BigCrunchBannerView.MIN_REFRESH_INTERVAL_MS)
|
|
305
|
+
let intervalSeconds = Double(intervalMs) / 1000.0
|
|
306
|
+
|
|
307
|
+
// Cancel any existing timer
|
|
308
|
+
cancelRefreshTimer()
|
|
309
|
+
|
|
310
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Scheduling refresh in \(intervalSeconds)s (count: \(refreshCount)/\(config.maxRefreshes)) for \(placementId ?? "")")
|
|
311
|
+
|
|
312
|
+
refreshTimer = Timer.scheduledTimer(withTimeInterval: intervalSeconds, repeats: false) { [weak self] _ in
|
|
313
|
+
self?.performRefresh()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private func performRefresh() {
|
|
318
|
+
guard !isDestroyed, !isPaused else { return }
|
|
319
|
+
|
|
320
|
+
guard let rootVC = rootViewControllerForRefresh else {
|
|
321
|
+
BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot refresh - root view controller is nil")
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
refreshCount += 1
|
|
326
|
+
BCLogger.debug("\(BigCrunchBannerView.TAG): Refreshing banner ad (refresh #\(refreshCount)) for \(placementId ?? "")")
|
|
327
|
+
|
|
328
|
+
loadAd(rootViewController: rootVC)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private func cancelRefreshTimer() {
|
|
332
|
+
refreshTimer?.invalidate()
|
|
333
|
+
refreshTimer = nil
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// MARK: - Private Methods
|
|
337
|
+
|
|
338
|
+
private func createAdOrchestrator() -> AdOrchestrator {
|
|
339
|
+
let configManager = BigCrunchAds.getConfigManager()
|
|
340
|
+
let analyticsClient = BigCrunchAds.getAnalyticsClient()
|
|
341
|
+
let googleAdsAdapter = GoogleAdsAdapter(analyticsClient: analyticsClient)
|
|
342
|
+
|
|
343
|
+
// BidRequestClient is shared across all views for batching
|
|
344
|
+
// If S2S is disabled, create a no-op client
|
|
345
|
+
let bidRequestClient = BigCrunchAds.getBidRequestClient() ?? BidRequestClient(
|
|
346
|
+
httpClient: HTTPClient(),
|
|
347
|
+
configManager: configManager,
|
|
348
|
+
privacyStore: BigCrunchAds.privacyStore,
|
|
349
|
+
s2sConfig: S2SConfig(enabled: false, serverUrl: "", timeoutMs: 0)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return AdOrchestrator(
|
|
353
|
+
configManager: configManager,
|
|
354
|
+
analyticsClient: analyticsClient,
|
|
355
|
+
bidRequestClient: bidRequestClient,
|
|
356
|
+
googleAdsAdapter: googleAdsAdapter
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private func findRootViewController() -> UIViewController? {
|
|
361
|
+
// Try to find key window and its root view controller
|
|
362
|
+
if #available(iOS 15.0, *) {
|
|
363
|
+
return UIApplication.shared.connectedScenes
|
|
364
|
+
.compactMap { $0 as? UIWindowScene }
|
|
365
|
+
.flatMap { $0.windows }
|
|
366
|
+
.first { $0.isKeyWindow }?
|
|
367
|
+
.rootViewController
|
|
368
|
+
} else {
|
|
369
|
+
return UIApplication.shared.windows
|
|
370
|
+
.first { $0.isKeyWindow }?
|
|
371
|
+
.rootViewController
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// MARK: - Delegate Protocol
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Delegate protocol for BigCrunchBannerView events
|
|
380
|
+
*/
|
|
381
|
+
public protocol BigCrunchBannerViewDelegate: AnyObject {
|
|
382
|
+
/**
|
|
383
|
+
* Called when the ad has been successfully loaded
|
|
384
|
+
*/
|
|
385
|
+
func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView)
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Called when the ad fails to load
|
|
389
|
+
*/
|
|
390
|
+
func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String)
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Called when the user clicks on the ad
|
|
394
|
+
*/
|
|
395
|
+
func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView)
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Called when an impression is recorded for the ad
|
|
399
|
+
*/
|
|
400
|
+
func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// MARK: - Default Implementations
|
|
404
|
+
|
|
405
|
+
public extension BigCrunchBannerViewDelegate {
|
|
406
|
+
func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView) {}
|
|
407
|
+
func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String) {}
|
|
408
|
+
func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView) {}
|
|
409
|
+
func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView) {}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// MARK: - Internal Callback Implementation
|
|
413
|
+
|
|
414
|
+
private class BannerCallbackImpl: BannerCallback {
|
|
415
|
+
private static let TAG = "BigCrunchBannerView"
|
|
416
|
+
weak var bannerView: BigCrunchBannerView?
|
|
417
|
+
|
|
418
|
+
init(bannerView: BigCrunchBannerView) {
|
|
419
|
+
self.bannerView = bannerView
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
func onAdLoaded() {
|
|
423
|
+
guard let bannerView = bannerView else { return }
|
|
424
|
+
BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad loaded: \(bannerView.placementId ?? "")")
|
|
425
|
+
bannerView.delegate?.bannerViewDidLoadAd(bannerView)
|
|
426
|
+
// Schedule next refresh after successful load
|
|
427
|
+
bannerView.onAdLoadedForRefresh()
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
func onAdFailedToLoad(error: String) {
|
|
431
|
+
guard let bannerView = bannerView else { return }
|
|
432
|
+
BCLogger.warning("\(BannerCallbackImpl.TAG): Banner ad failed to load: \(bannerView.placementId ?? "") - \(error)")
|
|
433
|
+
bannerView.delegate?.bannerView(bannerView, didFailToLoadWithError: error)
|
|
434
|
+
// Note: Do NOT schedule refresh on failure to avoid retry loops
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
func onAdClicked() {
|
|
438
|
+
guard let bannerView = bannerView else { return }
|
|
439
|
+
BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad clicked: \(bannerView.placementId ?? "")")
|
|
440
|
+
bannerView.delegate?.bannerViewDidRecordClick(bannerView)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
func onAdImpression() {
|
|
444
|
+
guard let bannerView = bannerView else { return }
|
|
445
|
+
BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad impression: \(bannerView.placementId ?? "")")
|
|
446
|
+
bannerView.delegate?.bannerViewDidRecordImpression(bannerView)
|
|
447
|
+
}
|
|
448
|
+
}
|