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
@@ -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,46 +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();
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()) {
157
+ return;
158
+ }
159
+ AdWhaleMediationAds.setTestDeviceIdentifiers(ids);
160
+ }
161
+
162
+ @ReactMethod
163
+ public void showAdInspector(final Promise promise) {
164
+ Activity currentActivity =
165
+ requireActivity(promise, "Activity is null. Cannot open Ad Inspector.");
102
166
  if (currentActivity == null) {
103
- promise.reject("NO_ACTIVITY", "Unable to initialize AdWhale SDK: No activity available");
104
167
  return;
105
168
  }
106
169
 
107
- AdWhaleLog.setLogLevel(AdWhaleLog.LogLevel.Verbose);
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 + ")");
174
+ WritableMap result = Arguments.createMap();
175
+ result.putInt(RNMethodArgConst.SEND_STATUS_CODE, statusCode);
176
+ result.putString(RNMethodArgConst.SEND_MESSAGE, message == null ? "" : message);
177
+ promise.resolve(result);
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
+ }
108
187
 
109
- AdWhaleMediationAds.init(currentActivity, new AdWhaleMediationOnInitCompleteListener() {
110
- @Override
111
- public void onInitComplete(int statusCode, String message) {
112
- Log.i(REACT_CLASS_NAME, ".onInitComplete(" + statusCode + ", " + message + ")");
113
- promise.resolve(statusCode);
114
- }
115
- });
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
+ };
116
223
  }
117
224
 
118
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,29 +121,40 @@ 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;
109
128
 
110
129
  ADWHALE_AD_SIZE adSize = ADWHALE_AD_SIZE.DEFAULT;
111
- if ("320x50".equals(adSizeName)) {
130
+
131
+ if ("BANNER320x50".equals(adSizeName)) {
112
132
  adSize = ADWHALE_AD_SIZE.BANNER320x50;
113
- } else if ("320x100".equals(adSizeName)) {
133
+ } else if ("BANNER320x100".equals(adSizeName)) {
114
134
  adSize = ADWHALE_AD_SIZE.BANNER320x100;
115
- } else if ("300x250".equals(adSizeName)) {
135
+ } else if ("BANNER300x250".equals(adSizeName)) {
116
136
  adSize = ADWHALE_AD_SIZE.BANNER300x250;
117
- } else if ("250x250".equals(adSizeName)) {
137
+ } else if ("BANNER250x250".equals(adSizeName)) {
118
138
  adSize = ADWHALE_AD_SIZE.BANNER250x250;
119
139
  } else if ("ADAPTIVE_ANCHOR".equals(adSizeName)) {
120
140
  adSize = ADWHALE_AD_SIZE.ADAPTIVE_ANCHOR;
121
141
  }
142
+
122
143
  adView.setAdwhaleAdSize(adSize);
123
144
  Log.d(REACT_CLASS_NAME, "Ad size set to: " + adSize.name());
124
145
  }
125
146
 
126
- @ReactProp(name = "loadAd")
127
- public void setLoadAd(RNWrapperView view, boolean loadAd) {
147
+ @ReactProp(name = RNMethodArgConst.RECEIVE_ADAPTIVE_ANCHOR_WIDTH, defaultInt = 0)
148
+ public void setAdaptiveAnchorWidth(RNWrapperView view, int width) {
149
+ AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
150
+ if (adView != null) {
151
+ adView.setAdaptiveAnchorWidth(width);
152
+ Log.d(REACT_CLASS_NAME, "Adaptive anchor width set to: " + width);
153
+ }
154
+ }
155
+
156
+ @ReactProp(name = RNMethodCallConst.LOAD_AD)
157
+ public void loadAd(RNWrapperView view, boolean loadAd) {
128
158
  if (loadAd) {
129
159
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
130
160
  if (adView != null) {
@@ -134,32 +164,32 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
134
164
  }
135
165
  }
136
166
 
137
- @ReactProp(name = "placementName")
167
+ @ReactProp(name = RNMethodArgConst.RECEIVE_PLACEMENT_NAME)
138
168
  public void setPlacementName(RNWrapperView view, String placementName) {
139
- Log.e(REACT_CLASS_NAME, "placementName: " + placementName);
169
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_PLACEMENT_NAME + ": " + placementName);
140
170
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
141
171
  if (adView != null) {
142
172
  adView.setPlacementName(placementName);
143
173
  }
144
174
  }
145
175
 
146
- @ReactProp(name = "region")
176
+ @ReactProp(name = RNMethodArgConst.RECEIVE_REGION)
147
177
  public void setRegion(RNWrapperView view, String region) {
148
- Log.e(REACT_CLASS_NAME, "region: " + region);
178
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_REGION + ": " + region);
149
179
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
150
180
  if (adView != null) {
151
181
  adView.setRegion(region);
152
182
  }
153
183
  }
154
184
 
155
- @ReactProp(name = "gcoder")
185
+ @ReactProp(name = RNMethodArgConst.RECEIVE_GCODER)
156
186
  public void setGcoder(RNWrapperView view, ReadableMap gcoderMap) {
157
- Log.e(REACT_CLASS_NAME, "gcoderMap: " + gcoderMap);
187
+ Log.e(REACT_CLASS_NAME, RNMethodArgConst.RECEIVE_GCODER + ": " + gcoderMap);
158
188
  AdWhaleMediationAdView adView = getAdWhaleMediationAdView(view);
159
189
 
160
190
  if (adView != null && gcoderMap != null) {
161
- double lt = gcoderMap.hasKey("lt") ? gcoderMap.getDouble("lt") : 0.0;
162
- 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;
163
193
 
164
194
  adView.setGcoder(lt, lng);
165
195
  }
@@ -191,12 +221,12 @@ public class RNAdWhaleMediationAdView extends ViewGroupManager<RNWrapperView> {
191
221
  public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
192
222
  Log.e(REACT_CLASS_NAME, "getExportedCustomDirectEventTypeConstants()");
193
223
  return MapBuilder.<String, Object>builder()
194
- .put("onAdLoaded",
195
- MapBuilder.of("registrationName", "onAdLoaded"))
196
- .put("onAdLoadFailed",
197
- MapBuilder.of("registrationName", "onAdLoadFailed"))
198
- .put("onAdClicked",
199
- 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))
200
230
  .build();
201
231
 
202
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