novvy-ads-react-native 1.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 (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/android/build.gradle +84 -0
  4. package/android/gradle.properties +4 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/ai/novvy/reactnative/NovvyAdContainerView.kt +63 -0
  7. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsModule.kt +390 -0
  8. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsPackage.kt +46 -0
  9. package/android/src/main/java/ai/novvy/reactnative/NovvyPauseAdViewManager.kt +68 -0
  10. package/android/src/main/java/ai/novvy/reactnative/NovvyScrollAdViewManager.kt +72 -0
  11. package/android/src/main/java/ai/novvy/reactnative/RnPlayerAdapter.kt +18 -0
  12. package/android/src/main/java/ai/novvy/reactnative/ViewReadyEvent.kt +26 -0
  13. package/app.plugin.js +4 -0
  14. package/ios/NovvyAds.mm +168 -0
  15. package/ios/NovvyAdsImpl.swift +409 -0
  16. package/ios/NovvyPauseAdView.mm +79 -0
  17. package/ios/NovvyScrollAdView.mm +80 -0
  18. package/ios/RnPlayerAdapter.swift +14 -0
  19. package/lib/commonjs/NovvyAds.js +87 -0
  20. package/lib/commonjs/NovvyAds.js.map +1 -0
  21. package/lib/commonjs/ads/NovvyAd.js +121 -0
  22. package/lib/commonjs/ads/NovvyAd.js.map +1 -0
  23. package/lib/commonjs/ads/NovvyEndAd.js +19 -0
  24. package/lib/commonjs/ads/NovvyEndAd.js.map +1 -0
  25. package/lib/commonjs/ads/NovvyFullScreenAd.js +49 -0
  26. package/lib/commonjs/ads/NovvyFullScreenAd.js.map +1 -0
  27. package/lib/commonjs/ads/NovvyInterstitialAd.js +19 -0
  28. package/lib/commonjs/ads/NovvyInterstitialAd.js.map +1 -0
  29. package/lib/commonjs/ads/NovvyMidAd.js +19 -0
  30. package/lib/commonjs/ads/NovvyMidAd.js.map +1 -0
  31. package/lib/commonjs/ads/NovvyPauseAd.js +116 -0
  32. package/lib/commonjs/ads/NovvyPauseAd.js.map +1 -0
  33. package/lib/commonjs/ads/NovvyRewardedAd.js +53 -0
  34. package/lib/commonjs/ads/NovvyRewardedAd.js.map +1 -0
  35. package/lib/commonjs/ads/NovvyScrollAd.js +136 -0
  36. package/lib/commonjs/ads/NovvyScrollAd.js.map +1 -0
  37. package/lib/commonjs/eventBus.js +60 -0
  38. package/lib/commonjs/eventBus.js.map +1 -0
  39. package/lib/commonjs/index.js +90 -0
  40. package/lib/commonjs/index.js.map +1 -0
  41. package/lib/commonjs/package.json +1 -0
  42. package/lib/commonjs/spec/NativeNovvyAds.js +27 -0
  43. package/lib/commonjs/spec/NativeNovvyAds.js.map +1 -0
  44. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js +16 -0
  45. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  46. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js +17 -0
  47. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  48. package/lib/commonjs/spec/codegen-event-emitter.d.js +2 -0
  49. package/lib/commonjs/spec/codegen-event-emitter.d.js.map +1 -0
  50. package/lib/commonjs/types.js +54 -0
  51. package/lib/commonjs/types.js.map +1 -0
  52. package/lib/commonjs/views/NovvyPauseAdView.js +76 -0
  53. package/lib/commonjs/views/NovvyPauseAdView.js.map +1 -0
  54. package/lib/commonjs/views/NovvyScrollAdView.js +63 -0
  55. package/lib/commonjs/views/NovvyScrollAdView.js.map +1 -0
  56. package/lib/module/NovvyAds.js +83 -0
  57. package/lib/module/NovvyAds.js.map +1 -0
  58. package/lib/module/ads/NovvyAd.js +117 -0
  59. package/lib/module/ads/NovvyAd.js.map +1 -0
  60. package/lib/module/ads/NovvyEndAd.js +15 -0
  61. package/lib/module/ads/NovvyEndAd.js.map +1 -0
  62. package/lib/module/ads/NovvyFullScreenAd.js +45 -0
  63. package/lib/module/ads/NovvyFullScreenAd.js.map +1 -0
  64. package/lib/module/ads/NovvyInterstitialAd.js +15 -0
  65. package/lib/module/ads/NovvyInterstitialAd.js.map +1 -0
  66. package/lib/module/ads/NovvyMidAd.js +15 -0
  67. package/lib/module/ads/NovvyMidAd.js.map +1 -0
  68. package/lib/module/ads/NovvyPauseAd.js +112 -0
  69. package/lib/module/ads/NovvyPauseAd.js.map +1 -0
  70. package/lib/module/ads/NovvyRewardedAd.js +49 -0
  71. package/lib/module/ads/NovvyRewardedAd.js.map +1 -0
  72. package/lib/module/ads/NovvyScrollAd.js +132 -0
  73. package/lib/module/ads/NovvyScrollAd.js.map +1 -0
  74. package/lib/module/eventBus.js +55 -0
  75. package/lib/module/eventBus.js.map +1 -0
  76. package/lib/module/index.js +22 -0
  77. package/lib/module/index.js.map +1 -0
  78. package/lib/module/package.json +1 -0
  79. package/lib/module/spec/NativeNovvyAds.js +28 -0
  80. package/lib/module/spec/NativeNovvyAds.js.map +1 -0
  81. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js +13 -0
  82. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  83. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js +14 -0
  84. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  85. package/lib/module/spec/codegen-event-emitter.d.js +2 -0
  86. package/lib/module/spec/codegen-event-emitter.d.js.map +1 -0
  87. package/lib/module/types.js +56 -0
  88. package/lib/module/types.js.map +1 -0
  89. package/lib/module/views/NovvyPauseAdView.js +70 -0
  90. package/lib/module/views/NovvyPauseAdView.js.map +1 -0
  91. package/lib/module/views/NovvyScrollAdView.js +57 -0
  92. package/lib/module/views/NovvyScrollAdView.js.map +1 -0
  93. package/lib/typescript/NovvyAds.d.ts +69 -0
  94. package/lib/typescript/NovvyAds.d.ts.map +1 -0
  95. package/lib/typescript/ads/NovvyAd.d.ts +57 -0
  96. package/lib/typescript/ads/NovvyAd.d.ts.map +1 -0
  97. package/lib/typescript/ads/NovvyEndAd.d.ts +7 -0
  98. package/lib/typescript/ads/NovvyEndAd.d.ts.map +1 -0
  99. package/lib/typescript/ads/NovvyFullScreenAd.d.ts +19 -0
  100. package/lib/typescript/ads/NovvyFullScreenAd.d.ts.map +1 -0
  101. package/lib/typescript/ads/NovvyInterstitialAd.d.ts +7 -0
  102. package/lib/typescript/ads/NovvyInterstitialAd.d.ts.map +1 -0
  103. package/lib/typescript/ads/NovvyMidAd.d.ts +7 -0
  104. package/lib/typescript/ads/NovvyMidAd.d.ts.map +1 -0
  105. package/lib/typescript/ads/NovvyPauseAd.d.ts +45 -0
  106. package/lib/typescript/ads/NovvyPauseAd.d.ts.map +1 -0
  107. package/lib/typescript/ads/NovvyRewardedAd.d.ts +19 -0
  108. package/lib/typescript/ads/NovvyRewardedAd.d.ts.map +1 -0
  109. package/lib/typescript/ads/NovvyScrollAd.d.ts +57 -0
  110. package/lib/typescript/ads/NovvyScrollAd.d.ts.map +1 -0
  111. package/lib/typescript/eventBus.d.ts +15 -0
  112. package/lib/typescript/eventBus.d.ts.map +1 -0
  113. package/lib/typescript/index.d.ts +15 -0
  114. package/lib/typescript/index.d.ts.map +1 -0
  115. package/lib/typescript/spec/NativeNovvyAds.d.ts +73 -0
  116. package/lib/typescript/spec/NativeNovvyAds.d.ts.map +1 -0
  117. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts +18 -0
  118. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts.map +1 -0
  119. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts +19 -0
  120. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts.map +1 -0
  121. package/lib/typescript/types.d.ts +77 -0
  122. package/lib/typescript/types.d.ts.map +1 -0
  123. package/lib/typescript/views/NovvyPauseAdView.d.ts +21 -0
  124. package/lib/typescript/views/NovvyPauseAdView.d.ts.map +1 -0
  125. package/lib/typescript/views/NovvyScrollAdView.d.ts +22 -0
  126. package/lib/typescript/views/NovvyScrollAdView.d.ts.map +1 -0
  127. package/novvy-ads-react-native.podspec +71 -0
  128. package/package.json +84 -0
  129. package/plugin/build/index.d.ts +11 -0
  130. package/plugin/build/index.js +53 -0
  131. package/react-native.config.js +7 -0
  132. package/src/NovvyAds.ts +98 -0
  133. package/src/ads/NovvyAd.ts +125 -0
  134. package/src/ads/NovvyEndAd.ts +13 -0
  135. package/src/ads/NovvyFullScreenAd.ts +46 -0
  136. package/src/ads/NovvyInterstitialAd.ts +13 -0
  137. package/src/ads/NovvyMidAd.ts +13 -0
  138. package/src/ads/NovvyPauseAd.ts +125 -0
  139. package/src/ads/NovvyRewardedAd.ts +49 -0
  140. package/src/ads/NovvyScrollAd.ts +138 -0
  141. package/src/eventBus.ts +59 -0
  142. package/src/index.tsx +29 -0
  143. package/src/spec/NativeNovvyAds.ts +134 -0
  144. package/src/spec/NovvyPauseAdViewNativeComponent.ts +20 -0
  145. package/src/spec/NovvyScrollAdViewNativeComponent.ts +21 -0
  146. package/src/spec/codegen-event-emitter.d.ts +10 -0
  147. package/src/types.ts +82 -0
  148. package/src/views/NovvyPauseAdView.tsx +80 -0
  149. package/src/views/NovvyScrollAdView.tsx +71 -0
@@ -0,0 +1,168 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTUtils.h>
3
+
4
+ // Swift implementation (generated umbrella header for this pod's Swift module).
5
+ #import "NovvyAdsReactNative-Swift.h"
6
+
7
+ // Codegen'd TurboModule spec (present when the new architecture is enabled).
8
+ #import "NovvyAdsSpec.h"
9
+
10
+ /**
11
+ * TurboModule entry point. All business logic lives in the Swift
12
+ * `NovvyAdsImpl` (which calls the NovvyAds SDK); this thin ObjC++ layer only
13
+ * bridges codegen method calls and the `onAdEvent` emitter to it.
14
+ */
15
+ @interface NovvyAds : NativeNovvyAdsSpecBase <NativeNovvyAdsSpec>
16
+ @end
17
+
18
+ @implementation NovvyAds {
19
+ NovvyAdsImpl *_impl;
20
+ }
21
+
22
+ RCT_EXPORT_MODULE(NovvyAds)
23
+
24
+ - (instancetype)init {
25
+ if (self = [super init]) {
26
+ _impl = NovvyAdsImpl.shared;
27
+ __weak NovvyAds *weakSelf = self;
28
+ _impl.eventSink = ^(NSDictionary *value) {
29
+ [weakSelf emitOnAdEvent:value];
30
+ };
31
+ }
32
+ return self;
33
+ }
34
+
35
+ + (BOOL)requiresMainQueueSetup {
36
+ return NO;
37
+ }
38
+
39
+ // All methods touch UIKit views (Fabric containers) or the Novvy SDK's inline
40
+ // player, both of which are main-thread-only. Force every TurboModule call
41
+ // onto the main queue instead of RN's default serial GCD queue.
42
+ - (dispatch_queue_t)methodQueue {
43
+ return dispatch_get_main_queue();
44
+ }
45
+
46
+ // MARK: - SDK & context
47
+
48
+ RCT_EXPORT_METHOD(initialize:(NSString *)appId
49
+ endpoint:(NSString *)endpoint
50
+ apiKey:(NSString *)apiKey
51
+ userContext:(NSDictionary *)userContext
52
+ resolve:(RCTPromiseResolveBlock)resolve
53
+ reject:(RCTPromiseRejectBlock)reject) {
54
+ [_impl initializeWithAppId:appId
55
+ endpoint:endpoint
56
+ apiKey:apiKey
57
+ userContext:userContext
58
+ completion:^(BOOL success) { resolve(@(success)); }];
59
+ }
60
+
61
+ RCT_EXPORT_METHOD(setUserContext:(NSDictionary *)context) {
62
+ [_impl setUserContext:context];
63
+ }
64
+
65
+ RCT_EXPORT_METHOD(updateUserContext:(NSDictionary *)partial) {
66
+ [_impl updateUserContext:partial];
67
+ }
68
+
69
+ RCT_EXPORT_METHOD(setContentContext:(NSDictionary *)context) {
70
+ [_impl setContentContext:context];
71
+ }
72
+
73
+ RCT_EXPORT_METHOD(updateContentContext:(NSDictionary *)partial) {
74
+ [_impl updateContentContext:partial];
75
+ }
76
+
77
+ // MARK: - Full-screen ads
78
+
79
+ RCT_EXPORT_METHOD(loadInterstitialAd:(NSString *)adUnitId adObjectId:(NSString *)adObjectId) {
80
+ [_impl loadInterstitialAdWithAdUnitId:adUnitId adObjectId:adObjectId];
81
+ }
82
+ RCT_EXPORT_METHOD(showInterstitialAd:(NSString *)adObjectId) {
83
+ [_impl showInterstitialAdWithAdObjectId:adObjectId];
84
+ }
85
+
86
+ RCT_EXPORT_METHOD(loadRewardedAd:(NSString *)adUnitId adObjectId:(NSString *)adObjectId) {
87
+ [_impl loadRewardedAdWithAdUnitId:adUnitId adObjectId:adObjectId];
88
+ }
89
+ RCT_EXPORT_METHOD(showRewardedAd:(NSString *)adObjectId) {
90
+ [_impl showRewardedAdWithAdObjectId:adObjectId];
91
+ }
92
+
93
+ RCT_EXPORT_METHOD(loadMidAd:(NSString *)adUnitId adObjectId:(NSString *)adObjectId) {
94
+ [_impl loadMidAdWithAdUnitId:adUnitId adObjectId:adObjectId];
95
+ }
96
+ RCT_EXPORT_METHOD(showMidAd:(NSString *)adObjectId) {
97
+ [_impl showMidAdWithAdObjectId:adObjectId];
98
+ }
99
+
100
+ RCT_EXPORT_METHOD(loadEndAd:(NSString *)adUnitId adObjectId:(NSString *)adObjectId) {
101
+ [_impl loadEndAdWithAdUnitId:adUnitId adObjectId:adObjectId];
102
+ }
103
+ RCT_EXPORT_METHOD(showEndAd:(NSString *)adObjectId) {
104
+ [_impl showEndAdWithAdObjectId:adObjectId];
105
+ }
106
+
107
+ // MARK: - Scroll
108
+
109
+ RCT_EXPORT_METHOD(loadScrollAd:(NSString *)adUnitId
110
+ adObjectId:(NSString *)adObjectId
111
+ episodeNumber:(NSNumber *)episodeNumber) {
112
+ [_impl loadScrollAdWithAdUnitId:adUnitId adObjectId:adObjectId episodeNumber:episodeNumber];
113
+ }
114
+
115
+ RCT_EXPORT_METHOD(attachScrollAd:(NSString *)adObjectId
116
+ durationSeconds:(NSNumber *)durationSeconds
117
+ useDefaultSwipeOverlay:(BOOL)useDefaultSwipeOverlay) {
118
+ [_impl attachScrollAdWithAdObjectId:adObjectId
119
+ durationSeconds:durationSeconds
120
+ useDefaultSwipeOverlay:useDefaultSwipeOverlay];
121
+ }
122
+
123
+ RCT_EXPORT_METHOD(setScrollAdPlaying:(NSString *)adObjectId playing:(BOOL)playing) {
124
+ [_impl setScrollAdPlayingWithAdObjectId:adObjectId playing:playing];
125
+ }
126
+
127
+ RCT_EXPORT_METHOD(detachScrollAd:(NSString *)adObjectId) {
128
+ [_impl detachScrollAdWithAdObjectId:adObjectId];
129
+ }
130
+
131
+ // MARK: - Pause
132
+
133
+ RCT_EXPORT_METHOD(attachPauseAd:(NSString *)adUnitId
134
+ adObjectId:(NSString *)adObjectId
135
+ positionMs:(double)positionMs
136
+ isPlaying:(BOOL)isPlaying) {
137
+ [_impl attachPauseAdWithAdUnitId:adUnitId
138
+ adObjectId:adObjectId
139
+ positionMs:(int64_t)positionMs
140
+ isPlaying:isPlaying];
141
+ }
142
+
143
+ RCT_EXPORT_METHOD(updatePauseAdPlayerState:(NSString *)adObjectId
144
+ positionMs:(double)positionMs
145
+ isPlaying:(BOOL)isPlaying) {
146
+ [_impl updatePauseAdPlayerStateWithAdObjectId:adObjectId
147
+ positionMs:(int64_t)positionMs
148
+ isPlaying:isPlaying];
149
+ }
150
+
151
+ RCT_EXPORT_METHOD(detachPauseAd:(NSString *)adObjectId) {
152
+ [_impl detachPauseAdWithAdObjectId:adObjectId];
153
+ }
154
+
155
+ // MARK: - Common
156
+
157
+ RCT_EXPORT_METHOD(disposeAd:(NSString *)adObjectId) {
158
+ [_impl disposeAdWithAdObjectId:adObjectId];
159
+ }
160
+
161
+ // MARK: - TurboModule
162
+
163
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
164
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
165
+ return std::make_shared<facebook::react::NativeNovvyAdsSpecJSI>(params);
166
+ }
167
+
168
+ @end
@@ -0,0 +1,409 @@
1
+ import Foundation
2
+ import UIKit
3
+ import NovvyAds
4
+
5
+ /**
6
+ * Business logic bridging the Novvy iOS ad SDK to React Native.
7
+ *
8
+ * Mirrors the Flutter plugin's `NovvyAdsApiImpl.swift` one-to-one. A single
9
+ * `eventSink` closure multiplexes every ad's lifecycle events to JS as an
10
+ * `NSDictionary`; each JS ad object filters by its own `adObjectId`.
11
+ *
12
+ * A process-wide `shared` instance lets the Fabric view classes register their
13
+ * containers with the same object the TurboModule drives.
14
+ */
15
+ @objc public final class NovvyAdsImpl: NSObject {
16
+
17
+ @objc public static let shared = NovvyAdsImpl()
18
+
19
+ /// Set by the TurboModule; forwards event dictionaries to `emitOnAdEvent`.
20
+ @objc public var eventSink: ((NSDictionary) -> Void)?
21
+
22
+ private var adMap: [String: AnyObject] = [:]
23
+ private var scrollContainerMap: [String: UIView] = [:]
24
+ private var pauseContainerMap: [String: UIView] = [:]
25
+ private var pauseAdapterMap: [String: RnPlayerAdapter] = [:]
26
+ /// The SDK holds delegates weakly, so retain them here per ad.
27
+ private var delegateMap: [String: AnyObject] = [:]
28
+
29
+ // Event name constants — must match the NovvyAdEvent string union in TS.
30
+ private enum Ev {
31
+ static let loaded = "loaded"
32
+ static let failed = "failedToLoad"
33
+ static let showed = "showed"
34
+ static let dismissed = "dismissed"
35
+ static let hidden = "hidden"
36
+ static let earnedReward = "earnedReward"
37
+ static let playbackTick = "playbackTick"
38
+ }
39
+
40
+ // MARK: - Event emission
41
+
42
+ private func emit(
43
+ _ adObjectId: String,
44
+ _ adUnitId: String,
45
+ _ event: String,
46
+ error: String? = nil,
47
+ remainingSeconds: Int? = nil
48
+ ) {
49
+ let dict = NSMutableDictionary()
50
+ dict["adObjectId"] = adObjectId
51
+ dict["adUnitId"] = adUnitId
52
+ dict["event"] = event
53
+ if let error { dict["error"] = error }
54
+ if let remainingSeconds { dict["remainingSeconds"] = remainingSeconds }
55
+ let sink = eventSink
56
+ if Thread.isMainThread {
57
+ sink?(dict)
58
+ } else {
59
+ DispatchQueue.main.async { sink?(dict) }
60
+ }
61
+ }
62
+
63
+ private func makeEmit(_ adObjectId: String, _ adUnitId: String)
64
+ -> (String, String?, Int?) -> Void {
65
+ return { [weak self] event, error, remaining in
66
+ self?.emit(adObjectId, adUnitId, event, error: error, remainingSeconds: remaining)
67
+ }
68
+ }
69
+
70
+ // MARK: - Initialization & context
71
+
72
+ @objc public func initialize(
73
+ appId: String,
74
+ endpoint: String,
75
+ apiKey: String,
76
+ userContext: NSDictionary?,
77
+ completion: @escaping (Bool) -> Void
78
+ ) {
79
+ NovvySDK.shared.initialize(
80
+ appId: appId,
81
+ endpoint: endpoint,
82
+ apiKey: apiKey,
83
+ userContext: userContext?.toSdkUserContext(),
84
+ completion: { success in
85
+ if Thread.isMainThread { completion(success) }
86
+ else { DispatchQueue.main.async { completion(success) } }
87
+ }
88
+ )
89
+ }
90
+
91
+ @objc public func setUserContext(_ context: NSDictionary) {
92
+ NovvySDK.shared.setUserContext(context.toSdkUserContext())
93
+ }
94
+
95
+ @objc public func updateUserContext(_ partial: NSDictionary) {
96
+ NovvySDK.shared.updateUserContext(
97
+ userId: partial.optString("userId"),
98
+ hashedEmail: partial.optString("hashedEmail"),
99
+ isPaidUser: partial.optBool("isPaidUser"),
100
+ age: partial.optInt("age"),
101
+ gender: partial.optString("gender"),
102
+ dramaWatchHistory: (partial["dramaWatchHistory"] as? [NSDictionary])?.toSdkRecords(),
103
+ maximumAdsInOneDrama: partial.optInt("maximumAdsInOneDrama")
104
+ )
105
+ }
106
+
107
+ @objc public func setContentContext(_ context: NSDictionary) {
108
+ NovvySDK.shared.setContentContext(NovvyAds.NovvyContentContext(
109
+ seriesName: context.optString("seriesName"),
110
+ episodeNumber: context.optInt("episodeNumber"),
111
+ url: context.optString("url")
112
+ ))
113
+ }
114
+
115
+ @objc public func updateContentContext(_ partial: NSDictionary) {
116
+ NovvySDK.shared.updateContentContext(
117
+ seriesName: partial.optString("seriesName"),
118
+ episodeNumber: partial.optInt("episodeNumber"),
119
+ url: partial.optString("url")
120
+ )
121
+ }
122
+
123
+ // MARK: - Interstitial
124
+
125
+ @objc public func loadInterstitialAd(adUnitId: String, adObjectId: String) {
126
+ let ad = NovvyInterstitialAd(adUnitId: adUnitId)
127
+ let delegate = InterstitialDelegate(emit: makeEmit(adObjectId, adUnitId))
128
+ ad.delegate = delegate
129
+ adMap[adObjectId] = ad
130
+ delegateMap[adObjectId] = delegate
131
+ ad.load()
132
+ }
133
+
134
+ @objc public func showInterstitialAd(adObjectId: String) {
135
+ guard let ad = adMap[adObjectId] as? NovvyInterstitialAd else { return }
136
+ guard let vc = Self.topViewController() else {
137
+ emit(adObjectId, ad.adUnitId, Ev.failed, error: "No active ViewController available")
138
+ return
139
+ }
140
+ ad.show(from: vc)
141
+ }
142
+
143
+ // MARK: - Rewarded
144
+
145
+ @objc public func loadRewardedAd(adUnitId: String, adObjectId: String) {
146
+ let ad = NovvyRewardedAd(adUnitId: adUnitId)
147
+ let delegate = RewardedDelegate(emit: makeEmit(adObjectId, adUnitId))
148
+ ad.delegate = delegate
149
+ adMap[adObjectId] = ad
150
+ delegateMap[adObjectId] = delegate
151
+ ad.load()
152
+ }
153
+
154
+ @objc public func showRewardedAd(adObjectId: String) {
155
+ guard let ad = adMap[adObjectId] as? NovvyRewardedAd else { return }
156
+ guard let vc = Self.topViewController() else {
157
+ emit(adObjectId, ad.adUnitId, Ev.failed, error: "No active ViewController available")
158
+ return
159
+ }
160
+ ad.show(from: vc)
161
+ }
162
+
163
+ // MARK: - Mid-roll
164
+
165
+ @objc public func loadMidAd(adUnitId: String, adObjectId: String) {
166
+ let ad = NovvyMidAd(adUnitId: adUnitId)
167
+ let delegate = MidDelegate(emit: makeEmit(adObjectId, adUnitId))
168
+ ad.delegate = delegate
169
+ adMap[adObjectId] = ad
170
+ delegateMap[adObjectId] = delegate
171
+ ad.load()
172
+ }
173
+
174
+ @objc public func showMidAd(adObjectId: String) {
175
+ guard let ad = adMap[adObjectId] as? NovvyMidAd else { return }
176
+ guard let vc = Self.topViewController() else {
177
+ emit(adObjectId, ad.adUnitId, Ev.failed, error: "No active ViewController available")
178
+ return
179
+ }
180
+ ad.show(from: vc)
181
+ }
182
+
183
+ // MARK: - End-roll
184
+
185
+ @objc public func loadEndAd(adUnitId: String, adObjectId: String) {
186
+ let ad = NovvyEndAd(adUnitId: adUnitId)
187
+ let delegate = EndDelegate(emit: makeEmit(adObjectId, adUnitId))
188
+ ad.delegate = delegate
189
+ adMap[adObjectId] = ad
190
+ delegateMap[adObjectId] = delegate
191
+ ad.load()
192
+ }
193
+
194
+ @objc public func showEndAd(adObjectId: String) {
195
+ guard let ad = adMap[adObjectId] as? NovvyEndAd else { return }
196
+ guard let vc = Self.topViewController() else {
197
+ emit(adObjectId, ad.adUnitId, Ev.failed, error: "No active ViewController available")
198
+ return
199
+ }
200
+ ad.show(from: vc)
201
+ }
202
+
203
+ // MARK: - Scroll (inline feed)
204
+
205
+ @objc public func loadScrollAd(adUnitId: String, adObjectId: String, episodeNumber: NSNumber?) {
206
+ let ad = NovvyScrollAd(adUnitId: adUnitId, episode: episodeNumber?.intValue)
207
+ let delegate = ScrollDelegate(emit: makeEmit(adObjectId, adUnitId))
208
+ ad.delegate = delegate
209
+ adMap[adObjectId] = ad
210
+ delegateMap[adObjectId] = delegate
211
+ ad.load()
212
+ }
213
+
214
+ @objc public func attachScrollAd(adObjectId: String, durationSeconds: NSNumber?, useDefaultSwipeOverlay: Bool) {
215
+ guard let ad = adMap[adObjectId] as? NovvyScrollAd,
216
+ let container = scrollContainerMap[adObjectId] else { return }
217
+ ad.attach(
218
+ to: container,
219
+ durationSeconds: durationSeconds?.intValue,
220
+ useDefaultSwipeOverlay: useDefaultSwipeOverlay,
221
+ onAdPlaybackTick: { [weak self] remaining in
222
+ self?.emit(adObjectId, "", Ev.playbackTick, remainingSeconds: remaining)
223
+ }
224
+ )
225
+ }
226
+
227
+ @objc public func setScrollAdPlaying(adObjectId: String, playing: Bool) {
228
+ (adMap[adObjectId] as? NovvyScrollAd)?.setPlaying(playing)
229
+ }
230
+
231
+ @objc public func detachScrollAd(adObjectId: String) {
232
+ (adMap[adObjectId] as? NovvyScrollAd)?.detach()
233
+ scrollContainerMap.removeValue(forKey: adObjectId)
234
+ }
235
+
236
+ // MARK: - Pause (realtime pause card)
237
+
238
+ @objc public func attachPauseAd(adUnitId: String, adObjectId: String, positionMs: Int64, isPlaying: Bool) {
239
+ guard let container = pauseContainerMap[adObjectId] else { return }
240
+ let adapter = RnPlayerAdapter()
241
+ adapter.currentPositionMs = positionMs
242
+ adapter.isPlaying = isPlaying
243
+ pauseAdapterMap[adObjectId] = adapter
244
+
245
+ let ad = NovvyPauseAd(adUnitId: adUnitId)
246
+ let delegate = PauseDelegate(emit: makeEmit(adObjectId, adUnitId))
247
+ ad.delegate = delegate
248
+ adMap[adObjectId] = ad
249
+ delegateMap[adObjectId] = delegate
250
+ ad.attach(to: container, player: adapter)
251
+ }
252
+
253
+ @objc public func updatePauseAdPlayerState(adObjectId: String, positionMs: Int64, isPlaying: Bool) {
254
+ guard let adapter = pauseAdapterMap[adObjectId] else { return }
255
+ adapter.currentPositionMs = positionMs
256
+ adapter.isPlaying = isPlaying
257
+ }
258
+
259
+ @objc public func detachPauseAd(adObjectId: String) {
260
+ (adMap[adObjectId] as? NovvyPauseAd)?.detach()
261
+ pauseContainerMap.removeValue(forKey: adObjectId)
262
+ pauseAdapterMap.removeValue(forKey: adObjectId)
263
+ }
264
+
265
+ // MARK: - Dispose
266
+
267
+ @objc public func disposeAd(adObjectId: String) {
268
+ switch adMap[adObjectId] {
269
+ case let ad as NovvyPauseAd:
270
+ ad.detach(); ad.destroy()
271
+ pauseContainerMap.removeValue(forKey: adObjectId)
272
+ pauseAdapterMap.removeValue(forKey: adObjectId)
273
+ case let ad as NovvyScrollAd:
274
+ ad.detach(); ad.destroy()
275
+ scrollContainerMap.removeValue(forKey: adObjectId)
276
+ case let ad as NovvyRewardedAd: ad.destroy()
277
+ case let ad as NovvyInterstitialAd: ad.destroy()
278
+ case let ad as NovvyMidAd: ad.destroy()
279
+ case let ad as NovvyEndAd: ad.destroy()
280
+ default: break
281
+ }
282
+ adMap.removeValue(forKey: adObjectId)
283
+ delegateMap.removeValue(forKey: adObjectId)
284
+ }
285
+
286
+ // MARK: - Platform view helpers (called by Fabric view classes)
287
+
288
+ @objc public func registerScrollContainer(adObjectId: String, container: UIView) {
289
+ scrollContainerMap[adObjectId] = container
290
+ }
291
+
292
+ @objc public func registerPauseContainer(adObjectId: String, container: UIView) {
293
+ pauseContainerMap[adObjectId] = container
294
+ }
295
+
296
+ @objc public func onScrollViewDisposed(adObjectId: String) {
297
+ detachScrollAd(adObjectId: adObjectId)
298
+ }
299
+
300
+ @objc public func onPauseViewDisposed(adObjectId: String) {
301
+ detachPauseAd(adObjectId: adObjectId)
302
+ }
303
+
304
+ // MARK: - Helpers
305
+
306
+ private static func topViewController() -> UIViewController? {
307
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
308
+ let activeScene = scenes.first(where: { $0.activationState == .foregroundActive }) ?? scenes.first
309
+ let keyWindow = activeScene?.windows.first(where: { $0.isKeyWindow }) ?? activeScene?.windows.first
310
+ var top = keyWindow?.rootViewController
311
+ while let presented = top?.presentedViewController {
312
+ top = presented
313
+ }
314
+ return top
315
+ }
316
+ }
317
+
318
+ // MARK: - NSDictionary parsing helpers
319
+
320
+ private extension NSDictionary {
321
+ func optString(_ key: String) -> String? { self[key] as? String }
322
+ func optBool(_ key: String) -> Bool? { (self[key] as? NSNumber)?.boolValue }
323
+ func optInt(_ key: String) -> Int? { (self[key] as? NSNumber)?.intValue }
324
+
325
+ func toSdkUserContext() -> NovvyAds.NovvyUserContext {
326
+ NovvyAds.NovvyUserContext(
327
+ userId: optString("userId"),
328
+ hashedEmail: optString("hashedEmail"),
329
+ isPaidUser: optBool("isPaidUser"),
330
+ age: optInt("age"),
331
+ gender: optString("gender"),
332
+ dramaWatchHistory: (self["dramaWatchHistory"] as? [NSDictionary])?.toSdkRecords(),
333
+ maximumAdsInOneDrama: optInt("maximumAdsInOneDrama")
334
+ )
335
+ }
336
+ }
337
+
338
+ private extension Array where Element == NSDictionary {
339
+ func toSdkRecords() -> [NovvyAds.NovvyDramaRecord] {
340
+ compactMap { record in
341
+ guard let seriesId = record["seriesId"] as? String else { return nil }
342
+ return NovvyAds.NovvyDramaRecord(
343
+ dramaTitle: record["dramaTitle"] as? String,
344
+ seriesId: seriesId,
345
+ lastWatchedEpisode: (record["lastWatchedEpisode"] as? NSNumber)?.intValue ?? 0,
346
+ unlockedEpisode: (record["unlockedEpisode"] as? NSNumber)?.intValue ?? 0
347
+ )
348
+ }
349
+ }
350
+ }
351
+
352
+ // MARK: - Delegate adapters
353
+ //
354
+ // Each captures an `emit` closure translating SDK delegate callbacks into
355
+ // event strings. Retained in `delegateMap` because the SDK holds delegates weakly.
356
+
357
+ private final class InterstitialDelegate: NSObject, NovvyInterstitialAdDelegate {
358
+ let emit: (String, String?, Int?) -> Void
359
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
360
+ func interstitialAdDidLoad(_ ad: NovvyInterstitialAd) { emit("loaded", nil, nil) }
361
+ func interstitialAd(_ ad: NovvyInterstitialAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
362
+ func interstitialAdDidShow(_ ad: NovvyInterstitialAd) { emit("showed", nil, nil) }
363
+ func interstitialAdDidClose(_ ad: NovvyInterstitialAd) { emit("dismissed", nil, nil) }
364
+ }
365
+
366
+ private final class RewardedDelegate: NSObject, NovvyRewardedAdDelegate {
367
+ let emit: (String, String?, Int?) -> Void
368
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
369
+ func rewardedAdDidLoad(_ ad: NovvyRewardedAd) { emit("loaded", nil, nil) }
370
+ func rewardedAd(_ ad: NovvyRewardedAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
371
+ func rewardedAdDidShow(_ ad: NovvyRewardedAd) { emit("showed", nil, nil) }
372
+ func rewardedAd(_ ad: NovvyRewardedAd, didEarnReward reward: NovvyReward) { emit("earnedReward", nil, nil) }
373
+ func rewardedAdDidClose(_ ad: NovvyRewardedAd) { emit("dismissed", nil, nil) }
374
+ }
375
+
376
+ private final class MidDelegate: NSObject, NovvyMidAdDelegate {
377
+ let emit: (String, String?, Int?) -> Void
378
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
379
+ func midAdDidLoad(_ ad: NovvyMidAd) { emit("loaded", nil, nil) }
380
+ func midAd(_ ad: NovvyMidAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
381
+ func midAdDidShow(_ ad: NovvyMidAd) { emit("showed", nil, nil) }
382
+ func midAdDidClose(_ ad: NovvyMidAd) { emit("dismissed", nil, nil) }
383
+ }
384
+
385
+ private final class EndDelegate: NSObject, NovvyEndAdDelegate {
386
+ let emit: (String, String?, Int?) -> Void
387
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
388
+ func endAdDidLoad(_ ad: NovvyEndAd) { emit("loaded", nil, nil) }
389
+ func endAd(_ ad: NovvyEndAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
390
+ func endAdDidShow(_ ad: NovvyEndAd) { emit("showed", nil, nil) }
391
+ func endAdDidClose(_ ad: NovvyEndAd) { emit("dismissed", nil, nil) }
392
+ }
393
+
394
+ private final class ScrollDelegate: NSObject, NovvyScrollAdDelegate {
395
+ let emit: (String, String?, Int?) -> Void
396
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
397
+ func scrollAdDidLoad(_ ad: NovvyScrollAd) { emit("loaded", nil, nil) }
398
+ func scrollAd(_ ad: NovvyScrollAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
399
+ func scrollAdDidShow(_ ad: NovvyScrollAd) { emit("showed", nil, nil) }
400
+ func scrollAdDidClose(_ ad: NovvyScrollAd) { emit("hidden", nil, nil) }
401
+ }
402
+
403
+ private final class PauseDelegate: NSObject, NovvyPauseAdDelegate {
404
+ let emit: (String, String?, Int?) -> Void
405
+ init(emit: @escaping (String, String?, Int?) -> Void) { self.emit = emit }
406
+ func pauseAd(_ ad: NovvyPauseAd, didFailToLoadWithError error: NovvyError) { emit("failedToLoad", error.localizedDescription, nil) }
407
+ func pauseAdDidShow(_ ad: NovvyPauseAd) { emit("showed", nil, nil) }
408
+ func pauseAdDidFinish(_ ad: NovvyPauseAd) { emit("hidden", nil, nil) }
409
+ }
@@ -0,0 +1,79 @@
1
+ #import "NovvyAdsReactNative-Swift.h"
2
+
3
+ #import <React/RCTViewComponentView.h>
4
+ #import <react/renderer/components/NovvyAdsSpec/ComponentDescriptors.h>
5
+ #import <react/renderer/components/NovvyAdsSpec/EventEmitters.h>
6
+ #import <react/renderer/components/NovvyAdsSpec/Props.h>
7
+ #import <react/renderer/components/NovvyAdsSpec/RCTComponentViewHelpers.h>
8
+
9
+ using namespace facebook::react;
10
+
11
+ /**
12
+ * Fabric component for the inline realtime pause-card ad container.
13
+ *
14
+ * Registers a transparent container with `NovvyAdsImpl` keyed by `adObjectId`,
15
+ * then fires `onViewReady` so JS issues `attachPauseAd` (no load step). Mirrors
16
+ * the Flutter plugin's `NovvyPausePlatformView`.
17
+ */
18
+ @interface NovvyPauseAdView : RCTViewComponentView
19
+ @end
20
+
21
+ @implementation NovvyPauseAdView {
22
+ UIView *_container;
23
+ NSString *_adObjectId;
24
+ // Fabric calls `updateProps` BEFORE `updateEventEmitter`, so the emitter is
25
+ // nil at the moment we learn the adObjectId. Buffer the ready-emit and fire
26
+ // it from `finalizeUpdates:`, which runs after both are set.
27
+ BOOL _viewReadyPending;
28
+ }
29
+
30
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
31
+ return concreteComponentDescriptorProvider<NovvyPauseAdViewComponentDescriptor>();
32
+ }
33
+
34
+ - (instancetype)initWithFrame:(CGRect)frame {
35
+ if (self = [super initWithFrame:frame]) {
36
+ static const auto defaultProps = std::make_shared<const NovvyPauseAdViewProps>();
37
+ _props = defaultProps;
38
+ _container = [[UIView alloc] initWithFrame:self.bounds];
39
+ _container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
40
+ _container.backgroundColor = UIColor.clearColor;
41
+ self.contentView = _container;
42
+ }
43
+ return self;
44
+ }
45
+
46
+ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
47
+ const auto &newProps = *std::static_pointer_cast<const NovvyPauseAdViewProps>(props);
48
+ NSString *adObjectId = [NSString stringWithUTF8String:newProps.adObjectId.c_str()];
49
+ if (adObjectId.length > 0 && ![adObjectId isEqualToString:_adObjectId]) {
50
+ _adObjectId = adObjectId;
51
+ [NovvyAdsImpl.shared registerPauseContainerWithAdObjectId:adObjectId container:_container];
52
+ _viewReadyPending = YES;
53
+ }
54
+ [super updateProps:props oldProps:oldProps];
55
+ }
56
+
57
+ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
58
+ [super finalizeUpdates:updateMask];
59
+ if (_viewReadyPending && _eventEmitter && _adObjectId.length > 0) {
60
+ _viewReadyPending = NO;
61
+ auto emitter = std::static_pointer_cast<const NovvyPauseAdViewEventEmitter>(_eventEmitter);
62
+ emitter->onViewReady({.adObjectId = std::string(_adObjectId.UTF8String)});
63
+ }
64
+ }
65
+
66
+ - (void)prepareForRecycle {
67
+ if (_adObjectId != nil) {
68
+ [NovvyAdsImpl.shared onPauseViewDisposedWithAdObjectId:_adObjectId];
69
+ _adObjectId = nil;
70
+ }
71
+ _viewReadyPending = NO;
72
+ [super prepareForRecycle];
73
+ }
74
+
75
+ @end
76
+
77
+ Class<RCTComponentViewProtocol> NovvyPauseAdViewCls(void) {
78
+ return NovvyPauseAdView.class;
79
+ }