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.
- package/AdwhaleSdkReactNative.podspec +22 -0
- package/README.md +105 -39
- package/android/build.gradle +15 -15
- package/android/proguard-rules.pro +52 -107
- package/android/src/main/java/com/adwhalesdkreactnative/AdWhaleCustomNativeBinderFactory.java +18 -0
- package/android/src/main/java/com/adwhalesdkreactnative/AdwhaleSdkReactNativePackage.java +51 -6
- package/android/src/main/java/com/adwhalesdkreactnative/BinderFactory.java +9 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdSettingModule.java +149 -42
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdView.java +60 -30
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAppOpenAd.java +136 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationCustomNativeAdView.java +37 -46
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationExitPopupAd.java +240 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationInterstitialAd.java +189 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationLoggerModule.java +2 -1
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationNativeAdViewListenerBridge.java +83 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationRewardAd.java +255 -6
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTemplateNativeAdView.java +191 -70
- package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTransitionPopupAd.java +218 -0
- package/android/src/main/java/com/adwhalesdkreactnative/RNWrapperView.java +137 -0
- package/android/src/main/java/com/adwhalesdkreactnative/SimpleBinderFactory.java +1 -1
- package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodArgConst.java +112 -0
- package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodCallConst.java +113 -0
- package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodResultConst.java +28 -0
- package/build/generated/ios/ReactAppDependencyProvider.podspec +34 -0
- package/ios/AdWhaleNativeCustomViewFactoryRegistry.swift +56 -0
- package/ios/AdwhaleSdkReactNative.mm +96 -5
- package/ios/AdwhaleSdkReactNativeModule.swift +148 -0
- package/ios/RNAdWhaleMediationAdViewManager.m +38 -0
- package/ios/RNAdWhaleMediationAdViewManager.swift +14 -0
- package/ios/RNAdWhaleMediationAppOpenAd.m +18 -0
- package/ios/RNAdWhaleMediationAppOpenAd.swift +175 -0
- package/ios/RNAdWhaleMediationCustomNativeAdViewManager.m +22 -0
- package/ios/RNAdWhaleMediationCustomNativeAdViewManager.swift +239 -0
- package/ios/RNAdWhaleMediationInterstitialAd.m +18 -0
- package/ios/RNAdWhaleMediationInterstitialAd.swift +161 -0
- package/ios/RNAdWhaleMediationRewardAd.m +18 -0
- package/ios/RNAdWhaleMediationRewardAd.swift +172 -0
- package/ios/WhaleMediationBannerContainer.swift +182 -0
- package/lib/module/AdWhaleAdView.js +80 -1
- package/lib/module/AdWhaleAdView.js.map +1 -1
- package/lib/module/AdWhaleAppOpenAd.js +223 -25
- package/lib/module/AdWhaleAppOpenAd.js.map +1 -1
- package/lib/module/AdWhaleExitPopupAd.js +93 -0
- package/lib/module/AdWhaleExitPopupAd.js.map +1 -0
- package/lib/module/AdWhaleInterstitialAd.js +146 -22
- package/lib/module/AdWhaleInterstitialAd.js.map +1 -1
- package/lib/module/AdWhaleMediationAds.js +75 -4
- package/lib/module/AdWhaleMediationAds.js.map +1 -1
- package/lib/module/AdWhaleNativeCustomView.js +59 -8
- package/lib/module/AdWhaleNativeCustomView.js.map +1 -1
- package/lib/module/AdWhaleNativeTemplateView.js +37 -19
- package/lib/module/AdWhaleNativeTemplateView.js.map +1 -1
- package/lib/module/AdWhaleRewardAd.js +169 -24
- package/lib/module/AdWhaleRewardAd.js.map +1 -1
- package/lib/module/AdWhaleTransitionPopupAd.js +87 -0
- package/lib/module/AdWhaleTransitionPopupAd.js.map +1 -0
- package/lib/module/NativeAdwhaleSdkReactNative.js +16 -1
- package/lib/module/NativeAdwhaleSdkReactNative.js.map +1 -1
- package/lib/module/constants/AdWhaleMethodArgConst.js +53 -0
- package/lib/module/constants/AdWhaleMethodArgConst.js.map +1 -0
- package/lib/module/constants/AdWhaleMethodCallConst.js +56 -0
- package/lib/module/constants/AdWhaleMethodCallConst.js.map +1 -0
- package/lib/module/constants/AdWhaleMethodResultConst.js +16 -0
- package/lib/module/constants/AdWhaleMethodResultConst.js.map +1 -0
- package/lib/module/index.js +10 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/AdWhaleAdView.d.ts +46 -2
- package/lib/typescript/src/AdWhaleAdView.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleAppOpenAd.d.ts +10 -0
- package/lib/typescript/src/AdWhaleAppOpenAd.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleExitPopupAd.d.ts +36 -0
- package/lib/typescript/src/AdWhaleExitPopupAd.d.ts.map +1 -0
- package/lib/typescript/src/AdWhaleInterstitialAd.d.ts +16 -0
- package/lib/typescript/src/AdWhaleInterstitialAd.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleMediationAds.d.ts +14 -5
- package/lib/typescript/src/AdWhaleMediationAds.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleNativeCustomView.d.ts +7 -4
- package/lib/typescript/src/AdWhaleNativeCustomView.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts +20 -7
- package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleRewardAd.d.ts +18 -2
- package/lib/typescript/src/AdWhaleRewardAd.d.ts.map +1 -1
- package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts +33 -0
- package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts.map +1 -0
- package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts +33 -6
- package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts.map +1 -1
- package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts +51 -0
- package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts.map +1 -0
- package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts +53 -0
- package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts.map +1 -0
- package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts +14 -0
- package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/AdWhaleAdView.tsx +126 -9
- package/src/AdWhaleAppOpenAd.ts +293 -38
- package/src/AdWhaleExitPopupAd.ts +183 -0
- package/src/AdWhaleInterstitialAd.ts +206 -36
- package/src/AdWhaleMediationAds.ts +109 -5
- package/src/AdWhaleNativeCustomView.tsx +85 -23
- package/src/AdWhaleNativeTemplateView.tsx +79 -42
- package/src/AdWhaleRewardAd.ts +245 -43
- package/src/AdWhaleTransitionPopupAd.ts +171 -0
- package/src/NativeAdwhaleSdkReactNative.ts +36 -6
- package/src/constants/AdWhaleMethodArgConst.ts +50 -0
- package/src/constants/AdWhaleMethodCallConst.ts +60 -0
- package/src/constants/AdWhaleMethodResultConst.ts +13 -0
- package/src/index.ts +34 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import AdWhaleSDK
|
|
4
|
+
|
|
5
|
+
/// AdWhale TurboModule 본문 — `AdwhaleSdkReactNative.mm` 브리지가 여기로 위임합니다.
|
|
6
|
+
@objcMembers
|
|
7
|
+
public final class AdwhaleSdkReactNativeModule: NSObject {
|
|
8
|
+
|
|
9
|
+
private static let errNoActivity = "NO_ACTIVITY"
|
|
10
|
+
|
|
11
|
+
private static func mapMaxAdContentRating(_ rating: String?) -> AdWhaleMaxAdContentRating {
|
|
12
|
+
guard let rating, !rating.isEmpty else { return .general }
|
|
13
|
+
switch rating {
|
|
14
|
+
case ".general": return .general
|
|
15
|
+
case ".parentalGuidance": return .parentalGuidance
|
|
16
|
+
case ".teen": return .teen
|
|
17
|
+
case ".matureAudience": return .matureAudience
|
|
18
|
+
default: return .general
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objc public func initializeWithResolve(
|
|
23
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
24
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
25
|
+
) {
|
|
26
|
+
AdWhaleAds.sharedInstance.initialize("0") {
|
|
27
|
+
DispatchQueue.main.async {
|
|
28
|
+
resolve([
|
|
29
|
+
"statusCode": 100,
|
|
30
|
+
"message": "AdWhale SDK initialization completed",
|
|
31
|
+
])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@objc public func setCoppa(_ isEnabled: Bool) {
|
|
37
|
+
AdWhaleAds.sharedInstance.setTagForChildDirectedTreatment(NSNumber(value: isEnabled))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@objc public func setTagForUnderAgeOfConsent(_ tag: Bool) {
|
|
41
|
+
AdWhaleAds.sharedInstance.setTagForUnderAgeOfConsent(NSNumber(value: tag))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@objc public func setMaxAdContentRating(_ rating: String?) {
|
|
45
|
+
AdWhaleAds.sharedInstance.maxAdContentRating(Self.mapMaxAdContentRating(rating))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@objc public func setTestDeviceIdentifiers(_ testDeviceIds: [Any]?) {
|
|
49
|
+
guard let raw = testDeviceIds, !raw.isEmpty else { return }
|
|
50
|
+
let strings = raw.compactMap { $0 as? String }
|
|
51
|
+
guard !strings.isEmpty else { return }
|
|
52
|
+
AdWhaleAds.sharedInstance.setTestDeviceIdentifiers(strings as NSArray)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@objc public func requestGdprConsent(
|
|
56
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
57
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
58
|
+
) {
|
|
59
|
+
DispatchQueue.main.async {
|
|
60
|
+
guard let vc = RCTPresentedViewController() else {
|
|
61
|
+
reject(
|
|
62
|
+
Self.errNoActivity,
|
|
63
|
+
"Root view controller is not available. Cannot request GDPR consent.",
|
|
64
|
+
nil
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
AdWhaleAds.sharedInstance.gdpr(vc, testDevices: nil) { success in
|
|
69
|
+
DispatchQueue.main.async {
|
|
70
|
+
resolve([
|
|
71
|
+
"success": success,
|
|
72
|
+
"message": success
|
|
73
|
+
? "Consent flow completed."
|
|
74
|
+
: "Consent not obtained or dismissed.",
|
|
75
|
+
])
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc public func resetGdprConsentStatus() {
|
|
82
|
+
DispatchQueue.main.async {
|
|
83
|
+
AdWhaleAds.sharedInstance.resetGDPR(completionHandler: {})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@objc public func setGdpr(_ consent: Bool) {
|
|
88
|
+
_ = consent
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@objc public func getConsentStatus(
|
|
92
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
93
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
94
|
+
) {
|
|
95
|
+
DispatchQueue.main.async {
|
|
96
|
+
let ads = AdWhaleAds.sharedInstance
|
|
97
|
+
let coppaNum = ads.getTagForChildDirectedTreatment()
|
|
98
|
+
let coppa = coppaNum?.boolValue ?? false
|
|
99
|
+
let gdprConsent = ads.getGdprConsentStatus()
|
|
100
|
+
let personalized = ads.isGDPR()
|
|
101
|
+
let gdprStr = gdprConsent ? "OBTAINED" : "NOT_OBTAINED"
|
|
102
|
+
resolve([
|
|
103
|
+
"coppa": coppa,
|
|
104
|
+
"gdpr": gdprStr,
|
|
105
|
+
"personalizedConsent": personalized,
|
|
106
|
+
])
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@objc public func showAdInspector(
|
|
111
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
112
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
113
|
+
) {
|
|
114
|
+
DispatchQueue.main.async {
|
|
115
|
+
guard let vc = RCTPresentedViewController() else {
|
|
116
|
+
reject(
|
|
117
|
+
Self.errNoActivity,
|
|
118
|
+
"Root view controller is not available. Cannot open Ad Inspector.",
|
|
119
|
+
nil
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
AdWhaleAds.sharedInstance.showAdInspector(viewController: vc) { error in
|
|
124
|
+
DispatchQueue.main.async {
|
|
125
|
+
if let error {
|
|
126
|
+
resolve([
|
|
127
|
+
"statusCode": -1,
|
|
128
|
+
"message": error.localizedDescription,
|
|
129
|
+
])
|
|
130
|
+
} else {
|
|
131
|
+
resolve([
|
|
132
|
+
"statusCode": 100,
|
|
133
|
+
"message": "Ad Inspector closed",
|
|
134
|
+
])
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@objc public func setAppMuted(_ isMuted: Bool) {
|
|
142
|
+
AdWhaleAds.sharedInstance.setMuted(isMuted)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@objc public func setAppVolume(_ volume: Double) {
|
|
146
|
+
AdWhaleAds.sharedInstance.setVolume(Float(volume))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
#import <React/RCTUIManager.h>
|
|
3
|
+
#import <React/RCTConvert.h>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Swift `WhaleMediationBannerContainer` + `RNAdWhaleMediationAdViewManager.swift` 와 연결.
|
|
7
|
+
* Swift 생성 헤더를 여기서 import 하지 않아 ScanDependencies 순서 문제를 피합니다 (KVC).
|
|
8
|
+
*/
|
|
9
|
+
@interface RCT_EXTERN_MODULE(RNAdWhaleMediationAdViewManager, RCTViewManager)
|
|
10
|
+
|
|
11
|
+
RCT_EXPORT_VIEW_PROPERTY(placementUid, NSString)
|
|
12
|
+
|
|
13
|
+
RCT_CUSTOM_VIEW_PROPERTY(adSize, NSString, UIView)
|
|
14
|
+
{
|
|
15
|
+
[view setValue:(json ? [RCTConvert NSString:json] : @"") forKey:@"adSizeString"];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
RCT_CUSTOM_VIEW_PROPERTY(loadAd, BOOL, UIView)
|
|
19
|
+
{
|
|
20
|
+
BOOL load = json ? [RCTConvert BOOL:json] : NO;
|
|
21
|
+
[view setValue:@(load) forKey:@"shouldLoadAd"];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
RCT_CUSTOM_VIEW_PROPERTY(adaptiveAnchorWidth, NSNumber, UIView)
|
|
25
|
+
{
|
|
26
|
+
NSInteger w = json ? [RCTConvert NSInteger:json] : 0;
|
|
27
|
+
[view setValue:@(w) forKey:@"adaptiveAnchorWidth"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
RCT_EXPORT_VIEW_PROPERTY(placementName, NSString)
|
|
31
|
+
RCT_EXPORT_VIEW_PROPERTY(region, NSString)
|
|
32
|
+
RCT_EXPORT_VIEW_PROPERTY(gcoder, NSDictionary)
|
|
33
|
+
|
|
34
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdLoaded, RCTDirectEventBlock)
|
|
35
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdLoadFailed, RCTDirectEventBlock)
|
|
36
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
|
|
37
|
+
|
|
38
|
+
@end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc(RNAdWhaleMediationAdViewManager)
|
|
5
|
+
public class RNAdWhaleMediationAdViewManager: RCTViewManager {
|
|
6
|
+
|
|
7
|
+
override public static func requiresMainQueueSetup() -> Bool {
|
|
8
|
+
true
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
override public func view() -> UIView! {
|
|
12
|
+
WhaleMediationBannerContainer()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(RNAdWhaleMediationAppOpenAd, 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(loadAppOpenAd:(NSDictionary *)args)
|
|
15
|
+
RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
|
|
16
|
+
RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
|
|
17
|
+
|
|
18
|
+
@end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import AdWhaleSDK
|
|
4
|
+
|
|
5
|
+
@objc(RNAdWhaleMediationAppOpenAd)
|
|
6
|
+
final class RNAdWhaleMediationAppOpenAd: RCTEventEmitter {
|
|
7
|
+
|
|
8
|
+
private struct Slot {
|
|
9
|
+
let adId: Int
|
|
10
|
+
let placementUid: String
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private final class DelegateBox: NSObject, AdWhaleAppOpenAdDelegate {
|
|
14
|
+
let adId: Int
|
|
15
|
+
weak var module: RNAdWhaleMediationAppOpenAd?
|
|
16
|
+
|
|
17
|
+
init(adId: Int, module: RNAdWhaleMediationAppOpenAd) {
|
|
18
|
+
self.adId = adId
|
|
19
|
+
self.module = module
|
|
20
|
+
super.init()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func adDidReceiveAppOpenAd(_ ad: AdWhaleAppOpenAd) {
|
|
24
|
+
_ = ad
|
|
25
|
+
module?.emit(adId: adId, eventName: "onAppOpenAdLoaded", payload: nil)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func adDidFailToReceiveAppOpenAd(error: any Error) {
|
|
29
|
+
module?.emitError(adId: adId, eventName: "onAppOpenAdFailedToLoad", error: error)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func adWillPresentAppOpenAd() {
|
|
33
|
+
module?.emit(adId: adId, eventName: "onAppOpenAdShowed", payload: nil)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func adDidDismissAppOpenAd() {
|
|
37
|
+
module?.emit(adId: adId, eventName: "onAppOpenAdDismissed", payload: nil)
|
|
38
|
+
module?.destroyAd(NSNumber(value: adId))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func adDidFailToPresentAppOpenAd(error: any Error) {
|
|
42
|
+
module?.emitError(adId: adId, eventName: "onAppOpenAdFailedToShow", error: error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// iOS SDK가 singleton(`AdWhaleAppOpenAd.shared`)이라 동시에 여러 adId를 완전 분리 운용할 수는 없습니다.
|
|
47
|
+
/// 그래도 Flutter/RN 인터페이스를 맞추기 위해, 마지막으로 로드된 adId를 active로 간주합니다.
|
|
48
|
+
private var slotsById: [Int: Slot] = [:]
|
|
49
|
+
private var delegateById: [Int: DelegateBox] = [:]
|
|
50
|
+
private var activeAdId: Int = 0
|
|
51
|
+
|
|
52
|
+
// 레거시 옵션(기존 JS API) — iOS SDK에 대응 API가 없어 저장만 함
|
|
53
|
+
private var legacyPlacementName: String?
|
|
54
|
+
private var legacyRegion: String?
|
|
55
|
+
private var legacyGcoder: (Double, Double)?
|
|
56
|
+
private var legacyLastAdId: Int = 0
|
|
57
|
+
|
|
58
|
+
override static func requiresMainQueueSetup() -> Bool { true }
|
|
59
|
+
|
|
60
|
+
override func supportedEvents() -> [String]! {
|
|
61
|
+
[
|
|
62
|
+
"onAppOpenAdLoaded",
|
|
63
|
+
"onAppOpenAdFailedToLoad",
|
|
64
|
+
"onAppOpenAdShowed",
|
|
65
|
+
"onAppOpenAdFailedToShow",
|
|
66
|
+
"onAppOpenAdDismissed",
|
|
67
|
+
"onAppOpenAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열 유지
|
|
68
|
+
"onAdEvent",
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// MARK: - Flutter-sync API (권장)
|
|
73
|
+
|
|
74
|
+
/// loadAppOpenAd({adId, placementUid, ...})
|
|
75
|
+
@objc func loadAppOpenAd(_ args: NSDictionary) {
|
|
76
|
+
let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
|
|
77
|
+
let placementUid = (args["placementUid"] as? String) ?? ""
|
|
78
|
+
if placementUid.isEmpty { return }
|
|
79
|
+
log("RNAdWhaleMediationAppOpenAd.loadAppOpenAd adId=\(adId) adUnitId=\(placementUid)")
|
|
80
|
+
|
|
81
|
+
slotsById[adId] = Slot(adId: adId, placementUid: placementUid)
|
|
82
|
+
activeAdId = adId
|
|
83
|
+
|
|
84
|
+
let delegate = DelegateBox(adId: adId, module: self)
|
|
85
|
+
delegateById[adId] = delegate
|
|
86
|
+
|
|
87
|
+
DispatchQueue.main.async {
|
|
88
|
+
let shared = AdWhaleAppOpenAd.shared
|
|
89
|
+
shared.appOpenAdDelegate = delegate
|
|
90
|
+
shared.adUnitId = placementUid
|
|
91
|
+
shared.loadAd()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// showAdWithoutView(adId)
|
|
96
|
+
@objc func showAdWithoutView(_ adId: NSNumber) {
|
|
97
|
+
let id = adId.intValue
|
|
98
|
+
activeAdId = id
|
|
99
|
+
DispatchQueue.main.async {
|
|
100
|
+
let shared = AdWhaleAppOpenAd.shared
|
|
101
|
+
if #available(iOS 13.0, *) {
|
|
102
|
+
shared.showAdIfAvailable(RCTPresentedViewController())
|
|
103
|
+
} else {
|
|
104
|
+
shared.showAdIfAvailable()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// destroyAd(adId)
|
|
110
|
+
@objc func destroyAd(_ adId: NSNumber) {
|
|
111
|
+
let id = adId.intValue
|
|
112
|
+
slotsById.removeValue(forKey: id)
|
|
113
|
+
delegateById.removeValue(forKey: id)
|
|
114
|
+
if activeAdId == id {
|
|
115
|
+
activeAdId = 0
|
|
116
|
+
}
|
|
117
|
+
DispatchQueue.main.async {
|
|
118
|
+
let shared = AdWhaleAppOpenAd.shared
|
|
119
|
+
shared.clearAd()
|
|
120
|
+
// delegate는 마지막 active를 기준으로만 유지
|
|
121
|
+
shared.appOpenAdDelegate = nil
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// MARK: - Legacy API (기존 JS와 호환)
|
|
126
|
+
|
|
127
|
+
@objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
|
|
128
|
+
@objc func setRegion(_ region: String) { legacyRegion = region }
|
|
129
|
+
@objc func setGcoder(_ gcoder: NSDictionary) {
|
|
130
|
+
let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
|
|
131
|
+
let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
|
|
132
|
+
legacyGcoder = (lt, lng)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@objc func loadAd(_ placementUid: String) {
|
|
136
|
+
legacyLastAdId += 1
|
|
137
|
+
_ = legacyPlacementName
|
|
138
|
+
_ = legacyRegion
|
|
139
|
+
_ = legacyGcoder
|
|
140
|
+
loadAppOpenAd([
|
|
141
|
+
"adId": legacyLastAdId,
|
|
142
|
+
"placementUid": placementUid,
|
|
143
|
+
])
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc func showAd() {
|
|
147
|
+
showAdWithoutView(NSNumber(value: legacyLastAdId))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - Event helpers
|
|
151
|
+
|
|
152
|
+
private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
|
|
153
|
+
log("RNAdWhaleMediationAppOpenAd sendEvent adId=\(adId) event=\(eventName)")
|
|
154
|
+
sendEvent(withName: eventName, body: payload)
|
|
155
|
+
|
|
156
|
+
var body: [String: Any] = ["adId": adId, "eventName": eventName]
|
|
157
|
+
if let payload {
|
|
158
|
+
for (k, v) in payload { body[k] = v }
|
|
159
|
+
}
|
|
160
|
+
sendEvent(withName: "onAdEvent", body: body)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func emitError(adId: Int, eventName: String, error: Error) {
|
|
164
|
+
let ns = error as NSError
|
|
165
|
+
emit(adId: adId, eventName: eventName, payload: [
|
|
166
|
+
"statusCode": ns.code,
|
|
167
|
+
"message": ns.localizedDescription,
|
|
168
|
+
])
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private func log(_ message: String) {
|
|
172
|
+
print("[adwhale_sdk_react_native] \(message)")
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(RNAdWhaleMediationCustomNativeAdViewManager, RCTViewManager)
|
|
4
|
+
|
|
5
|
+
RCT_EXPORT_VIEW_PROPERTY(placementUid, NSString)
|
|
6
|
+
RCT_EXPORT_VIEW_PROPERTY(factoryId, NSString)
|
|
7
|
+
RCT_EXPORT_VIEW_PROPERTY(placementName, NSString)
|
|
8
|
+
RCT_EXPORT_VIEW_PROPERTY(region, NSString)
|
|
9
|
+
RCT_EXPORT_VIEW_PROPERTY(gcoder, NSDictionary)
|
|
10
|
+
|
|
11
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativeAdLoaded, RCTDirectEventBlock)
|
|
12
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativeAdFailedToLoad, RCTDirectEventBlock)
|
|
13
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativeAdShowFailed, RCTDirectEventBlock)
|
|
14
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativeAdClicked, RCTDirectEventBlock)
|
|
15
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativeAdClosed, RCTDirectEventBlock)
|
|
16
|
+
|
|
17
|
+
// Commands: loadAd / showAd (JS UIManager.dispatchViewManagerCommand)
|
|
18
|
+
RCT_EXTERN_METHOD(loadAd:(nonnull NSNumber *)reactTag)
|
|
19
|
+
RCT_EXTERN_METHOD(showAd:(nonnull NSNumber *)reactTag)
|
|
20
|
+
|
|
21
|
+
@end
|
|
22
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
import AdWhaleSDK
|
|
5
|
+
|
|
6
|
+
@objc(RNAdWhaleMediationCustomNativeAdViewManager)
|
|
7
|
+
final class RNAdWhaleMediationCustomNativeAdViewManager: RCTViewManager {
|
|
8
|
+
override static func requiresMainQueueSetup() -> Bool { true }
|
|
9
|
+
|
|
10
|
+
override func view() -> UIView! {
|
|
11
|
+
log("view() create WhaleMediationNativeCustomContainer")
|
|
12
|
+
return WhaleMediationNativeCustomContainer()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// iOS에서 UIManager.dispatchViewManagerCommand 사용을 위해 .m extern에서 호출됨
|
|
16
|
+
@objc func loadAd(_ reactTag: NSNumber) {
|
|
17
|
+
log("loadAd command received reactTag=\(reactTag)")
|
|
18
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
19
|
+
let view = self.resolveContainerView(reactTag: reactTag, viewRegistry: viewRegistry)
|
|
20
|
+
self.log("loadAd UIBlock resolved view=\(view != nil)")
|
|
21
|
+
if let view {
|
|
22
|
+
Task { @MainActor in view.loadAd() }
|
|
23
|
+
} else {
|
|
24
|
+
self.retryCommand(reactTag: reactTag, commandName: "loadAd", attempt: 1)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@objc func showAd(_ reactTag: NSNumber) {
|
|
30
|
+
log("showAd command received reactTag=\(reactTag)")
|
|
31
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
32
|
+
let view = self.resolveContainerView(reactTag: reactTag, viewRegistry: viewRegistry)
|
|
33
|
+
self.log("showAd UIBlock resolved view=\(view != nil)")
|
|
34
|
+
if let view {
|
|
35
|
+
Task { @MainActor in view.showAd() }
|
|
36
|
+
} else {
|
|
37
|
+
self.retryCommand(reactTag: reactTag, commandName: "showAd", attempt: 1)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func log(_ message: String) {
|
|
43
|
+
print("[adwhale_sdk_react_native] RNAdWhaleMediationCustomNativeAdViewManager \(message)")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private func resolveContainerView(
|
|
47
|
+
reactTag: NSNumber,
|
|
48
|
+
viewRegistry: [NSNumber: UIView]?
|
|
49
|
+
) -> WhaleMediationNativeCustomContainer? {
|
|
50
|
+
if let direct = viewRegistry?[reactTag] as? WhaleMediationNativeCustomContainer {
|
|
51
|
+
return direct
|
|
52
|
+
}
|
|
53
|
+
let normalizedTag = NSNumber(value: reactTag.intValue)
|
|
54
|
+
if let normalized = viewRegistry?[normalizedTag] as? WhaleMediationNativeCustomContainer {
|
|
55
|
+
return normalized
|
|
56
|
+
}
|
|
57
|
+
if let uiManagerView = bridge.uiManager.view(forReactTag: reactTag) as? WhaleMediationNativeCustomContainer {
|
|
58
|
+
return uiManagerView
|
|
59
|
+
}
|
|
60
|
+
if let uiManagerView = bridge.uiManager.view(forReactTag: normalizedTag) as? WhaleMediationNativeCustomContainer {
|
|
61
|
+
return uiManagerView
|
|
62
|
+
}
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func retryCommand(reactTag: NSNumber, commandName: String, attempt: Int) {
|
|
67
|
+
guard attempt <= 5 else {
|
|
68
|
+
log("\(commandName) retry exhausted reactTag=\(reactTag)")
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
|
72
|
+
guard
|
|
73
|
+
let view = self.bridge.uiManager.view(forReactTag: reactTag) as? WhaleMediationNativeCustomContainer
|
|
74
|
+
else {
|
|
75
|
+
self.log("\(commandName) retry \(attempt) failed reactTag=\(reactTag)")
|
|
76
|
+
self.retryCommand(reactTag: reactTag, commandName: commandName, attempt: attempt + 1)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
self.log("\(commandName) retry \(attempt) resolved view=true")
|
|
80
|
+
Task { @MainActor in
|
|
81
|
+
if commandName == "loadAd" {
|
|
82
|
+
view.loadAd()
|
|
83
|
+
} else {
|
|
84
|
+
view.showAd()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@objcMembers
|
|
92
|
+
final class WhaleMediationNativeCustomContainer: UIView, AdWhaleNativeAdLoaderDelegate, AdWhaleNativeAdDelegate {
|
|
93
|
+
// Props
|
|
94
|
+
var placementUid: String = ""
|
|
95
|
+
var factoryId: String = ""
|
|
96
|
+
var placementName: String = ""
|
|
97
|
+
var region: String = ""
|
|
98
|
+
var gcoder: NSDictionary?
|
|
99
|
+
|
|
100
|
+
// Events
|
|
101
|
+
var onNativeAdLoaded: RCTDirectEventBlock?
|
|
102
|
+
var onNativeAdFailedToLoad: RCTDirectEventBlock?
|
|
103
|
+
var onNativeAdShowFailed: RCTDirectEventBlock?
|
|
104
|
+
var onNativeAdClicked: RCTDirectEventBlock?
|
|
105
|
+
var onNativeAdClosed: RCTDirectEventBlock?
|
|
106
|
+
|
|
107
|
+
private var nativeAdView: AdWhaleNativeAdView?
|
|
108
|
+
private var currentNativeAd: AdWhaleNativeAd?
|
|
109
|
+
private var loader: AdWhaleNativeAdLoader?
|
|
110
|
+
|
|
111
|
+
deinit {
|
|
112
|
+
loader?.destroy()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override func layoutSubviews() {
|
|
116
|
+
super.layoutSubviews()
|
|
117
|
+
nativeAdView?.frame = bounds
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@MainActor func loadAd() {
|
|
121
|
+
log("loadAd() called placementUid=\(placementUid) factoryId=\(factoryId) bounds=\(bounds)")
|
|
122
|
+
guard !placementUid.isEmpty else {
|
|
123
|
+
log("loadAd() failed: placementUid is empty")
|
|
124
|
+
onNativeAdFailedToLoad?([
|
|
125
|
+
"statusCode": -1,
|
|
126
|
+
"message": "Placement UID is not set.",
|
|
127
|
+
])
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
guard !factoryId.isEmpty else {
|
|
131
|
+
log("loadAd() failed: factoryId is empty")
|
|
132
|
+
onNativeAdFailedToLoad?([
|
|
133
|
+
"statusCode": RNAdWhaleNativeErrorConst.nativeBinderConfigurationErrorCode,
|
|
134
|
+
"message": "Native custom binder: factoryId is required.",
|
|
135
|
+
])
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
guard let root = RCTPresentedViewController() else {
|
|
139
|
+
log("loadAd() failed: rootViewController is nil")
|
|
140
|
+
onNativeAdFailedToLoad?([
|
|
141
|
+
"statusCode": -1,
|
|
142
|
+
"message": "No root view controller.",
|
|
143
|
+
])
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
guard let view = AdWhaleNativeCustomViewFactoryRegistry.make(factoryId: factoryId, frame: bounds) else {
|
|
147
|
+
log("loadAd() failed: factory not found for factoryId=\(factoryId)")
|
|
148
|
+
onNativeAdFailedToLoad?([
|
|
149
|
+
"statusCode": RNAdWhaleNativeErrorConst.nativeBinderConfigurationErrorCode,
|
|
150
|
+
"message": "NativeAdView factory not found for factoryId: \(factoryId)",
|
|
151
|
+
])
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Cleanup previous
|
|
156
|
+
loader?.destroy()
|
|
157
|
+
loader = nil
|
|
158
|
+
currentNativeAd = nil
|
|
159
|
+
|
|
160
|
+
nativeAdView?.removeFromSuperview()
|
|
161
|
+
nativeAdView = view
|
|
162
|
+
view.frame = bounds
|
|
163
|
+
addSubview(view)
|
|
164
|
+
|
|
165
|
+
let l = AdWhaleNativeAdLoader(adUnitId: placementUid, rootViewController: root)
|
|
166
|
+
l.delegate = self
|
|
167
|
+
loader = l
|
|
168
|
+
log("loadAd() start loader.loadAd() adUnitId=\(placementUid)")
|
|
169
|
+
l.loadAd()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// iOS는 Custom binding이므로 load 성공 후 bind(view)로 렌더링을 완료합니다.
|
|
173
|
+
@MainActor func showAd() {
|
|
174
|
+
log("showAd() called hasLoader=\(loader != nil) hasNativeAdView=\(nativeAdView != nil) hasCurrentNativeAd=\(currentNativeAd != nil)")
|
|
175
|
+
guard let loader, let nativeAdView, currentNativeAd != nil else {
|
|
176
|
+
log("showAd() failed: native ad is not loaded")
|
|
177
|
+
onNativeAdShowFailed?([
|
|
178
|
+
"statusCode": -1,
|
|
179
|
+
"message": "Native ad is not loaded.",
|
|
180
|
+
])
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
log("showAd() bind native ad view")
|
|
184
|
+
loader.bind(nativeAdView)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// MARK: - AdWhaleNativeAdLoaderDelegate
|
|
188
|
+
nonisolated func nativeAdLoaderDidReceiveAd(_ nativeAd: AdWhaleNativeAd) {
|
|
189
|
+
Task { @MainActor in
|
|
190
|
+
self.log("nativeAdLoaderDidReceiveAd")
|
|
191
|
+
self.currentNativeAd = nativeAd
|
|
192
|
+
nativeAd.delegate = self
|
|
193
|
+
self.onNativeAdLoaded?([:])
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
nonisolated func nativeAdLoaderDidFailToReceiveAd(_ nativeAd: AdWhaleNativeAdLoader, error: any Error) {
|
|
198
|
+
_ = nativeAd
|
|
199
|
+
Task { @MainActor in
|
|
200
|
+
let ns = error as NSError
|
|
201
|
+
self.log("nativeAdLoaderDidFailToReceiveAd code=\(ns.code) message=\(ns.localizedDescription)")
|
|
202
|
+
self.onNativeAdFailedToLoad?([
|
|
203
|
+
"statusCode": ns.code,
|
|
204
|
+
"message": ns.localizedDescription,
|
|
205
|
+
])
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// MARK: - AdWhaleNativeAdDelegate
|
|
210
|
+
nonisolated func nativeAdDidClickAd(_ nativeAd: AdWhaleNativeAd) {
|
|
211
|
+
_ = nativeAd
|
|
212
|
+
Task { @MainActor in
|
|
213
|
+
self.log("nativeAdDidClickAd")
|
|
214
|
+
self.onNativeAdClicked?([:])
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
nonisolated func nativeAdDidDismissScreen(_ nativeAd: AdWhaleNativeAd) {
|
|
219
|
+
_ = nativeAd
|
|
220
|
+
Task { @MainActor in
|
|
221
|
+
self.log("nativeAdDidDismissScreen")
|
|
222
|
+
self.onNativeAdClosed?([:])
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
nonisolated func nativeAdDidImpression(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
|
|
227
|
+
nonisolated func nativeAdWillPresentScreen(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
|
|
228
|
+
nonisolated func nativeAdWillDismissScreen(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
|
|
229
|
+
nonisolated func nativeAdWillLeaveApplication(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
|
|
230
|
+
|
|
231
|
+
private func log(_ message: String) {
|
|
232
|
+
print("[adwhale_sdk_react_native] WhaleMediationNativeCustomContainer \(message)")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
enum RNAdWhaleNativeErrorConst {
|
|
237
|
+
static let nativeBinderConfigurationErrorCode: Int = -2000
|
|
238
|
+
}
|
|
239
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(RNAdWhaleMediationInterstitialAd, 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(loadInterstitialAd:(NSDictionary *)args)
|
|
15
|
+
RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
|
|
16
|
+
RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
|
|
17
|
+
|
|
18
|
+
@end
|