adwhale-sdk-react-native 2.7.204 → 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 (108) hide show
  1. package/AdwhaleSdkReactNative.podspec +22 -0
  2. package/README.md +80 -37
  3. package/android/proguard-rules.pro +56 -0
  4. package/android/src/main/java/com/adwhalesdkreactnative/AdWhaleCustomNativeBinderFactory.java +18 -0
  5. package/android/src/main/java/com/adwhalesdkreactnative/AdwhaleSdkReactNativePackage.java +51 -6
  6. package/android/src/main/java/com/adwhalesdkreactnative/BinderFactory.java +9 -0
  7. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdSettingModule.java +148 -49
  8. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdView.java +46 -27
  9. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAppOpenAd.java +136 -0
  10. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationCustomNativeAdView.java +37 -46
  11. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationExitPopupAd.java +240 -0
  12. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationInterstitialAd.java +189 -0
  13. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationLoggerModule.java +2 -1
  14. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationNativeAdViewListenerBridge.java +83 -0
  15. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationRewardAd.java +255 -6
  16. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTemplateNativeAdView.java +191 -70
  17. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTransitionPopupAd.java +218 -0
  18. package/android/src/main/java/com/adwhalesdkreactnative/RNWrapperView.java +137 -0
  19. package/android/src/main/java/com/adwhalesdkreactnative/SimpleBinderFactory.java +1 -1
  20. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodArgConst.java +112 -0
  21. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodCallConst.java +113 -0
  22. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodResultConst.java +28 -0
  23. package/build/generated/ios/ReactAppDependencyProvider.podspec +34 -0
  24. package/ios/AdWhaleNativeCustomViewFactoryRegistry.swift +56 -0
  25. package/ios/AdwhaleSdkReactNative.mm +96 -5
  26. package/ios/AdwhaleSdkReactNativeModule.swift +148 -0
  27. package/ios/RNAdWhaleMediationAdViewManager.m +38 -0
  28. package/ios/RNAdWhaleMediationAdViewManager.swift +14 -0
  29. package/ios/RNAdWhaleMediationAppOpenAd.m +18 -0
  30. package/ios/RNAdWhaleMediationAppOpenAd.swift +175 -0
  31. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.m +22 -0
  32. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.swift +239 -0
  33. package/ios/RNAdWhaleMediationInterstitialAd.m +18 -0
  34. package/ios/RNAdWhaleMediationInterstitialAd.swift +161 -0
  35. package/ios/RNAdWhaleMediationRewardAd.m +18 -0
  36. package/ios/RNAdWhaleMediationRewardAd.swift +172 -0
  37. package/ios/WhaleMediationBannerContainer.swift +182 -0
  38. package/lib/module/AdWhaleAdView.js +58 -3
  39. package/lib/module/AdWhaleAdView.js.map +1 -1
  40. package/lib/module/AdWhaleAppOpenAd.js +223 -25
  41. package/lib/module/AdWhaleAppOpenAd.js.map +1 -1
  42. package/lib/module/AdWhaleExitPopupAd.js +93 -0
  43. package/lib/module/AdWhaleExitPopupAd.js.map +1 -0
  44. package/lib/module/AdWhaleInterstitialAd.js +146 -22
  45. package/lib/module/AdWhaleInterstitialAd.js.map +1 -1
  46. package/lib/module/AdWhaleMediationAds.js +75 -4
  47. package/lib/module/AdWhaleMediationAds.js.map +1 -1
  48. package/lib/module/AdWhaleNativeCustomView.js +59 -8
  49. package/lib/module/AdWhaleNativeCustomView.js.map +1 -1
  50. package/lib/module/AdWhaleNativeTemplateView.js +37 -19
  51. package/lib/module/AdWhaleNativeTemplateView.js.map +1 -1
  52. package/lib/module/AdWhaleRewardAd.js +169 -24
  53. package/lib/module/AdWhaleRewardAd.js.map +1 -1
  54. package/lib/module/AdWhaleTransitionPopupAd.js +87 -0
  55. package/lib/module/AdWhaleTransitionPopupAd.js.map +1 -0
  56. package/lib/module/NativeAdwhaleSdkReactNative.js +16 -1
  57. package/lib/module/NativeAdwhaleSdkReactNative.js.map +1 -1
  58. package/lib/module/constants/AdWhaleMethodArgConst.js +53 -0
  59. package/lib/module/constants/AdWhaleMethodArgConst.js.map +1 -0
  60. package/lib/module/constants/AdWhaleMethodCallConst.js +56 -0
  61. package/lib/module/constants/AdWhaleMethodCallConst.js.map +1 -0
  62. package/lib/module/constants/AdWhaleMethodResultConst.js +16 -0
  63. package/lib/module/constants/AdWhaleMethodResultConst.js.map +1 -0
  64. package/lib/module/index.js +10 -1
  65. package/lib/module/index.js.map +1 -1
  66. package/lib/typescript/src/AdWhaleAdView.d.ts +20 -2
  67. package/lib/typescript/src/AdWhaleAdView.d.ts.map +1 -1
  68. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts +10 -0
  69. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts.map +1 -1
  70. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts +36 -0
  71. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts.map +1 -0
  72. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts +16 -0
  73. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts.map +1 -1
  74. package/lib/typescript/src/AdWhaleMediationAds.d.ts +13 -5
  75. package/lib/typescript/src/AdWhaleMediationAds.d.ts.map +1 -1
  76. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts +7 -4
  77. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts.map +1 -1
  78. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts +20 -7
  79. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts.map +1 -1
  80. package/lib/typescript/src/AdWhaleRewardAd.d.ts +18 -2
  81. package/lib/typescript/src/AdWhaleRewardAd.d.ts.map +1 -1
  82. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts +33 -0
  83. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts.map +1 -0
  84. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts +28 -6
  85. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts.map +1 -1
  86. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts +51 -0
  87. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts.map +1 -0
  88. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts +53 -0
  89. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts.map +1 -0
  90. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts +14 -0
  91. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts.map +1 -0
  92. package/lib/typescript/src/index.d.ts +9 -1
  93. package/lib/typescript/src/index.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/AdWhaleAdView.tsx +92 -4
  96. package/src/AdWhaleAppOpenAd.ts +293 -38
  97. package/src/AdWhaleExitPopupAd.ts +183 -0
  98. package/src/AdWhaleInterstitialAd.ts +206 -36
  99. package/src/AdWhaleMediationAds.ts +108 -4
  100. package/src/AdWhaleNativeCustomView.tsx +85 -23
  101. package/src/AdWhaleNativeTemplateView.tsx +79 -42
  102. package/src/AdWhaleRewardAd.ts +245 -43
  103. package/src/AdWhaleTransitionPopupAd.ts +171 -0
  104. package/src/NativeAdwhaleSdkReactNative.ts +30 -6
  105. package/src/constants/AdWhaleMethodArgConst.ts +50 -0
  106. package/src/constants/AdWhaleMethodCallConst.ts +60 -0
  107. package/src/constants/AdWhaleMethodResultConst.ts +13 -0
  108. package/src/index.ts +33 -0
@@ -3,26 +3,40 @@ package com.adwhalesdkreactnative;
3
3
  import android.app.Activity;
4
4
  import android.util.Log;
5
5
 
6
+ import androidx.annotation.NonNull;
6
7
  import androidx.annotation.Nullable;
7
8
 
9
+ import com.adwhalesdkreactnative.common.RNMethodArgConst;
10
+ import com.adwhalesdkreactnative.common.RNMethodCallConst;
11
+ import com.adwhalesdkreactnative.common.RNMethodResultConst;
8
12
  import com.facebook.react.bridge.Arguments;
9
13
  import com.facebook.react.bridge.Promise;
10
14
  import com.facebook.react.bridge.ReactApplicationContext;
11
15
  import com.facebook.react.bridge.ReactMethod;
16
+ import com.facebook.react.bridge.ReadableArray;
17
+ import com.facebook.react.bridge.ReadableType;
12
18
  import com.facebook.react.bridge.WritableMap;
13
19
  import com.facebook.react.module.annotations.ReactModule;
14
20
 
15
21
  import net.adwhale.sdk.mediation.ads.AdWhaleMediationAds;
16
22
  import net.adwhale.sdk.mediation.ads.AdWhaleMediationOnInitCompleteListener;
17
23
  import net.adwhale.sdk.mediation.ads.GdprConsentStatus;
18
- import net.adwhale.sdk.utils.AdWhaleLog;
19
-
24
+ import java.util.ArrayList;
20
25
  import java.util.HashMap;
26
+ import java.util.List;
21
27
  import java.util.Map;
22
28
 
23
29
  /**
24
- * 초기화 모듈은 최신 TurboModule을 적용
25
- * JS <--> 네이티브 호출이 더 빠르고 타입 안정성이 제공되기 때문
30
+ * 글로벌 SDK 설정 TurboModule.
31
+ * <p>
32
+ * Flutter {@code AdWhaleSdkFlutterMethodCallHandler} 와
33
+ * 동일한 분기·응답 형태를 맞춥니다 ({@link com.adwhalesdkreactnative.common.RNMethodCallConst}).
34
+ * <p>
35
+ * 성공 시 맵은 {@link RNMethodArgConst#SEND_STATUS_CODE},
36
+ * {@link RNMethodArgConst#SEND_MESSAGE} 만 포함합니다 (Flutter {@code twoKeyMap} 와 동일).
37
+ * TurboModule 메서드명은 codegen 규약상 {@link RNMethodCallConst#INITIALIZE} 입니다
38
+ * (Flutter MethodChannel {@link RNMethodCallConst#FLUTTER_INIT} 와 문자열은 다름).
39
+ * Activity 가 없으면 Flutter 와 같이 {@link RNMethodResultConst#NO_ACTIVITY} 로 reject 합니다.
26
40
  */
27
41
  @ReactModule(name = RNAdWhaleMediationAdSettingModule.REACT_CLASS_NAME)
28
42
  public class RNAdWhaleMediationAdSettingModule extends NativeAdwhaleSdkReactNativeSpec {
@@ -33,38 +47,65 @@ public class RNAdWhaleMediationAdSettingModule extends NativeAdwhaleSdkReactNati
33
47
  }
34
48
 
35
49
  @Override
36
- public String getName() {
37
- return REACT_CLASS_NAME;
38
- }
50
+ public void initialize(final Promise promise) {
51
+ Log.i(REACT_CLASS_NAME, RNMethodCallConst.INITIALIZE + "()");
52
+ Activity currentActivity =
53
+ requireActivity(promise, "Activity is null. Cannot initialize SDK.");
54
+ if (currentActivity == null) {
55
+ return;
56
+ }
39
57
 
40
- @ReactMethod
41
- public void setCoppa(boolean isEnabled) {
42
- AdWhaleMediationAds.setCoppa(isEnabled);
43
- Log.d(REACT_CLASS_NAME, "COPPA setting applied: " + isEnabled);
58
+ // Flutter INIT 분기: disposeAllAds() — RN 은 인스턴스 매니저가 없어 생략
59
+
60
+ AdWhaleMediationAds.init(currentActivity, new AdWhaleMediationOnInitCompleteListener() {
61
+ @Override
62
+ public void onInitComplete(int statusCode, String message) {
63
+ Log.i(REACT_CLASS_NAME, RNMethodCallConst.ON_INIT_COMPLETE + "(" + statusCode + ", " + message + ")");
64
+ WritableMap result = Arguments.createMap();
65
+ result.putInt(RNMethodArgConst.SEND_STATUS_CODE, statusCode);
66
+ result.putString(RNMethodArgConst.SEND_MESSAGE, message == null ? "" : message);
67
+ promise.resolve(result);
68
+ }
69
+ });
44
70
  }
45
71
 
46
72
  @ReactMethod
47
73
  public void requestGdprConsent(final Promise promise) {
48
- Activity currentActivity = getCurrentActivity();
74
+ Activity currentActivity =
75
+ requireActivity(promise, "Activity is null. Cannot request GDPR consent.");
49
76
  if (currentActivity == null) {
50
- promise.reject("NO_ACTIVITY", "Current activity is null.");
51
77
  return;
52
78
  }
53
79
  currentActivity.runOnUiThread(() ->
54
80
  AdWhaleMediationAds.requestGdprConsent(currentActivity, (isSuccess, message) -> {
55
81
  WritableMap result = Arguments.createMap();
56
- result.putBoolean("isSuccess", isSuccess);
57
- result.putString("message", message);
82
+ result.putBoolean(RNMethodArgConst.SEND_SUCCESS, isSuccess);
83
+ result.putString(RNMethodArgConst.SEND_MESSAGE, message == null ? "" : message);
58
84
  promise.resolve(result);
59
85
  })
60
86
  );
61
87
  }
62
88
 
63
89
  @ReactMethod
64
- public void getConsentStatus(Promise promise) {
90
+ public void resetGdprConsentStatus() {
65
91
  Activity currentActivity = getCurrentActivity();
92
+ if (currentActivity != null) {
93
+ currentActivity.runOnUiThread(() -> AdWhaleMediationAds.resetGdprConsentStatus(currentActivity));
94
+ Log.d(REACT_CLASS_NAME, "GDPR consent status has been reset.");
95
+ }
96
+ }
97
+
98
+ @ReactMethod
99
+ public void setGdpr(boolean consent) {
100
+ AdWhaleMediationAds.setGdpr(consent);
101
+ Log.d(REACT_CLASS_NAME, "Personalized Consent set to: " + consent);
102
+ }
103
+
104
+ @ReactMethod
105
+ public void getConsentStatus(Promise promise) {
106
+ Activity currentActivity =
107
+ requireActivity(promise, "Activity is null. Cannot get consent status.");
66
108
  if (currentActivity == null) {
67
- promise.reject("NO_ACTIVITY", "Current activity is null.");
68
109
  return;
69
110
  }
70
111
 
@@ -73,54 +114,112 @@ public class RNAdWhaleMediationAdSettingModule extends NativeAdwhaleSdkReactNati
73
114
  boolean personalizedConsent = AdWhaleMediationAds.getAdwhaleGDPR(currentActivity);
74
115
 
75
116
  WritableMap statusMap = Arguments.createMap();
76
- statusMap.putBoolean("coppa", coppa);
77
- statusMap.putString("gdpr", gdpr.name());
78
- statusMap.putBoolean("personalizedConsent", personalizedConsent);
117
+ statusMap.putBoolean(RNMethodArgConst.SEND_COPPA, coppa);
118
+ statusMap.putString(RNMethodArgConst.SEND_GDPR, gdpr.name());
119
+ statusMap.putBoolean(RNMethodArgConst.SEND_PERSONALIZED_CONSENT, personalizedConsent);
79
120
 
80
121
  promise.resolve(statusMap);
81
122
  }
82
123
 
83
124
  @ReactMethod
84
- public void resetGdprConsentStatus() {
85
- Activity currentActivity = getCurrentActivity();
86
- if (currentActivity != null) {
87
- currentActivity.runOnUiThread(() -> AdWhaleMediationAds.resetGdprConsentStatus(currentActivity));
88
- Log.d(REACT_CLASS_NAME, "GDPR consent status has been reset.");
89
- }
125
+ public void setTagForUnderAgeOfConsent(boolean tag) {
126
+ int tagIdx = tag ? AdWhaleMediationAds.TAG_FOR_UNDER_AGE_OF_CONSENT_TRUE : AdWhaleMediationAds.TAG_FOR_UNDER_AGE_OF_CONSENT_FALSE;
127
+ AdWhaleMediationAds.setTagForUnderAgeOfConsent(tagIdx);
128
+ Log.d(REACT_CLASS_NAME, RNMethodCallConst.SET_TAG_FOR_UNDER_AGE_OF_CONSENT + ": " + tagIdx);
90
129
  }
91
130
 
92
131
  @ReactMethod
93
- public void setGdpr(boolean consent) {
94
- AdWhaleMediationAds.setGdpr(consent);
95
- Log.d(REACT_CLASS_NAME, "Personalized Consent set to: " + consent);
132
+ public void setMaxAdContentRating(String rating) {
133
+ if (rating == null || rating.isEmpty()) {
134
+ AdWhaleMediationAds.setMaxAdContentRating(null);
135
+ } else {
136
+ AdWhaleMediationAds.setMaxAdContentRating(mapMaxAdContentRating(rating));
137
+ }
138
+ Log.d(REACT_CLASS_NAME, RNMethodCallConst.SET_MAX_AD_CONTENT_RATING + ": " + rating);
96
139
  }
97
140
 
98
- @ReactMethod(isBlockingSynchronousMethod = true)
99
- public void initialize(final Promise promise) {
100
- Log.i(REACT_CLASS_NAME, ".initialize()");
101
- Activity currentActivity = getCurrentActivity();
102
- if (currentActivity == null) {
103
- WritableMap errorResult = Arguments.createMap();
104
- errorResult.putInt("statusCode", -1);
105
- errorResult.putString("message", "Unable to initialize AdWhale SDK: No activity available");
106
- errorResult.putBoolean("isSuccess", false);
107
- promise.resolve(errorResult);
141
+ @Override
142
+ public void setTestDeviceIdentifiers(ReadableArray testDeviceIds) {
143
+ if (testDeviceIds == null || testDeviceIds.size() == 0) {
144
+ return;
145
+ }
146
+ List<String> ids = new ArrayList<>(testDeviceIds.size());
147
+ for (int i = 0; i < testDeviceIds.size(); i++) {
148
+ if (testDeviceIds.getType(i) != ReadableType.String) {
149
+ continue;
150
+ }
151
+ String id = testDeviceIds.getString(i);
152
+ if (id != null) {
153
+ ids.add(id);
154
+ }
155
+ }
156
+ if (ids.isEmpty()) {
108
157
  return;
109
158
  }
159
+ AdWhaleMediationAds.setTestDeviceIdentifiers(ids);
160
+ }
110
161
 
111
- AdWhaleLog.setLogLevel(AdWhaleLog.LogLevel.Verbose);
162
+ @ReactMethod
163
+ public void showAdInspector(final Promise promise) {
164
+ Activity currentActivity =
165
+ requireActivity(promise, "Activity is null. Cannot open Ad Inspector.");
166
+ if (currentActivity == null) {
167
+ return;
168
+ }
112
169
 
113
- AdWhaleMediationAds.init(currentActivity, new AdWhaleMediationOnInitCompleteListener() {
114
- @Override
115
- public void onInitComplete(int statusCode, String message) {
116
- Log.i(REACT_CLASS_NAME, ".onInitComplete(" + statusCode + ", " + message + ")");
170
+ // MobileAds.openAdInspector WebView 초기화는 메인(UI) 스레드에서만 가능
171
+ currentActivity.runOnUiThread(() ->
172
+ AdWhaleMediationAds.openAdInspector(currentActivity, (statusCode, message) -> {
173
+ Log.i(REACT_CLASS_NAME, RNMethodCallConst.ON_AD_INSPECTOR_CLOSED + "(" + statusCode + ", " + message + ")");
117
174
  WritableMap result = Arguments.createMap();
118
- result.putInt("statusCode", statusCode);
119
- result.putString("message", message);
120
- result.putBoolean("isSuccess", statusCode == 100);
175
+ result.putInt(RNMethodArgConst.SEND_STATUS_CODE, statusCode);
176
+ result.putString(RNMethodArgConst.SEND_MESSAGE, message == null ? "" : message);
121
177
  promise.resolve(result);
122
- }
123
- });
178
+ })
179
+ );
180
+ }
181
+
182
+ @ReactMethod
183
+ public void setCoppa(boolean isEnabled) {
184
+ AdWhaleMediationAds.setCoppa(isEnabled);
185
+ Log.d(REACT_CLASS_NAME, RNMethodCallConst.SET_COPPA + ": " + isEnabled);
186
+ }
187
+
188
+ @ReactMethod
189
+ public void setAppMuted(boolean isMuted) {
190
+ AdWhaleMediationAds.setAppMuted(isMuted);
191
+ Log.d(REACT_CLASS_NAME, RNMethodCallConst.SET_APP_MUTED + ": " + isMuted);
192
+ }
193
+
194
+ @Override
195
+ public void setAppVolume(double volume) {
196
+ AdWhaleMediationAds.setAppVolume((float) volume);
197
+ Log.d(REACT_CLASS_NAME, RNMethodCallConst.SET_APP_VOLUME + ": " + volume);
198
+ }
199
+
200
+ /**
201
+ * Flutter {@code AdWhaleSdkFlutterMethodCallHandler.requireActivity} 와 동일 —
202
+ * Activity 가 없으면 {@link RNMethodResultConst#NO_ACTIVITY} 로 reject 하고 null 을 반환합니다.
203
+ */
204
+ @Nullable
205
+ private Activity requireActivity(@NonNull Promise promise, @NonNull String message) {
206
+ Activity activity = getCurrentActivity();
207
+ if (activity == null) {
208
+ promise.reject(RNMethodResultConst.NO_ACTIVITY, message);
209
+ return null;
210
+ }
211
+ return activity;
212
+ }
213
+
214
+ @Nullable
215
+ private static String mapMaxAdContentRating(@NonNull String rating) {
216
+ return switch (rating) {
217
+ case ".general" -> AdWhaleMediationAds.MAX_AD_CONTENT_RATING_G;
218
+ case ".parentalGuidance" -> AdWhaleMediationAds.MAX_AD_CONTENT_RATING_PG;
219
+ case ".teen" -> AdWhaleMediationAds.MAX_AD_CONTENT_RATING_T;
220
+ case ".matureAudience" -> AdWhaleMediationAds.MAX_AD_CONTENT_RATING_MA;
221
+ default -> null;
222
+ };
124
223
  }
125
224
 
126
225
  @Nullable
@@ -9,6 +9,8 @@ import android.widget.RelativeLayout;
9
9
  import androidx.annotation.NonNull;
10
10
  import androidx.annotation.Nullable;
11
11
 
12
+ import com.adwhalesdkreactnative.common.RNMethodArgConst;
13
+ import com.adwhalesdkreactnative.common.RNMethodCallConst;
12
14
  import com.facebook.react.bridge.Arguments;
13
15
  import com.facebook.react.bridge.ReactContext;
14
16
  import com.facebook.react.bridge.ReadableMap;
@@ -25,6 +27,13 @@ import net.adwhale.sdk.mediation.ads.AdWhaleMediationAdViewListener;
25
27
 
26
28
  import java.util.Map;
27
29
 
30
+ /**
31
+ * 인라인 배너 {@link AdWhaleMediationAdView}.
32
+ * <p>
33
+ * Flutter 의 {@code FlutterBannerAd}(일반 배너)와 동일 SDK 뷰를 쓰지만, 연결 방식은
34
+ * React Native ViewManager 직접 이벤트입니다. 이벤트 이름은 {@link RNMethodCallConst#ON_AD_LOADED} 등
35
+ * Flutter {@code FlutterMethodCallConst} 와 동일 문자열입니다.
36
+ */
28
37
  public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
29
38
 
30
39
  public static final String REACT_CLASS_NAME = RNAdWhaleMediationAdView.class.getSimpleName();
@@ -50,37 +59,42 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
50
59
  adWhaleMediationAdView.setAdWhaleMediationAdViewListener(new AdWhaleMediationAdViewListener() {
51
60
  @Override
52
61
  public void onAdLoaded() {
53
- Log.e(REACT_CLASS_NAME, "onAdLoaded()");
62
+ Log.e(REACT_CLASS_NAME, RNMethodCallConst.ON_AD_LOADED + "()");
54
63
  ReactContext context = (ReactContext) wrapperView.getContext();
55
64
  if (context != null) {
65
+ WritableMap params = Arguments.createMap();
66
+ params.putInt(RNMethodArgConst.RECEIVE_AD_ID, wrapperView.getAdId());
56
67
  context.getJSModule(RCTEventEmitter.class)
57
68
  .receiveEvent(wrapperView.getId(),
58
- "onAdLoaded", null);
69
+ RNMethodCallConst.ON_AD_LOADED, params);
59
70
  }
60
71
  }
61
72
 
62
73
  @Override
63
74
  public void onAdLoadFailed(int statusCode, String message) {
64
- Log.e(REACT_CLASS_NAME, "onAdLoadFailed(" + statusCode + "," + message + ")");
75
+ Log.e(REACT_CLASS_NAME, RNMethodCallConst.ON_AD_LOAD_FAILED + "(" + statusCode + "," + message + ")");
65
76
  ReactContext context = (ReactContext) wrapperView.getContext();
66
77
  if (context != null) {
67
78
  WritableMap params = Arguments.createMap();
68
- params.putInt("statusCode", statusCode);
69
- params.putString("message", message);
79
+ params.putInt(RNMethodArgConst.RECEIVE_AD_ID, wrapperView.getAdId());
80
+ params.putInt(RNMethodArgConst.SEND_STATUS_CODE, statusCode);
81
+ params.putString(RNMethodArgConst.SEND_MESSAGE, message);
70
82
  context.getJSModule(RCTEventEmitter.class)
71
83
  .receiveEvent(wrapperView.getId(),
72
- "onAdLoadFailed", params);
84
+ RNMethodCallConst.ON_AD_LOAD_FAILED, params);
73
85
  }
74
86
  }
75
87
 
76
88
  @Override
77
89
  public void onAdClicked() {
78
- Log.e(REACT_CLASS_NAME, "onAdClicked()");
90
+ Log.e(REACT_CLASS_NAME, RNMethodCallConst.ON_AD_CLICKED + "()");
79
91
  ReactContext context = (ReactContext) wrapperView.getContext();
80
92
  if (context != null) {
93
+ WritableMap params = Arguments.createMap();
94
+ params.putInt(RNMethodArgConst.RECEIVE_AD_ID, wrapperView.getAdId());
81
95
  context.getJSModule(RCTEventEmitter.class)
82
96
  .receiveEvent(wrapperView.getId(),
83
- "onAdClicked", null);
97
+ RNMethodCallConst.ON_AD_CLICKED, params);
84
98
  }
85
99
  }
86
100
  });
@@ -94,7 +108,12 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
94
108
  return wrapperView;
95
109
  }
96
110
 
97
- @ReactProp(name = "placementUid")
111
+ @ReactProp(name = RNMethodArgConst.RECEIVE_AD_ID, defaultInt = 0)
112
+ public void setAdId(RNWrapperView view, int adId) {
113
+ view.setAdId(adId);
114
+ }
115
+
116
+ @ReactProp(name = RNMethodCallConst.PLACEMENT_UID)
98
117
  public void setPlacementUid(RNWrapperView view, String placementUid) {
99
118
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
100
119
  if (adView != null) {
@@ -102,7 +121,7 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
102
121
  }
103
122
  }
104
123
 
105
- @ReactProp(name = "adSize")
124
+ @ReactProp(name = RNMethodCallConst.AD_SIZE)
106
125
  public void setAdSize(RNWrapperView view, String adSizeName) {
107
126
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
108
127
  if (adView == null) return;
@@ -125,7 +144,7 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
125
144
  Log.d(REACT_CLASS_NAME, "Ad size set to: " + adSize.name());
126
145
  }
127
146
 
128
- @ReactProp(name = "adaptiveAnchorWidth", defaultInt = 0)
147
+ @ReactProp(name = RNMethodArgConst.RECEIVE_ADAPTIVE_ANCHOR_WIDTH, defaultInt = 0)
129
148
  public void setAdaptiveAnchorWidth(RNWrapperView view, int width) {
130
149
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
131
150
  if (adView != null) {
@@ -134,8 +153,8 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
134
153
  }
135
154
  }
136
155
 
137
- @ReactProp(name = "loadAd")
138
- public void setLoadAd(RNWrapperView view, boolean loadAd) {
156
+ @ReactProp(name = RNMethodCallConst.LOAD_AD)
157
+ public void loadAd(RNWrapperView view, boolean loadAd) {
139
158
  if (loadAd) {
140
159
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
141
160
  if (adView != null) {
@@ -145,32 +164,32 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
145
164
  }
146
165
  }
147
166
 
148
- @ReactProp(name = "placementName")
167
+ @ReactProp(name = RNMethodArgConst.RECEIVE_PLACEMENT_NAME)
149
168
  public void setPlacementName(RNWrapperView view, String placementName) {
150
- Log.e(REACT_CLASS_NAME, "placementName: " + placementName);
169
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_PLACEMENT_NAME + ": " + placementName);
151
170
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
152
171
  if (adView != null) {
153
172
  adView.setPlacementName(placementName);
154
173
  }
155
174
  }
156
175
 
157
- @ReactProp(name = "region")
176
+ @ReactProp(name = RNMethodArgConst.RECEIVE_REGION)
158
177
  public void setRegion(RNWrapperView view, String region) {
159
- Log.e(REACT_CLASS_NAME, "region: " + region);
178
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_REGION + ": " + region);
160
179
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
161
180
  if (adView != null) {
162
181
  adView.setRegion(region);
163
182
  }
164
183
  }
165
184
 
166
- @ReactProp(name = "gcoder")
185
+ @ReactProp(name = RNMethodArgConst.RECEIVE_GCODER)
167
186
  public void setGcoder(RNWrapperView view, ReadableMap gcoderMap) {
168
- Log.e(REACT_CLASS_NAME, "gcoderMap: " + gcoderMap);
187
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_GCODER + ": " + gcoderMap);
169
188
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
170
189
 
171
190
  if (adView != null && gcoderMap != null) {
172
- double lt = gcoderMap.hasKey("lt") ? gcoderMap.getDouble("lt") : 0.0;
173
- double lng = gcoderMap.hasKey("lng") ? gcoderMap.getDouble("lng") : 0.0;
191
+ double lt = gcoderMap.hasKey(RNMethodArgConst.RECEIVE_LT) ? gcoderMap.getDouble(RNMethodArgConst.RECEIVE_LT) : 0.0;
192
+ double lng = gcoderMap.hasKey(RNMethodArgConst.RECEIVE_LNG) ? gcoderMap.getDouble(RNMethodArgConst.RECEIVE_LNG) : 0.0;
174
193
 
175
194
  adView.setGcoder(lt, lng);
176
195
  }
@@ -202,12 +221,12 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
202
221
  public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
203
222
  Log.e(REACT_CLASS_NAME, "getExportedCustomDirectEventTypeConstants()");
204
223
  return MapBuilder.<String, Object>builder()
205
- .put("onAdLoaded",
206
- MapBuilder.of("registrationName", "onAdLoaded"))
207
- .put("onAdLoadFailed",
208
- MapBuilder.of("registrationName", "onAdLoadFailed"))
209
- .put("onAdClicked",
210
- MapBuilder.of("registrationName", "onAdClicked"))
224
+ .put(RNMethodCallConst.ON_AD_LOADED,
225
+ MapBuilder.of(RNMethodArgConst.SEND_REGISTRATION_NAME, RNMethodCallConst.ON_AD_LOADED))
226
+ .put(RNMethodCallConst.ON_AD_LOAD_FAILED,
227
+ MapBuilder.of(RNMethodArgConst.SEND_REGISTRATION_NAME, RNMethodCallConst.ON_AD_LOAD_FAILED))
228
+ .put(RNMethodCallConst.ON_AD_CLICKED,
229
+ MapBuilder.of(RNMethodArgConst.SEND_REGISTRATION_NAME, RNMethodCallConst.ON_AD_CLICKED))
211
230
  .build();
212
231
 
213
232
  }
@@ -14,6 +14,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
14
14
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
15
15
  import com.facebook.react.bridge.ReactMethod;
16
16
  import com.facebook.react.bridge.ReadableMap;
17
+ import com.facebook.react.bridge.ReadableArray;
18
+ import com.facebook.react.bridge.ReadableType;
17
19
  import com.facebook.react.bridge.UiThreadUtil;
18
20
  import com.facebook.react.bridge.WritableMap;
19
21
  import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -24,6 +26,11 @@ import net.adwhale.sdk.mediation.ads.AdWhaleMediationAppOpenAdListener;
24
26
  public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule implements AdWhaleMediationAppOpenAdListener, LifecycleEventListener {
25
27
 
26
28
  public static final String REACT_CLASS_NAME = RNAdWhaleMediationAppOpenAd.class.getSimpleName();
29
+
30
+ // Flutter와 싱크: adId 기반 멀티 인스턴스 지원 (AppOpen은 실사용에선 보통 1개지만 테스트/멀티 지원)
31
+ private final java.util.Map<Integer, AdWhaleMediationAppOpenAd> adsById = new java.util.HashMap<>();
32
+ private int lastActiveAdId = 0;
33
+
27
34
  private AdWhaleMediationAppOpenAd adWhaleMediationAppOpenAd;
28
35
  private final ReactApplicationContext reactContext;
29
36
 
@@ -142,6 +149,86 @@ public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule impl
142
149
  });
143
150
  }
144
151
 
152
+ /**
153
+ * Flutter MethodChannel과 동일 네이밍:
154
+ * loadAppOpenAd({adId, placementUid, region, gcoder:[lt,lng], placementName})
155
+ */
156
+ @ReactMethod
157
+ public void loadAppOpenAd(ReadableMap args) {
158
+ if (args == null) {
159
+ Log.e(REACT_CLASS_NAME, "loadAppOpenAd args is null");
160
+ return;
161
+ }
162
+ if (!args.hasKey("adId") || args.getType("adId") != ReadableType.Number) {
163
+ Log.e(REACT_CLASS_NAME, "loadAppOpenAd missing adId (number)");
164
+ return;
165
+ }
166
+ if (!args.hasKey("placementUid") || args.getType("placementUid") != ReadableType.String) {
167
+ Log.e(REACT_CLASS_NAME, "loadAppOpenAd missing placementUid (string)");
168
+ return;
169
+ }
170
+ final int adId = args.getInt("adId");
171
+ final String placementUid = args.getString("placementUid");
172
+
173
+ UiThreadUtil.runOnUiThread(() -> {
174
+ if (getCurrentActivity() == null) {
175
+ WritableMap params = Arguments.createMap();
176
+ params.putInt("statusCode", -1);
177
+ params.putString("message", "Activity not available to load ad.");
178
+ sendEvent("onAppOpenAdFailedToLoad", params);
179
+ sendAdEvent(adId, "onAppOpenAdLoadFailed", params);
180
+ return;
181
+ }
182
+
183
+ AdWhaleMediationAppOpenAd old = adsById.remove(adId);
184
+ if (old != null) {
185
+ try {
186
+ old.destroy();
187
+ } catch (Throwable ignore) {}
188
+ }
189
+
190
+ AdWhaleMediationAppOpenAd ad = new AdWhaleMediationAppOpenAd(getCurrentActivity(), placementUid);
191
+ ad.setAdWhaleMediationAppOpenAdListener(this);
192
+
193
+ try {
194
+ if (args.hasKey("placementName") && args.getType("placementName") == ReadableType.String) {
195
+ String pn = args.getString("placementName");
196
+ if (pn != null && !pn.isEmpty()) ad.setPlacementName(pn);
197
+ }
198
+ } catch (Throwable t) {
199
+ Log.w(REACT_CLASS_NAME, "setPlacementName failed: " + t.getMessage());
200
+ }
201
+
202
+ try {
203
+ if (args.hasKey("region") && args.getType("region") == ReadableType.String) {
204
+ String r = args.getString("region");
205
+ if (r != null && !r.isEmpty()) ad.setRegion(r);
206
+ }
207
+ } catch (Throwable t) {
208
+ Log.w(REACT_CLASS_NAME, "setRegion failed: " + t.getMessage());
209
+ }
210
+
211
+ try {
212
+ if (args.hasKey("gcoder") && args.getType("gcoder") == ReadableType.Array) {
213
+ ReadableArray arr = args.getArray("gcoder");
214
+ if (arr != null && arr.size() >= 2
215
+ && arr.getType(0) == ReadableType.Number
216
+ && arr.getType(1) == ReadableType.Number) {
217
+ double lat = arr.getDouble(0);
218
+ double lng = arr.getDouble(1);
219
+ ad.setGcoder(lat, lng);
220
+ }
221
+ }
222
+ } catch (Throwable t) {
223
+ Log.w(REACT_CLASS_NAME, "setGcoder failed: " + t.getMessage());
224
+ }
225
+
226
+ lastActiveAdId = adId;
227
+ adsById.put(adId, ad);
228
+ ad.loadAd();
229
+ });
230
+ }
231
+
145
232
  @ReactMethod
146
233
  public void showAd() {
147
234
  Log.d(REACT_CLASS_NAME, "showAd()");
@@ -158,6 +245,35 @@ public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule impl
158
245
  });
159
246
  }
160
247
 
248
+ /** Flutter 네이밍: showAdWithoutView(adId) */
249
+ @ReactMethod
250
+ public void showAdWithoutView(int adId) {
251
+ AdWhaleMediationAppOpenAd ad = adsById.get(adId);
252
+ if (ad == null) {
253
+ Log.e(REACT_CLASS_NAME, "showAdWithoutView: ad not found. adId=" + adId);
254
+ return;
255
+ }
256
+ lastActiveAdId = adId;
257
+ UiThreadUtil.runOnUiThread(() -> {
258
+ if (getCurrentActivity() != null) {
259
+ ad.showAd(getCurrentActivity());
260
+ }
261
+ });
262
+ }
263
+
264
+ /** Flutter 네이밍: destroyAd(adId) */
265
+ @ReactMethod
266
+ public void destroyAd(int adId) {
267
+ AdWhaleMediationAppOpenAd ad = adsById.remove(adId);
268
+ if (ad != null) {
269
+ try {
270
+ ad.destroy();
271
+ } catch (Throwable t) {
272
+ Log.w(REACT_CLASS_NAME, "destroyAd failed: " + t.getMessage());
273
+ }
274
+ }
275
+ }
276
+
161
277
  private void sendEvent(String eventName, @Nullable WritableMap params) {
162
278
  if (reactContext.hasActiveCatalystInstance()) {
163
279
  reactContext
@@ -166,10 +282,22 @@ public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule impl
166
282
  }
167
283
  }
168
284
 
285
+ private void sendAdEvent(int adId, @NonNull String eventName, @Nullable WritableMap original) {
286
+ WritableMap payload = Arguments.createMap();
287
+ payload.putInt("adId", adId);
288
+ payload.putString("eventName", eventName);
289
+ if (original != null) {
290
+ if (original.hasKey("statusCode")) payload.putInt("statusCode", original.getInt("statusCode"));
291
+ if (original.hasKey("message")) payload.putString("message", original.getString("message"));
292
+ }
293
+ sendEvent("onAdEvent", payload);
294
+ }
295
+
169
296
  @Override
170
297
  public void onAdLoaded() {
171
298
  Log.i(REACT_CLASS_NAME, ".onAdLoaded()");
172
299
  sendEvent("onAppOpenAdLoaded", null);
300
+ sendAdEvent(lastActiveAdId, "onAppOpenAdLoaded", null);
173
301
  }
174
302
 
175
303
  @Override
@@ -179,12 +307,14 @@ public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule impl
179
307
  params.putInt("statusCode", statusCode);
180
308
  params.putString("message", message);
181
309
  sendEvent("onAppOpenAdFailedToLoad", params);
310
+ sendAdEvent(lastActiveAdId, "onAppOpenAdLoadFailed", params);
182
311
  }
183
312
 
184
313
  @Override
185
314
  public void onAdShowed() {
186
315
  Log.i(REACT_CLASS_NAME, ".onAdShowed()");
187
316
  sendEvent("onAppOpenAdShowed", null);
317
+ sendAdEvent(lastActiveAdId, "onAppOpenAdShowed", null);
188
318
  }
189
319
 
190
320
  @Override
@@ -194,22 +324,28 @@ public class RNAdWhaleMediationAppOpenAd extends ReactContextBaseJavaModule impl
194
324
  params.putInt("statusCode", statusCode);
195
325
  params.putString("message", message);
196
326
  sendEvent("onAppOpenAdFailedToShow", params);
327
+ sendAdEvent(lastActiveAdId, "onAppOpenAdShowFailed", params);
197
328
  }
198
329
 
199
330
  @Override
200
331
  public void onAdDismissed() {
201
332
  Log.i(REACT_CLASS_NAME, ".onAdDismissed()");
202
333
  sendEvent("onAppOpenAdDismissed", null);
334
+ sendAdEvent(lastActiveAdId, "onAppOpenAdDismissed", null);
203
335
  if (adWhaleMediationAppOpenAd != null) {
204
336
  adWhaleMediationAppOpenAd.destroy();
205
337
  adWhaleMediationAppOpenAd = null;
206
338
  }
339
+ if (adsById.containsKey(lastActiveAdId)) {
340
+ destroyAd(lastActiveAdId);
341
+ }
207
342
  }
208
343
 
209
344
  @Override
210
345
  public void onAdClicked() {
211
346
  Log.i(REACT_CLASS_NAME, ".onAdClicked()");
212
347
  sendEvent("onAppOpenAdClicked", null);
348
+ sendAdEvent(lastActiveAdId, "onAppOpenAdClicked", null);
213
349
  }
214
350
 
215
351
  @ReactMethod