adwhale-sdk-react-native 2.7.203 → 2.7.300

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.
Files changed (109) hide show
  1. package/AdwhaleSdkReactNative.podspec +22 -0
  2. package/README.md +105 -39
  3. package/android/build.gradle +15 -15
  4. package/android/proguard-rules.pro +52 -107
  5. package/android/src/main/java/com/adwhalesdkreactnative/AdWhaleCustomNativeBinderFactory.java +18 -0
  6. package/android/src/main/java/com/adwhalesdkreactnative/AdwhaleSdkReactNativePackage.java +51 -6
  7. package/android/src/main/java/com/adwhalesdkreactnative/BinderFactory.java +9 -0
  8. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdSettingModule.java +149 -42
  9. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdView.java +60 -30
  10. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAppOpenAd.java +136 -0
  11. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationCustomNativeAdView.java +37 -46
  12. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationExitPopupAd.java +240 -0
  13. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationInterstitialAd.java +189 -0
  14. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationLoggerModule.java +2 -1
  15. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationNativeAdViewListenerBridge.java +83 -0
  16. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationRewardAd.java +255 -6
  17. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTemplateNativeAdView.java +191 -70
  18. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTransitionPopupAd.java +218 -0
  19. package/android/src/main/java/com/adwhalesdkreactnative/RNWrapperView.java +137 -0
  20. package/android/src/main/java/com/adwhalesdkreactnative/SimpleBinderFactory.java +1 -1
  21. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodArgConst.java +112 -0
  22. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodCallConst.java +113 -0
  23. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodResultConst.java +28 -0
  24. package/build/generated/ios/ReactAppDependencyProvider.podspec +34 -0
  25. package/ios/AdWhaleNativeCustomViewFactoryRegistry.swift +56 -0
  26. package/ios/AdwhaleSdkReactNative.mm +96 -5
  27. package/ios/AdwhaleSdkReactNativeModule.swift +148 -0
  28. package/ios/RNAdWhaleMediationAdViewManager.m +38 -0
  29. package/ios/RNAdWhaleMediationAdViewManager.swift +14 -0
  30. package/ios/RNAdWhaleMediationAppOpenAd.m +18 -0
  31. package/ios/RNAdWhaleMediationAppOpenAd.swift +175 -0
  32. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.m +22 -0
  33. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.swift +239 -0
  34. package/ios/RNAdWhaleMediationInterstitialAd.m +18 -0
  35. package/ios/RNAdWhaleMediationInterstitialAd.swift +161 -0
  36. package/ios/RNAdWhaleMediationRewardAd.m +18 -0
  37. package/ios/RNAdWhaleMediationRewardAd.swift +172 -0
  38. package/ios/WhaleMediationBannerContainer.swift +182 -0
  39. package/lib/module/AdWhaleAdView.js +80 -1
  40. package/lib/module/AdWhaleAdView.js.map +1 -1
  41. package/lib/module/AdWhaleAppOpenAd.js +223 -25
  42. package/lib/module/AdWhaleAppOpenAd.js.map +1 -1
  43. package/lib/module/AdWhaleExitPopupAd.js +93 -0
  44. package/lib/module/AdWhaleExitPopupAd.js.map +1 -0
  45. package/lib/module/AdWhaleInterstitialAd.js +146 -22
  46. package/lib/module/AdWhaleInterstitialAd.js.map +1 -1
  47. package/lib/module/AdWhaleMediationAds.js +75 -4
  48. package/lib/module/AdWhaleMediationAds.js.map +1 -1
  49. package/lib/module/AdWhaleNativeCustomView.js +59 -8
  50. package/lib/module/AdWhaleNativeCustomView.js.map +1 -1
  51. package/lib/module/AdWhaleNativeTemplateView.js +37 -19
  52. package/lib/module/AdWhaleNativeTemplateView.js.map +1 -1
  53. package/lib/module/AdWhaleRewardAd.js +169 -24
  54. package/lib/module/AdWhaleRewardAd.js.map +1 -1
  55. package/lib/module/AdWhaleTransitionPopupAd.js +87 -0
  56. package/lib/module/AdWhaleTransitionPopupAd.js.map +1 -0
  57. package/lib/module/NativeAdwhaleSdkReactNative.js +16 -1
  58. package/lib/module/NativeAdwhaleSdkReactNative.js.map +1 -1
  59. package/lib/module/constants/AdWhaleMethodArgConst.js +53 -0
  60. package/lib/module/constants/AdWhaleMethodArgConst.js.map +1 -0
  61. package/lib/module/constants/AdWhaleMethodCallConst.js +56 -0
  62. package/lib/module/constants/AdWhaleMethodCallConst.js.map +1 -0
  63. package/lib/module/constants/AdWhaleMethodResultConst.js +16 -0
  64. package/lib/module/constants/AdWhaleMethodResultConst.js.map +1 -0
  65. package/lib/module/index.js +10 -1
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/typescript/src/AdWhaleAdView.d.ts +46 -2
  68. package/lib/typescript/src/AdWhaleAdView.d.ts.map +1 -1
  69. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts +10 -0
  70. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts.map +1 -1
  71. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts +36 -0
  72. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts.map +1 -0
  73. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts +16 -0
  74. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts.map +1 -1
  75. package/lib/typescript/src/AdWhaleMediationAds.d.ts +14 -5
  76. package/lib/typescript/src/AdWhaleMediationAds.d.ts.map +1 -1
  77. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts +7 -4
  78. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts.map +1 -1
  79. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts +20 -7
  80. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts.map +1 -1
  81. package/lib/typescript/src/AdWhaleRewardAd.d.ts +18 -2
  82. package/lib/typescript/src/AdWhaleRewardAd.d.ts.map +1 -1
  83. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts +33 -0
  84. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts.map +1 -0
  85. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts +33 -6
  86. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts.map +1 -1
  87. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts +51 -0
  88. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts.map +1 -0
  89. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts +53 -0
  90. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts.map +1 -0
  91. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts +14 -0
  92. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts.map +1 -0
  93. package/lib/typescript/src/index.d.ts +10 -2
  94. package/lib/typescript/src/index.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/src/AdWhaleAdView.tsx +126 -9
  97. package/src/AdWhaleAppOpenAd.ts +293 -38
  98. package/src/AdWhaleExitPopupAd.ts +183 -0
  99. package/src/AdWhaleInterstitialAd.ts +206 -36
  100. package/src/AdWhaleMediationAds.ts +109 -5
  101. package/src/AdWhaleNativeCustomView.tsx +85 -23
  102. package/src/AdWhaleNativeTemplateView.tsx +79 -42
  103. package/src/AdWhaleRewardAd.ts +245 -43
  104. package/src/AdWhaleTransitionPopupAd.ts +171 -0
  105. package/src/NativeAdwhaleSdkReactNative.ts +36 -6
  106. package/src/constants/AdWhaleMethodArgConst.ts +50 -0
  107. package/src/constants/AdWhaleMethodCallConst.ts +60 -0
  108. package/src/constants/AdWhaleMethodResultConst.ts +13 -0
  109. package/src/index.ts +34 -1
@@ -0,0 +1,161 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ @objc(RNAdWhaleMediationInterstitialAd)
6
+ final class RNAdWhaleMediationInterstitialAd: RCTEventEmitter {
7
+
8
+ private final class Holder: NSObject, AdWhaleInterstitialDelegate {
9
+ let adId: Int
10
+ let module: RNAdWhaleMediationInterstitialAd
11
+ let ad: AdWhaleInterstitialAd
12
+
13
+ init(adId: Int, module: RNAdWhaleMediationInterstitialAd, ad: AdWhaleInterstitialAd) {
14
+ self.adId = adId
15
+ self.module = module
16
+ self.ad = ad
17
+ super.init()
18
+ self.ad.interstitialDelegate = self
19
+ }
20
+
21
+ func adDidReceiveInterstitialAd(_ ad: AdWhaleInterstitialAd) {
22
+ _ = ad
23
+ module.emit(adId: adId, eventName: "onInterstitialAdLoaded", payload: nil)
24
+ }
25
+
26
+ func adDidFailToReceiveInterstitialAdWithError(_ error: any Error) {
27
+ module.emitError(adId: adId, eventName: "onInterstitialAdLoadFailed", error: error)
28
+ }
29
+
30
+ func ad(_ ad: AdWhaleInterstitialAd, didFailToPresentInterstitialAdWithError error: any Error) {
31
+ _ = ad
32
+ module.emitError(adId: adId, eventName: "onInterstitialAdShowFailed", error: error)
33
+ }
34
+
35
+ func adWillPresentInterstitialAd(_ ad: AdWhaleInterstitialAd) {
36
+ _ = ad
37
+ module.emit(adId: adId, eventName: "onInterstitialAdShowed", payload: nil)
38
+ }
39
+
40
+ func adDidDismissInterstitialAd(_ ad: AdWhaleInterstitialAd) {
41
+ _ = ad
42
+ module.emit(adId: adId, eventName: "onInterstitialAdClosed", payload: nil)
43
+ module.destroyAd(adId)
44
+ }
45
+ }
46
+
47
+ private var holdersById: [Int: Holder] = [:]
48
+
49
+ // 레거시 옵션(기존 JS API)
50
+ private var legacyPlacementName: String?
51
+ private var legacyRegion: String?
52
+ private var legacyGcoder: (Double, Double)?
53
+ private var legacyLastAdId: Int = 0
54
+
55
+ override static func requiresMainQueueSetup() -> Bool { true }
56
+
57
+ override func supportedEvents() -> [String]! {
58
+ [
59
+ "onInterstitialAdLoaded",
60
+ "onInterstitialAdLoadFailed",
61
+ "onInterstitialAdShowed",
62
+ "onInterstitialAdShowFailed",
63
+ "onInterstitialAdClosed",
64
+ "onInterstitialAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열은 유지
65
+ "onAdEvent",
66
+ ]
67
+ }
68
+
69
+ // MARK: - Flutter-sync API (권장)
70
+
71
+ /// loadInterstitialAd({adId, placementUid, placementName?, region?, gcoder?})
72
+ @objc func loadInterstitialAd(_ args: NSDictionary) {
73
+ let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
74
+ let placementUid = (args["placementUid"] as? String) ?? ""
75
+ if placementUid.isEmpty { return }
76
+
77
+ let ad = AdWhaleInterstitialAd()
78
+ // iOS SDK 공개 API에는 setRegion/setGcoder/setPlacementName 등이 없음 (Android 전용). 무시.
79
+ let holder = Holder(adId: adId, module: self, ad: ad)
80
+ holdersById[adId] = holder
81
+ DispatchQueue.main.async {
82
+ ad.load(placementUid)
83
+ }
84
+ }
85
+
86
+ /// showAdWithoutView(adId)
87
+ @objc func showAdWithoutView(_ adId: NSNumber) {
88
+ let id = adId.intValue
89
+ guard let holder = holdersById[id] else { return }
90
+ DispatchQueue.main.async {
91
+ guard let vc = RCTPresentedViewController() else {
92
+ self.emit(
93
+ adId: id,
94
+ eventName: "onInterstitialAdShowFailed",
95
+ payload: ["statusCode": -1, "message": "No root view controller"]
96
+ )
97
+ return
98
+ }
99
+ holder.ad.show(vc)
100
+ }
101
+ }
102
+
103
+ /// destroyAd(adId)
104
+ @objc func destroyAd(_ adId: NSNumber) {
105
+ destroyAd(adId.intValue)
106
+ }
107
+
108
+ private func destroyAd(_ adId: Int) {
109
+ if let holder = holdersById.removeValue(forKey: adId) {
110
+ holder.ad.interstitialDelegate = nil
111
+ }
112
+ }
113
+
114
+ // MARK: - Legacy API (기존 JS와 호환)
115
+
116
+ @objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
117
+ @objc func setRegion(_ region: String) { legacyRegion = region }
118
+ @objc func setGcoder(_ gcoder: NSDictionary) {
119
+ let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
120
+ let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
121
+ legacyGcoder = (lt, lng)
122
+ }
123
+
124
+ @objc func loadAd(_ placementUid: String) {
125
+ legacyLastAdId += 1
126
+ loadInterstitialAd([
127
+ "adId": legacyLastAdId,
128
+ "placementUid": placementUid,
129
+ "placementName": legacyPlacementName ?? "",
130
+ "region": legacyRegion ?? "",
131
+ "gcoder": legacyGcoder != nil ? [legacyGcoder!.0, legacyGcoder!.1] : [],
132
+ ])
133
+ }
134
+
135
+ @objc func showAd() {
136
+ showAdWithoutView(NSNumber(value: legacyLastAdId))
137
+ }
138
+
139
+ // MARK: - Event helpers
140
+
141
+ private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
142
+ // 레거시 이벤트
143
+ sendEvent(withName: eventName, body: payload)
144
+
145
+ // Flutter-style 단일 이벤트
146
+ var body: [String: Any] = ["adId": adId, "eventName": eventName]
147
+ if let payload {
148
+ for (k, v) in payload { body[k] = v }
149
+ }
150
+ sendEvent(withName: "onAdEvent", body: body)
151
+ }
152
+
153
+ private func emitError(adId: Int, eventName: String, error: Error) {
154
+ let ns = error as NSError
155
+ emit(adId: adId, eventName: eventName, payload: [
156
+ "statusCode": ns.code,
157
+ "message": ns.localizedDescription,
158
+ ])
159
+ }
160
+ }
161
+
@@ -0,0 +1,18 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationRewardAd, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(setPlacementName:(NSString *)placementName)
6
+ RCT_EXTERN_METHOD(setRegion:(NSString *)region)
7
+ RCT_EXTERN_METHOD(setGcoder:(NSDictionary *)gcoder)
8
+
9
+ // Legacy API
10
+ RCT_EXTERN_METHOD(loadAd:(NSString *)placementUid)
11
+ RCT_EXTERN_METHOD(showAd)
12
+
13
+ // Flutter-sync API
14
+ RCT_EXTERN_METHOD(loadRewardAd:(NSDictionary *)args)
15
+ RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
16
+ RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
17
+
18
+ @end
@@ -0,0 +1,172 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ @objc(RNAdWhaleMediationRewardAd)
6
+ final class RNAdWhaleMediationRewardAd: RCTEventEmitter {
7
+
8
+ private final class Holder: NSObject, AdWhaleRewardDelegate {
9
+ let adId: Int
10
+ let module: RNAdWhaleMediationRewardAd
11
+ let ad: AdWhaleRewardAd
12
+
13
+ init(adId: Int, module: RNAdWhaleMediationRewardAd, ad: AdWhaleRewardAd) {
14
+ self.adId = adId
15
+ self.module = module
16
+ self.ad = ad
17
+ super.init()
18
+ self.ad.rewardDelegate = self
19
+ }
20
+
21
+ func adDidReceiveRewardAd(_ ad: AdWhaleRewardAd) {
22
+ _ = ad
23
+ module.emit(adId: adId, eventName: "onRewardAdLoaded", payload: nil)
24
+ }
25
+
26
+ func adDidEarnReward(_ reward: AdWhaleReward) {
27
+ let type = reward.type
28
+ let amountInt = Int(truncating: reward.amount)
29
+ module.emit(adId: adId, eventName: "onUserRewarded", payload: [
30
+ "RewardType": type,
31
+ "RewardAmount": amountInt,
32
+ // 레거시 키도 유지
33
+ "type": type,
34
+ "amount": amountInt,
35
+ ])
36
+ }
37
+
38
+ func adDidFailToReceiveRewardAdWithError(_ error: any Error) {
39
+ module.emitError(adId: adId, eventName: "onRewardAdFailedToLoad", error: error)
40
+ }
41
+
42
+ func ad(_ ad: AdWhaleRewardAd, didFailToPresentRewardAdWithError error: any Error) {
43
+ _ = ad
44
+ module.emitError(adId: adId, eventName: "onRewardAdFailedToShow", error: error)
45
+ }
46
+
47
+ func adWillPresentRewardAd(_ ad: AdWhaleRewardAd) {
48
+ _ = ad
49
+ module.emit(adId: adId, eventName: "onRewardAdShowed", payload: nil)
50
+ }
51
+
52
+ func adDidDismissRewardAd(_ ad: AdWhaleRewardAd) {
53
+ _ = ad
54
+ module.emit(adId: adId, eventName: "onRewardAdDismissed", payload: nil)
55
+ module.destroyAd(adId)
56
+ }
57
+ }
58
+
59
+ private var holdersById: [Int: Holder] = [:]
60
+
61
+ // 레거시 옵션(기존 JS API)
62
+ private var legacyPlacementName: String?
63
+ private var legacyRegion: String?
64
+ private var legacyGcoder: (Double, Double)?
65
+ private var legacyLastAdId: Int = 0
66
+
67
+ override static func requiresMainQueueSetup() -> Bool { true }
68
+
69
+ override func supportedEvents() -> [String]! {
70
+ [
71
+ "onRewardAdLoaded",
72
+ "onRewardAdFailedToLoad",
73
+ "onRewardAdShowed",
74
+ "onRewardAdFailedToShow",
75
+ "onRewardAdDismissed",
76
+ "onRewardAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열 유지
77
+ "onUserRewarded",
78
+ "onAdEvent",
79
+ ]
80
+ }
81
+
82
+ // MARK: - Flutter-sync API (권장)
83
+
84
+ /// loadRewardAd({adId, placementUid, placementName?, region?, gcoder?})
85
+ @objc func loadRewardAd(_ args: NSDictionary) {
86
+ let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
87
+ let placementUid = (args["placementUid"] as? String) ?? ""
88
+ if placementUid.isEmpty { return }
89
+
90
+ let ad = AdWhaleRewardAd()
91
+ let holder = Holder(adId: adId, module: self, ad: ad)
92
+ holdersById[adId] = holder
93
+
94
+ DispatchQueue.main.async {
95
+ ad.load(placementUid)
96
+ }
97
+ }
98
+
99
+ /// showAdWithoutView(adId)
100
+ @objc func showAdWithoutView(_ adId: NSNumber) {
101
+ let id = adId.intValue
102
+ guard let holder = holdersById[id] else { return }
103
+ DispatchQueue.main.async {
104
+ guard let vc = RCTPresentedViewController() else {
105
+ self.emit(
106
+ adId: id,
107
+ eventName: "onRewardAdFailedToShow",
108
+ payload: ["statusCode": -1, "message": "No root view controller"]
109
+ )
110
+ return
111
+ }
112
+ holder.ad.show(vc)
113
+ }
114
+ }
115
+
116
+ /// destroyAd(adId)
117
+ @objc func destroyAd(_ adId: NSNumber) {
118
+ destroyAd(adId.intValue)
119
+ }
120
+
121
+ private func destroyAd(_ adId: Int) {
122
+ if let holder = holdersById.removeValue(forKey: adId) {
123
+ holder.ad.rewardDelegate = nil
124
+ }
125
+ }
126
+
127
+ // MARK: - Legacy API (기존 JS와 호환)
128
+
129
+ @objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
130
+ @objc func setRegion(_ region: String) { legacyRegion = region }
131
+ @objc func setGcoder(_ gcoder: NSDictionary) {
132
+ let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
133
+ let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
134
+ legacyGcoder = (lt, lng)
135
+ }
136
+
137
+ @objc func loadAd(_ placementUid: String) {
138
+ legacyLastAdId += 1
139
+ loadRewardAd([
140
+ "adId": legacyLastAdId,
141
+ "placementUid": placementUid,
142
+ "placementName": legacyPlacementName ?? "",
143
+ "region": legacyRegion ?? "",
144
+ "gcoder": legacyGcoder != nil ? [legacyGcoder!.0, legacyGcoder!.1] : [],
145
+ ])
146
+ }
147
+
148
+ @objc func showAd() {
149
+ showAdWithoutView(NSNumber(value: legacyLastAdId))
150
+ }
151
+
152
+ // MARK: - Event helpers
153
+
154
+ private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
155
+ sendEvent(withName: eventName, body: payload)
156
+
157
+ var body: [String: Any] = ["adId": adId, "eventName": eventName]
158
+ if let payload {
159
+ for (k, v) in payload { body[k] = v }
160
+ }
161
+ sendEvent(withName: "onAdEvent", body: body)
162
+ }
163
+
164
+ private func emitError(adId: Int, eventName: String, error: Error) {
165
+ let ns = error as NSError
166
+ emit(adId: adId, eventName: eventName, payload: [
167
+ "statusCode": ns.code,
168
+ "message": ns.localizedDescription,
169
+ ])
170
+ }
171
+ }
172
+
@@ -0,0 +1,182 @@
1
+ import UIKit
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ /// RN `RNAdWhaleMediationAdView` — Android `RNAdWhaleMediationAdView` 와 동일 역할.
6
+ @objcMembers
7
+ @MainActor
8
+ public final class WhaleMediationBannerContainer: UIView, AdWhaleBannerDelegate {
9
+
10
+ @objc public var onAdLoaded: RCTDirectEventBlock?
11
+ @objc public var onAdLoadFailed: RCTDirectEventBlock?
12
+ @objc public var onAdClicked: RCTDirectEventBlock?
13
+
14
+ /// Flutter와 동일한 개념의 배너 인스턴스 식별자(선택).
15
+ @objc public var adId: Int = 0
16
+
17
+ @objc public var placementUid: String = "" {
18
+ didSet {
19
+ banner?.setAdUnitID(placementUid)
20
+ scheduleLoadIfNeeded()
21
+ }
22
+ }
23
+
24
+ /// RN prop `adSize` → `RCT_REMAP_VIEW_PROPERTY(adSize, adSizeString, NSString)`
25
+ @objc public var adSizeString: String = "" {
26
+ didSet {
27
+ if !Self.isBannerAdSizeSupportedOnIOS(adSizeString) {
28
+ if let b = banner {
29
+ b.destroy()
30
+ b.removeFromSuperview()
31
+ banner = nil
32
+ }
33
+ scheduleLoadIfNeeded()
34
+ return
35
+ }
36
+ let sz = Self.mapRNAdSizeString(adSizeString)
37
+ banner?.setAdSize(sz)
38
+ scheduleLoadIfNeeded()
39
+ }
40
+ }
41
+
42
+ /// RN prop `loadAd`
43
+ @objc public var shouldLoadAd: Bool = false {
44
+ didSet {
45
+ if oldValue == shouldLoadAd { return }
46
+ scheduleLoadIfNeeded()
47
+ }
48
+ }
49
+
50
+ @objc public var adaptiveAnchorWidth: Int = 0
51
+
52
+ @objc public var placementName: String = ""
53
+ @objc public var region: String = ""
54
+ @objc public var gcoder: NSDictionary?
55
+
56
+ private var banner: AdWhaleBannerAd?
57
+
58
+ deinit {
59
+ banner?.destroy()
60
+ }
61
+
62
+ public override func layoutSubviews() {
63
+ super.layoutSubviews()
64
+ banner?.frame = bounds
65
+ }
66
+
67
+ private static func isBannerAdSizeSupportedOnIOS(_ name: String?) -> Bool {
68
+ guard let name, !name.isEmpty else { return true }
69
+ if name == "BANNER250x250" || name == "ADAPTIVE_ANCHOR" { return false }
70
+ return true
71
+ }
72
+
73
+ private static func unsupportedMessage(_ name: String?) -> String {
74
+ if name == "BANNER250x250" {
75
+ return "AdWhale iOS SDK does not support BANNER250x250."
76
+ }
77
+ if name == "ADAPTIVE_ANCHOR" {
78
+ return "AdWhale iOS SDK does not support ADAPTIVE_ANCHOR."
79
+ }
80
+ return "AdWhale iOS SDK does not support this banner size."
81
+ }
82
+
83
+ private static func mapRNAdSizeString(_ name: String?) -> AdWhaleAdSize {
84
+ guard let name, !name.isEmpty else { return .banner }
85
+ switch name {
86
+ case "BANNER320x50": return .banner
87
+ case "BANNER320x100": return .largeBanner
88
+ case "BANNER300x250": return .mediumRectangle
89
+ default: return .banner
90
+ }
91
+ }
92
+
93
+ private func scheduleLoadIfNeeded() {
94
+ guard shouldLoadAd, !placementUid.isEmpty else { return }
95
+ weak var weakSelf = self
96
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
97
+ weakSelf?.performBannerLoad()
98
+ }
99
+ }
100
+
101
+ private func ensureBanner() {
102
+ guard banner == nil else { return }
103
+ let sz = Self.mapRNAdSizeString(adSizeString)
104
+ let b = AdWhaleBannerAd(sz)
105
+ b.translatesAutoresizingMaskIntoConstraints = true
106
+ b.setDelegate(self)
107
+ addSubview(b)
108
+ banner = b
109
+ if !placementUid.isEmpty {
110
+ b.setAdUnitID(placementUid)
111
+ }
112
+ b.setAdSize(sz)
113
+ b.frame = bounds
114
+ }
115
+
116
+ private func performBannerLoad() {
117
+ guard shouldLoadAd, !placementUid.isEmpty else { return }
118
+ if !Self.isBannerAdSizeSupportedOnIOS(adSizeString) {
119
+ onAdLoadFailed?([
120
+ "adId": adId,
121
+ "statusCode": -100,
122
+ "message": Self.unsupportedMessage(adSizeString),
123
+ ])
124
+ return
125
+ }
126
+ ensureBanner()
127
+ guard let root = RCTPresentedViewController() else {
128
+ onAdLoadFailed?([
129
+ "adId": adId,
130
+ "statusCode": -1,
131
+ "message": "Root view controller is not available for banner load.",
132
+ ])
133
+ return
134
+ }
135
+ banner?.setRootViewController(root)
136
+ banner?.setAdUnitID(placementUid)
137
+ banner?.setAdSize(Self.mapRNAdSizeString(adSizeString))
138
+ banner?.load()
139
+ }
140
+
141
+ // MARK: - AdWhaleBannerDelegate
142
+
143
+ public func bannerViewDidReceiveAd(_ bannerView: AdWhaleBannerAd) {
144
+ _ = bannerView
145
+ onAdLoaded?([
146
+ "adId": adId,
147
+ ])
148
+ }
149
+
150
+ public func bannerView(_ bannerView: AdWhaleBannerAd, didFailToReceiveAdWithError error: Error) {
151
+ _ = bannerView
152
+ let ns = error as NSError
153
+ onAdLoadFailed?([
154
+ "adId": adId,
155
+ "statusCode": ns.code,
156
+ "message": error.localizedDescription,
157
+ ])
158
+ }
159
+
160
+ public func bannerViewDidRecordImpression(_ bannerView: AdWhaleBannerAd) {
161
+ _ = bannerView
162
+ }
163
+
164
+ public func bannerViewWillPresentScreen(_ bannerView: AdWhaleBannerAd) {
165
+ _ = bannerView
166
+ }
167
+
168
+ public func bannerViewWillDismissScreen(_ bannerView: AdWhaleBannerAd) {
169
+ _ = bannerView
170
+ }
171
+
172
+ public func bannerViewDidDismissScreen(_ bannerView: AdWhaleBannerAd) {
173
+ _ = bannerView
174
+ }
175
+
176
+ public func bannerViewDidRecordClick(_ bannerView: AdWhaleBannerAd) {
177
+ _ = bannerView
178
+ onAdClicked?([
179
+ "adId": adId,
180
+ ])
181
+ }
182
+ }
@@ -2,18 +2,97 @@
2
2
 
3
3
  // src/AdWhaleAdView.tsx
4
4
  import React from 'react';
5
- import { requireNativeComponent } from 'react-native';
5
+ import { Platform, requireNativeComponent } from 'react-native';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
+ /**
8
+ * AdWhale 배너 광고 사이즈 enum
9
+ * Android SDK의 ADWHALE_AD_SIZE enum과 매핑됩니다.
10
+ */
11
+ export let AdWhaleAdSize = /*#__PURE__*/function (AdWhaleAdSize) {
12
+ /** 표준 배너 (320x50) */
13
+ AdWhaleAdSize["BANNER_320x50"] = "BANNER320x50";
14
+ /** 큰 배너 (320x100) */
15
+ AdWhaleAdSize["BANNER_320x100"] = "BANNER320x100";
16
+ /** 중간 직사각형 (300x250) */
17
+ AdWhaleAdSize["BANNER_300x250"] = "BANNER300x250";
18
+ /**
19
+ * 정사각형 (250x250)
20
+ * iOS: AdWhale iOS SDK 미지원 — 네이티브에서 로드하지 않으며 `onAdLoadFailed`(statusCode -100) 가 호출됩니다.
21
+ */
22
+ AdWhaleAdSize["BANNER_250x250"] = "BANNER250x250";
23
+ /**
24
+ * 적응형 앵커 배너 - adaptiveAnchorWidth와 함께 사용
25
+ * iOS: AdWhale iOS SDK 미지원 — 네이티브에서 로드하지 않으며 `onAdLoadFailed`(statusCode -100) 가 호출됩니다.
26
+ */
27
+ AdWhaleAdSize["ADAPTIVE_ANCHOR"] = "ADAPTIVE_ANCHOR";
28
+ return AdWhaleAdSize;
29
+ }({});
30
+
31
+ /**
32
+ * AdWhale iOS SDK 가 해당 배너 사이즈를 지원하는지 (UI 비활성화 등에 사용).
33
+ * Android / 기타 플랫폼에서는 항상 true.
34
+ */
35
+ export function isAdWhaleBannerAdSizeSupportedOnIos(adSize) {
36
+ if (Platform.OS !== 'ios') {
37
+ return true;
38
+ }
39
+ return adSize !== AdWhaleAdSize.BANNER_250x250 && adSize !== AdWhaleAdSize.ADAPTIVE_ANCHOR;
40
+ }
41
+ /** iOS: BANNER250x250 / ADAPTIVE_ANCHOR 요청 시 네이티브 `onAdLoadFailed` 의 statusCode */
42
+ export const ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE = -100;
7
43
  const RNAdWhaleMediationAdView = requireNativeComponent('RNAdWhaleMediationAdView');
44
+ const AD_SIZE_ENUM_NAME_BY_VALUE = {
45
+ [AdWhaleAdSize.BANNER_320x50]: 'BANNER_320x50',
46
+ [AdWhaleAdSize.BANNER_320x100]: 'BANNER_320x100',
47
+ [AdWhaleAdSize.BANNER_300x250]: 'BANNER_300x250',
48
+ [AdWhaleAdSize.BANNER_250x250]: 'BANNER_250x250',
49
+ [AdWhaleAdSize.ADAPTIVE_ANCHOR]: 'ADAPTIVE_ANCHOR'
50
+ };
8
51
  export const AdWhaleAdView = ({
52
+ onAdLoaded,
9
53
  onAdLoadFailed,
54
+ adSize,
55
+ adaptiveAnchorWidth,
56
+ loadAd,
10
57
  ...restProps
11
58
  }) => {
59
+ const lastCreateBannerLogKeyRef = React.useRef(null);
60
+ React.useEffect(() => {
61
+ if (!loadAd) {
62
+ return;
63
+ }
64
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
65
+ const adaptiveWidthForLog = adSize === AdWhaleAdSize.ADAPTIVE_ANCHOR ? String(adaptiveAnchorWidth ?? 0) : 'null';
66
+ const logKey = `${enumName}:${adaptiveWidthForLog}`;
67
+ if (lastCreateBannerLogKeyRef.current === logKey) {
68
+ return;
69
+ }
70
+ lastCreateBannerLogKeyRef.current = logKey;
71
+ console.log(`AdWhaleAdView.tsx _createBanner size=AdWhaleAdSize.${enumName}(${adSize}), adaptiveWidth=${adaptiveWidthForLog}`);
72
+ }, [loadAd, adSize, adaptiveAnchorWidth]);
73
+ const handleAdLoaded = () => {
74
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
75
+ console.log(`AdWhaleAdView.tsx BannerAd onLoaded for size AdWhaleAdSize.${enumName}(${adSize})`);
76
+ onAdLoaded?.();
77
+ };
12
78
  const handleAdLoadFailed = e => {
79
+ if (Platform.OS === 'ios' && e.nativeEvent.statusCode === ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE) {
80
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
81
+ const errorMessage = adSize === AdWhaleAdSize.BANNER_250x250 ? '250x250 is not supported on iOS' : 'adaptive banner is not supported on iOS';
82
+ console.log(`AdWhaleAdView.tsx BannerAd onLoadFailed for AdWhaleAdSize.${enumName}: errorCode: UNSUPPORTED_BANNER_SIZE, errorMessage: ${errorMessage}`);
83
+ return;
84
+ }
13
85
  onAdLoadFailed?.(e.nativeEvent);
14
86
  };
87
+
88
+ // ADAPTIVE_ANCHOR일 때만 adaptiveAnchorWidth를 네이티브로 전달
89
+ const nativeAdaptiveAnchorWidth = adSize === AdWhaleAdSize.ADAPTIVE_ANCHOR ? adaptiveAnchorWidth : undefined;
15
90
  return /*#__PURE__*/_jsx(RNAdWhaleMediationAdView, {
16
91
  ...restProps,
92
+ adSize: adSize,
93
+ adaptiveAnchorWidth: nativeAdaptiveAnchorWidth,
94
+ loadAd: loadAd,
95
+ onAdLoaded: handleAdLoaded,
17
96
  onAdLoadFailed: onAdLoadFailed ? handleAdLoadFailed : undefined
18
97
  });
19
98
  };
@@ -1 +1 @@
1
- {"version":3,"names":["React","requireNativeComponent","jsx","_jsx","RNAdWhaleMediationAdView","AdWhaleAdView","onAdLoadFailed","restProps","handleAdLoadFailed","e","nativeEvent","undefined"],"sourceRoot":"../../src","sources":["AdWhaleAdView.tsx"],"mappings":";;AAAA;AACA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,sBAAsB,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAyCtD,MAAMC,wBAAwB,GAC5BH,sBAAsB,CAAoB,0BAA0B,CAAC;AAEvE,OAAO,MAAMI,aAA+C,GAAGA,CAAC;EAC9DC,cAAc;EACd,GAAGC;AACL,CAAC,KAAK;EACJ,MAAMC,kBAAuD,GAAGC,CAAC,IAAI;IACnEH,cAAc,GAAGG,CAAC,CAACC,WAAW,CAAC;EACjC,CAAC;EAED,oBACEP,IAAA,CAACC,wBAAwB;IAAA,GACnBG,SAAS;IACbD,cAAc,EAAEA,cAAc,GAAGE,kBAAkB,GAAGG;EAAU,CACjE,CAAC;AAEN,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","Platform","requireNativeComponent","jsx","_jsx","AdWhaleAdSize","isAdWhaleBannerAdSizeSupportedOnIos","adSize","OS","BANNER_250x250","ADAPTIVE_ANCHOR","ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE","RNAdWhaleMediationAdView","AD_SIZE_ENUM_NAME_BY_VALUE","BANNER_320x50","BANNER_320x100","BANNER_300x250","AdWhaleAdView","onAdLoaded","onAdLoadFailed","adaptiveAnchorWidth","loadAd","restProps","lastCreateBannerLogKeyRef","useRef","useEffect","enumName","adaptiveWidthForLog","String","logKey","current","console","log","handleAdLoaded","handleAdLoadFailed","e","nativeEvent","statusCode","errorMessage","nativeAdaptiveAnchorWidth","undefined"],"sourceRoot":"../../src","sources":["AdWhaleAdView.tsx"],"mappings":";;AAAA;AACA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,QAAQ,EAAEC,sBAAsB,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAGhE;AACA;AACA;AACA;AACA,WAAYC,aAAa,0BAAbA,aAAa;EACvB;EADUA,aAAa;EAGvB;EAHUA,aAAa;EAKvB;EALUA,aAAa;EAOvB;AACF;AACA;AACA;EAVYA,aAAa;EAYvB;AACF;AACA;AACA;EAfYA,aAAa;EAAA,OAAbA,aAAa;AAAA;;AAmBzB;AACA;AACA;AACA;AACA,OAAO,SAASC,mCAAmCA,CACjDC,MAAqB,EACZ;EACT,IAAIN,QAAQ,CAACO,EAAE,KAAK,KAAK,EAAE;IACzB,OAAO,IAAI;EACb;EACA,OACED,MAAM,KAAKF,aAAa,CAACI,cAAc,IACvCF,MAAM,KAAKF,aAAa,CAACK,eAAe;AAE5C;AAOA;AACA,OAAO,MAAMC,0CAA0C,GAAG,CAAC,GAAG;AA4C9D,MAAMC,wBAAwB,GAC5BV,sBAAsB,CAAoB,0BAA0B,CAAC;AAEvE,MAAMW,0BAAyD,GAAG;EAChE,CAACR,aAAa,CAACS,aAAa,GAAG,eAAe;EAC9C,CAACT,aAAa,CAACU,cAAc,GAAG,gBAAgB;EAChD,CAACV,aAAa,CAACW,cAAc,GAAG,gBAAgB;EAChD,CAACX,aAAa,CAACI,cAAc,GAAG,gBAAgB;EAChD,CAACJ,aAAa,CAACK,eAAe,GAAG;AACnC,CAAC;AAED,OAAO,MAAMO,aAA+C,GAAGA,CAAC;EAC9DC,UAAU;EACVC,cAAc;EACdZ,MAAM;EACNa,mBAAmB;EACnBC,MAAM;EACN,GAAGC;AACL,CAAC,KAAK;EACJ,MAAMC,yBAAyB,GAAGvB,KAAK,CAACwB,MAAM,CAAgB,IAAI,CAAC;EAEnExB,KAAK,CAACyB,SAAS,CAAC,MAAM;IACpB,IAAI,CAACJ,MAAM,EAAE;MACX;IACF;IAEA,MAAMK,QAAQ,GAAGb,0BAA0B,CAACN,MAAM,CAAC,IAAIA,MAAM;IAC7D,MAAMoB,mBAAmB,GACvBpB,MAAM,KAAKF,aAAa,CAACK,eAAe,GACpCkB,MAAM,CAACR,mBAAmB,IAAI,CAAC,CAAC,GAChC,MAAM;IACZ,MAAMS,MAAM,GAAG,GAAGH,QAAQ,IAAIC,mBAAmB,EAAE;IAEnD,IAAIJ,yBAAyB,CAACO,OAAO,KAAKD,MAAM,EAAE;MAChD;IACF;IACAN,yBAAyB,CAACO,OAAO,GAAGD,MAAM;IAE1CE,OAAO,CAACC,GAAG,CACT,sDAAsDN,QAAQ,IAAInB,MAAM,oBAAoBoB,mBAAmB,EACjH,CAAC;EACH,CAAC,EAAE,CAACN,MAAM,EAAEd,MAAM,EAAEa,mBAAmB,CAAC,CAAC;EAEzC,MAAMa,cAAc,GAAGA,CAAA,KAAM;IAC3B,MAAMP,QAAQ,GAAGb,0BAA0B,CAACN,MAAM,CAAC,IAAIA,MAAM;IAC7DwB,OAAO,CAACC,GAAG,CACT,8DAA8DN,QAAQ,IAAInB,MAAM,GAClF,CAAC;IACDW,UAAU,GAAG,CAAC;EAChB,CAAC;EAED,MAAMgB,kBAAuD,GAAGC,CAAC,IAAI;IACnE,IACElC,QAAQ,CAACO,EAAE,KAAK,KAAK,IACrB2B,CAAC,CAACC,WAAW,CAACC,UAAU,KAAK1B,0CAA0C,EACvE;MACA,MAAMe,QAAQ,GAAGb,0BAA0B,CAACN,MAAM,CAAC,IAAIA,MAAM;MAC7D,MAAM+B,YAAY,GAChB/B,MAAM,KAAKF,aAAa,CAACI,cAAc,GACnC,iCAAiC,GACjC,yCAAyC;MAC/CsB,OAAO,CAACC,GAAG,CACT,6DAA6DN,QAAQ,uDAAuDY,YAAY,EAC1I,CAAC;MACD;IACF;IACAnB,cAAc,GAAGgB,CAAC,CAACC,WAAW,CAAC;EACjC,CAAC;;EAED;EACA,MAAMG,yBAAyB,GAC7BhC,MAAM,KAAKF,aAAa,CAACK,eAAe,GAAGU,mBAAmB,GAAGoB,SAAS;EAE5E,oBACEpC,IAAA,CAACQ,wBAAwB;IAAA,GACnBU,SAAS;IACbf,MAAM,EAAEA,MAAO;IACfa,mBAAmB,EAAEmB,yBAA0B;IAC/ClB,MAAM,EAAEA,MAAO;IACfH,UAAU,EAAEe,cAAe;IAC3Bd,cAAc,EAAEA,cAAc,GAAGe,kBAAkB,GAAGM;EAAU,CACjE,CAAC;AAEN,CAAC","ignoreList":[]}