cloudx-react-native 2.0.0-beta.1

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 (171) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +1258 -0
  3. package/android/build.gradle +78 -0
  4. package/android/gradle.properties +7 -0
  5. package/android/src/main/AndroidManifest.xml +7 -0
  6. package/android/src/main/java/com/cloudxreactnative/CloudXAdViewEvent.kt +15 -0
  7. package/android/src/main/java/com/cloudxreactnative/CloudXBannerViewManagerImpl.kt +472 -0
  8. package/android/src/main/java/com/cloudxreactnative/CloudXReactNativeModuleImpl.kt +861 -0
  9. package/android/src/newarch/com/cloudxreactnative/CloudXBannerViewManager.kt +90 -0
  10. package/android/src/newarch/com/cloudxreactnative/CloudXReactNativeModule.kt +73 -0
  11. package/android/src/newarch/com/cloudxreactnative/CloudXReactNativePackage.kt +37 -0
  12. package/android/src/oldarch/com/cloudxreactnative/CloudXBannerViewManager.kt +76 -0
  13. package/android/src/oldarch/com/cloudxreactnative/CloudXReactNativeModule.kt +73 -0
  14. package/android/src/oldarch/com/cloudxreactnative/CloudXReactNativePackage.kt +16 -0
  15. package/cloudx-react-native.podspec +46 -0
  16. package/ios/AppTrackingTransparency.h +13 -0
  17. package/ios/AppTrackingTransparency.mm +59 -0
  18. package/ios/RNCloudXBannerView.h +51 -0
  19. package/ios/RNCloudXBannerView.mm +633 -0
  20. package/ios/RNCloudXBannerViewManager.h +11 -0
  21. package/ios/RNCloudXBannerViewManager.mm +60 -0
  22. package/ios/RNCloudXModule.h +30 -0
  23. package/ios/RNCloudXModule.mm +1088 -0
  24. package/lib/commonjs/AdView.js +151 -0
  25. package/lib/commonjs/AdView.js.map +1 -0
  26. package/lib/commonjs/AppTrackingTransparency.js +62 -0
  27. package/lib/commonjs/AppTrackingTransparency.js.map +1 -0
  28. package/lib/commonjs/BannerAd.js +117 -0
  29. package/lib/commonjs/BannerAd.js.map +1 -0
  30. package/lib/commonjs/CloudX.js +49 -0
  31. package/lib/commonjs/CloudX.js.map +1 -0
  32. package/lib/commonjs/ErrorCode.js +116 -0
  33. package/lib/commonjs/ErrorCode.js.map +1 -0
  34. package/lib/commonjs/EventEmitter.js +44 -0
  35. package/lib/commonjs/EventEmitter.js.map +1 -0
  36. package/lib/commonjs/InterstitialAd.js +94 -0
  37. package/lib/commonjs/InterstitialAd.js.map +1 -0
  38. package/lib/commonjs/MRecAd.js +117 -0
  39. package/lib/commonjs/MRecAd.js.map +1 -0
  40. package/lib/commonjs/RewardedAd.js +103 -0
  41. package/lib/commonjs/RewardedAd.js.map +1 -0
  42. package/lib/commonjs/hooks.js +239 -0
  43. package/lib/commonjs/hooks.js.map +1 -0
  44. package/lib/commonjs/index.js +144 -0
  45. package/lib/commonjs/index.js.map +1 -0
  46. package/lib/commonjs/specs/CloudXAdViewNativeComponent.js +26 -0
  47. package/lib/commonjs/specs/CloudXAdViewNativeComponent.js.map +1 -0
  48. package/lib/commonjs/specs/NativeCloudXModule.js +18 -0
  49. package/lib/commonjs/specs/NativeCloudXModule.js.map +1 -0
  50. package/lib/commonjs/types/AdEvent.js +6 -0
  51. package/lib/commonjs/types/AdEvent.js.map +1 -0
  52. package/lib/commonjs/types/AdInfo.js +6 -0
  53. package/lib/commonjs/types/AdInfo.js.map +1 -0
  54. package/lib/commonjs/types/BannerAd.js +6 -0
  55. package/lib/commonjs/types/BannerAd.js.map +1 -0
  56. package/lib/commonjs/types/CloudX.js +6 -0
  57. package/lib/commonjs/types/CloudX.js.map +1 -0
  58. package/lib/commonjs/types/Configuration.js +2 -0
  59. package/lib/commonjs/types/Configuration.js.map +1 -0
  60. package/lib/commonjs/types/FullscreenAd.js +6 -0
  61. package/lib/commonjs/types/FullscreenAd.js.map +1 -0
  62. package/lib/commonjs/types/RewardedAd.js +6 -0
  63. package/lib/commonjs/types/RewardedAd.js.map +1 -0
  64. package/lib/commonjs/types/index.js +2 -0
  65. package/lib/commonjs/types/index.js.map +1 -0
  66. package/lib/module/AdView.js +144 -0
  67. package/lib/module/AdView.js.map +1 -0
  68. package/lib/module/AppTrackingTransparency.js +55 -0
  69. package/lib/module/AppTrackingTransparency.js.map +1 -0
  70. package/lib/module/BannerAd.js +110 -0
  71. package/lib/module/BannerAd.js.map +1 -0
  72. package/lib/module/CloudX.js +42 -0
  73. package/lib/module/CloudX.js.map +1 -0
  74. package/lib/module/ErrorCode.js +110 -0
  75. package/lib/module/ErrorCode.js.map +1 -0
  76. package/lib/module/EventEmitter.js +35 -0
  77. package/lib/module/EventEmitter.js.map +1 -0
  78. package/lib/module/InterstitialAd.js +87 -0
  79. package/lib/module/InterstitialAd.js.map +1 -0
  80. package/lib/module/MRecAd.js +110 -0
  81. package/lib/module/MRecAd.js.map +1 -0
  82. package/lib/module/RewardedAd.js +96 -0
  83. package/lib/module/RewardedAd.js.map +1 -0
  84. package/lib/module/hooks.js +230 -0
  85. package/lib/module/hooks.js.map +1 -0
  86. package/lib/module/index.js +33 -0
  87. package/lib/module/index.js.map +1 -0
  88. package/lib/module/specs/CloudXAdViewNativeComponent.js +20 -0
  89. package/lib/module/specs/CloudXAdViewNativeComponent.js.map +1 -0
  90. package/lib/module/specs/NativeCloudXModule.js +14 -0
  91. package/lib/module/specs/NativeCloudXModule.js.map +1 -0
  92. package/lib/module/types/AdEvent.js +2 -0
  93. package/lib/module/types/AdEvent.js.map +1 -0
  94. package/lib/module/types/AdInfo.js +2 -0
  95. package/lib/module/types/AdInfo.js.map +1 -0
  96. package/lib/module/types/BannerAd.js +2 -0
  97. package/lib/module/types/BannerAd.js.map +1 -0
  98. package/lib/module/types/CloudX.js +2 -0
  99. package/lib/module/types/CloudX.js.map +1 -0
  100. package/lib/module/types/Configuration.js +2 -0
  101. package/lib/module/types/Configuration.js.map +1 -0
  102. package/lib/module/types/FullscreenAd.js +2 -0
  103. package/lib/module/types/FullscreenAd.js.map +1 -0
  104. package/lib/module/types/RewardedAd.js +2 -0
  105. package/lib/module/types/RewardedAd.js.map +1 -0
  106. package/lib/module/types/index.js +2 -0
  107. package/lib/module/types/index.js.map +1 -0
  108. package/lib/typescript/src/AdView.d.ts +69 -0
  109. package/lib/typescript/src/AdView.d.ts.map +1 -0
  110. package/lib/typescript/src/AppTrackingTransparency.d.ts +28 -0
  111. package/lib/typescript/src/AppTrackingTransparency.d.ts.map +1 -0
  112. package/lib/typescript/src/BannerAd.d.ts +4 -0
  113. package/lib/typescript/src/BannerAd.d.ts.map +1 -0
  114. package/lib/typescript/src/CloudX.d.ts +4 -0
  115. package/lib/typescript/src/CloudX.d.ts.map +1 -0
  116. package/lib/typescript/src/ErrorCode.d.ts +102 -0
  117. package/lib/typescript/src/ErrorCode.d.ts.map +1 -0
  118. package/lib/typescript/src/EventEmitter.d.ts +16 -0
  119. package/lib/typescript/src/EventEmitter.d.ts.map +1 -0
  120. package/lib/typescript/src/InterstitialAd.d.ts +4 -0
  121. package/lib/typescript/src/InterstitialAd.d.ts.map +1 -0
  122. package/lib/typescript/src/MRecAd.d.ts +4 -0
  123. package/lib/typescript/src/MRecAd.d.ts.map +1 -0
  124. package/lib/typescript/src/RewardedAd.d.ts +4 -0
  125. package/lib/typescript/src/RewardedAd.d.ts.map +1 -0
  126. package/lib/typescript/src/hooks.d.ts +50 -0
  127. package/lib/typescript/src/hooks.d.ts.map +1 -0
  128. package/lib/typescript/src/index.d.ts +23 -0
  129. package/lib/typescript/src/index.d.ts.map +1 -0
  130. package/lib/typescript/src/specs/CloudXAdViewNativeComponent.d.ts +61 -0
  131. package/lib/typescript/src/specs/CloudXAdViewNativeComponent.d.ts.map +1 -0
  132. package/lib/typescript/src/specs/NativeCloudXModule.d.ts +106 -0
  133. package/lib/typescript/src/specs/NativeCloudXModule.d.ts.map +1 -0
  134. package/lib/typescript/src/types/AdEvent.d.ts +10 -0
  135. package/lib/typescript/src/types/AdEvent.d.ts.map +1 -0
  136. package/lib/typescript/src/types/AdInfo.d.ts +48 -0
  137. package/lib/typescript/src/types/AdInfo.d.ts.map +1 -0
  138. package/lib/typescript/src/types/BannerAd.d.ts +39 -0
  139. package/lib/typescript/src/types/BannerAd.d.ts.map +1 -0
  140. package/lib/typescript/src/types/CloudX.d.ts +25 -0
  141. package/lib/typescript/src/types/CloudX.d.ts.map +1 -0
  142. package/lib/typescript/src/types/Configuration.d.ts +10 -0
  143. package/lib/typescript/src/types/Configuration.d.ts.map +1 -0
  144. package/lib/typescript/src/types/FullscreenAd.d.ts +29 -0
  145. package/lib/typescript/src/types/FullscreenAd.d.ts.map +1 -0
  146. package/lib/typescript/src/types/RewardedAd.d.ts +10 -0
  147. package/lib/typescript/src/types/RewardedAd.d.ts.map +1 -0
  148. package/lib/typescript/src/types/index.d.ts +8 -0
  149. package/lib/typescript/src/types/index.d.ts.map +1 -0
  150. package/package.json +78 -0
  151. package/src/AdView.tsx +213 -0
  152. package/src/AppTrackingTransparency.ts +56 -0
  153. package/src/BannerAd.ts +133 -0
  154. package/src/CloudX.ts +55 -0
  155. package/src/ErrorCode.ts +157 -0
  156. package/src/EventEmitter.ts +38 -0
  157. package/src/InterstitialAd.ts +103 -0
  158. package/src/MRecAd.ts +133 -0
  159. package/src/RewardedAd.ts +113 -0
  160. package/src/hooks.ts +292 -0
  161. package/src/index.ts +50 -0
  162. package/src/specs/CloudXAdViewNativeComponent.ts +82 -0
  163. package/src/specs/NativeCloudXModule.ts +162 -0
  164. package/src/types/AdEvent.ts +11 -0
  165. package/src/types/AdInfo.ts +60 -0
  166. package/src/types/BannerAd.ts +59 -0
  167. package/src/types/CloudX.ts +33 -0
  168. package/src/types/Configuration.ts +10 -0
  169. package/src/types/FullscreenAd.ts +39 -0
  170. package/src/types/RewardedAd.ts +10 -0
  171. package/src/types/index.ts +7 -0
@@ -0,0 +1,861 @@
1
+ package com.cloudxreactnative
2
+
3
+ import android.graphics.Color
4
+ import android.os.Build
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import android.util.Log
8
+ import android.view.Gravity
9
+ import android.view.OrientationEventListener
10
+ import android.view.Surface
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.view.WindowInsets
14
+ import android.widget.FrameLayout
15
+ import com.facebook.react.bridge.*
16
+ import com.facebook.react.modules.core.DeviceEventManagerModule
17
+ import io.cloudx.sdk.*
18
+ import java.util.concurrent.ConcurrentHashMap
19
+
20
+ class CloudXReactNativeModuleImpl(reactContext: ReactApplicationContext) {
21
+
22
+ // Strong reference is safe — module lifecycle is tied to the ReactApplicationContext
23
+ private val reactApplicationContext: ReactApplicationContext = reactContext
24
+
25
+ private val interstitials = ConcurrentHashMap<String, CloudXInterstitialAd>()
26
+ private val rewardeds = ConcurrentHashMap<String, CloudXRewardedAd>()
27
+ private val banners = ConcurrentHashMap<String, CloudXAdView>()
28
+ private val mrecs = ConcurrentHashMap<String, CloudXAdView>()
29
+ private val windowPositionedAds = ConcurrentHashMap.newKeySet<String>()
30
+ private val adViewPositions = ConcurrentHashMap<String, String>()
31
+ private val adViewOffsets = ConcurrentHashMap<String, Pair<Float, Float>>()
32
+ private val adUnitIdsToShowAfterCreate = ConcurrentHashMap.newKeySet<String>()
33
+ private var orientationListener: OrientationEventListener? = null
34
+ private var currentRotation: Int = -1
35
+
36
+ companion object {
37
+ private const val TAG = "CloudXModule"
38
+ const val MODULE_NAME = "CloudXModule"
39
+
40
+ private const val ON_BANNER_AD_LOADED_EVENT = "OnBannerAdLoadedEvent"
41
+ private const val ON_BANNER_AD_LOAD_FAILED_EVENT = "OnBannerAdLoadFailedEvent"
42
+ private const val ON_BANNER_AD_CLICKED_EVENT = "OnBannerAdClickedEvent"
43
+ private const val ON_BANNER_AD_EXPANDED_EVENT = "OnBannerAdExpandedEvent"
44
+ private const val ON_BANNER_AD_COLLAPSED_EVENT = "OnBannerAdCollapsedEvent"
45
+ private const val ON_BANNER_AD_REVENUE_PAID = "OnBannerAdRevenuePaid"
46
+
47
+ private const val ON_MREC_AD_LOADED_EVENT = "OnMRecAdLoadedEvent"
48
+ private const val ON_MREC_AD_LOAD_FAILED_EVENT = "OnMRecAdLoadFailedEvent"
49
+ private const val ON_MREC_AD_CLICKED_EVENT = "OnMRecAdClickedEvent"
50
+ private const val ON_MREC_AD_EXPANDED_EVENT = "OnMRecAdExpandedEvent"
51
+ private const val ON_MREC_AD_COLLAPSED_EVENT = "OnMRecAdCollapsedEvent"
52
+ private const val ON_MREC_AD_REVENUE_PAID = "OnMRecAdRevenuePaid"
53
+
54
+ private const val ON_INTERSTITIAL_LOADED_EVENT = "OnInterstitialLoadedEvent"
55
+ private const val ON_INTERSTITIAL_LOAD_FAILED_EVENT = "OnInterstitialLoadFailedEvent"
56
+ private const val ON_INTERSTITIAL_CLICKED_EVENT = "OnInterstitialClickedEvent"
57
+ private const val ON_INTERSTITIAL_DISPLAYED_EVENT = "OnInterstitialDisplayedEvent"
58
+ private const val ON_INTERSTITIAL_DISPLAY_FAILED_EVENT = "OnInterstitialDisplayFailedEvent"
59
+ private const val ON_INTERSTITIAL_HIDDEN_EVENT = "OnInterstitialHiddenEvent"
60
+ private const val ON_INTERSTITIAL_AD_REVENUE_PAID = "OnInterstitialAdRevenuePaid"
61
+
62
+ private const val ON_REWARDED_AD_LOADED_EVENT = "OnRewardedAdLoadedEvent"
63
+ private const val ON_REWARDED_AD_LOAD_FAILED_EVENT = "OnRewardedAdLoadFailedEvent"
64
+ private const val ON_REWARDED_AD_CLICKED_EVENT = "OnRewardedAdClickedEvent"
65
+ private const val ON_REWARDED_AD_DISPLAYED_EVENT = "OnRewardedAdDisplayedEvent"
66
+ private const val ON_REWARDED_AD_DISPLAY_FAILED_EVENT = "OnRewardedAdDisplayFailedEvent"
67
+ private const val ON_REWARDED_AD_HIDDEN_EVENT = "OnRewardedAdHiddenEvent"
68
+ private const val ON_REWARDED_AD_RECEIVED_REWARD_EVENT = "OnRewardedAdReceivedRewardEvent"
69
+ private const val ON_REWARDED_AD_REVENUE_PAID = "OnRewardedAdRevenuePaid"
70
+ }
71
+
72
+ fun getConstants(): MutableMap<String, Any> = mutableMapOf(
73
+ "ON_BANNER_AD_LOADED_EVENT" to ON_BANNER_AD_LOADED_EVENT,
74
+ "ON_BANNER_AD_LOAD_FAILED_EVENT" to ON_BANNER_AD_LOAD_FAILED_EVENT,
75
+ "ON_BANNER_AD_CLICKED_EVENT" to ON_BANNER_AD_CLICKED_EVENT,
76
+ "ON_BANNER_AD_EXPANDED_EVENT" to ON_BANNER_AD_EXPANDED_EVENT,
77
+ "ON_BANNER_AD_COLLAPSED_EVENT" to ON_BANNER_AD_COLLAPSED_EVENT,
78
+ "ON_BANNER_AD_REVENUE_PAID" to ON_BANNER_AD_REVENUE_PAID,
79
+ "ON_MREC_AD_LOADED_EVENT" to ON_MREC_AD_LOADED_EVENT,
80
+ "ON_MREC_AD_LOAD_FAILED_EVENT" to ON_MREC_AD_LOAD_FAILED_EVENT,
81
+ "ON_MREC_AD_CLICKED_EVENT" to ON_MREC_AD_CLICKED_EVENT,
82
+ "ON_MREC_AD_EXPANDED_EVENT" to ON_MREC_AD_EXPANDED_EVENT,
83
+ "ON_MREC_AD_COLLAPSED_EVENT" to ON_MREC_AD_COLLAPSED_EVENT,
84
+ "ON_MREC_AD_REVENUE_PAID" to ON_MREC_AD_REVENUE_PAID,
85
+ "ON_INTERSTITIAL_LOADED_EVENT" to ON_INTERSTITIAL_LOADED_EVENT,
86
+ "ON_INTERSTITIAL_LOAD_FAILED_EVENT" to ON_INTERSTITIAL_LOAD_FAILED_EVENT,
87
+ "ON_INTERSTITIAL_CLICKED_EVENT" to ON_INTERSTITIAL_CLICKED_EVENT,
88
+ "ON_INTERSTITIAL_DISPLAYED_EVENT" to ON_INTERSTITIAL_DISPLAYED_EVENT,
89
+ "ON_INTERSTITIAL_DISPLAY_FAILED_EVENT" to ON_INTERSTITIAL_DISPLAY_FAILED_EVENT,
90
+ "ON_INTERSTITIAL_HIDDEN_EVENT" to ON_INTERSTITIAL_HIDDEN_EVENT,
91
+ "ON_INTERSTITIAL_AD_REVENUE_PAID" to ON_INTERSTITIAL_AD_REVENUE_PAID,
92
+ "ON_REWARDED_AD_LOADED_EVENT" to ON_REWARDED_AD_LOADED_EVENT,
93
+ "ON_REWARDED_AD_LOAD_FAILED_EVENT" to ON_REWARDED_AD_LOAD_FAILED_EVENT,
94
+ "ON_REWARDED_AD_CLICKED_EVENT" to ON_REWARDED_AD_CLICKED_EVENT,
95
+ "ON_REWARDED_AD_DISPLAYED_EVENT" to ON_REWARDED_AD_DISPLAYED_EVENT,
96
+ "ON_REWARDED_AD_DISPLAY_FAILED_EVENT" to ON_REWARDED_AD_DISPLAY_FAILED_EVENT,
97
+ "ON_REWARDED_AD_HIDDEN_EVENT" to ON_REWARDED_AD_HIDDEN_EVENT,
98
+ "ON_REWARDED_AD_RECEIVED_REWARD_EVENT" to ON_REWARDED_AD_RECEIVED_REWARD_EVENT,
99
+ "ON_REWARDED_AD_REVENUE_PAID" to ON_REWARDED_AD_REVENUE_PAID,
100
+
101
+ // Position constants
102
+ "TOP_LEFT_POSITION" to "top_left",
103
+ "TOP_CENTER_POSITION" to "top_center",
104
+ "TOP_RIGHT_POSITION" to "top_right",
105
+ "CENTER_LEFT_POSITION" to "center_left",
106
+ "CENTERED_POSITION" to "centered",
107
+ "CENTER_RIGHT_POSITION" to "center_right",
108
+ "BOTTOM_LEFT_POSITION" to "bottom_left",
109
+ "BOTTOM_CENTER_POSITION" to "bottom_center",
110
+ "BOTTOM_RIGHT_POSITION" to "bottom_right",
111
+
112
+ // Error code constants
113
+ "ERROR_CODE_INTERNAL_ERROR" to 0,
114
+ "ERROR_CODE_NETWORK_ERROR" to 100,
115
+ "ERROR_CODE_NETWORK_TIMEOUT" to 101,
116
+ "ERROR_CODE_NO_FILL" to 302,
117
+ "ERROR_CODE_AD_NOT_READY" to 400,
118
+ "ERROR_CODE_AD_ALREADY_SHOWING" to 401,
119
+ "ERROR_CODE_INVALID_AD_UNIT" to 300,
120
+ "ERROR_CODE_NOT_INITIALIZED" to 200,
121
+ "ERROR_CODE_NO_CONNECTION" to 106,
122
+ )
123
+
124
+ fun onCatalystInstanceDestroy() {
125
+ orientationListener?.disable()
126
+ orientationListener = null
127
+
128
+ Handler(Looper.getMainLooper()).post {
129
+ banners.values.forEach {
130
+ it.listener = null; it.revenueListener = null; it.stopAutoRefresh()
131
+ (it.parent as? ViewGroup)?.removeView(it)
132
+ it.destroy()
133
+ }
134
+ mrecs.values.forEach {
135
+ it.listener = null; it.revenueListener = null; it.stopAutoRefresh()
136
+ (it.parent as? ViewGroup)?.removeView(it)
137
+ it.destroy()
138
+ }
139
+ interstitials.values.forEach { it.listener = null; it.revenueListener = null; it.destroy() }
140
+ rewardeds.values.forEach { it.listener = null; it.revenueListener = null; it.destroy() }
141
+ banners.clear()
142
+ mrecs.clear()
143
+ interstitials.clear()
144
+ rewardeds.clear()
145
+ windowPositionedAds.clear()
146
+ adViewPositions.clear()
147
+ adViewOffsets.clear()
148
+ adUnitIdsToShowAfterCreate.clear()
149
+ }
150
+ }
151
+
152
+ // =========================================================================
153
+ // Initialization
154
+ // =========================================================================
155
+
156
+ private enum class InitState { IDLE, INITIALIZING, INITIALIZED }
157
+ @Volatile private var initState = InitState.IDLE
158
+
159
+ @Synchronized
160
+ fun initialize(pluginVersion: String, appKey: String, promise: Promise) {
161
+ if (initState == InitState.INITIALIZED) {
162
+ val result = Arguments.createMap()
163
+ result.putBoolean("success", true)
164
+ result.putString("message", "SDK already initialized")
165
+ promise.resolve(result)
166
+ return
167
+ }
168
+
169
+ if (initState == InitState.INITIALIZING) {
170
+ promise.reject("INIT_IN_PROGRESS", "SDK initialization already in progress", null)
171
+ return
172
+ }
173
+
174
+ initState = InitState.INITIALIZING
175
+
176
+ val config = CloudXInitializationConfiguration.builder(appKey)
177
+ .setPluginVersion(pluginVersion)
178
+ .build()
179
+
180
+ CloudX.initialize(config, object : CloudXInitializationListener {
181
+ override fun onInitialized(configuration: CloudXSdkConfiguration) {
182
+ initState = InitState.INITIALIZED
183
+ setupOrientationListener()
184
+ val result = Arguments.createMap()
185
+ result.putBoolean("success", true)
186
+ result.putString("message", "SDK initialized successfully")
187
+ promise.resolve(result)
188
+ }
189
+
190
+ override fun onInitializationFailed(cloudXError: CloudXError) {
191
+ Log.e(TAG, "CloudX SDK initialization failed: ${cloudXError.message}")
192
+ initState = InitState.IDLE
193
+ promise.reject(cloudXError.code.name, cloudXError.message, null)
194
+ }
195
+ })
196
+ }
197
+
198
+ fun isInitialized(promise: Promise) {
199
+ promise.resolve(CloudX.isInitialized())
200
+ }
201
+
202
+ fun isTablet(promise: Promise) {
203
+ val isTablet = reactApplicationContext.resources.configuration.smallestScreenWidthDp >= 600
204
+ promise.resolve(isTablet)
205
+ }
206
+
207
+ fun getVersion(promise: Promise) {
208
+ promise.resolve(CloudX.getVersion())
209
+ }
210
+
211
+ // =========================================================================
212
+ // Configuration
213
+ // =========================================================================
214
+
215
+ fun setMinLogLevel(level: String) {
216
+ val logLevel = when (level.lowercase()) {
217
+ "verbose" -> CloudXLogLevel.VERBOSE
218
+ "debug" -> CloudXLogLevel.DEBUG
219
+ "info" -> CloudXLogLevel.INFO
220
+ "warn" -> CloudXLogLevel.WARN
221
+ "error" -> CloudXLogLevel.ERROR
222
+ "none" -> CloudXLogLevel.NONE
223
+ else -> CloudXLogLevel.INFO
224
+ }
225
+ CloudX.setMinLogLevel(logLevel)
226
+ }
227
+
228
+ fun setVisualDebuggingEnabled(enabled: Boolean) {
229
+ // Visual debugging is iOS-only; no-op on Android
230
+ }
231
+
232
+ // =========================================================================
233
+ // User Targeting
234
+ // =========================================================================
235
+
236
+ fun setHashedUserID(hashedUserID: String) {
237
+ CloudX.setHashedUserId(hashedUserID)
238
+ }
239
+
240
+ fun setUserKeyValue(key: String, value: String) {
241
+ CloudX.setUserKeyValue(key, value)
242
+ }
243
+
244
+ fun setAppKeyValue(key: String, value: String) {
245
+ CloudX.setAppKeyValue(key, value)
246
+ }
247
+
248
+ fun clearAllKeyValues() {
249
+ CloudX.clearAllKeyValues()
250
+ }
251
+
252
+ // =========================================================================
253
+ // Programmatic Banner/MREC Positioning
254
+ // =========================================================================
255
+
256
+ private fun attachAdToWindow(adView: CloudXAdView, position: String, isMREC: Boolean, xOffsetDp: Float = 0f, yOffsetDp: Float = 0f) {
257
+ val activity = reactApplicationContext.currentActivity
258
+ if (activity == null) {
259
+ Log.e(TAG, "attachAdToWindow: no activity available")
260
+ return
261
+ }
262
+ val contentFrame = activity.window.decorView.findViewById<FrameLayout>(android.R.id.content)
263
+ if (contentFrame == null) {
264
+ Log.e(TAG, "attachAdToWindow: no content frame available")
265
+ return
266
+ }
267
+
268
+ (adView.parent as? ViewGroup)?.removeView(adView)
269
+ val density = reactApplicationContext.resources.displayMetrics.density
270
+
271
+ val isTablet = reactApplicationContext.resources.configuration.smallestScreenWidthDp >= 600
272
+ val bannerWidthPx = ((if (isMREC) 300 else if (isTablet) 728 else 320) * density).toInt()
273
+ val bannerHeightPx = ((if (isMREC) 250 else if (isTablet) 90 else 50) * density).toInt()
274
+
275
+ val decorView = activity.window.decorView
276
+ var insetTop = 0; var insetBottom = 0; var insetLeft = 0; var insetRight = 0
277
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
278
+ decorView.rootWindowInsets?.let { insets ->
279
+ val systemBars = insets.getInsets(WindowInsets.Type.systemBars())
280
+ insetTop = systemBars.top; insetBottom = systemBars.bottom
281
+ insetLeft = systemBars.left; insetRight = systemBars.right
282
+ }
283
+ } else {
284
+ @Suppress("DEPRECATION")
285
+ decorView.rootWindowInsets?.let { insets ->
286
+ insetTop = insets.systemWindowInsetTop; insetBottom = insets.systemWindowInsetBottom
287
+ insetLeft = insets.systemWindowInsetLeft; insetRight = insets.systemWindowInsetRight
288
+ }
289
+ }
290
+
291
+ val pos = position.uppercase()
292
+ val gravity = when (pos) {
293
+ "TOP_LEFT" -> Gravity.TOP or Gravity.START
294
+ "TOP_CENTER" -> Gravity.TOP or Gravity.CENTER_HORIZONTAL
295
+ "TOP_RIGHT" -> Gravity.TOP or Gravity.END
296
+ "CENTER_LEFT" -> Gravity.CENTER_VERTICAL or Gravity.START
297
+ "CENTERED" -> Gravity.CENTER
298
+ "CENTER_RIGHT" -> Gravity.CENTER_VERTICAL or Gravity.END
299
+ "BOTTOM_LEFT" -> Gravity.BOTTOM or Gravity.START
300
+ "BOTTOM_CENTER" -> Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
301
+ "BOTTOM_RIGHT" -> Gravity.BOTTOM or Gravity.END
302
+ else -> Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
303
+ }
304
+
305
+ val offsetXPx = (xOffsetDp * density).toInt()
306
+ val offsetYPx = (yOffsetDp * density).toInt()
307
+
308
+ val marginTop = (if (pos.startsWith("TOP")) insetTop else 0) + offsetYPx
309
+ val marginBottom = (if (pos.startsWith("BOTTOM")) insetBottom else 0) + offsetYPx
310
+ val marginLeft = (if (pos.endsWith("LEFT")) insetLeft else 0) + offsetXPx
311
+ val marginRight = (if (pos.endsWith("RIGHT")) insetRight else 0) + offsetXPx
312
+
313
+ val layoutParams = FrameLayout.LayoutParams(bannerWidthPx, bannerHeightPx).apply {
314
+ this.gravity = gravity
315
+ setMargins(marginLeft, marginTop, marginRight, marginBottom)
316
+ }
317
+
318
+ contentFrame.addView(adView, layoutParams)
319
+ adView.visibility = View.VISIBLE
320
+ adView.bringToFront()
321
+ }
322
+
323
+ // =========================================================================
324
+ // Banner (Programmatic)
325
+ // =========================================================================
326
+
327
+ // Use Handler(Looper.getMainLooper()) so the block executes even when
328
+ // currentActivity is null (e.g., during backgrounding or config changes).
329
+ fun createBanner(adUnitId: String, position: String) {
330
+ createBannerWithOffsets(adUnitId, position, 0.0, 0.0)
331
+ }
332
+
333
+ fun createBannerWithOffsets(adUnitId: String, position: String, xOffset: Double, yOffset: Double) {
334
+ Handler(Looper.getMainLooper()).post {
335
+ banners.remove(adUnitId)?.let { old ->
336
+ old.listener = null
337
+ old.revenueListener = null
338
+ old.stopAutoRefresh()
339
+ if (windowPositionedAds.remove(adUnitId)) {
340
+ (old.parent as? ViewGroup)?.removeView(old)
341
+ }
342
+ old.destroy()
343
+ }
344
+
345
+ val banner = CloudX.createBanner(adUnitId)
346
+ banner.listener = createBannerListener(adUnitId, false)
347
+ banner.revenueListener = createBannerRevenueListener(adUnitId, false)
348
+ banners[adUnitId] = banner
349
+ adViewPositions[adUnitId] = position
350
+ adViewOffsets[adUnitId] = Pair(xOffset.toFloat(), yOffset.toFloat())
351
+ attachAdToWindow(banner, position, false, xOffset.toFloat(), yOffset.toFloat())
352
+ windowPositionedAds.add(adUnitId)
353
+ banner.load()
354
+
355
+ if (adUnitIdsToShowAfterCreate.remove(adUnitId)) {
356
+ banner.visibility = View.VISIBLE
357
+ }
358
+ }
359
+ }
360
+
361
+ fun showBanner(adUnitId: String) {
362
+ val banner = banners[adUnitId]
363
+ if (banner != null) {
364
+ val activity = reactApplicationContext.currentActivity
365
+ if (activity == null) { Log.w(TAG, "showBanner: no activity"); return }
366
+ activity.runOnUiThread { banner.visibility = View.VISIBLE }
367
+ } else {
368
+ adUnitIdsToShowAfterCreate.add(adUnitId)
369
+ }
370
+ }
371
+
372
+ fun hideBanner(adUnitId: String) {
373
+ val activity = reactApplicationContext.currentActivity
374
+ if (activity == null) { Log.w(TAG, "hideBanner: no activity"); return }
375
+ activity.runOnUiThread { banners[adUnitId]?.visibility = View.GONE }
376
+ }
377
+
378
+ fun destroyBanner(adUnitId: String) {
379
+ Handler(Looper.getMainLooper()).post {
380
+ banners.remove(adUnitId)?.let {
381
+ it.listener = null
382
+ it.revenueListener = null
383
+ it.stopAutoRefresh()
384
+ if (windowPositionedAds.remove(adUnitId)) {
385
+ (it.parent as? ViewGroup)?.removeView(it)
386
+ }
387
+ it.destroy()
388
+ }
389
+ adViewPositions.remove(adUnitId)
390
+ adViewOffsets.remove(adUnitId)
391
+ adUnitIdsToShowAfterCreate.remove(adUnitId)
392
+ }
393
+ }
394
+
395
+ fun setBannerPlacement(adUnitId: String, placement: String?) {
396
+ val activity = reactApplicationContext.currentActivity
397
+ if (activity == null) { Log.w(TAG, "setBannerPlacement: no activity"); return }
398
+ activity.runOnUiThread { banners[adUnitId]?.setPlacement(placement) }
399
+ }
400
+
401
+ fun setBannerCustomData(adUnitId: String, customData: String?) {
402
+ val activity = reactApplicationContext.currentActivity
403
+ if (activity == null) { Log.w(TAG, "setBannerCustomData: no activity"); return }
404
+ activity.runOnUiThread { banners[adUnitId]?.setCustomData(customData) }
405
+ }
406
+
407
+ fun startBannerAutoRefresh(adUnitId: String) {
408
+ val activity = reactApplicationContext.currentActivity
409
+ if (activity == null) { Log.w(TAG, "startBannerAutoRefresh: no activity"); return }
410
+ activity.runOnUiThread { banners[adUnitId]?.startAutoRefresh() }
411
+ }
412
+
413
+ fun stopBannerAutoRefresh(adUnitId: String) {
414
+ val activity = reactApplicationContext.currentActivity
415
+ if (activity == null) { Log.w(TAG, "stopBannerAutoRefresh: no activity"); return }
416
+ activity.runOnUiThread { banners[adUnitId]?.stopAutoRefresh() }
417
+ }
418
+
419
+ // =========================================================================
420
+ // MREC (Programmatic)
421
+ // =========================================================================
422
+
423
+ fun createMRec(adUnitId: String, position: String) {
424
+ createMRecWithOffsets(adUnitId, position, 0.0, 0.0)
425
+ }
426
+
427
+ fun createMRecWithOffsets(adUnitId: String, position: String, xOffset: Double, yOffset: Double) {
428
+ Handler(Looper.getMainLooper()).post {
429
+ mrecs.remove(adUnitId)?.let { old ->
430
+ old.listener = null
431
+ old.revenueListener = null
432
+ old.stopAutoRefresh()
433
+ if (windowPositionedAds.remove(adUnitId)) {
434
+ (old.parent as? ViewGroup)?.removeView(old)
435
+ }
436
+ old.destroy()
437
+ }
438
+
439
+ val mrec = CloudX.createMREC(adUnitId)
440
+ mrec.listener = createBannerListener(adUnitId, true)
441
+ mrec.revenueListener = createBannerRevenueListener(adUnitId, true)
442
+ mrecs[adUnitId] = mrec
443
+ adViewPositions[adUnitId] = position
444
+ adViewOffsets[adUnitId] = Pair(xOffset.toFloat(), yOffset.toFloat())
445
+ attachAdToWindow(mrec, position, true, xOffset.toFloat(), yOffset.toFloat())
446
+ windowPositionedAds.add(adUnitId)
447
+ mrec.load()
448
+
449
+ if (adUnitIdsToShowAfterCreate.remove(adUnitId)) {
450
+ mrec.visibility = View.VISIBLE
451
+ }
452
+ }
453
+ }
454
+
455
+ fun showMRec(adUnitId: String) {
456
+ val mrec = mrecs[adUnitId]
457
+ if (mrec != null) {
458
+ val activity = reactApplicationContext.currentActivity
459
+ if (activity == null) { Log.w(TAG, "showMRec: no activity"); return }
460
+ activity.runOnUiThread { mrec.visibility = View.VISIBLE }
461
+ } else {
462
+ adUnitIdsToShowAfterCreate.add(adUnitId)
463
+ }
464
+ }
465
+
466
+ fun hideMRec(adUnitId: String) {
467
+ val activity = reactApplicationContext.currentActivity
468
+ if (activity == null) { Log.w(TAG, "hideMRec: no activity"); return }
469
+ activity.runOnUiThread { mrecs[adUnitId]?.visibility = View.GONE }
470
+ }
471
+
472
+ fun destroyMRec(adUnitId: String) {
473
+ Handler(Looper.getMainLooper()).post {
474
+ mrecs.remove(adUnitId)?.let {
475
+ it.listener = null
476
+ it.revenueListener = null
477
+ it.stopAutoRefresh()
478
+ if (windowPositionedAds.remove(adUnitId)) {
479
+ (it.parent as? ViewGroup)?.removeView(it)
480
+ }
481
+ it.destroy()
482
+ }
483
+ adViewPositions.remove(adUnitId)
484
+ adViewOffsets.remove(adUnitId)
485
+ adUnitIdsToShowAfterCreate.remove(adUnitId)
486
+ }
487
+ }
488
+
489
+ fun setMRecPlacement(adUnitId: String, placement: String?) {
490
+ val activity = reactApplicationContext.currentActivity
491
+ if (activity == null) { Log.w(TAG, "setMRecPlacement: no activity"); return }
492
+ activity.runOnUiThread { mrecs[adUnitId]?.setPlacement(placement) }
493
+ }
494
+
495
+ fun setMRecCustomData(adUnitId: String, customData: String?) {
496
+ val activity = reactApplicationContext.currentActivity
497
+ if (activity == null) { Log.w(TAG, "setMRecCustomData: no activity"); return }
498
+ activity.runOnUiThread { mrecs[adUnitId]?.setCustomData(customData) }
499
+ }
500
+
501
+ fun startMRecAutoRefresh(adUnitId: String) {
502
+ val activity = reactApplicationContext.currentActivity
503
+ if (activity == null) { Log.w(TAG, "startMRecAutoRefresh: no activity"); return }
504
+ activity.runOnUiThread { mrecs[adUnitId]?.startAutoRefresh() }
505
+ }
506
+
507
+ fun stopMRecAutoRefresh(adUnitId: String) {
508
+ val activity = reactApplicationContext.currentActivity
509
+ if (activity == null) { Log.w(TAG, "stopMRecAutoRefresh: no activity"); return }
510
+ activity.runOnUiThread { mrecs[adUnitId]?.stopAutoRefresh() }
511
+ }
512
+
513
+ // =========================================================================
514
+ // Dynamic Repositioning
515
+ // =========================================================================
516
+
517
+ fun updateBannerPosition(adUnitId: String, position: String) {
518
+ Handler(Looper.getMainLooper()).post {
519
+ val banner = banners[adUnitId] ?: return@post
520
+ adViewPositions[adUnitId] = position
521
+ val offsets = adViewOffsets[adUnitId] ?: Pair(0f, 0f)
522
+ attachAdToWindow(banner, position, false, offsets.first, offsets.second)
523
+ }
524
+ }
525
+
526
+ fun updateMRecPosition(adUnitId: String, position: String) {
527
+ Handler(Looper.getMainLooper()).post {
528
+ val mrec = mrecs[adUnitId] ?: return@post
529
+ adViewPositions[adUnitId] = position
530
+ val offsets = adViewOffsets[adUnitId] ?: Pair(0f, 0f)
531
+ attachAdToWindow(mrec, position, true, offsets.first, offsets.second)
532
+ }
533
+ }
534
+
535
+ fun updateBannerOffsets(adUnitId: String, xOffset: Double, yOffset: Double) {
536
+ Handler(Looper.getMainLooper()).post {
537
+ val banner = banners[adUnitId] ?: return@post
538
+ adViewOffsets[adUnitId] = Pair(xOffset.toFloat(), yOffset.toFloat())
539
+ val position = adViewPositions[adUnitId] ?: "BOTTOM_CENTER"
540
+ attachAdToWindow(banner, position, false, xOffset.toFloat(), yOffset.toFloat())
541
+ }
542
+ }
543
+
544
+ fun updateMRecOffsets(adUnitId: String, xOffset: Double, yOffset: Double) {
545
+ Handler(Looper.getMainLooper()).post {
546
+ val mrec = mrecs[adUnitId] ?: return@post
547
+ adViewOffsets[adUnitId] = Pair(xOffset.toFloat(), yOffset.toFloat())
548
+ val position = adViewPositions[adUnitId] ?: "BOTTOM_CENTER"
549
+ attachAdToWindow(mrec, position, true, xOffset.toFloat(), yOffset.toFloat())
550
+ }
551
+ }
552
+
553
+ // =========================================================================
554
+ // Background Color
555
+ // =========================================================================
556
+
557
+ fun setBannerBackgroundColor(adUnitId: String, hexColorString: String) {
558
+ Handler(Looper.getMainLooper()).post {
559
+ banners[adUnitId]?.setBackgroundColor(parseHexColor(hexColorString))
560
+ }
561
+ }
562
+
563
+ fun setMRecBackgroundColor(adUnitId: String, hexColorString: String) {
564
+ Handler(Looper.getMainLooper()).post {
565
+ mrecs[adUnitId]?.setBackgroundColor(parseHexColor(hexColorString))
566
+ }
567
+ }
568
+
569
+ private fun parseHexColor(hex: String): Int {
570
+ val cleaned = if (hex.startsWith("#")) hex else "#$hex"
571
+ return Color.parseColor(cleaned)
572
+ }
573
+
574
+ // =========================================================================
575
+ // Orientation Change Handler
576
+ // =========================================================================
577
+
578
+ private fun setupOrientationListener() {
579
+ if (orientationListener != null) return
580
+
581
+ orientationListener = object : OrientationEventListener(reactApplicationContext) {
582
+ override fun onOrientationChanged(orientation: Int) {
583
+ val activity = reactApplicationContext.currentActivity ?: return
584
+ @Suppress("DEPRECATION")
585
+ val newRotation = activity.windowManager.defaultDisplay.rotation
586
+ if (currentRotation != newRotation) {
587
+ currentRotation = newRotation
588
+ repositionAllAdViews()
589
+ }
590
+ }
591
+ }
592
+ orientationListener?.enable()
593
+ }
594
+
595
+ private fun repositionAllAdViews() {
596
+ Handler(Looper.getMainLooper()).post {
597
+ for ((adUnitId, banner) in banners) {
598
+ val position = adViewPositions[adUnitId] ?: continue
599
+ if (!windowPositionedAds.contains(adUnitId)) continue
600
+ val offsets = adViewOffsets[adUnitId] ?: Pair(0f, 0f)
601
+ attachAdToWindow(banner, position, false, offsets.first, offsets.second)
602
+ }
603
+ for ((adUnitId, mrec) in mrecs) {
604
+ val position = adViewPositions[adUnitId] ?: continue
605
+ if (!windowPositionedAds.contains(adUnitId)) continue
606
+ val offsets = adViewOffsets[adUnitId] ?: Pair(0f, 0f)
607
+ attachAdToWindow(mrec, position, true, offsets.first, offsets.second)
608
+ }
609
+ }
610
+ }
611
+
612
+ // =========================================================================
613
+ // Interstitial
614
+ // =========================================================================
615
+
616
+ fun loadInterstitial(adUnitId: String) {
617
+ Handler(Looper.getMainLooper()).post {
618
+ var interstitial = interstitials[adUnitId]
619
+ if (interstitial == null) {
620
+ interstitial = CloudX.createInterstitial(adUnitId)
621
+ interstitial.listener = createInterstitialListener(adUnitId)
622
+ interstitial.revenueListener = createFullscreenRevenueListener(adUnitId, ON_INTERSTITIAL_AD_REVENUE_PAID)
623
+ interstitials[adUnitId] = interstitial
624
+ }
625
+ interstitial.load()
626
+ }
627
+ }
628
+
629
+ fun isInterstitialReady(adUnitId: String, promise: Promise) {
630
+ promise.resolve(interstitials[adUnitId]?.isAdReady ?: false)
631
+ }
632
+
633
+ fun showInterstitial(adUnitId: String, placement: String?, customData: String?) {
634
+ val activity = reactApplicationContext.currentActivity
635
+ if (activity == null) {
636
+ Log.e(TAG, "showInterstitial: no activity available")
637
+ sendEvent(ON_INTERSTITIAL_DISPLAY_FAILED_EVENT, Arguments.createMap().apply {
638
+ putString("adUnitId", adUnitId)
639
+ putInt("code", -1)
640
+ putString("message", "No activity available")
641
+ })
642
+ return
643
+ }
644
+ val interstitial = interstitials[adUnitId]
645
+ if (interstitial == null) {
646
+ sendEvent(ON_INTERSTITIAL_DISPLAY_FAILED_EVENT, Arguments.createMap().apply {
647
+ putString("adUnitId", adUnitId)
648
+ putInt("code", -1)
649
+ putString("message", "Interstitial not loaded")
650
+ })
651
+ return
652
+ }
653
+ // Show unconditionally — the native SDK fires onAdDisplayFailed if the ad
654
+ // cannot be presented, ensuring JS always receives a display-failed event.
655
+ activity.runOnUiThread { interstitial.show(activity, placement, customData) }
656
+ }
657
+
658
+ fun destroyInterstitial(adUnitId: String) {
659
+ Handler(Looper.getMainLooper()).post {
660
+ interstitials.remove(adUnitId)?.let {
661
+ it.listener = null
662
+ it.revenueListener = null
663
+ it.destroy()
664
+ }
665
+ }
666
+ }
667
+
668
+ // =========================================================================
669
+ // Rewarded
670
+ // =========================================================================
671
+
672
+ fun loadRewarded(adUnitId: String) {
673
+ Handler(Looper.getMainLooper()).post {
674
+ var rewarded = rewardeds[adUnitId]
675
+ if (rewarded == null) {
676
+ rewarded = CloudX.createRewarded(adUnitId)
677
+ rewarded.listener = createRewardedListener(adUnitId)
678
+ rewarded.revenueListener = createFullscreenRevenueListener(adUnitId, ON_REWARDED_AD_REVENUE_PAID)
679
+ rewardeds[adUnitId] = rewarded
680
+ }
681
+ rewarded.load()
682
+ }
683
+ }
684
+
685
+ fun isRewardedReady(adUnitId: String, promise: Promise) {
686
+ promise.resolve(rewardeds[adUnitId]?.isAdReady ?: false)
687
+ }
688
+
689
+ fun showRewarded(adUnitId: String, placement: String?, customData: String?) {
690
+ val activity = reactApplicationContext.currentActivity
691
+ if (activity == null) {
692
+ Log.e(TAG, "showRewarded: no activity available")
693
+ sendEvent(ON_REWARDED_AD_DISPLAY_FAILED_EVENT, Arguments.createMap().apply {
694
+ putString("adUnitId", adUnitId)
695
+ putInt("code", -1)
696
+ putString("message", "No activity available")
697
+ })
698
+ return
699
+ }
700
+ val rewarded = rewardeds[adUnitId]
701
+ if (rewarded == null) {
702
+ sendEvent(ON_REWARDED_AD_DISPLAY_FAILED_EVENT, Arguments.createMap().apply {
703
+ putString("adUnitId", adUnitId)
704
+ putInt("code", -1)
705
+ putString("message", "Rewarded ad not loaded")
706
+ })
707
+ return
708
+ }
709
+ // Show unconditionally — the native SDK fires onAdDisplayFailed if the ad
710
+ // cannot be presented, ensuring JS always receives a display-failed event.
711
+ activity.runOnUiThread { rewarded.show(activity, placement, customData) }
712
+ }
713
+
714
+ fun destroyRewarded(adUnitId: String) {
715
+ Handler(Looper.getMainLooper()).post {
716
+ rewardeds.remove(adUnitId)?.let {
717
+ it.listener = null
718
+ it.revenueListener = null
719
+ it.destroy()
720
+ }
721
+ }
722
+ }
723
+
724
+ // =========================================================================
725
+ // Event Helpers
726
+ // =========================================================================
727
+
728
+ private fun sendEvent(eventName: String, data: WritableMap) {
729
+ try {
730
+ reactApplicationContext
731
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
732
+ ?.emit(eventName, data)
733
+ } catch (e: Exception) {
734
+ Log.e(TAG, "Failed to send event: $eventName", e)
735
+ }
736
+ }
737
+
738
+ private fun adInfoMap(ad: CloudXAd): WritableMap {
739
+ val map = Arguments.createMap()
740
+ map.putString("adUnitId", ad.adUnitId)
741
+ map.putString("adFormat", ad.adFormat.name)
742
+ map.putString("networkName", ad.networkName)
743
+ map.putString("networkPlacement", ad.networkPlacement ?: "")
744
+ map.putString("placement", ad.placement ?: "")
745
+ map.putDouble("revenue", ad.revenue)
746
+ return map
747
+ }
748
+
749
+ private fun errorMap(adUnitId: String, error: CloudXError): WritableMap {
750
+ val map = Arguments.createMap()
751
+ map.putString("adUnitId", adUnitId)
752
+ map.putInt("code", error.code.code)
753
+ map.putString("message", error.message)
754
+ return map
755
+ }
756
+
757
+ // =========================================================================
758
+ // Listener Factories
759
+ // =========================================================================
760
+
761
+ private fun createInterstitialListener(adUnitId: String): CloudXInterstitialListener {
762
+ return object : CloudXInterstitialListener {
763
+ override fun onAdLoaded(cloudXAd: CloudXAd) {
764
+ sendEvent(ON_INTERSTITIAL_LOADED_EVENT, adInfoMap(cloudXAd))
765
+ }
766
+ override fun onAdLoadFailed(failedAdUnitId: String, cloudXError: CloudXError) {
767
+ sendEvent(ON_INTERSTITIAL_LOAD_FAILED_EVENT, errorMap(failedAdUnitId, cloudXError))
768
+ }
769
+ override fun onAdClicked(cloudXAd: CloudXAd) {
770
+ sendEvent(ON_INTERSTITIAL_CLICKED_EVENT, adInfoMap(cloudXAd))
771
+ }
772
+ override fun onAdDisplayed(cloudXAd: CloudXAd) {
773
+ sendEvent(ON_INTERSTITIAL_DISPLAYED_EVENT, adInfoMap(cloudXAd))
774
+ }
775
+ override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
776
+ val data = adInfoMap(cloudXAd)
777
+ data.putInt("code", cloudXError.code.code)
778
+ data.putString("message", cloudXError.message)
779
+ sendEvent(ON_INTERSTITIAL_DISPLAY_FAILED_EVENT, data)
780
+ }
781
+ override fun onAdHidden(cloudXAd: CloudXAd) {
782
+ sendEvent(ON_INTERSTITIAL_HIDDEN_EVENT, adInfoMap(cloudXAd))
783
+ }
784
+ }
785
+ }
786
+
787
+ private fun createRewardedListener(adUnitId: String): CloudXRewardedListener {
788
+ return object : CloudXRewardedListener {
789
+ override fun onAdLoaded(cloudXAd: CloudXAd) {
790
+ sendEvent(ON_REWARDED_AD_LOADED_EVENT, adInfoMap(cloudXAd))
791
+ }
792
+ override fun onAdLoadFailed(failedAdUnitId: String, cloudXError: CloudXError) {
793
+ sendEvent(ON_REWARDED_AD_LOAD_FAILED_EVENT, errorMap(failedAdUnitId, cloudXError))
794
+ }
795
+ override fun onAdClicked(cloudXAd: CloudXAd) {
796
+ sendEvent(ON_REWARDED_AD_CLICKED_EVENT, adInfoMap(cloudXAd))
797
+ }
798
+ override fun onAdDisplayed(cloudXAd: CloudXAd) {
799
+ sendEvent(ON_REWARDED_AD_DISPLAYED_EVENT, adInfoMap(cloudXAd))
800
+ }
801
+ override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
802
+ val data = adInfoMap(cloudXAd)
803
+ data.putInt("code", cloudXError.code.code)
804
+ data.putString("message", cloudXError.message)
805
+ sendEvent(ON_REWARDED_AD_DISPLAY_FAILED_EVENT, data)
806
+ }
807
+ override fun onAdHidden(cloudXAd: CloudXAd) {
808
+ sendEvent(ON_REWARDED_AD_HIDDEN_EVENT, adInfoMap(cloudXAd))
809
+ }
810
+ override fun onUserRewarded(cloudXAd: CloudXAd, reward: CloudXReward) {
811
+ val data = adInfoMap(cloudXAd)
812
+ data.putString("rewardLabel", reward.label)
813
+ data.putInt("rewardAmount", reward.amount)
814
+ sendEvent(ON_REWARDED_AD_RECEIVED_REWARD_EVENT, data)
815
+ }
816
+ }
817
+ }
818
+
819
+ private fun createBannerListener(adUnitId: String, isMrec: Boolean): CloudXAdViewListener {
820
+ val loadedEvent = if (isMrec) ON_MREC_AD_LOADED_EVENT else ON_BANNER_AD_LOADED_EVENT
821
+ val loadFailedEvent = if (isMrec) ON_MREC_AD_LOAD_FAILED_EVENT else ON_BANNER_AD_LOAD_FAILED_EVENT
822
+ val clickedEvent = if (isMrec) ON_MREC_AD_CLICKED_EVENT else ON_BANNER_AD_CLICKED_EVENT
823
+ val expandedEvent = if (isMrec) ON_MREC_AD_EXPANDED_EVENT else ON_BANNER_AD_EXPANDED_EVENT
824
+ val collapsedEvent = if (isMrec) ON_MREC_AD_COLLAPSED_EVENT else ON_BANNER_AD_COLLAPSED_EVENT
825
+
826
+ return object : CloudXAdViewListener {
827
+ override fun onAdLoaded(cloudXAd: CloudXAd) {
828
+ sendEvent(loadedEvent, adInfoMap(cloudXAd))
829
+ }
830
+ override fun onAdLoadFailed(failedAdUnitId: String, cloudXError: CloudXError) {
831
+ sendEvent(loadFailedEvent, errorMap(failedAdUnitId, cloudXError))
832
+ }
833
+ override fun onAdClicked(cloudXAd: CloudXAd) {
834
+ sendEvent(clickedEvent, adInfoMap(cloudXAd))
835
+ }
836
+ override fun onAdExpanded(cloudXAd: CloudXAd) {
837
+ sendEvent(expandedEvent, adInfoMap(cloudXAd))
838
+ }
839
+ override fun onAdCollapsed(cloudXAd: CloudXAd) {
840
+ sendEvent(collapsedEvent, adInfoMap(cloudXAd))
841
+ }
842
+ }
843
+ }
844
+
845
+ private fun createBannerRevenueListener(adUnitId: String, isMrec: Boolean): CloudXAdRevenueListener {
846
+ val revenueEvent = if (isMrec) ON_MREC_AD_REVENUE_PAID else ON_BANNER_AD_REVENUE_PAID
847
+ return object : CloudXAdRevenueListener {
848
+ override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
849
+ sendEvent(revenueEvent, adInfoMap(cloudXAd))
850
+ }
851
+ }
852
+ }
853
+
854
+ private fun createFullscreenRevenueListener(adUnitId: String, eventName: String): CloudXAdRevenueListener {
855
+ return object : CloudXAdRevenueListener {
856
+ override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
857
+ sendEvent(eventName, adInfoMap(cloudXAd))
858
+ }
859
+ }
860
+ }
861
+ }