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
@@ -4,6 +4,7 @@ import android.util.Log;
4
4
 
5
5
  import androidx.annotation.NonNull;
6
6
 
7
+ import com.adwhalesdkreactnative.common.RNMethodResultConst;
7
8
  import com.facebook.react.bridge.ReactApplicationContext;
8
9
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
9
10
  import com.facebook.react.bridge.ReactMethod;
@@ -43,7 +44,7 @@ public class RNAdWhaleMediationLoggerModule extends ReactContextBaseJavaModule {
43
44
  promise.resolve(logLevelString);
44
45
  } catch (Exception e) {
45
46
  Log.e(REACT_CLASS_NAME, "로그 레벨 조회 실패", e);
46
- promise.reject("GET_LOG_LEVEL_ERROR", "Failed to get log level", e);
47
+ promise.reject(RNMethodResultConst.GET_LOG_LEVEL_ERROR, "Failed to get log level", e);
47
48
  }
48
49
  }
49
50
  }
@@ -0,0 +1,83 @@
1
+ package com.adwhalesdkreactnative;
2
+
3
+ import android.util.Log;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+
8
+ import com.adwhalesdkreactnative.common.RNMethodArgConst;
9
+ import com.adwhalesdkreactnative.common.RNMethodCallConst;
10
+ import com.facebook.react.bridge.Arguments;
11
+ import com.facebook.react.bridge.WritableMap;
12
+
13
+ import net.adwhale.sdk.mediation.ads.AdWhaleMediationNativeAdViewListener;
14
+
15
+ /**
16
+ * {@link AdWhaleMediationNativeAdViewListener} 단일 구현체.
17
+ * Flutter {@code FlutterNativeTemplateAd} / {@code FlutterNativeBinderAd}와 동일하게
18
+ * 모든 콜백을 동일한 이벤트 이름({@link RNMethodCallConst#ON_NATIVE_AD_*})으로 정규화합니다.
19
+ */
20
+ public final class RNAdWhaleMediationNativeAdViewListenerBridge implements AdWhaleMediationNativeAdViewListener {
21
+
22
+ /** Flutter {@link net.adwhale.sdk.adwhale_sdk_flutter.nativeads.FlutterNativeBinderAd} 와 동일. */
23
+ public static final int NATIVE_BINDER_CONFIGURATION_ERROR_CODE = 300;
24
+
25
+ /**
26
+ * 네이티브 → JS 로 이벤트를 보낼 때 사용. Flutter AdWhaleAdInstanceManager 의 emit 과 대응.
27
+ */
28
+ public interface Emitter {
29
+ void emit(@NonNull String eventName, @Nullable WritableMap payload);
30
+ }
31
+
32
+ @NonNull private final String logTag;
33
+ @NonNull private final Emitter emitter;
34
+
35
+ public RNAdWhaleMediationNativeAdViewListenerBridge(
36
+ @NonNull String logTag,
37
+ @NonNull Emitter emitter) {
38
+ this.logTag = logTag;
39
+ this.emitter = emitter;
40
+ }
41
+
42
+ @NonNull
43
+ public static WritableMap createErrorPayload(int statusCode, @Nullable String message) {
44
+ WritableMap m = Arguments.createMap();
45
+ m.putInt(RNMethodArgConst.SEND_STATUS_CODE, statusCode);
46
+ m.putString(RNMethodArgConst.SEND_MESSAGE, message != null ? message : "");
47
+ return m;
48
+ }
49
+
50
+ @Override
51
+ public void onNativeAdLoaded() {
52
+ Log.d(logTag, "onNativeAdLoaded");
53
+ emitter.emit(RNMethodCallConst.ON_NATIVE_AD_LOADED, null);
54
+ }
55
+
56
+ @Override
57
+ public void onNativeAdFailedToLoad(int errorCode, String errorMessage) {
58
+ Log.d(logTag, "onNativeAdFailedToLoad: " + errorCode + ", " + errorMessage);
59
+ emitter.emit(
60
+ RNMethodCallConst.ON_NATIVE_AD_FAILED_TO_LOAD,
61
+ createErrorPayload(errorCode, errorMessage));
62
+ }
63
+
64
+ @Override
65
+ public void onNativeAdShowFailed(int errorCode, String errorMessage) {
66
+ Log.d(logTag, "onNativeAdShowFailed: " + errorCode + ", " + errorMessage);
67
+ emitter.emit(
68
+ RNMethodCallConst.ON_NATIVE_AD_SHOW_FAILED,
69
+ createErrorPayload(errorCode, errorMessage));
70
+ }
71
+
72
+ @Override
73
+ public void onNativeAdClicked() {
74
+ Log.d(logTag, "onNativeAdClicked");
75
+ emitter.emit(RNMethodCallConst.ON_NATIVE_AD_CLICKED, null);
76
+ }
77
+
78
+ @Override
79
+ public void onNativeAdClosed() {
80
+ Log.d(logTag, "onNativeAdClosed");
81
+ emitter.emit(RNMethodCallConst.ON_NATIVE_AD_CLOSED, null);
82
+ }
83
+ }
@@ -1,5 +1,6 @@
1
1
  package com.adwhalesdkreactnative;
2
2
 
3
+ import android.app.Activity;
3
4
  import android.util.Log;
4
5
 
5
6
  import androidx.annotation.NonNull;
@@ -10,6 +11,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
10
11
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
11
12
  import com.facebook.react.bridge.ReactMethod;
12
13
  import com.facebook.react.bridge.ReadableMap;
14
+ import com.facebook.react.bridge.ReadableArray;
15
+ import com.facebook.react.bridge.ReadableType;
13
16
  import com.facebook.react.bridge.UiThreadUtil;
14
17
  import com.facebook.react.bridge.WritableMap;
15
18
  import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -30,6 +33,13 @@ import java.util.Map;
30
33
  public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule implements AdWhaleMediationRewardedAdLoadCallback, AdWhaleMediationFullScreenContentCallback, AdWhaleMediationUserEarnedRewardListener {
31
34
 
32
35
  public static final String REACT_CLASS_NAME = RNAdWhaleMediationRewardAd.class.getSimpleName();
36
+
37
+ // Flutter와 싱크: adId 기반 멀티 인스턴스 지원
38
+ private final Map<Integer, AdWhaleMediationRewardAd> adsById = new HashMap<>();
39
+ /** 멀티 로드 시 콜백마다 올바른 adId로 onAdEvent 를 보내기 위한 리스너 (공유 this + lastActiveAdId 는 경합) */
40
+ private final Map<Integer, PerAdRewardListener> rewardListenersById = new HashMap<>();
41
+ private int lastActiveAdId = 0;
42
+
33
43
  private AdWhaleMediationRewardAd adWhaleMediationRewardAd;
34
44
  private String placementUid;
35
45
  private String placementName;
@@ -78,6 +88,7 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
78
88
  @ReactMethod
79
89
  public void loadAd(String placementUid) {
80
90
  Log.e(REACT_CLASS_NAME, "loadAd()");
91
+ lastActiveAdId = 0;
81
92
  this.placementUid = placementUid;
82
93
  adWhaleMediationRewardAd = new AdWhaleMediationRewardAd(placementUid);
83
94
  adWhaleMediationRewardAd.setAdWhaleMediationFullScreenContentCallback(this);
@@ -90,6 +101,70 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
90
101
  });
91
102
  }
92
103
 
104
+ /**
105
+ * Flutter MethodChannel과 동일 네이밍:
106
+ * loadRewardAd({adId, placementUid, region, gcoder:[lt,lng], placementName})
107
+ */
108
+ @ReactMethod
109
+ public void loadRewardAd(ReadableMap args) {
110
+ if (args == null) {
111
+ Log.e(REACT_CLASS_NAME, "loadRewardAd args is null");
112
+ return;
113
+ }
114
+ if (!args.hasKey("adId") || args.getType("adId") != ReadableType.Number) {
115
+ Log.e(REACT_CLASS_NAME, "loadRewardAd missing adId (number)");
116
+ return;
117
+ }
118
+ if (!args.hasKey("placementUid") || args.getType("placementUid") != ReadableType.String) {
119
+ Log.e(REACT_CLASS_NAME, "loadRewardAd missing placementUid (string)");
120
+ return;
121
+ }
122
+ final int adId = args.getInt("adId");
123
+ final String placementUid = args.getString("placementUid");
124
+
125
+ final AdWhaleMediationRewardAd rewardAd = new AdWhaleMediationRewardAd(placementUid);
126
+ final PerAdRewardListener perAdListener = new PerAdRewardListener(adId);
127
+ rewardListenersById.put(adId, perAdListener);
128
+ rewardAd.setAdWhaleMediationFullScreenContentCallback(perAdListener);
129
+
130
+ try {
131
+ if (args.hasKey("region") && args.getType("region") == ReadableType.String) {
132
+ String r = args.getString("region");
133
+ if (r != null && !r.isEmpty()) rewardAd.setRegion(r);
134
+ }
135
+ } catch (Throwable t) {
136
+ Log.w(REACT_CLASS_NAME, "setRegion failed: " + t.getMessage());
137
+ }
138
+
139
+ try {
140
+ if (args.hasKey("placementName") && args.getType("placementName") == ReadableType.String) {
141
+ String pn = args.getString("placementName");
142
+ if (pn != null && !pn.isEmpty()) rewardAd.setPlacementName(pn);
143
+ }
144
+ } catch (Throwable t) {
145
+ Log.w(REACT_CLASS_NAME, "setPlacementName failed: " + t.getMessage());
146
+ }
147
+
148
+ try {
149
+ if (args.hasKey("gcoder") && args.getType("gcoder") == ReadableType.Array) {
150
+ ReadableArray arr = args.getArray("gcoder");
151
+ if (arr != null && arr.size() >= 2
152
+ && arr.getType(0) == ReadableType.Number
153
+ && arr.getType(1) == ReadableType.Number) {
154
+ double lat = arr.getDouble(0);
155
+ double lng = arr.getDouble(1);
156
+ rewardAd.setGcoder(lat, lng);
157
+ }
158
+ }
159
+ } catch (Throwable t) {
160
+ Log.w(REACT_CLASS_NAME, "setGcoder failed: " + t.getMessage());
161
+ }
162
+
163
+ adsById.put(adId, rewardAd);
164
+
165
+ UiThreadUtil.runOnUiThread(() -> rewardAd.loadAd(perAdListener));
166
+ }
167
+
93
168
  @ReactMethod
94
169
  public void showAd() {
95
170
  Log.e(REACT_CLASS_NAME, "showAd()");
@@ -98,9 +173,63 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
98
173
  return;
99
174
  }
100
175
 
101
- UiThreadUtil.runOnUiThread(() -> {
102
- adWhaleMediationRewardAd.showAd(this);
103
- });
176
+ lastActiveAdId = 0;
177
+ UiThreadUtil.runOnUiThread(() -> showRewardAdOnUi(adWhaleMediationRewardAd, this));
178
+ }
179
+
180
+ /** Flutter 네이밍: showAdWithoutView(adId) */
181
+ @ReactMethod
182
+ public void showAdWithoutView(int adId) {
183
+ AdWhaleMediationRewardAd ad = adsById.get(adId);
184
+ if (ad == null) {
185
+ Log.e(REACT_CLASS_NAME, "showAdWithoutView: ad not found. adId=" + adId);
186
+ return;
187
+ }
188
+ PerAdRewardListener listener = rewardListenersById.get(adId);
189
+ if (listener == null) {
190
+ Log.e(REACT_CLASS_NAME, "showAdWithoutView: listener not found. adId=" + adId);
191
+ return;
192
+ }
193
+ UiThreadUtil.runOnUiThread(() -> showRewardAdOnUi(ad, listener));
194
+ }
195
+
196
+ /**
197
+ * SDK는 Activity 생명주기와 연동해 onAdDismissed / 보상 콜백을 지연·발행합니다.
198
+ * {@code showAd(Listener)}만 쓰면 현재 Activity를 못 잡아 콜백이 누락될 수 있어
199
+ * {@code showAd(Activity, Listener)}를 우선 사용합니다.
200
+ */
201
+ /**
202
+ * @param listener 전면/로드/보상 콜백을 동일 adId로 보낼 리스턴스 (싱글턴은 {@code this})
203
+ */
204
+ private void showRewardAdOnUi(
205
+ @NonNull AdWhaleMediationRewardAd ad,
206
+ @NonNull AdWhaleMediationUserEarnedRewardListener listener) {
207
+ Activity activity = getCurrentActivity();
208
+ if (activity != null && !activity.isFinishing()) {
209
+ try {
210
+ ad.showAd(activity, listener);
211
+ return;
212
+ } catch (Throwable t) {
213
+ Log.w(REACT_CLASS_NAME, "showAd(activity, listener) failed, fallback: " + t.getMessage());
214
+ }
215
+ } else {
216
+ Log.w(REACT_CLASS_NAME, "showRewardAdOnUi: no valid Activity, using showAd(listener)");
217
+ }
218
+ ad.showAd(listener);
219
+ }
220
+
221
+ /** Flutter 네이밍: destroyAd(adId) */
222
+ @ReactMethod
223
+ public void destroyAd(int adId) {
224
+ rewardListenersById.remove(adId);
225
+ AdWhaleMediationRewardAd ad = adsById.remove(adId);
226
+ if (ad != null) {
227
+ try {
228
+ ad.destroy();
229
+ } catch (Throwable t) {
230
+ Log.w(REACT_CLASS_NAME, "destroyAd failed: " + t.getMessage());
231
+ }
232
+ }
104
233
  }
105
234
 
106
235
 
@@ -109,12 +238,17 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
109
238
  public void onAdClicked() {
110
239
  Log.i(REACT_CLASS_NAME, "onAdClicked()");
111
240
  sendEvnet("onRewardAdClicked", null);
241
+ sendAdEvent(lastActiveAdId, "onRewardAdClicked", null);
112
242
  }
113
243
 
114
244
  @Override
115
245
  public void onAdDismissed() {
116
246
  Log.i(REACT_CLASS_NAME, "onAdDismissed()");
117
247
  sendEvnet("onRewardAdDismissed", null);
248
+ sendAdEvent(lastActiveAdId, "onRewardAdDismissed", null);
249
+ if (adsById.containsKey(lastActiveAdId)) {
250
+ destroyAd(lastActiveAdId);
251
+ }
118
252
  }
119
253
 
120
254
  @Override
@@ -123,6 +257,8 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
123
257
  WritableMap params = Arguments.createMap();
124
258
  params.putInt("statusCode", statusCode);
125
259
  params.putString("message", message);
260
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
261
+ sendAdEvent(lastActiveAdId, "onRewardAdFailedToShow", params);
126
262
  sendEvnet("onRewardAdFailedToShow", params);
127
263
  }
128
264
 
@@ -130,6 +266,7 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
130
266
  public void onAdShowed() {
131
267
  Log.i(REACT_CLASS_NAME, "onAdShowed()");
132
268
  sendEvnet("onRewardAdShowed", null);
269
+ sendAdEvent(lastActiveAdId, "onRewardAdShowed", null);
133
270
  }
134
271
 
135
272
  /// AdWhaleMediationRewardedAdLoadCallback
@@ -137,6 +274,7 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
137
274
  public void onAdLoaded(AdWhaleMediationRewardAd adWhaleMediationRewardAd, String message) {
138
275
  Log.i(REACT_CLASS_NAME, "onAdLoaded(+ " + message + ")");
139
276
  sendEvnet("onRewardAdLoaded", null);
277
+ sendAdEvent(lastActiveAdId, "onRewardAdLoaded", null);
140
278
  }
141
279
 
142
280
  @Override
@@ -145,6 +283,8 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
145
283
  WritableMap params = Arguments.createMap();
146
284
  params.putInt("statusCode", statusCode);
147
285
  params.putString("message", message);
286
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
287
+ sendAdEvent(lastActiveAdId, "onRewardAdFailedToLoad", params);
148
288
  sendEvnet("onRewardAdFailedToLoad", params);
149
289
  }
150
290
 
@@ -154,8 +294,14 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
154
294
  public void onUserRewarded(AdWhaleMediationRewardItem adWhaleMediationRewardItem) {
155
295
  Log.i(REACT_CLASS_NAME, "onUserRewarded(" + adWhaleMediationRewardItem.toString() + ")");
156
296
  WritableMap params = Arguments.createMap();
297
+ // Flutter와 동일 키: RewardType / RewardAmount (대소문자 포함)
298
+ params.putString("RewardType", adWhaleMediationRewardItem.getRewardType());
299
+ params.putInt("RewardAmount", adWhaleMediationRewardItem.getRewardAmount());
300
+ // 레거시 호환 키도 유지
157
301
  params.putString("type", adWhaleMediationRewardItem.getRewardType());
158
302
  params.putInt("amount", adWhaleMediationRewardItem.getRewardAmount());
303
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
304
+ sendAdEvent(lastActiveAdId, "onUserRewarded", params);
159
305
  sendEvnet("onUserRewarded", params);
160
306
  }
161
307
 
@@ -167,9 +313,36 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
167
313
  }
168
314
 
169
315
  private void sendEvnet(String eventName, @Nullable WritableMap params) {
170
- getReactApplicationContext()
171
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
316
+ final ReactApplicationContext ctx = getReactApplicationContext();
317
+ if (ctx == null) {
318
+ return;
319
+ }
320
+ // SDK 콜백이 메인이 아닌 스레드에서 올 수 있어 RN 브리지는 UI 큐에서 emit
321
+ ctx.runOnUiQueueThread(() -> {
322
+ try {
323
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
172
324
  .emit(eventName, params);
325
+ } catch (Throwable t) {
326
+ Log.w(REACT_CLASS_NAME, "emit failed: " + eventName + " — " + t.getMessage());
327
+ }
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Flutter `onAdEvent` 스타일: payload에 eventName + adId 포함.
333
+ * payload: { adId, eventName, statusCode?, message?, RewardType?, RewardAmount? }
334
+ */
335
+ private void sendAdEvent(int adId, @NonNull String eventName, @Nullable WritableMap original) {
336
+ WritableMap payload = Arguments.createMap();
337
+ payload.putInt("adId", adId);
338
+ payload.putString("eventName", eventName);
339
+ if (original != null) {
340
+ if (original.hasKey("statusCode")) payload.putInt("statusCode", original.getInt("statusCode"));
341
+ if (original.hasKey("message")) payload.putString("message", original.getString("message"));
342
+ if (original.hasKey("RewardType")) payload.putString("RewardType", original.getString("RewardType"));
343
+ if (original.hasKey("RewardAmount")) payload.putInt("RewardAmount", original.getInt("RewardAmount"));
344
+ }
345
+ sendEvnet("onAdEvent", payload);
173
346
  }
174
347
 
175
348
  @ReactMethod
@@ -178,7 +351,83 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
178
351
  }
179
352
 
180
353
  @ReactMethod
181
- public void removeListeners(Integer count) {
354
+ public void removeListeners(Integer count) {
182
355
  //TODO NOTHING
183
356
  }
357
+
358
+ /**
359
+ * 멀티 보상형 인스턴스당 1개: 로드/전면/보상 콜백 모두 boundAdId 로 onAdEvent 전달.
360
+ */
361
+ private final class PerAdRewardListener implements AdWhaleMediationRewardedAdLoadCallback,
362
+ AdWhaleMediationFullScreenContentCallback,
363
+ AdWhaleMediationUserEarnedRewardListener {
364
+ private final int boundAdId;
365
+
366
+ PerAdRewardListener(int boundAdId) {
367
+ this.boundAdId = boundAdId;
368
+ }
369
+
370
+ @Override
371
+ public void onAdClicked() {
372
+ Log.i(REACT_CLASS_NAME, "onAdClicked() adId=" + boundAdId);
373
+ sendEvnet("onRewardAdClicked", null);
374
+ sendAdEvent(boundAdId, "onRewardAdClicked", null);
375
+ }
376
+
377
+ @Override
378
+ public void onAdDismissed() {
379
+ Log.i(REACT_CLASS_NAME, "onAdDismissed() adId=" + boundAdId);
380
+ sendEvnet("onRewardAdDismissed", null);
381
+ sendAdEvent(boundAdId, "onRewardAdDismissed", null);
382
+ if (adsById.containsKey(boundAdId)) {
383
+ destroyAd(boundAdId);
384
+ }
385
+ }
386
+
387
+ @Override
388
+ public void onFailedToShow(int statusCode, String message) {
389
+ Log.i(REACT_CLASS_NAME, "onFailedToShow(" + statusCode + ", " + message + ") adId=" + boundAdId);
390
+ WritableMap params = Arguments.createMap();
391
+ params.putInt("statusCode", statusCode);
392
+ params.putString("message", message);
393
+ sendAdEvent(boundAdId, "onRewardAdFailedToShow", params);
394
+ sendEvnet("onRewardAdFailedToShow", params);
395
+ }
396
+
397
+ @Override
398
+ public void onAdShowed() {
399
+ Log.i(REACT_CLASS_NAME, "onAdShowed() adId=" + boundAdId);
400
+ sendEvnet("onRewardAdShowed", null);
401
+ sendAdEvent(boundAdId, "onRewardAdShowed", null);
402
+ }
403
+
404
+ @Override
405
+ public void onAdLoaded(AdWhaleMediationRewardAd adWhaleMediationRewardAd, String message) {
406
+ Log.i(REACT_CLASS_NAME, "onAdLoaded(+ " + message + ") adId=" + boundAdId);
407
+ sendEvnet("onRewardAdLoaded", null);
408
+ sendAdEvent(boundAdId, "onRewardAdLoaded", null);
409
+ }
410
+
411
+ @Override
412
+ public void onAdFailedToLoad(int statusCode, String message) {
413
+ Log.i(REACT_CLASS_NAME, "onAdFailedToLoad(" + statusCode + ", " + message + ") adId=" + boundAdId);
414
+ WritableMap params = Arguments.createMap();
415
+ params.putInt("statusCode", statusCode);
416
+ params.putString("message", message);
417
+ sendAdEvent(boundAdId, "onRewardAdFailedToLoad", params);
418
+ sendEvnet("onRewardAdFailedToLoad", params);
419
+ }
420
+
421
+ @Override
422
+ public void onUserRewarded(AdWhaleMediationRewardItem adWhaleMediationRewardItem) {
423
+ Log.i(REACT_CLASS_NAME, "onUserRewarded(" + adWhaleMediationRewardItem + ") adId=" + boundAdId);
424
+ WritableMap params = Arguments.createMap();
425
+ params.putString("RewardType", adWhaleMediationRewardItem.getRewardType());
426
+ params.putInt("RewardAmount", adWhaleMediationRewardItem.getRewardAmount());
427
+ params.putString("type", adWhaleMediationRewardItem.getRewardType());
428
+ params.putInt("amount", adWhaleMediationRewardItem.getRewardAmount());
429
+ sendAdEvent(boundAdId, "onUserRewarded", params);
430
+ sendEvnet("onUserRewarded", params);
431
+ }
432
+ }
184
433
  }