cloudx-react-native 2.0.0-beta.1 → 2.0.0

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 (197) hide show
  1. package/README.md +420 -1046
  2. package/android/src/main/AndroidManifest.xml +0 -4
  3. package/android/src/main/java/com/cloudxreactnative/CloudXAdEvents.kt +39 -0
  4. package/android/src/main/java/com/cloudxreactnative/CloudXAdInfo.kt +32 -0
  5. package/android/src/main/java/com/cloudxreactnative/CloudXModuleImpl.kt +694 -0
  6. package/android/src/{newarch/com/cloudxreactnative/CloudXReactNativePackage.kt → main/java/com/cloudxreactnative/CloudXPackage.kt} +14 -13
  7. package/android/src/newarch/com/cloudxreactnative/{CloudXReactNativeModule.kt → CloudXModule.kt} +31 -34
  8. package/android/src/oldarch/com/cloudxreactnative/{CloudXReactNativeModule.kt → CloudXModule.kt} +27 -32
  9. package/cloudx-react-native.podspec +1 -1
  10. package/ios/CloudX.h +47 -0
  11. package/ios/CloudX.mm +1406 -0
  12. package/lib/commonjs/AppTrackingTransparency.js +13 -13
  13. package/lib/commonjs/AppTrackingTransparency.js.map +1 -1
  14. package/lib/commonjs/CloudX.js +16 -12
  15. package/lib/commonjs/CloudX.js.map +1 -1
  16. package/lib/commonjs/CloudXAdPosition.js +23 -0
  17. package/lib/commonjs/CloudXAdPosition.js.map +1 -0
  18. package/lib/commonjs/{BannerAd.js → CloudXBannerAd.js} +42 -37
  19. package/lib/commonjs/CloudXBannerAd.js.map +1 -0
  20. package/lib/commonjs/CloudXErrorCode.js +116 -0
  21. package/lib/commonjs/CloudXErrorCode.js.map +1 -0
  22. package/lib/commonjs/{InterstitialAd.js → CloudXInterstitialAd.js} +10 -10
  23. package/lib/commonjs/CloudXInterstitialAd.js.map +1 -0
  24. package/lib/commonjs/{MRecAd.js → CloudXMRECAd.js} +42 -37
  25. package/lib/commonjs/CloudXMRECAd.js.map +1 -0
  26. package/lib/commonjs/{RewardedAd.js → CloudXRewardedAd.js} +10 -10
  27. package/lib/commonjs/CloudXRewardedAd.js.map +1 -0
  28. package/lib/commonjs/EventEmitter.js +3 -3
  29. package/lib/commonjs/EventEmitter.js.map +1 -1
  30. package/lib/commonjs/hooks.js +25 -26
  31. package/lib/commonjs/hooks.js.map +1 -1
  32. package/lib/commonjs/index.js +27 -41
  33. package/lib/commonjs/index.js.map +1 -1
  34. package/lib/commonjs/specs/{NativeCloudXModule.js → NativeCloudX.js} +2 -2
  35. package/lib/commonjs/specs/NativeCloudX.js.map +1 -0
  36. package/lib/commonjs/types/AdProps.js +6 -0
  37. package/lib/commonjs/types/AdProps.js.map +1 -0
  38. package/lib/commonjs/types/InterstitialAd.js +6 -0
  39. package/lib/commonjs/types/InterstitialAd.js.map +1 -0
  40. package/lib/commonjs/types/MRECAd.js +6 -0
  41. package/lib/commonjs/types/MRECAd.js.map +1 -0
  42. package/lib/commonjs/types/ViewAd.js +6 -0
  43. package/lib/commonjs/types/ViewAd.js.map +1 -0
  44. package/lib/module/AppTrackingTransparency.js +12 -12
  45. package/lib/module/AppTrackingTransparency.js.map +1 -1
  46. package/lib/module/CloudX.js +16 -12
  47. package/lib/module/CloudX.js.map +1 -1
  48. package/lib/module/CloudXAdPosition.js +17 -0
  49. package/lib/module/CloudXAdPosition.js.map +1 -0
  50. package/lib/module/{BannerAd.js → CloudXBannerAd.js} +41 -36
  51. package/lib/module/CloudXBannerAd.js.map +1 -0
  52. package/lib/module/CloudXErrorCode.js +110 -0
  53. package/lib/module/CloudXErrorCode.js.map +1 -0
  54. package/lib/module/{InterstitialAd.js → CloudXInterstitialAd.js} +9 -9
  55. package/lib/module/CloudXInterstitialAd.js.map +1 -0
  56. package/lib/module/{MRecAd.js → CloudXMRECAd.js} +41 -36
  57. package/lib/module/CloudXMRECAd.js.map +1 -0
  58. package/lib/module/{RewardedAd.js → CloudXRewardedAd.js} +9 -9
  59. package/lib/module/CloudXRewardedAd.js.map +1 -0
  60. package/lib/module/EventEmitter.js +2 -2
  61. package/lib/module/EventEmitter.js.map +1 -1
  62. package/lib/module/hooks.js +14 -15
  63. package/lib/module/hooks.js.map +1 -1
  64. package/lib/module/index.js +9 -10
  65. package/lib/module/index.js.map +1 -1
  66. package/lib/module/specs/{NativeCloudXModule.js → NativeCloudX.js} +2 -2
  67. package/lib/module/specs/NativeCloudX.js.map +1 -0
  68. package/lib/module/types/AdProps.js +2 -0
  69. package/lib/module/types/AdProps.js.map +1 -0
  70. package/lib/module/types/InterstitialAd.js +2 -0
  71. package/lib/module/types/InterstitialAd.js.map +1 -0
  72. package/lib/module/types/MRECAd.js +2 -0
  73. package/lib/module/types/MRECAd.js.map +1 -0
  74. package/lib/module/types/ViewAd.js +2 -0
  75. package/lib/module/types/ViewAd.js.map +1 -0
  76. package/lib/typescript/src/AppTrackingTransparency.d.ts +4 -4
  77. package/lib/typescript/src/AppTrackingTransparency.d.ts.map +1 -1
  78. package/lib/typescript/src/CloudX.d.ts.map +1 -1
  79. package/lib/typescript/src/CloudXAdPosition.d.ts +16 -0
  80. package/lib/typescript/src/CloudXAdPosition.d.ts.map +1 -0
  81. package/lib/typescript/src/CloudXBannerAd.d.ts +4 -0
  82. package/lib/typescript/src/CloudXBannerAd.d.ts.map +1 -0
  83. package/lib/typescript/src/{ErrorCode.d.ts → CloudXErrorCode.d.ts} +2 -2
  84. package/lib/typescript/src/CloudXErrorCode.d.ts.map +1 -0
  85. package/lib/typescript/src/CloudXInterstitialAd.d.ts +4 -0
  86. package/lib/typescript/src/CloudXInterstitialAd.d.ts.map +1 -0
  87. package/lib/typescript/src/CloudXMRECAd.d.ts +4 -0
  88. package/lib/typescript/src/CloudXMRECAd.d.ts.map +1 -0
  89. package/lib/typescript/src/CloudXRewardedAd.d.ts +4 -0
  90. package/lib/typescript/src/CloudXRewardedAd.d.ts.map +1 -0
  91. package/lib/typescript/src/EventEmitter.d.ts +4 -2
  92. package/lib/typescript/src/EventEmitter.d.ts.map +1 -1
  93. package/lib/typescript/src/hooks.d.ts +7 -8
  94. package/lib/typescript/src/hooks.d.ts.map +1 -1
  95. package/lib/typescript/src/index.d.ts +12 -13
  96. package/lib/typescript/src/index.d.ts.map +1 -1
  97. package/lib/typescript/src/specs/{NativeCloudXModule.d.ts → NativeCloudX.d.ts} +47 -64
  98. package/lib/typescript/src/specs/NativeCloudX.d.ts.map +1 -0
  99. package/lib/typescript/src/types/AdEvent.d.ts +3 -3
  100. package/lib/typescript/src/types/AdEvent.d.ts.map +1 -1
  101. package/lib/typescript/src/types/AdInfo.d.ts +7 -7
  102. package/lib/typescript/src/types/AdInfo.d.ts.map +1 -1
  103. package/lib/typescript/src/types/AdProps.d.ts +21 -0
  104. package/lib/typescript/src/types/AdProps.d.ts.map +1 -0
  105. package/lib/typescript/src/types/BannerAd.d.ts +8 -34
  106. package/lib/typescript/src/types/BannerAd.d.ts.map +1 -1
  107. package/lib/typescript/src/types/CloudX.d.ts +7 -4
  108. package/lib/typescript/src/types/CloudX.d.ts.map +1 -1
  109. package/lib/typescript/src/types/Configuration.d.ts +7 -3
  110. package/lib/typescript/src/types/Configuration.d.ts.map +1 -1
  111. package/lib/typescript/src/types/FullscreenAd.d.ts +10 -9
  112. package/lib/typescript/src/types/FullscreenAd.d.ts.map +1 -1
  113. package/lib/typescript/src/types/InterstitialAd.d.ts +8 -0
  114. package/lib/typescript/src/types/InterstitialAd.d.ts.map +1 -0
  115. package/lib/typescript/src/types/MRECAd.d.ts +13 -0
  116. package/lib/typescript/src/types/MRECAd.d.ts.map +1 -0
  117. package/lib/typescript/src/types/RewardedAd.d.ts +5 -4
  118. package/lib/typescript/src/types/RewardedAd.d.ts.map +1 -1
  119. package/lib/typescript/src/types/ViewAd.d.ts +29 -0
  120. package/lib/typescript/src/types/ViewAd.d.ts.map +1 -0
  121. package/lib/typescript/src/types/index.d.ts +11 -7
  122. package/lib/typescript/src/types/index.d.ts.map +1 -1
  123. package/package.json +8 -10
  124. package/src/AppTrackingTransparency.ts +10 -10
  125. package/src/CloudX.ts +21 -16
  126. package/src/CloudXAdPosition.ts +16 -0
  127. package/src/{BannerAd.ts → CloudXBannerAd.ts} +69 -47
  128. package/src/{ErrorCode.ts → CloudXErrorCode.ts} +1 -1
  129. package/src/{InterstitialAd.ts → CloudXInterstitialAd.ts} +17 -17
  130. package/src/{MRecAd.ts → CloudXMRECAd.ts} +69 -47
  131. package/src/{RewardedAd.ts → CloudXRewardedAd.ts} +18 -18
  132. package/src/EventEmitter.ts +4 -4
  133. package/src/hooks.ts +20 -22
  134. package/src/index.ts +27 -24
  135. package/src/specs/{NativeCloudXModule.ts → NativeCloudX.ts} +61 -82
  136. package/src/types/AdEvent.ts +3 -3
  137. package/src/types/AdInfo.ts +7 -7
  138. package/src/types/AdProps.ts +27 -0
  139. package/src/types/BannerAd.ts +8 -54
  140. package/src/types/CloudX.ts +10 -5
  141. package/src/types/Configuration.ts +8 -3
  142. package/src/types/FullscreenAd.ts +10 -9
  143. package/src/types/InterstitialAd.ts +8 -0
  144. package/src/types/MRECAd.ts +13 -0
  145. package/src/types/RewardedAd.ts +5 -4
  146. package/src/types/ViewAd.ts +38 -0
  147. package/src/types/index.ts +11 -7
  148. package/android/src/main/java/com/cloudxreactnative/CloudXAdViewEvent.kt +0 -15
  149. package/android/src/main/java/com/cloudxreactnative/CloudXBannerViewManagerImpl.kt +0 -472
  150. package/android/src/main/java/com/cloudxreactnative/CloudXReactNativeModuleImpl.kt +0 -861
  151. package/android/src/newarch/com/cloudxreactnative/CloudXBannerViewManager.kt +0 -90
  152. package/android/src/oldarch/com/cloudxreactnative/CloudXBannerViewManager.kt +0 -76
  153. package/android/src/oldarch/com/cloudxreactnative/CloudXReactNativePackage.kt +0 -16
  154. package/ios/RNCloudXBannerView.h +0 -51
  155. package/ios/RNCloudXBannerView.mm +0 -633
  156. package/ios/RNCloudXBannerViewManager.h +0 -11
  157. package/ios/RNCloudXBannerViewManager.mm +0 -60
  158. package/ios/RNCloudXModule.h +0 -30
  159. package/ios/RNCloudXModule.mm +0 -1088
  160. package/lib/commonjs/AdView.js +0 -151
  161. package/lib/commonjs/AdView.js.map +0 -1
  162. package/lib/commonjs/BannerAd.js.map +0 -1
  163. package/lib/commonjs/ErrorCode.js +0 -116
  164. package/lib/commonjs/ErrorCode.js.map +0 -1
  165. package/lib/commonjs/InterstitialAd.js.map +0 -1
  166. package/lib/commonjs/MRecAd.js.map +0 -1
  167. package/lib/commonjs/RewardedAd.js.map +0 -1
  168. package/lib/commonjs/specs/CloudXAdViewNativeComponent.js +0 -26
  169. package/lib/commonjs/specs/CloudXAdViewNativeComponent.js.map +0 -1
  170. package/lib/commonjs/specs/NativeCloudXModule.js.map +0 -1
  171. package/lib/module/AdView.js +0 -144
  172. package/lib/module/AdView.js.map +0 -1
  173. package/lib/module/BannerAd.js.map +0 -1
  174. package/lib/module/ErrorCode.js +0 -110
  175. package/lib/module/ErrorCode.js.map +0 -1
  176. package/lib/module/InterstitialAd.js.map +0 -1
  177. package/lib/module/MRecAd.js.map +0 -1
  178. package/lib/module/RewardedAd.js.map +0 -1
  179. package/lib/module/specs/CloudXAdViewNativeComponent.js +0 -20
  180. package/lib/module/specs/CloudXAdViewNativeComponent.js.map +0 -1
  181. package/lib/module/specs/NativeCloudXModule.js.map +0 -1
  182. package/lib/typescript/src/AdView.d.ts +0 -69
  183. package/lib/typescript/src/AdView.d.ts.map +0 -1
  184. package/lib/typescript/src/BannerAd.d.ts +0 -4
  185. package/lib/typescript/src/BannerAd.d.ts.map +0 -1
  186. package/lib/typescript/src/ErrorCode.d.ts.map +0 -1
  187. package/lib/typescript/src/InterstitialAd.d.ts +0 -4
  188. package/lib/typescript/src/InterstitialAd.d.ts.map +0 -1
  189. package/lib/typescript/src/MRecAd.d.ts +0 -4
  190. package/lib/typescript/src/MRecAd.d.ts.map +0 -1
  191. package/lib/typescript/src/RewardedAd.d.ts +0 -4
  192. package/lib/typescript/src/RewardedAd.d.ts.map +0 -1
  193. package/lib/typescript/src/specs/CloudXAdViewNativeComponent.d.ts +0 -61
  194. package/lib/typescript/src/specs/CloudXAdViewNativeComponent.d.ts.map +0 -1
  195. package/lib/typescript/src/specs/NativeCloudXModule.d.ts.map +0 -1
  196. package/src/AdView.tsx +0 -213
  197. package/src/specs/CloudXAdViewNativeComponent.ts +0 -82
package/ios/CloudX.mm ADDED
@@ -0,0 +1,1406 @@
1
+ //
2
+ // CloudX.mm
3
+ // cloudx-react-native
4
+ //
5
+
6
+ #import "CloudX.h"
7
+ #import <React/RCTUtils.h>
8
+
9
+
10
+ @interface UIColor (CLXUtils)
11
+ + (nullable UIColor *)clx_colorWithHexString:(NSString *)hexString;
12
+ @end
13
+
14
+ @interface CloudX()
15
+
16
+ // A simple BOOL tracks initialization state. Concurrent initialize calls are serialized
17
+ // by methodQueue (main queue), making the check-then-set atomic. The "initializing"
18
+ // middle state is unnecessary here because
19
+ // CloudXCore's own completion block handles any duplicate init attempts internally.
20
+ @property (nonatomic, assign, getter=isPluginInitialized) BOOL pluginInitialized;
21
+
22
+ // Fullscreen ad storage — lazily created on first load/show, keyed by ad unit ID.
23
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, CLXInterstitial *> *interstitials;
24
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, CLXRewarded *> *rewardedAds;
25
+
26
+ // Overlay banner/MREC state — each ad unit ID owns exactly one CLXBannerAdView,
27
+ // positioned window-level via NSLayoutConstraints against the root view controller.
28
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, CLXBannerAdView *> *adViews;
29
+ // CLXBannerType stored as NSNumber so didFailToLoadAd: can route the event to the
30
+ // correct JS listener without a CLXAd object (that callback only receives adUnitId).
31
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *adViewAdFormats;
32
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *adViewPositions;
33
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSValue *> *adViewOffsets;
34
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSArray<NSLayoutConstraint *> *> *adViewConstraints;
35
+ @property (nonatomic, strong) NSMutableArray<NSString *> *adUnitIdentifiersToShowAfterCreate;
36
+ // One safe-area background UIView per ad unit ID. Each banner/MREC that is positioned
37
+ // at TOP_CENTER or BOTTOM_CENTER with a publisher background color gets its own view so
38
+ // multiple simultaneous overlay ads don't compete for a single shared view.
39
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, UIView *> *safeAreaBackgrounds;
40
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, UIColor *> *publisherBannerBackgroundColors;
41
+
42
+ @property (nonatomic, assign) BOOL hasListeners;
43
+
44
+ @end
45
+
46
+ @implementation CloudX
47
+
48
+ static NSString *const SDK_TAG = @"CloudXCore";
49
+ static NSString *const TAG = @"CloudX";
50
+
51
+ static NSString *const ON_BANNER_AD_LOADED_EVENT = @"OnBannerAdLoadedEvent";
52
+ static NSString *const ON_BANNER_AD_LOAD_FAILED_EVENT = @"OnBannerAdLoadFailedEvent";
53
+ static NSString *const ON_BANNER_AD_CLICKED_EVENT = @"OnBannerAdClickedEvent";
54
+ static NSString *const ON_BANNER_AD_COLLAPSED_EVENT = @"OnBannerAdCollapsedEvent";
55
+ static NSString *const ON_BANNER_AD_EXPANDED_EVENT = @"OnBannerAdExpandedEvent";
56
+ static NSString *const ON_BANNER_AD_REVENUE_PAID = @"OnBannerAdRevenuePaid";
57
+
58
+ static NSString *const ON_MREC_AD_LOADED_EVENT = @"OnMRecAdLoadedEvent";
59
+ static NSString *const ON_MREC_AD_LOAD_FAILED_EVENT = @"OnMRecAdLoadFailedEvent";
60
+ static NSString *const ON_MREC_AD_CLICKED_EVENT = @"OnMRecAdClickedEvent";
61
+ static NSString *const ON_MREC_AD_COLLAPSED_EVENT = @"OnMRecAdCollapsedEvent";
62
+ static NSString *const ON_MREC_AD_EXPANDED_EVENT = @"OnMRecAdExpandedEvent";
63
+ static NSString *const ON_MREC_AD_REVENUE_PAID = @"OnMRecAdRevenuePaid";
64
+
65
+ static NSString *const ON_INTERSTITIAL_LOADED_EVENT = @"OnInterstitialLoadedEvent";
66
+ static NSString *const ON_INTERSTITIAL_LOAD_FAILED_EVENT = @"OnInterstitialLoadFailedEvent";
67
+ static NSString *const ON_INTERSTITIAL_CLICKED_EVENT = @"OnInterstitialClickedEvent";
68
+ static NSString *const ON_INTERSTITIAL_DISPLAYED_EVENT = @"OnInterstitialDisplayedEvent";
69
+ static NSString *const ON_INTERSTITIAL_DISPLAY_FAILED_EVENT = @"OnInterstitialDisplayFailedEvent";
70
+ static NSString *const ON_INTERSTITIAL_HIDDEN_EVENT = @"OnInterstitialHiddenEvent";
71
+ static NSString *const ON_INTERSTITIAL_AD_REVENUE_PAID = @"OnInterstitialAdRevenuePaid";
72
+
73
+ static NSString *const ON_REWARDED_AD_LOADED_EVENT = @"OnRewardedAdLoadedEvent";
74
+ static NSString *const ON_REWARDED_AD_LOAD_FAILED_EVENT = @"OnRewardedAdLoadFailedEvent";
75
+ static NSString *const ON_REWARDED_AD_CLICKED_EVENT = @"OnRewardedAdClickedEvent";
76
+ static NSString *const ON_REWARDED_AD_DISPLAYED_EVENT = @"OnRewardedAdDisplayedEvent";
77
+ static NSString *const ON_REWARDED_AD_DISPLAY_FAILED_EVENT = @"OnRewardedAdDisplayFailedEvent";
78
+ static NSString *const ON_REWARDED_AD_HIDDEN_EVENT = @"OnRewardedAdHiddenEvent";
79
+ static NSString *const ON_REWARDED_AD_RECEIVED_REWARD_EVENT = @"OnRewardedAdReceivedRewardEvent";
80
+ static NSString *const ON_REWARDED_AD_REVENUE_PAID = @"OnRewardedAdRevenuePaid";
81
+
82
+ static NSString *const TOP_CENTER = @"top_center";
83
+ static NSString *const TOP_LEFT = @"top_left";
84
+ static NSString *const TOP_RIGHT = @"top_right";
85
+ static NSString *const CENTERED = @"centered";
86
+ static NSString *const CENTER_LEFT = @"center_left";
87
+ static NSString *const CENTER_RIGHT = @"center_right";
88
+ static NSString *const BOTTOM_LEFT = @"bottom_left";
89
+ static NSString *const BOTTOM_CENTER = @"bottom_center";
90
+ static NSString *const BOTTOM_RIGHT = @"bottom_right";
91
+
92
+ static CloudX *CloudXShared;
93
+
94
+ RCT_EXPORT_MODULE(CloudX)
95
+
96
+ + (BOOL)requiresMainQueueSetup
97
+ {
98
+ return YES;
99
+ }
100
+
101
+ - (dispatch_queue_t)methodQueue
102
+ {
103
+ return dispatch_get_main_queue();
104
+ }
105
+
106
+ + (CloudX *)shared
107
+ {
108
+ return CloudXShared;
109
+ }
110
+
111
+ - (instancetype)init
112
+ {
113
+ self = [super init];
114
+ if ( self )
115
+ {
116
+ CloudXShared = self;
117
+
118
+ self.interstitials = [NSMutableDictionary dictionaryWithCapacity: 2];
119
+ self.rewardedAds = [NSMutableDictionary dictionaryWithCapacity: 2];
120
+
121
+ self.adViews = [NSMutableDictionary dictionaryWithCapacity: 2];
122
+ self.adViewAdFormats = [NSMutableDictionary dictionaryWithCapacity: 2];
123
+ self.adViewPositions = [NSMutableDictionary dictionaryWithCapacity: 2];
124
+ self.adViewOffsets = [NSMutableDictionary dictionaryWithCapacity: 2];
125
+ self.adViewConstraints = [NSMutableDictionary dictionaryWithCapacity: 2];
126
+ self.adUnitIdentifiersToShowAfterCreate = [NSMutableArray arrayWithCapacity: 2];
127
+ self.safeAreaBackgrounds = [NSMutableDictionary dictionaryWithCapacity: 2];
128
+ self.publisherBannerBackgroundColors = [NSMutableDictionary dictionaryWithCapacity: 2];
129
+ }
130
+ return self;
131
+ }
132
+
133
+ #pragma mark - Initialization
134
+
135
+ RCT_EXPORT_METHOD(isInitialized:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
136
+ {
137
+ resolve(@([self isPluginInitialized] && [CloudXCore shared].isInitialized));
138
+ }
139
+
140
+ RCT_EXPORT_METHOD(initialize:(NSString *)pluginVersion appKey:(NSString *)appKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
141
+ {
142
+ if ( [self isPluginInitialized] )
143
+ {
144
+ resolve(@{@"success": @(YES)});
145
+ return;
146
+ }
147
+
148
+ self.pluginInitialized = YES;
149
+
150
+ [self log: @"Initializing CloudX React Native v%@...", pluginVersion];
151
+
152
+ if ( !appKey || appKey.length == 0 )
153
+ {
154
+ reject(RCTErrorUnspecified, @"Unable to initialize CloudX SDK - no SDK key provided!", nil);
155
+ return;
156
+ }
157
+
158
+ CLXInitializationConfiguration *initConfig = [CLXInitializationConfiguration configurationWithAppKey: appKey builderBlock:^(CLXInitializationConfigurationBuilder *builder) {
159
+ builder.pluginVersion = [@"React-Native-" stringByAppendingString: pluginVersion];
160
+ }];
161
+
162
+ [[CloudXCore shared] initializeWithConfiguration: initConfig completion:^(CLXSdkConfiguration *sdkConfiguration, CLXError *error) {
163
+ if ( error )
164
+ {
165
+ NSString *message = error.userInfo[NSLocalizedDescriptionKey] ?: @"Unknown initialization error";
166
+ [self log: @"SDK initialization failed: %@", message];
167
+ resolve(@{@"success": @(NO), @"message": message});
168
+ return;
169
+ }
170
+ [self log: @"SDK initialized"];
171
+ resolve(@{@"success": @(YES)});
172
+ }];
173
+ }
174
+
175
+ #pragma mark - General Public API
176
+
177
+ RCT_EXPORT_METHOD(isTablet:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
178
+ {
179
+ resolve(@([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad));
180
+ }
181
+
182
+ RCT_EXPORT_METHOD(getVersion:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
183
+ {
184
+ resolve([CloudXCore shared].sdkVersion ?: @"");
185
+ }
186
+
187
+ RCT_EXPORT_METHOD(setMinLogLevel:(NSString *)level)
188
+ {
189
+ [CloudXCore setMinLogLevel: [self logLevelFromString: level]];
190
+ }
191
+
192
+ RCT_EXPORT_METHOD(setVisualDebuggingEnabled:(BOOL)enabled)
193
+ {
194
+ [CloudXCore setVisualDebuggingEnabled: enabled];
195
+ }
196
+
197
+ RCT_EXPORT_METHOD(setHashedUserID:(NSString *)hashedUserID)
198
+ {
199
+ [[CloudXCore shared] setHashedUserID: hashedUserID];
200
+ }
201
+
202
+ RCT_EXPORT_METHOD(setUserKeyValue:(NSString *)key value:(NSString *)value)
203
+ {
204
+ [[CloudXCore shared] setUserKeyValue: key value: value];
205
+ }
206
+
207
+ RCT_EXPORT_METHOD(setAppKeyValue:(NSString *)key value:(NSString *)value)
208
+ {
209
+ [[CloudXCore shared] setAppKeyValue: key value: value];
210
+ }
211
+
212
+ RCT_EXPORT_METHOD(clearAllKeyValues)
213
+ {
214
+ [[CloudXCore shared] clearAllKeyValues];
215
+ }
216
+
217
+ #pragma mark - Banners
218
+
219
+ RCT_EXPORT_METHOD(createBanner:(NSString *)adUnitIdentifier position:(NSString *)bannerPosition)
220
+ {
221
+ [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT atPosition: bannerPosition withOffset: CGPointZero];
222
+ }
223
+
224
+ RCT_EXPORT_METHOD(createBannerWithOffsets:(NSString *)adUnitIdentifier position:(NSString *)bannerPosition xOffset:(double)xOffset yOffset:(double)yOffset)
225
+ {
226
+ [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT atPosition: bannerPosition withOffset: CGPointMake(xOffset, yOffset)];
227
+ }
228
+
229
+ RCT_EXPORT_METHOD(setBannerBackgroundColor:(NSString *)adUnitIdentifier hexColorString:(NSString *)hexColorString)
230
+ {
231
+ [self setAdViewBackgroundColorForAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT hexColorCode: hexColorString];
232
+ }
233
+
234
+ RCT_EXPORT_METHOD(setBannerPlacement:(NSString *)adUnitIdentifier placement:(nullable NSString *)placement)
235
+ {
236
+ [self setAdViewPlacement: placement forAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
237
+ }
238
+
239
+ RCT_EXPORT_METHOD(setBannerCustomData:(NSString *)adUnitIdentifier customData:(nullable NSString *)customData)
240
+ {
241
+ [self setAdViewCustomData: customData forAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
242
+ }
243
+
244
+ RCT_EXPORT_METHOD(updateBannerPosition:(NSString *)adUnitIdentifier position:(NSString *)bannerPosition)
245
+ {
246
+ CGPoint existingOffset = [self.adViewOffsets[adUnitIdentifier] CGPointValue];
247
+ [self updateAdViewPosition: bannerPosition withOffset: existingOffset forAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
248
+ }
249
+
250
+ RCT_EXPORT_METHOD(updateBannerOffsets:(NSString *)adUnitIdentifier xOffset:(double)xOffset yOffset:(double)yOffset)
251
+ {
252
+ [self updateAdViewPosition: self.adViewPositions[adUnitIdentifier] withOffset: CGPointMake(xOffset, yOffset) forAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
253
+ }
254
+
255
+ RCT_EXPORT_METHOD(startBannerAutoRefresh:(NSString *)adUnitIdentifier)
256
+ {
257
+ [self startAutoRefresh: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
258
+ }
259
+
260
+ RCT_EXPORT_METHOD(stopBannerAutoRefresh:(NSString *)adUnitIdentifier)
261
+ {
262
+ [self stopAutoRefresh: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
263
+ }
264
+
265
+ RCT_EXPORT_METHOD(showBanner:(NSString *)adUnitIdentifier)
266
+ {
267
+ [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
268
+ }
269
+
270
+ RCT_EXPORT_METHOD(hideBanner:(NSString *)adUnitIdentifier)
271
+ {
272
+ [self hideAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
273
+ }
274
+
275
+ RCT_EXPORT_METHOD(destroyBanner:(NSString *)adUnitIdentifier)
276
+ {
277
+ [self destroyAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: DEVICE_SPECIFIC_ADVIEW_AD_FORMAT];
278
+ }
279
+
280
+ #pragma mark - MRECs
281
+
282
+ RCT_EXPORT_METHOD(createMREC:(NSString *)adUnitIdentifier position:(NSString *)mrecPosition)
283
+ {
284
+ [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC atPosition: mrecPosition withOffset: CGPointZero];
285
+ }
286
+
287
+ RCT_EXPORT_METHOD(createMRECWithOffsets:(NSString *)adUnitIdentifier position:(NSString *)mrecPosition xOffset:(double)xOffset yOffset:(double)yOffset)
288
+ {
289
+ [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC atPosition: mrecPosition withOffset: CGPointMake(xOffset, yOffset)];
290
+ }
291
+
292
+ RCT_EXPORT_METHOD(setMRECPlacement:(NSString *)adUnitIdentifier placement:(nullable NSString *)placement)
293
+ {
294
+ [self setAdViewPlacement: placement forAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
295
+ }
296
+
297
+ RCT_EXPORT_METHOD(setMRECCustomData:(NSString *)adUnitIdentifier customData:(nullable NSString *)customData)
298
+ {
299
+ [self setAdViewCustomData: customData forAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
300
+ }
301
+
302
+ RCT_EXPORT_METHOD(updateMRECPosition:(NSString *)adUnitIdentifier position:(NSString *)mrecPosition)
303
+ {
304
+ CGPoint existingOffset = [self.adViewOffsets[adUnitIdentifier] CGPointValue];
305
+ [self updateAdViewPosition: mrecPosition withOffset: existingOffset forAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
306
+ }
307
+
308
+ RCT_EXPORT_METHOD(updateMRECOffsets:(NSString *)adUnitIdentifier xOffset:(double)xOffset yOffset:(double)yOffset)
309
+ {
310
+ [self updateAdViewPosition: self.adViewPositions[adUnitIdentifier] withOffset: CGPointMake(xOffset, yOffset) forAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
311
+ }
312
+
313
+ RCT_EXPORT_METHOD(setMRECBackgroundColor:(NSString *)adUnitIdentifier hexColorString:(NSString *)hexColorString)
314
+ {
315
+ [self setAdViewBackgroundColorForAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC hexColorCode: hexColorString];
316
+ }
317
+
318
+ RCT_EXPORT_METHOD(startMRECAutoRefresh:(NSString *)adUnitIdentifier)
319
+ {
320
+ [self startAutoRefresh: adUnitIdentifier adFormat: CLXBannerTypeMREC];
321
+ }
322
+
323
+ RCT_EXPORT_METHOD(stopMRECAutoRefresh:(NSString *)adUnitIdentifier)
324
+ {
325
+ [self stopAutoRefresh: adUnitIdentifier adFormat: CLXBannerTypeMREC];
326
+ }
327
+
328
+ RCT_EXPORT_METHOD(showMREC:(NSString *)adUnitIdentifier)
329
+ {
330
+ [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
331
+ }
332
+
333
+ RCT_EXPORT_METHOD(hideMREC:(NSString *)adUnitIdentifier)
334
+ {
335
+ [self hideAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
336
+ }
337
+
338
+ RCT_EXPORT_METHOD(destroyMREC:(NSString *)adUnitIdentifier)
339
+ {
340
+ [self destroyAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: CLXBannerTypeMREC];
341
+ }
342
+
343
+ #pragma mark - Interstitials
344
+
345
+ RCT_EXPORT_METHOD(loadInterstitial:(NSString *)adUnitIdentifier)
346
+ {
347
+ [self log: @"🔴 loadInterstitial called from JS — adUnitId: %@", adUnitIdentifier];
348
+ CLXInterstitial *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier];
349
+ [interstitial load];
350
+ }
351
+
352
+ RCT_EXPORT_METHOD(isInterstitialReady:(NSString *)adUnitIdentifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
353
+ {
354
+ CLXInterstitial *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier];
355
+ resolve(@([interstitial isReady]));
356
+ }
357
+
358
+ RCT_EXPORT_METHOD(showInterstitial:(NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData)
359
+ {
360
+ UIViewController *vc = [self topViewController];
361
+ if ( !vc )
362
+ {
363
+ [self log: @"showInterstitial: no visible view controller — emitting display failure"];
364
+ NSDictionary *body = @{@"adUnitId": adUnitIdentifier ?: @"",
365
+ @"code": @(-1),
366
+ @"message": @"No visible view controller to present interstitial from"};
367
+ [self sendReactNativeEventWithName: ON_INTERSTITIAL_DISPLAY_FAILED_EVENT body: body];
368
+ return;
369
+ }
370
+
371
+ CLXInterstitial *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier];
372
+ [interstitial showFromViewController: vc placement: placement customData: customData];
373
+ }
374
+
375
+ RCT_EXPORT_METHOD(destroyInterstitial:(NSString *)adUnitIdentifier)
376
+ {
377
+ [self log: @"Destroying interstitial with ad unit identifier \"%@\"", adUnitIdentifier];
378
+
379
+ CLXInterstitial *interstitial = self.interstitials[adUnitIdentifier];
380
+ if ( interstitial )
381
+ {
382
+ interstitial.delegate = nil;
383
+ interstitial.revenueDelegate = nil;
384
+ [interstitial destroy];
385
+ [self.interstitials removeObjectForKey: adUnitIdentifier];
386
+ }
387
+ }
388
+
389
+ #pragma mark - Rewarded
390
+
391
+ RCT_EXPORT_METHOD(loadRewarded:(NSString *)adUnitIdentifier)
392
+ {
393
+ [self log: @"🔴 loadRewarded called from JS — adUnitId: %@", adUnitIdentifier];
394
+ CLXRewarded *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier];
395
+ [rewardedAd load];
396
+ }
397
+
398
+ RCT_EXPORT_METHOD(isRewardedReady:(NSString *)adUnitIdentifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
399
+ {
400
+ CLXRewarded *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier];
401
+ resolve(@([rewardedAd isReady]));
402
+ }
403
+
404
+ RCT_EXPORT_METHOD(showRewarded:(NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData)
405
+ {
406
+ UIViewController *vc = [self topViewController];
407
+ if ( !vc )
408
+ {
409
+ [self log: @"showRewarded: no visible view controller — emitting display failure"];
410
+ NSDictionary *body = @{@"adUnitId": adUnitIdentifier ?: @"",
411
+ @"code": @(-1),
412
+ @"message": @"No visible view controller to present rewarded ad from"};
413
+ [self sendReactNativeEventWithName: ON_REWARDED_AD_DISPLAY_FAILED_EVENT body: body];
414
+ return;
415
+ }
416
+
417
+ CLXRewarded *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier];
418
+ [rewardedAd showFromViewController: vc placement: placement customData: customData];
419
+ }
420
+
421
+ RCT_EXPORT_METHOD(destroyRewarded:(NSString *)adUnitIdentifier)
422
+ {
423
+ [self log: @"Destroying rewarded ad with ad unit identifier \"%@\"", adUnitIdentifier];
424
+
425
+ CLXRewarded *rewardedAd = self.rewardedAds[adUnitIdentifier];
426
+ if ( rewardedAd )
427
+ {
428
+ rewardedAd.delegate = nil;
429
+ rewardedAd.revenueDelegate = nil;
430
+ [rewardedAd destroy];
431
+ [self.rewardedAds removeObjectForKey: adUnitIdentifier];
432
+ }
433
+ }
434
+
435
+ #pragma mark - Ad Callbacks
436
+
437
+ - (void)didLoadAd:(CLXAd *)ad
438
+ {
439
+ dispatch_async(dispatch_get_main_queue(), ^{
440
+ NSString *name;
441
+ CLXAdFormat adFormat = ad.adFormat;
442
+ if ( adFormat == CLXAdFormatBanner || adFormat == CLXAdFormatMREC )
443
+ {
444
+ CLXBannerType bannerType = ( adFormat == CLXAdFormatMREC ) ? CLXBannerTypeMREC : CLXBannerTypeW320H50;
445
+ CLXBannerAdView *adView = [self retrieveAdViewForAdUnitIdentifier: ad.adUnitId adFormat: bannerType];
446
+ adView.userInteractionEnabled = YES;
447
+
448
+ name = ( adFormat == CLXAdFormatMREC ) ? ON_MREC_AD_LOADED_EVENT : ON_BANNER_AD_LOADED_EVENT;
449
+ [self positionAdViewForAd: ad];
450
+
451
+ if ( adView && adView.isHidden )
452
+ {
453
+ [adView stopAutoRefresh];
454
+ }
455
+ }
456
+ else if ( adFormat == CLXAdFormatInterstitial )
457
+ {
458
+ name = ON_INTERSTITIAL_LOADED_EVENT;
459
+ }
460
+ else if ( adFormat == CLXAdFormatRewarded )
461
+ {
462
+ name = ON_REWARDED_AD_LOADED_EVENT;
463
+ }
464
+ else
465
+ {
466
+ [self logInvalidAdFormat: adFormat];
467
+ return;
468
+ }
469
+
470
+ [self sendReactNativeEventWithName: name body: [self adInfoForAd: ad]];
471
+ });
472
+ }
473
+
474
+ - (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error
475
+ {
476
+ dispatch_async(dispatch_get_main_queue(), ^{
477
+ if ( !adUnitId )
478
+ {
479
+ [self log: @"didFailToLoadAd received nil adUnitId — broadcasting to all fullscreen listeners. Error: %@", error];
480
+ // Nil adUnitId means the core SDK couldn't resolve the ad unit.
481
+ // Broadcast failure to all registered fullscreen ads so JS pendingShow state resets.
482
+ for ( NSString *key in self.interstitials )
483
+ {
484
+ [self sendReactNativeEventWithName: ON_INTERSTITIAL_LOAD_FAILED_EVENT
485
+ body: [self adLoadFailedInfoForAd: key withError: error]];
486
+ }
487
+ for ( NSString *key in self.rewardedAds )
488
+ {
489
+ [self sendReactNativeEventWithName: ON_REWARDED_AD_LOAD_FAILED_EVENT
490
+ body: [self adLoadFailedInfoForAd: key withError: error]];
491
+ }
492
+ return;
493
+ }
494
+
495
+ NSString *name;
496
+ if ( self.adViews[adUnitId] )
497
+ {
498
+ CLXBannerType adFormat = (CLXBannerType)[self.adViewAdFormats[adUnitId] integerValue];
499
+ name = ( adFormat == CLXBannerTypeMREC ) ? ON_MREC_AD_LOAD_FAILED_EVENT : ON_BANNER_AD_LOAD_FAILED_EVENT;
500
+ }
501
+ else if ( self.interstitials[adUnitId] )
502
+ {
503
+ name = ON_INTERSTITIAL_LOAD_FAILED_EVENT;
504
+ }
505
+ else if ( self.rewardedAds[adUnitId] )
506
+ {
507
+ name = ON_REWARDED_AD_LOAD_FAILED_EVENT;
508
+ }
509
+ else
510
+ {
511
+ [self log: @"didFailToLoadAd — adUnitId '%@' not found in any dictionary (keys: adViews=%@, interstitials=%@, rewardedAds=%@)",
512
+ adUnitId, self.adViews.allKeys, self.interstitials.allKeys, self.rewardedAds.allKeys];
513
+ return;
514
+ }
515
+
516
+ [self sendReactNativeEventWithName: name body: [self adLoadFailedInfoForAd: adUnitId withError: error]];
517
+ });
518
+ }
519
+
520
+ - (void)didClickAd:(CLXAd *)ad
521
+ {
522
+ dispatch_async(dispatch_get_main_queue(), ^{
523
+ NSString *name;
524
+ CLXAdFormat adFormat = ad.adFormat;
525
+ if ( adFormat == CLXAdFormatBanner )
526
+ {
527
+ name = ON_BANNER_AD_CLICKED_EVENT;
528
+ }
529
+ else if ( adFormat == CLXAdFormatMREC )
530
+ {
531
+ name = ON_MREC_AD_CLICKED_EVENT;
532
+ }
533
+ else if ( adFormat == CLXAdFormatInterstitial )
534
+ {
535
+ name = ON_INTERSTITIAL_CLICKED_EVENT;
536
+ }
537
+ else if ( adFormat == CLXAdFormatRewarded )
538
+ {
539
+ name = ON_REWARDED_AD_CLICKED_EVENT;
540
+ }
541
+ else
542
+ {
543
+ [self logInvalidAdFormat: adFormat];
544
+ return;
545
+ }
546
+
547
+ [self sendReactNativeEventWithName: name body: [self adInfoForAd: ad]];
548
+ });
549
+ }
550
+
551
+ - (void)didDisplayAd:(CLXAd *)ad
552
+ {
553
+ dispatch_async(dispatch_get_main_queue(), ^{
554
+ CLXAdFormat adFormat = ad.adFormat;
555
+ if ( adFormat != CLXAdFormatInterstitial && adFormat != CLXAdFormatRewarded ) return;
556
+
557
+ NSString *name;
558
+ if ( adFormat == CLXAdFormatInterstitial )
559
+ {
560
+ name = ON_INTERSTITIAL_DISPLAYED_EVENT;
561
+ }
562
+ else
563
+ {
564
+ name = ON_REWARDED_AD_DISPLAYED_EVENT;
565
+ }
566
+
567
+ [self sendReactNativeEventWithName: name body: [self adInfoForAd: ad]];
568
+ });
569
+ }
570
+
571
+ - (void)didFailToDisplayAd:(CLXAd *)ad error:(CLXError *)error
572
+ {
573
+ dispatch_async(dispatch_get_main_queue(), ^{
574
+ CLXAdFormat adFormat = ad.adFormat;
575
+ if ( adFormat != CLXAdFormatInterstitial && adFormat != CLXAdFormatRewarded ) return;
576
+
577
+ NSString *name;
578
+ if ( adFormat == CLXAdFormatInterstitial )
579
+ {
580
+ name = ON_INTERSTITIAL_DISPLAY_FAILED_EVENT;
581
+ }
582
+ else
583
+ {
584
+ name = ON_REWARDED_AD_DISPLAY_FAILED_EVENT;
585
+ }
586
+
587
+ [self sendReactNativeEventWithName: name body: [self adDisplayFailedInfoForAd: ad withError: error]];
588
+ });
589
+ }
590
+
591
+ - (void)didHideAd:(CLXAd *)ad
592
+ {
593
+ dispatch_async(dispatch_get_main_queue(), ^{
594
+ CLXAdFormat adFormat = ad.adFormat;
595
+ if ( adFormat != CLXAdFormatInterstitial && adFormat != CLXAdFormatRewarded ) return;
596
+
597
+ NSString *name;
598
+ if ( adFormat == CLXAdFormatInterstitial )
599
+ {
600
+ name = ON_INTERSTITIAL_HIDDEN_EVENT;
601
+ }
602
+ else
603
+ {
604
+ name = ON_REWARDED_AD_HIDDEN_EVENT;
605
+ }
606
+
607
+ [self sendReactNativeEventWithName: name body: [self adInfoForAd: ad]];
608
+ });
609
+ }
610
+
611
+ - (void)didExpandAd:(CLXAd *)ad
612
+ {
613
+ dispatch_async(dispatch_get_main_queue(), ^{
614
+ CLXAdFormat adFormat = ad.adFormat;
615
+ if ( adFormat != CLXAdFormatBanner && adFormat != CLXAdFormatMREC )
616
+ {
617
+ [self logInvalidAdFormat: adFormat];
618
+ return;
619
+ }
620
+
621
+ [self sendReactNativeEventWithName: ( adFormat == CLXAdFormatMREC ) ? ON_MREC_AD_EXPANDED_EVENT : ON_BANNER_AD_EXPANDED_EVENT
622
+ body: [self adInfoForAd: ad]];
623
+ });
624
+ }
625
+
626
+ - (void)didCollapseAd:(CLXAd *)ad
627
+ {
628
+ dispatch_async(dispatch_get_main_queue(), ^{
629
+ CLXAdFormat adFormat = ad.adFormat;
630
+ if ( adFormat != CLXAdFormatBanner && adFormat != CLXAdFormatMREC )
631
+ {
632
+ [self logInvalidAdFormat: adFormat];
633
+ return;
634
+ }
635
+
636
+ [self sendReactNativeEventWithName: ( adFormat == CLXAdFormatMREC ) ? ON_MREC_AD_COLLAPSED_EVENT : ON_BANNER_AD_COLLAPSED_EVENT
637
+ body: [self adInfoForAd: ad]];
638
+ });
639
+ }
640
+
641
+ - (void)didPayRevenueForAd:(CLXAd *)ad
642
+ {
643
+ dispatch_async(dispatch_get_main_queue(), ^{
644
+ NSString *name;
645
+ CLXAdFormat adFormat = ad.adFormat;
646
+ if ( adFormat == CLXAdFormatBanner )
647
+ {
648
+ name = ON_BANNER_AD_REVENUE_PAID;
649
+ }
650
+ else if ( adFormat == CLXAdFormatMREC )
651
+ {
652
+ name = ON_MREC_AD_REVENUE_PAID;
653
+ }
654
+ else if ( adFormat == CLXAdFormatInterstitial )
655
+ {
656
+ name = ON_INTERSTITIAL_AD_REVENUE_PAID;
657
+ }
658
+ else if ( adFormat == CLXAdFormatRewarded )
659
+ {
660
+ name = ON_REWARDED_AD_REVENUE_PAID;
661
+ }
662
+ else
663
+ {
664
+ [self logInvalidAdFormat: adFormat];
665
+ return;
666
+ }
667
+
668
+ [self sendReactNativeEventWithName: name body: [self adInfoForAd: ad]];
669
+ });
670
+ }
671
+
672
+ - (void)didRewardUserForAd:(CLXAd *)ad withReward:(CLXReward *)reward
673
+ {
674
+ dispatch_async(dispatch_get_main_queue(), ^{
675
+ CLXAdFormat adFormat = ad.adFormat;
676
+ if ( adFormat != CLXAdFormatRewarded )
677
+ {
678
+ [self logInvalidAdFormat: adFormat];
679
+ return;
680
+ }
681
+
682
+ NSString *rewardLabel = reward ? reward.label : @"";
683
+ NSInteger rewardAmount = reward ? reward.amount : 0;
684
+
685
+ NSMutableDictionary *body = [@{@"rewardLabel": rewardLabel,
686
+ @"rewardAmount": @(rewardAmount)} mutableCopy];
687
+ [body addEntriesFromDictionary: [self adInfoForAd: ad]];
688
+
689
+ [self sendReactNativeEventWithName: ON_REWARDED_AD_RECEIVED_REWARD_EVENT body: body];
690
+ });
691
+ }
692
+
693
+ #pragma mark - Internal Methods
694
+
695
+ - (void)executeOnMainQueue:(dispatch_block_t)block
696
+ {
697
+ if ( [NSThread isMainThread] )
698
+ {
699
+ block();
700
+ }
701
+ else
702
+ {
703
+ dispatch_async(dispatch_get_main_queue(), block);
704
+ }
705
+ }
706
+
707
+ - (void)createAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset
708
+ {
709
+ [self executeOnMainQueue:^{
710
+ [self log: @"Creating %ld with ad unit identifier \"%@\", position: \"%@\", and offset: %@", (long)adFormat, adUnitIdentifier, adViewPosition, NSStringFromCGPoint(offset)];
711
+
712
+ CLXBannerAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: adViewPosition withOffset: offset];
713
+ adView.hidden = YES;
714
+ self.safeAreaBackgrounds[adUnitIdentifier].hidden = YES;
715
+
716
+ self.adViewAdFormats[adUnitIdentifier] = @(adFormat);
717
+ [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
718
+
719
+ [adView load];
720
+
721
+ // If showBanner/showMRec arrived before createBanner/createMRec finished,
722
+ // the identifier was queued; flush it now that the view exists.
723
+ if ( [self.adUnitIdentifiersToShowAfterCreate containsObject: adUnitIdentifier] )
724
+ {
725
+ [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
726
+ [self.adUnitIdentifiersToShowAfterCreate removeObject: adUnitIdentifier];
727
+ }
728
+ }];
729
+ }
730
+
731
+ - (void)setAdViewBackgroundColorForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat hexColorCode:(NSString *)hexColorCode
732
+ {
733
+ [self executeOnMainQueue:^{
734
+ [self log: @"Setting %ld with ad unit identifier \"%@\" to color: \"%@\"", (long)adFormat, adUnitIdentifier, hexColorCode];
735
+
736
+ UIColor *convertedColor;
737
+ // FF000000 is opaque black, which UIColor renders identically to clearColor
738
+ // when composited. Nudge to FF000001 so the view is visibly black, not transparent.
739
+ if ( [hexColorCode containsString: @"FF000000"] )
740
+ {
741
+ convertedColor = [UIColor clx_colorWithHexString: @"FF000001"];
742
+ }
743
+ else
744
+ {
745
+ convertedColor = [UIColor clx_colorWithHexString: hexColorCode];
746
+ }
747
+
748
+ CLXBannerAdView *view = [self retrieveOrCreateBannerViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
749
+ self.publisherBannerBackgroundColors[adUnitIdentifier] = convertedColor;
750
+ self.safeAreaBackgrounds[adUnitIdentifier].backgroundColor = view.backgroundColor = convertedColor;
751
+ }];
752
+ }
753
+
754
+ - (void)setAdViewPlacement:(nullable NSString *)placement forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
755
+ {
756
+ [self executeOnMainQueue:^{
757
+ [self log: @"Setting placement \"%@\" for %ld with ad unit identifier \"%@\"", placement, (long)adFormat, adUnitIdentifier];
758
+
759
+ CLXBannerAdView *adView = [self retrieveOrCreateBannerViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
760
+ adView.placement = placement;
761
+ }];
762
+ }
763
+
764
+ - (void)setAdViewCustomData:(nullable NSString *)customData forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
765
+ {
766
+ [self executeOnMainQueue:^{
767
+ [self log: @"Setting custom data \"%@\" for %ld with ad unit identifier \"%@\"", customData, (long)adFormat, adUnitIdentifier];
768
+
769
+ CLXBannerAdView *adView = [self retrieveOrCreateBannerViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
770
+ adView.customData = customData;
771
+ }];
772
+ }
773
+
774
+ - (void)updateAdViewPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
775
+ {
776
+ [self executeOnMainQueue:^{
777
+ self.adViewPositions[adUnitIdentifier] = adViewPosition;
778
+ self.adViewOffsets[adUnitIdentifier] = [NSValue valueWithCGPoint: offset];
779
+ [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
780
+ }];
781
+ }
782
+
783
+ - (void)startAutoRefresh:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
784
+ {
785
+ [self executeOnMainQueue:^{
786
+ [self log: @"Starting auto refresh %ld with ad unit identifier \"%@\"", (long)adFormat, adUnitIdentifier];
787
+
788
+ CLXBannerAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
789
+ [view startAutoRefresh];
790
+ }];
791
+ }
792
+
793
+ - (void)stopAutoRefresh:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
794
+ {
795
+ [self executeOnMainQueue:^{
796
+ [self log: @"Stopping auto refresh %ld with ad unit identifier \"%@\"", (long)adFormat, adUnitIdentifier];
797
+
798
+ CLXBannerAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
799
+ [view stopAutoRefresh];
800
+ }];
801
+ }
802
+
803
+ - (void)showAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
804
+ {
805
+ [self executeOnMainQueue:^{
806
+ [self log: @"Showing %ld with ad unit identifier \"%@\"", (long)adFormat, adUnitIdentifier];
807
+
808
+ CLXBannerAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
809
+ if ( !view )
810
+ {
811
+ [self log: @"%ld does not exist for ad unit identifier %@. Deferring show.", (long)adFormat, adUnitIdentifier];
812
+ [self.adUnitIdentifiersToShowAfterCreate addObject: adUnitIdentifier];
813
+ }
814
+
815
+ self.safeAreaBackgrounds[adUnitIdentifier].hidden = NO;
816
+ view.hidden = NO;
817
+
818
+ [view startAutoRefresh];
819
+ }];
820
+ }
821
+
822
+ - (void)hideAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
823
+ {
824
+ [self executeOnMainQueue:^{
825
+ [self log: @"Hiding %ld with ad unit identifier \"%@\"", (long)adFormat, adUnitIdentifier];
826
+ [self.adUnitIdentifiersToShowAfterCreate removeObject: adUnitIdentifier];
827
+
828
+ CLXBannerAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
829
+ view.hidden = YES;
830
+ self.safeAreaBackgrounds[adUnitIdentifier].hidden = YES;
831
+
832
+ [view stopAutoRefresh];
833
+ }];
834
+ }
835
+
836
+ - (void)destroyAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
837
+ {
838
+ [self executeOnMainQueue:^{
839
+ [self log: @"Destroying %ld with ad unit identifier \"%@\"", (long)adFormat, adUnitIdentifier];
840
+
841
+ [self.adUnitIdentifiersToShowAfterCreate removeObject: adUnitIdentifier];
842
+
843
+ CLXBannerAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
844
+ view.delegate = nil;
845
+ view.revenueDelegate = nil;
846
+
847
+ NSArray<NSLayoutConstraint *> *activeConstraints = self.adViewConstraints[adUnitIdentifier];
848
+ [NSLayoutConstraint deactivateConstraints: activeConstraints];
849
+
850
+ [view destroy];
851
+ [view removeFromSuperview];
852
+
853
+ UIView *safeAreaBg = self.safeAreaBackgrounds[adUnitIdentifier];
854
+ [NSLayoutConstraint deactivateConstraints: safeAreaBg.constraints];
855
+ [safeAreaBg removeFromSuperview];
856
+
857
+ [self.adViews removeObjectForKey: adUnitIdentifier];
858
+ [self.adViewPositions removeObjectForKey: adUnitIdentifier];
859
+ [self.adViewOffsets removeObjectForKey: adUnitIdentifier];
860
+ [self.adViewAdFormats removeObjectForKey: adUnitIdentifier];
861
+ [self.adViewConstraints removeObjectForKey: adUnitIdentifier];
862
+ [self.safeAreaBackgrounds removeObjectForKey: adUnitIdentifier];
863
+ [self.publisherBannerBackgroundColors removeObjectForKey: adUnitIdentifier];
864
+ }];
865
+ }
866
+
867
+ - (CLXInterstitial *)retrieveInterstitialForAdUnitIdentifier:(NSString *)adUnitIdentifier
868
+ {
869
+ CLXInterstitial *result = self.interstitials[adUnitIdentifier];
870
+ if ( !result )
871
+ {
872
+ result = [[CloudXCore shared] createInterstitialWithAdUnitId: adUnitIdentifier];
873
+ result.delegate = self;
874
+ result.revenueDelegate = self;
875
+
876
+ self.interstitials[adUnitIdentifier] = result;
877
+ }
878
+
879
+ return result;
880
+ }
881
+
882
+ - (CLXRewarded *)retrieveRewardedAdForAdUnitIdentifier:(NSString *)adUnitIdentifier
883
+ {
884
+ CLXRewarded *result = self.rewardedAds[adUnitIdentifier];
885
+ if ( !result )
886
+ {
887
+ result = [[CloudXCore shared] createRewardedWithAdUnitId: adUnitIdentifier];
888
+ result.delegate = self;
889
+ result.revenueDelegate = self;
890
+
891
+ self.rewardedAds[adUnitIdentifier] = result;
892
+ }
893
+
894
+ return result;
895
+ }
896
+
897
+ - (nullable CLXBannerAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
898
+ {
899
+ return [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: nil withOffset: CGPointZero];
900
+ }
901
+
902
+ - (CLXBannerAdView *)retrieveOrCreateBannerViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
903
+ {
904
+ NSString *resolvedPosition = self.adViewPositions[adUnitIdentifier];
905
+ if ( !resolvedPosition || resolvedPosition.length == 0 )
906
+ {
907
+ // Use a stable default when setters run before create/show.
908
+ resolvedPosition = CENTERED;
909
+ }
910
+
911
+ NSValue *resolvedOffsetValue = self.adViewOffsets[adUnitIdentifier];
912
+ CGPoint resolvedOffset = resolvedOffsetValue ? [resolvedOffsetValue CGPointValue] : CGPointZero;
913
+
914
+ return [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier
915
+ adFormat: adFormat
916
+ atPosition: resolvedPosition
917
+ withOffset: resolvedOffset];
918
+ }
919
+
920
+ - (nullable CLXBannerAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat atPosition:(nullable NSString *)adViewPosition withOffset:(CGPoint)offset
921
+ {
922
+ if ( ![NSThread isMainThread] )
923
+ {
924
+ __block CLXBannerAdView *result = nil;
925
+ dispatch_sync(dispatch_get_main_queue(), ^{
926
+ result = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: adViewPosition withOffset: offset];
927
+ });
928
+ return result;
929
+ }
930
+
931
+ CLXBannerAdView *result = self.adViews[adUnitIdentifier];
932
+ // Only create a new view when a position is supplied; nil position means
933
+ // "read-only" lookup (e.g. from delegate callbacks or setter helpers).
934
+ if ( !result && adViewPosition )
935
+ {
936
+ if ( adFormat == CLXBannerTypeMREC )
937
+ {
938
+ result = [[CloudXCore shared] createMRECWithAdUnitId: adUnitIdentifier];
939
+ }
940
+ else
941
+ {
942
+ result = [[CloudXCore shared] createBannerWithAdUnitId: adUnitIdentifier];
943
+ }
944
+
945
+ result.delegate = self;
946
+ result.revenueDelegate = self;
947
+ result.userInteractionEnabled = NO;
948
+ result.translatesAutoresizingMaskIntoConstraints = NO;
949
+
950
+ self.adViews[adUnitIdentifier] = result;
951
+ self.adViewPositions[adUnitIdentifier] = adViewPosition;
952
+ self.adViewOffsets[adUnitIdentifier] = [NSValue valueWithCGPoint: offset];
953
+
954
+ UIView *rootView = [self topViewController].view;
955
+ if ( rootView )
956
+ {
957
+ [rootView addSubview: result];
958
+ }
959
+ else
960
+ {
961
+ [self log: @"retrieveAdView: no visible root view for ad unit \"%@\"", adUnitIdentifier];
962
+ }
963
+ }
964
+
965
+ return result;
966
+ }
967
+
968
+ - (void)positionAdViewForAd:(CLXAd *)ad
969
+ {
970
+ [self executeOnMainQueue:^{
971
+ CLXBannerType bannerType = ( ad.adFormat == CLXAdFormatMREC ) ? CLXBannerTypeMREC : CLXBannerTypeW320H50;
972
+ [self positionAdViewForAdUnitIdentifier: ad.adUnitId adFormat: bannerType];
973
+ }];
974
+ }
975
+
976
+ - (void)positionAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
977
+ {
978
+ [self executeOnMainQueue:^{
979
+ CLXBannerAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
980
+ NSString *adViewPosition = self.adViewPositions[adUnitIdentifier];
981
+
982
+ NSValue *adViewOffsetValue = self.adViewOffsets[adUnitIdentifier];
983
+ CGPoint adViewOffset = [adViewOffsetValue CGPointValue];
984
+ CGFloat xOffset = adViewOffset.x;
985
+ CGFloat yOffset = adViewOffset.y;
986
+
987
+ if ( [adViewPosition containsString: @"right"] )
988
+ {
989
+ xOffset = -xOffset;
990
+ }
991
+
992
+ if ( [adViewPosition containsString: @"bottom"] )
993
+ {
994
+ yOffset = -yOffset;
995
+ }
996
+
997
+ UIView *superview = adView.superview;
998
+ if ( !superview ) return;
999
+
1000
+ NSArray<NSLayoutConstraint *> *activeConstraints = self.adViewConstraints[adUnitIdentifier];
1001
+ [NSLayoutConstraint deactivateConstraints: activeConstraints];
1002
+
1003
+ // Lazily create the per-ad-unit safe-area background on first position call.
1004
+ UIView *safeAreaBg = self.safeAreaBackgrounds[adUnitIdentifier];
1005
+ if ( !safeAreaBg )
1006
+ {
1007
+ safeAreaBg = [[UIView alloc] init];
1008
+ safeAreaBg.hidden = YES;
1009
+ safeAreaBg.backgroundColor = self.publisherBannerBackgroundColors[adUnitIdentifier] ?: UIColor.clearColor;
1010
+ safeAreaBg.translatesAutoresizingMaskIntoConstraints = NO;
1011
+ safeAreaBg.userInteractionEnabled = NO;
1012
+ self.safeAreaBackgrounds[adUnitIdentifier] = safeAreaBg;
1013
+ }
1014
+
1015
+ if ( ![superview.subviews containsObject: safeAreaBg] )
1016
+ {
1017
+ [safeAreaBg removeFromSuperview];
1018
+ [superview insertSubview: safeAreaBg belowSubview: adView];
1019
+ }
1020
+
1021
+ [NSLayoutConstraint deactivateConstraints: safeAreaBg.constraints];
1022
+ safeAreaBg.hidden = adView.hidden;
1023
+
1024
+ CGFloat adViewHeight = [self adViewHeightForFormat: adFormat];
1025
+ CGFloat adViewWidth;
1026
+
1027
+ if ( [adViewPosition isEqual: TOP_CENTER] || [adViewPosition isEqual: BOTTOM_CENTER] )
1028
+ {
1029
+ adViewWidth = CGRectGetWidth(superview.bounds);
1030
+ }
1031
+ else
1032
+ {
1033
+ adViewWidth = [self adViewWidthForFormat: adFormat];
1034
+ }
1035
+
1036
+ CGSize adViewSize = CGSizeMake(adViewWidth, adViewHeight);
1037
+
1038
+ NSMutableArray<NSLayoutConstraint *> *constraints = [NSMutableArray arrayWithObject: [adView.heightAnchor constraintEqualToConstant: adViewSize.height]];
1039
+
1040
+ UILayoutGuide *layoutGuide = superview.safeAreaLayoutGuide;
1041
+
1042
+ // TOP_CENTER / BOTTOM_CENTER with a publisher background color and no offset:
1043
+ // extend the safe-area background view beyond the layout guide so the color
1044
+ // bleeds into the notch/home-indicator region and looks like a solid bar.
1045
+ if ( [adViewPosition isEqual: TOP_CENTER] || [adViewPosition isEqual: BOTTOM_CENTER] )
1046
+ {
1047
+ if ( self.publisherBannerBackgroundColors[adUnitIdentifier] && adFormat != CLXBannerTypeMREC && CGPointEqualToPoint(adViewOffset, CGPointZero) )
1048
+ {
1049
+ [constraints addObjectsFromArray: @[[adView.widthAnchor constraintEqualToConstant: adViewWidth],
1050
+ [adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor],
1051
+ [safeAreaBg.widthAnchor constraintEqualToConstant: adViewWidth],
1052
+ [safeAreaBg.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor]]];
1053
+
1054
+ if ( [adViewPosition isEqual: TOP_CENTER] )
1055
+ {
1056
+ [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor],
1057
+ [safeAreaBg.topAnchor constraintEqualToAnchor: superview.topAnchor],
1058
+ [safeAreaBg.bottomAnchor constraintEqualToAnchor: adView.topAnchor]]];
1059
+ }
1060
+ else
1061
+ {
1062
+ [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor],
1063
+ [safeAreaBg.topAnchor constraintEqualToAnchor: adView.bottomAnchor],
1064
+ [safeAreaBg.bottomAnchor constraintEqualToAnchor: superview.bottomAnchor]]];
1065
+ }
1066
+ }
1067
+ else
1068
+ {
1069
+ safeAreaBg.hidden = YES;
1070
+
1071
+ [constraints addObjectsFromArray: @[[adView.widthAnchor constraintEqualToConstant: adViewWidth],
1072
+ [adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor]]];
1073
+
1074
+ if ( [adViewPosition isEqual: TOP_CENTER] )
1075
+ {
1076
+ [constraints addObject: [adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor constant: yOffset]];
1077
+ }
1078
+ else
1079
+ {
1080
+ [constraints addObject: [adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor constant: yOffset]];
1081
+ }
1082
+ }
1083
+ }
1084
+ else
1085
+ {
1086
+ safeAreaBg.hidden = YES;
1087
+
1088
+ [constraints addObject: [adView.widthAnchor constraintEqualToConstant: adViewWidth]];
1089
+
1090
+ if ( [adViewPosition isEqual: TOP_LEFT] )
1091
+ {
1092
+ [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor constant: yOffset],
1093
+ [adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor constant: xOffset]]];
1094
+ }
1095
+ else if ( [adViewPosition isEqual: TOP_RIGHT] )
1096
+ {
1097
+ [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor constant: yOffset],
1098
+ [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor constant: xOffset]]];
1099
+ }
1100
+ else if ( [adViewPosition isEqual: CENTER_LEFT] )
1101
+ {
1102
+ [constraints addObjectsFromArray: @[[adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor constant: yOffset],
1103
+ [adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor constant: xOffset]]];
1104
+ }
1105
+ else if ( [adViewPosition isEqual: CENTERED] )
1106
+ {
1107
+ [constraints addObjectsFromArray: @[[adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor constant: xOffset],
1108
+ [adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor constant: yOffset]]];
1109
+ }
1110
+ else if ( [adViewPosition isEqual: CENTER_RIGHT] )
1111
+ {
1112
+ [constraints addObjectsFromArray: @[[adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor constant: yOffset],
1113
+ [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor constant: xOffset]]];
1114
+ }
1115
+ else if ( [adViewPosition isEqual: BOTTOM_LEFT] )
1116
+ {
1117
+ [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor constant: yOffset],
1118
+ [adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor constant: xOffset]]];
1119
+ }
1120
+ else if ( [adViewPosition isEqual: BOTTOM_RIGHT] )
1121
+ {
1122
+ [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor constant: yOffset],
1123
+ [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor constant: xOffset]]];
1124
+ }
1125
+ else
1126
+ {
1127
+ [self log: @"Unrecognized ad view position: \"%@\", defaulting to bottom_center", adViewPosition];
1128
+ [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor],
1129
+ [adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor]]];
1130
+ }
1131
+ }
1132
+
1133
+ self.adViewConstraints[adUnitIdentifier] = constraints;
1134
+
1135
+ [NSLayoutConstraint activateConstraints: constraints];
1136
+ }];
1137
+ }
1138
+
1139
+ - (CGFloat)adViewWidthForFormat:(CLXBannerType)adFormat
1140
+ {
1141
+ if ( adFormat == CLXBannerTypeMREC ) return 300.0;
1142
+ return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ? 728.0 : 320.0;
1143
+ }
1144
+
1145
+ - (CGFloat)adViewHeightForFormat:(CLXBannerType)adFormat
1146
+ {
1147
+ if ( adFormat == CLXBannerTypeMREC ) return 250.0;
1148
+ return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ? 90.0 : 50.0;
1149
+ }
1150
+
1151
+ - (UIViewController *)topViewController
1152
+ {
1153
+ return [self deepestPresentedViewControllerFrom: [self keyWindowRootViewController]];
1154
+ }
1155
+
1156
+ /// Scene-based key-window lookup (iOS 13+). Returns the root view controller of the
1157
+ /// foreground-active scene's key window, resolving the window at call time so callers
1158
+ /// never hold a stale reference.
1159
+ - (nullable UIViewController *)keyWindowRootViewController
1160
+ {
1161
+ for ( UIScene *scene in UIApplication.sharedApplication.connectedScenes )
1162
+ {
1163
+ if ( scene.activationState != UISceneActivationStateForegroundActive ) continue;
1164
+ if ( ![scene isKindOfClass: [UIWindowScene class]] ) continue;
1165
+
1166
+ UIWindowScene *windowScene = (UIWindowScene *)scene;
1167
+ for ( UIWindow *window in windowScene.windows )
1168
+ {
1169
+ if ( window.isKeyWindow ) return window.rootViewController;
1170
+ }
1171
+ }
1172
+ return nil;
1173
+ }
1174
+
1175
+ /// Walks the view-controller containment hierarchy to find the deepest visible leaf.
1176
+ /// Needed because UIKit rejects `presentViewController:` calls on a VC that already
1177
+ /// has a presented child. Terminates when no deeper child exists (next becomes nil).
1178
+ - (nullable UIViewController *)deepestPresentedViewControllerFrom:(nullable UIViewController *)root
1179
+ {
1180
+ UIViewController *vc = nil;
1181
+ UIViewController *next = root;
1182
+
1183
+ while ( next )
1184
+ {
1185
+ vc = next;
1186
+ next = vc.presentedViewController;
1187
+
1188
+ if ( !next && [vc isKindOfClass: [UINavigationController class]] )
1189
+ {
1190
+ UINavigationController *nav = (UINavigationController *)vc;
1191
+ next = nav.topViewController;
1192
+ }
1193
+
1194
+ if ( !next && [vc isKindOfClass: [UITabBarController class]] )
1195
+ {
1196
+ UITabBarController *tab = (UITabBarController *)vc;
1197
+ next = tab.selectedViewController;
1198
+ }
1199
+ }
1200
+
1201
+ return vc;
1202
+ }
1203
+
1204
+ - (CLXLogLevel)logLevelFromString:(NSString *)level
1205
+ {
1206
+ NSString *lower = [level lowercaseString];
1207
+ if ( [lower isEqualToString: @"verbose"] ) return CLXLogLevelVerbose;
1208
+ if ( [lower isEqualToString: @"debug"] ) return CLXLogLevelDebug;
1209
+ if ( [lower isEqualToString: @"info"] ) return CLXLogLevelInfo;
1210
+ if ( [lower isEqualToString: @"warn"] ) return CLXLogLevelWarn;
1211
+ if ( [lower isEqualToString: @"error"] ) return CLXLogLevelError;
1212
+ if ( [lower isEqualToString: @"none"] ) return CLXLogLevelNone;
1213
+ return CLXLogLevelInfo;
1214
+ }
1215
+
1216
+ - (void)logInvalidAdFormat:(CLXAdFormat)adFormat
1217
+ {
1218
+ NSLog(@"[%@] [%@] invalid ad format: %ld, from %@", SDK_TAG, TAG, (long)adFormat, [NSThread callStackSymbols]);
1219
+ }
1220
+
1221
+ - (void)log:(NSString *)format, ...
1222
+ {
1223
+ va_list valist;
1224
+ va_start(valist, format);
1225
+ NSString *message = [[NSString alloc] initWithFormat: format arguments: valist];
1226
+ va_end(valist);
1227
+
1228
+ NSLog(@"[%@] [%@] %@", SDK_TAG, TAG, message);
1229
+ }
1230
+
1231
+ #pragma mark - Ad Info
1232
+
1233
+ - (NSDictionary<NSString *, id> *)adInfoForAd:(CLXAd *)ad
1234
+ {
1235
+ return @{@"adUnitId": ad.adUnitId ?: @"",
1236
+ @"adFormat": [self adFormatStringForAd: ad],
1237
+ @"networkName": ad.networkName ?: @"",
1238
+ @"networkPlacement": ad.networkPlacement ?: @"",
1239
+ @"placement": ad.placement ?: @"",
1240
+ @"revenue": ad.revenue ?: @(0)};
1241
+ }
1242
+
1243
+ - (NSDictionary<NSString *, id> *)adLoadFailedInfoForAd:(NSString *)adUnitIdentifier withError:(nullable CLXError *)error
1244
+ {
1245
+ return error ?
1246
+ @{@"adUnitId": adUnitIdentifier,
1247
+ @"code": @(error.code),
1248
+ @"message": error.localizedDescription ?: @""}
1249
+ :
1250
+ @{@"adUnitId": adUnitIdentifier,
1251
+ @"code": @(CLXErrorCodeInternalError),
1252
+ @"message": @""};
1253
+ }
1254
+
1255
+ - (NSDictionary<NSString *, id> *)adDisplayFailedInfoForAd:(CLXAd *)ad withError:(CLXError *)error
1256
+ {
1257
+ NSMutableDictionary *body = [@{@"code": @(error.code),
1258
+ @"message": error.localizedDescription ?: @""} mutableCopy];
1259
+ [body addEntriesFromDictionary: [self adInfoForAd: ad]];
1260
+ return body;
1261
+ }
1262
+
1263
+ - (NSString *)adFormatStringForAd:(CLXAd *)ad
1264
+ {
1265
+ switch ( ad.adFormat )
1266
+ {
1267
+ case CLXAdFormatBanner: return @"BANNER";
1268
+ case CLXAdFormatMREC: return @"MREC";
1269
+ case CLXAdFormatInterstitial: return @"INTERSTITIAL";
1270
+ case CLXAdFormatRewarded: return @"REWARDED";
1271
+ default: return @"";
1272
+ }
1273
+ }
1274
+
1275
+ #pragma mark - React Native Event Bridge
1276
+
1277
+ - (void)sendReactNativeEventWithName:(NSString *)name body:(NSDictionary<NSString *, id> *)body
1278
+ {
1279
+ [self sendEventWithName: name body: body];
1280
+ }
1281
+
1282
+ - (NSArray<NSString *> *)supportedEvents
1283
+ {
1284
+ return @[ON_MREC_AD_LOADED_EVENT,
1285
+ ON_MREC_AD_LOAD_FAILED_EVENT,
1286
+ ON_MREC_AD_CLICKED_EVENT,
1287
+ ON_MREC_AD_COLLAPSED_EVENT,
1288
+ ON_MREC_AD_EXPANDED_EVENT,
1289
+ ON_MREC_AD_REVENUE_PAID,
1290
+
1291
+ ON_BANNER_AD_LOADED_EVENT,
1292
+ ON_BANNER_AD_LOAD_FAILED_EVENT,
1293
+ ON_BANNER_AD_CLICKED_EVENT,
1294
+ ON_BANNER_AD_COLLAPSED_EVENT,
1295
+ ON_BANNER_AD_EXPANDED_EVENT,
1296
+ ON_BANNER_AD_REVENUE_PAID,
1297
+
1298
+ ON_INTERSTITIAL_LOADED_EVENT,
1299
+ ON_INTERSTITIAL_LOAD_FAILED_EVENT,
1300
+ ON_INTERSTITIAL_CLICKED_EVENT,
1301
+ ON_INTERSTITIAL_DISPLAYED_EVENT,
1302
+ ON_INTERSTITIAL_DISPLAY_FAILED_EVENT,
1303
+ ON_INTERSTITIAL_HIDDEN_EVENT,
1304
+ ON_INTERSTITIAL_AD_REVENUE_PAID,
1305
+
1306
+ ON_REWARDED_AD_LOADED_EVENT,
1307
+ ON_REWARDED_AD_LOAD_FAILED_EVENT,
1308
+ ON_REWARDED_AD_CLICKED_EVENT,
1309
+ ON_REWARDED_AD_DISPLAYED_EVENT,
1310
+ ON_REWARDED_AD_DISPLAY_FAILED_EVENT,
1311
+ ON_REWARDED_AD_HIDDEN_EVENT,
1312
+ ON_REWARDED_AD_RECEIVED_REWARD_EVENT,
1313
+ ON_REWARDED_AD_REVENUE_PAID];
1314
+ }
1315
+
1316
+ - (NSDictionary *)constantsToExport
1317
+ {
1318
+ return @{@"ON_MREC_AD_LOADED_EVENT": ON_MREC_AD_LOADED_EVENT,
1319
+ @"ON_MREC_AD_LOAD_FAILED_EVENT": ON_MREC_AD_LOAD_FAILED_EVENT,
1320
+ @"ON_MREC_AD_CLICKED_EVENT": ON_MREC_AD_CLICKED_EVENT,
1321
+ @"ON_MREC_AD_COLLAPSED_EVENT": ON_MREC_AD_COLLAPSED_EVENT,
1322
+ @"ON_MREC_AD_EXPANDED_EVENT": ON_MREC_AD_EXPANDED_EVENT,
1323
+ @"ON_MREC_AD_REVENUE_PAID": ON_MREC_AD_REVENUE_PAID,
1324
+
1325
+ @"ON_BANNER_AD_LOADED_EVENT": ON_BANNER_AD_LOADED_EVENT,
1326
+ @"ON_BANNER_AD_LOAD_FAILED_EVENT": ON_BANNER_AD_LOAD_FAILED_EVENT,
1327
+ @"ON_BANNER_AD_CLICKED_EVENT": ON_BANNER_AD_CLICKED_EVENT,
1328
+ @"ON_BANNER_AD_COLLAPSED_EVENT": ON_BANNER_AD_COLLAPSED_EVENT,
1329
+ @"ON_BANNER_AD_EXPANDED_EVENT": ON_BANNER_AD_EXPANDED_EVENT,
1330
+ @"ON_BANNER_AD_REVENUE_PAID": ON_BANNER_AD_REVENUE_PAID,
1331
+
1332
+ @"ON_INTERSTITIAL_LOADED_EVENT": ON_INTERSTITIAL_LOADED_EVENT,
1333
+ @"ON_INTERSTITIAL_LOAD_FAILED_EVENT": ON_INTERSTITIAL_LOAD_FAILED_EVENT,
1334
+ @"ON_INTERSTITIAL_CLICKED_EVENT": ON_INTERSTITIAL_CLICKED_EVENT,
1335
+ @"ON_INTERSTITIAL_DISPLAYED_EVENT": ON_INTERSTITIAL_DISPLAYED_EVENT,
1336
+ @"ON_INTERSTITIAL_DISPLAY_FAILED_EVENT": ON_INTERSTITIAL_DISPLAY_FAILED_EVENT,
1337
+ @"ON_INTERSTITIAL_HIDDEN_EVENT": ON_INTERSTITIAL_HIDDEN_EVENT,
1338
+ @"ON_INTERSTITIAL_AD_REVENUE_PAID": ON_INTERSTITIAL_AD_REVENUE_PAID,
1339
+
1340
+ @"ON_REWARDED_AD_LOADED_EVENT": ON_REWARDED_AD_LOADED_EVENT,
1341
+ @"ON_REWARDED_AD_LOAD_FAILED_EVENT": ON_REWARDED_AD_LOAD_FAILED_EVENT,
1342
+ @"ON_REWARDED_AD_CLICKED_EVENT": ON_REWARDED_AD_CLICKED_EVENT,
1343
+ @"ON_REWARDED_AD_DISPLAYED_EVENT": ON_REWARDED_AD_DISPLAYED_EVENT,
1344
+ @"ON_REWARDED_AD_DISPLAY_FAILED_EVENT": ON_REWARDED_AD_DISPLAY_FAILED_EVENT,
1345
+ @"ON_REWARDED_AD_HIDDEN_EVENT": ON_REWARDED_AD_HIDDEN_EVENT,
1346
+ @"ON_REWARDED_AD_RECEIVED_REWARD_EVENT": ON_REWARDED_AD_RECEIVED_REWARD_EVENT,
1347
+ @"ON_REWARDED_AD_REVENUE_PAID": ON_REWARDED_AD_REVENUE_PAID,
1348
+
1349
+ @"TOP_CENTER_POSITION": TOP_CENTER,
1350
+ @"TOP_LEFT_POSITION": TOP_LEFT,
1351
+ @"TOP_RIGHT_POSITION": TOP_RIGHT,
1352
+ @"CENTERED_POSITION": CENTERED,
1353
+ @"CENTER_LEFT_POSITION": CENTER_LEFT,
1354
+ @"CENTER_RIGHT_POSITION": CENTER_RIGHT,
1355
+ @"BOTTOM_LEFT_POSITION": BOTTOM_LEFT,
1356
+ @"BOTTOM_CENTER_POSITION": BOTTOM_CENTER,
1357
+ @"BOTTOM_RIGHT_POSITION": BOTTOM_RIGHT};
1358
+ }
1359
+
1360
+ - (NSDictionary *)getConstants
1361
+ {
1362
+ return [self constantsToExport];
1363
+ }
1364
+
1365
+ - (void)startObserving
1366
+ {
1367
+ self.hasListeners = YES;
1368
+ }
1369
+
1370
+ - (void)stopObserving
1371
+ {
1372
+ self.hasListeners = NO;
1373
+ }
1374
+
1375
+ #ifdef RCT_NEW_ARCH_ENABLED
1376
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
1377
+ {
1378
+ return std::make_shared<facebook::react::NativeCloudXSpecJSI>(params);
1379
+ }
1380
+ #endif
1381
+
1382
+ @end
1383
+
1384
+ @implementation UIColor (CLXUtils)
1385
+
1386
+ // Parses 6-digit (#RRGGBB) and 8-digit (#AARRGGBB) hex color strings into UIColor.
1387
+ // The AARRGGBB byte order matches Android's Color.parseColor convention, keeping
1388
+ // the cross-platform API surface consistent for JS callers.
1389
+ + (nullable UIColor *)clx_colorWithHexString:(NSString *)hexString
1390
+ {
1391
+ NSString *hex = [hexString stringByReplacingOccurrencesOfString: @"#" withString: @""];
1392
+ if ( hex.length == 6 ) hex = [@"FF" stringByAppendingString: hex];
1393
+ if ( hex.length != 8 ) return nil;
1394
+
1395
+ unsigned int argb = 0;
1396
+ [[NSScanner scannerWithString: hex] scanHexInt: &argb];
1397
+
1398
+ CGFloat alpha = ((argb >> 24) & 0xFF) / 255.0;
1399
+ CGFloat red = ((argb >> 16) & 0xFF) / 255.0;
1400
+ CGFloat green = ((argb >> 8) & 0xFF) / 255.0;
1401
+ CGFloat blue = (argb & 0xFF) / 255.0;
1402
+
1403
+ return [UIColor colorWithRed: red green: green blue: blue alpha: alpha];
1404
+ }
1405
+
1406
+ @end