@ezoic/react-native-sdk 1.2.0 → 1.3.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.
@@ -20,6 +20,9 @@ Pod::Spec.new do |s|
20
20
 
21
21
  # Native Ezoic Ads SDK (vends the `EzoicAdsSDKBinary` module). Brings in
22
22
  # PrebidMobile + Google-Mobile-Ads-SDK transitively.
23
- s.dependency "EzoicAdsSDK", "~> 1.2"
23
+ s.dependency "EzoicAdsSDK", "~> 1.3"
24
+ # The native-ad host imports GoogleMobileAds directly (NativeAdView,
25
+ # MediaView, NativeAd). Pin GMA 12 so the module is on the compile path.
26
+ s.dependency "Google-Mobile-Ads-SDK", "~> 12.0"
24
27
  s.swift_version = "5.9"
25
28
  end
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # @ezoic/react-native-sdk
2
2
 
3
- Ezoic Ads SDK for React Native (Prebid + Google Ad Manager banner ads).
3
+ Ezoic Ads SDK for React Native (Prebid + Google Ad Manager banner, native, interstitial and rewarded ads).
4
4
 
5
5
  A thin React Native (New Architecture) wrapper over the native Ezoic Ads SDKs
6
6
  for iOS (`EzoicAdsSDK`, via CocoaPods) and Android
7
7
  (`com.ezoic.sdk:ezoic-ads-sdk`, via Maven Central). It exposes an imperative
8
- `EzoicAds` TurboModule and an `EzoicBannerView` Fabric component.
8
+ `EzoicAds` TurboModule plus `EzoicBannerView` and `EzoicNativeAdView` Fabric
9
+ components.
9
10
 
10
11
  ## Requirements
11
12
 
@@ -68,6 +69,28 @@ const tracked = await EzoicAds.trackPageview();
68
69
  `adUnitIdentifier` is a string coerced to a native integer. `size` is a `"WxH"`
69
70
  string or comma-separated list (e.g. `"300x250"`, `"300x250,320x50"`).
70
71
 
72
+ ### Native ads
73
+
74
+ `EzoicNativeAdView` loads a native ad and renders it in an SDK-built template
75
+ `NativeAdView` (headline, icon, media, body and a call-to-action). Unlike the
76
+ banner it has no `size` prop — size it with `style` and the template lays its
77
+ assets out inside those bounds.
78
+
79
+ ```tsx
80
+ import { EzoicAds, EzoicNativeAdView } from '@ezoic/react-native-sdk';
81
+
82
+ <EzoicNativeAdView
83
+ adUnitIdentifier="123456"
84
+ style={{ width: '100%', height: 300 }}
85
+ onLoad={() => console.log('loaded')}
86
+ onError={(e) => console.log('error', e.message, e.code)}
87
+ onImpression={() => console.log('impression')}
88
+ onClick={() => console.log('click')}
89
+ onOpen={() => console.log('open')}
90
+ onClose={() => console.log('close')}
91
+ />;
92
+ ```
93
+
71
94
  ## API
72
95
 
73
96
  - `EzoicAds.initialize(config)` → `Promise<void>`
@@ -76,6 +99,7 @@ string or comma-separated list (e.g. `"300x250"`, `"300x250,320x50"`).
76
99
  - `EzoicAds.setSubjectToCOPPA(value)` → `void`
77
100
  - `EzoicAds.trackPageview()` → `Promise<boolean>`
78
101
  - `<EzoicBannerView adUnitIdentifier size onLoad onError onImpression onClick onOpen onClose />`
102
+ - `<EzoicNativeAdView adUnitIdentifier onLoad onError onImpression onClick onOpen onClose />`
79
103
 
80
104
  ## License
81
105
 
@@ -68,5 +68,11 @@ dependencies {
68
68
  // Native Ezoic Ads SDK (resolved from Maven Central). Brings in Google Mobile
69
69
  // Ads + Prebid transitively. Requires mavenCentral() + google() in the
70
70
  // consuming app's repositories (the RN template provides both).
71
- implementation "com.ezoic.sdk:ezoic-ads-sdk:1.2.0"
71
+ implementation "com.ezoic.sdk:ezoic-ads-sdk:1.3.0"
72
+
73
+ // The native-ad wrapper references GMA's NativeAdView/MediaView/NativeAd
74
+ // types at compile time. The Ezoic SDK POM scopes Google Mobile Ads at
75
+ // runtime only, so pin the compile-time classpath here (runtime resolution
76
+ // still comes transitively from the Ezoic SDK).
77
+ compileOnly "com.google.android.gms:play-services-ads:22.6.0"
72
78
  }
@@ -0,0 +1,314 @@
1
+ package com.ezoic.reactnative
2
+
3
+ import android.content.Context
4
+ import android.graphics.Typeface
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import android.widget.Button
8
+ import android.widget.FrameLayout
9
+ import android.widget.ImageView
10
+ import android.widget.LinearLayout
11
+ import android.widget.TextView
12
+ import com.facebook.react.bridge.Arguments
13
+ import com.facebook.react.bridge.ReactApplicationContext
14
+ import com.facebook.react.bridge.WritableMap
15
+ import com.facebook.react.module.annotations.ReactModule
16
+ import com.facebook.react.uimanager.SimpleViewManager
17
+ import com.facebook.react.uimanager.ThemedReactContext
18
+ import com.facebook.react.uimanager.annotations.ReactProp
19
+ import com.facebook.react.uimanager.events.RCTEventEmitter
20
+ import com.ezoic.ads.sdk.adunits.EzoicNativeAd
21
+ import com.ezoic.ads.sdk.adunits.EzoicNativeAdListener
22
+ import com.ezoic.ads.sdk.adunits.EzoicNativeAdLoadListener
23
+ import com.ezoic.ads.sdk.core.EzoicError
24
+ import com.google.android.gms.ads.nativead.MediaView
25
+ import com.google.android.gms.ads.nativead.NativeAd
26
+ import com.google.android.gms.ads.nativead.NativeAdView
27
+
28
+ @ReactModule(name = EzoicNativeAdViewManager.NAME)
29
+ class EzoicNativeAdViewManager(private val ctx: ReactApplicationContext) :
30
+ SimpleViewManager<EzoicNativeAdViewManager.NativeAdContainer>() {
31
+
32
+ override fun getName() = NAME
33
+
34
+ override fun createViewInstance(reactContext: ThemedReactContext): NativeAdContainer {
35
+ val container = NativeAdContainer(reactContext)
36
+ container.layoutParams = ViewGroup.LayoutParams(
37
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
38
+ )
39
+ return container
40
+ }
41
+
42
+ @ReactProp(name = "adUnitIdentifier")
43
+ fun setAdUnitIdentifier(view: NativeAdContainer, value: String?) {
44
+ val newAdUnitId = value?.toIntOrNull() ?: 0
45
+ // Ad unit changed after a load already started: tear down the loaded/loading
46
+ // ad and clear the started flag so onAfterUpdateTransaction's maybeLoad
47
+ // starts a fresh load for the new id.
48
+ if (newAdUnitId != view.adUnitId && view.loadStarted) {
49
+ view.ezoicNativeAd?.destroy()
50
+ view.ezoicNativeAd = null
51
+ view.removeAllViews()
52
+ view.loadStarted = false
53
+ view.loadGeneration++
54
+ }
55
+ view.adUnitId = newAdUnitId
56
+ view.rawAdUnitId = value ?: ""
57
+ }
58
+
59
+ // Fabric sets every prop for the mount transaction before this runs, so the
60
+ // ad unit id is final here. `view.post` escapes the mount transaction so the
61
+ // synchronous native-SDK failure path (uninitialized) can't reenter the
62
+ // mounting layer; the started flag survives repeated transactions.
63
+ override fun onAfterUpdateTransaction(view: NativeAdContainer) {
64
+ super.onAfterUpdateTransaction(view)
65
+ view.post { maybeLoad(view) }
66
+ }
67
+
68
+ private fun maybeLoad(view: NativeAdContainer) {
69
+ if (view.loadStarted || view.disposed) return
70
+ if (view.adUnitId <= 0) {
71
+ // Non-numeric or missing id coerces to 0 in setAdUnitIdentifier. Only
72
+ // emit an error when a prop was actually supplied (non-empty raw
73
+ // string); an unset prop should stay silent.
74
+ if (view.rawAdUnitId.isNotEmpty()) {
75
+ view.loadStarted = true
76
+ emit(view, "topError", errorMap("Invalid ad unit identifier", 0))
77
+ }
78
+ return
79
+ }
80
+ view.loadStarted = true
81
+ val generation = view.loadGeneration
82
+ EzoicNativeAd.load(view.context, view.adUnitId, object : EzoicNativeAdLoadListener {
83
+ override fun onNativeAdLoaded(nativeAd: EzoicNativeAd) {
84
+ // dispose() and this callback both arrive on the main thread, so the
85
+ // check is race-free. A late-arriving ad is destroyed, not rendered.
86
+ // A generation mismatch means the ad unit changed mid-load (mirrors
87
+ // the SDK's isCurrentLoad token pattern): destroy and emit nothing.
88
+ if (view.disposed || view.loadGeneration != generation) {
89
+ nativeAd.destroy()
90
+ return
91
+ }
92
+ val gmaAd = nativeAd.nativeAd ?: run {
93
+ // Empty-content ad: destroy it instead of keeping an unrenderable,
94
+ // errored ad alive on the view.
95
+ nativeAd.destroy()
96
+ emit(view, "topError", errorMap("Native ad loaded without content", 0))
97
+ return
98
+ }
99
+ view.ezoicNativeAd = nativeAd
100
+ // Attach the lifecycle listener before the rendered NativeAdView
101
+ // registers — the impression fires as soon as the view is displayed.
102
+ nativeAd.listener = object : EzoicNativeAdListener {
103
+ override fun onNativeAdImpression(nativeAd: EzoicNativeAd) =
104
+ emit(view, "topImpression", Arguments.createMap())
105
+ override fun onNativeAdClicked(nativeAd: EzoicNativeAd) =
106
+ emit(view, "topAdClick", Arguments.createMap())
107
+ override fun onNativeAdOpened(nativeAd: EzoicNativeAd) =
108
+ emit(view, "topOpen", Arguments.createMap())
109
+ override fun onNativeAdClosed(nativeAd: EzoicNativeAd) =
110
+ emit(view, "topClose", Arguments.createMap())
111
+ }
112
+ val adView = buildTemplate(view.context, gmaAd)
113
+ view.removeAllViews()
114
+ view.addView(adView)
115
+ emit(view, "topLoad", Arguments.createMap())
116
+ }
117
+
118
+ override fun onNativeAdFailedToLoad(error: EzoicError) {
119
+ if (view.disposed || view.loadGeneration != generation) return
120
+ emit(view, "topError", errorMap(error.message, error.code))
121
+ }
122
+ })
123
+ }
124
+
125
+ /**
126
+ * Builds a template [NativeAdView] entirely in code (the module ships no
127
+ * `res/` layouts). Layout: a vertical column of a header row (icon +
128
+ * headline/advertiser), a [MediaView], the body text and a call-to-action
129
+ * button. Only the asset views actually present on [gmaAd] are created and
130
+ * registered; [NativeAdView.setNativeAd] is called last, as GMA requires.
131
+ */
132
+ private fun buildTemplate(context: Context, gmaAd: NativeAd): NativeAdView {
133
+ val adView = NativeAdView(context)
134
+
135
+ val root = LinearLayout(context).apply {
136
+ orientation = LinearLayout.VERTICAL
137
+ layoutParams = FrameLayout.LayoutParams(
138
+ FrameLayout.LayoutParams.MATCH_PARENT,
139
+ FrameLayout.LayoutParams.WRAP_CONTENT,
140
+ )
141
+ val pad = dp(context, 8)
142
+ setPadding(pad, pad, pad, pad)
143
+ }
144
+
145
+ val headerRow = LinearLayout(context).apply {
146
+ orientation = LinearLayout.HORIZONTAL
147
+ layoutParams = LinearLayout.LayoutParams(
148
+ LinearLayout.LayoutParams.MATCH_PARENT,
149
+ LinearLayout.LayoutParams.WRAP_CONTENT,
150
+ )
151
+ }
152
+
153
+ var iconView: ImageView? = null
154
+ gmaAd.icon?.drawable?.let { drawable ->
155
+ val iv = ImageView(context).apply {
156
+ layoutParams = LinearLayout.LayoutParams(dp(context, 40), dp(context, 40))
157
+ setImageDrawable(drawable)
158
+ }
159
+ headerRow.addView(iv)
160
+ iconView = iv
161
+ }
162
+
163
+ val textColumn = LinearLayout(context).apply {
164
+ orientation = LinearLayout.VERTICAL
165
+ layoutParams = LinearLayout.LayoutParams(
166
+ 0,
167
+ LinearLayout.LayoutParams.WRAP_CONTENT,
168
+ 1f,
169
+ ).apply { leftMargin = dp(context, 8) }
170
+ }
171
+
172
+ var headlineView: TextView? = null
173
+ gmaAd.headline?.let { text ->
174
+ val tv = TextView(context).apply {
175
+ this.text = text
176
+ setTypeface(typeface, Typeface.BOLD)
177
+ textSize = 16f
178
+ }
179
+ textColumn.addView(tv)
180
+ headlineView = tv
181
+ }
182
+
183
+ var advertiserView: TextView? = null
184
+ gmaAd.advertiser?.let { text ->
185
+ val tv = TextView(context).apply {
186
+ this.text = text
187
+ textSize = 12f
188
+ }
189
+ textColumn.addView(tv)
190
+ advertiserView = tv
191
+ }
192
+
193
+ headerRow.addView(textColumn)
194
+ root.addView(headerRow)
195
+
196
+ var mediaView: MediaView? = null
197
+ gmaAd.mediaContent?.let { content ->
198
+ val mv = MediaView(context).apply {
199
+ layoutParams = LinearLayout.LayoutParams(
200
+ LinearLayout.LayoutParams.MATCH_PARENT,
201
+ dp(context, 175),
202
+ ).apply { topMargin = dp(context, 8) }
203
+ mediaContent = content
204
+ }
205
+ root.addView(mv)
206
+ mediaView = mv
207
+ }
208
+
209
+ var bodyView: TextView? = null
210
+ gmaAd.body?.let { text ->
211
+ val tv = TextView(context).apply {
212
+ this.text = text
213
+ textSize = 14f
214
+ setPadding(0, dp(context, 8), 0, 0)
215
+ }
216
+ root.addView(tv)
217
+ bodyView = tv
218
+ }
219
+
220
+ var callToActionView: Button? = null
221
+ gmaAd.callToAction?.let { text ->
222
+ val btn = Button(context).apply {
223
+ this.text = text
224
+ layoutParams = LinearLayout.LayoutParams(
225
+ LinearLayout.LayoutParams.MATCH_PARENT,
226
+ LinearLayout.LayoutParams.WRAP_CONTENT,
227
+ ).apply { topMargin = dp(context, 8) }
228
+ }
229
+ root.addView(btn)
230
+ callToActionView = btn
231
+ }
232
+
233
+ adView.addView(root)
234
+ // Register only the asset views that were populated; a null assignment
235
+ // leaves that asset unregistered.
236
+ adView.headlineView = headlineView
237
+ adView.bodyView = bodyView
238
+ adView.iconView = iconView
239
+ adView.advertiserView = advertiserView
240
+ adView.callToActionView = callToActionView
241
+ adView.mediaView = mediaView
242
+ adView.setNativeAd(gmaAd)
243
+ return adView
244
+ }
245
+
246
+ override fun onDropViewInstance(view: NativeAdContainer) {
247
+ super.onDropViewInstance(view)
248
+ view.disposed = true
249
+ view.loadGeneration++
250
+ view.ezoicNativeAd?.destroy()
251
+ view.ezoicNativeAd = null
252
+ view.removeAllViews()
253
+ }
254
+
255
+ private fun emit(view: NativeAdContainer, event: String, payload: WritableMap) {
256
+ if (view.disposed) return
257
+ ctx.getJSModule(RCTEventEmitter::class.java).receiveEvent(view.id, event, payload)
258
+ }
259
+
260
+ private fun errorMap(message: String?, code: Int): WritableMap {
261
+ val map = Arguments.createMap()
262
+ map.putString("message", message ?: "")
263
+ map.putInt("code", code)
264
+ return map
265
+ }
266
+
267
+ private fun dp(context: Context, value: Int): Int =
268
+ (value * context.resources.displayMetrics.density).toInt()
269
+
270
+ override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
271
+ fun reg(on: String) = mapOf("phasedRegistrationNames" to mapOf("bubbled" to on))
272
+ return mapOf(
273
+ "topLoad" to reg("onLoad"),
274
+ "topError" to reg("onError"),
275
+ "topImpression" to reg("onImpression"),
276
+ "topAdClick" to reg("onAdClick"),
277
+ "topOpen" to reg("onOpen"),
278
+ "topClose" to reg("onClose")
279
+ )
280
+ }
281
+
282
+ /**
283
+ * Container for the template ad view. RN lays out only Yoga-managed views;
284
+ * the template [NativeAdView] is added from native code and stays unmeasured
285
+ * (blank) unless we force a measure/layout pass. Overriding [requestLayout]
286
+ * to post a manual measure(EXACTLY)+layout of the current bounds is the
287
+ * proven fix (react-native-google-mobile-ads uses the same approach).
288
+ */
289
+ class NativeAdContainer(context: Context) : FrameLayout(context) {
290
+ var adUnitId: Int = 0
291
+ var rawAdUnitId: String = ""
292
+ var loadStarted: Boolean = false
293
+ var disposed: Boolean = false
294
+ var loadGeneration: Int = 0
295
+ var ezoicNativeAd: EzoicNativeAd? = null
296
+
297
+ private val measureAndLayout = Runnable {
298
+ measure(
299
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
300
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
301
+ )
302
+ layout(left, top, right, bottom)
303
+ }
304
+
305
+ override fun requestLayout() {
306
+ super.requestLayout()
307
+ post(measureAndLayout)
308
+ }
309
+ }
310
+
311
+ companion object {
312
+ const val NAME = "EzoicNativeAdView"
313
+ }
314
+ }
@@ -19,7 +19,10 @@ class EzoicReactNativeSdkPackage : BaseReactPackage() {
19
19
  override fun createViewManagers(
20
20
  reactContext: ReactApplicationContext
21
21
  ): List<ViewManager<*, *>> {
22
- return listOf(EzoicBannerViewManager(reactContext))
22
+ return listOf(
23
+ EzoicBannerViewManager(reactContext),
24
+ EzoicNativeAdViewManager(reactContext)
25
+ )
23
26
  }
24
27
 
25
28
  override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
@@ -0,0 +1,181 @@
1
+ import UIKit
2
+ import EzoicAdsSDKBinary
3
+ import GoogleMobileAds
4
+
5
+ @objc public protocol EzoicNativeAdHostViewDelegate: AnyObject {
6
+ func nativeAdDidLoad()
7
+ func nativeAdDidFail(_ message: String, code: Int)
8
+ func nativeAdDidRecordImpression()
9
+ func nativeAdDidRecordClick()
10
+ func nativeAdWillPresentScreen()
11
+ func nativeAdDidDismissScreen()
12
+ }
13
+
14
+ @objc public class EzoicNativeAdHostView: UIView, EzoicNativeAdDelegate {
15
+
16
+ @objc public weak var hostDelegate: EzoicNativeAdHostViewDelegate?
17
+
18
+ private var adUnitId: Int = 0
19
+ private var ezoicNativeAd: EzoicNativeAd?
20
+ private var adView: NativeAdView?
21
+ private var loadStarted = false
22
+
23
+ /// Stores the ad unit id. The load is NOT started here — the Fabric
24
+ /// component view calls `startLoad()` from `finalizeUpdates`, after the
25
+ /// event emitter is attached, so a synchronous SDK failure (uninitialized)
26
+ /// can deliver `onError` instead of being dropped.
27
+ @objc public func configure(adUnitIdentifier: String) {
28
+ self.adUnitId = Int(adUnitIdentifier) ?? 0
29
+ }
30
+
31
+ /// Starts the native-ad load once. A second call is a no-op; the guard
32
+ /// survives repeated `finalizeUpdates` calls.
33
+ @objc public func startLoad() {
34
+ if loadStarted { return }
35
+ loadStarted = true
36
+ EzoicNativeAd.load(adUnitIdentifier: adUnitId) { [weak self] result in
37
+ guard let self = self else { return }
38
+ switch result {
39
+ case .success(let ad):
40
+ guard let gmaAd = ad.nativeAd else {
41
+ // Empty-content ad: destroy it and do not retain it instead of
42
+ // keeping an unrenderable, errored ad alive.
43
+ ad.destroy()
44
+ self.hostDelegate?.nativeAdDidFail("Native ad loaded without content", code: 0)
45
+ return
46
+ }
47
+ self.ezoicNativeAd = ad
48
+ // Attach the delegate before rendering so the impression, which fires
49
+ // as soon as the NativeAdView is displayed, is delivered.
50
+ ad.delegate = self
51
+ self.render(gmaAd)
52
+ self.hostDelegate?.nativeAdDidLoad()
53
+ case .failure(let error):
54
+ self.hostDelegate?.nativeAdDidFail(error.localizedDescription, code: error.code)
55
+ }
56
+ }
57
+ }
58
+
59
+ /// Builds a template `NativeAdView` in code (mirrors the Android template):
60
+ /// a header row (icon + headline/advertiser), a `MediaView`, the body text
61
+ /// and a call-to-action button. Optional text/image assets are created and
62
+ /// registered only when present, but the `MediaView` is always built: on
63
+ /// GMA 12 `NativeAd.mediaContent` is non-optional and the media view is a
64
+ /// required asset. `adView.nativeAd` is assigned last.
65
+ private func render(_ gmaAd: GoogleMobileAds.NativeAd) {
66
+ let adView = NativeAdView()
67
+ adView.translatesAutoresizingMaskIntoConstraints = false
68
+
69
+ let mainStack = UIStackView()
70
+ mainStack.axis = .vertical
71
+ mainStack.spacing = 8
72
+ mainStack.translatesAutoresizingMaskIntoConstraints = false
73
+
74
+ let headerRow = UIStackView()
75
+ headerRow.axis = .horizontal
76
+ headerRow.spacing = 8
77
+ headerRow.alignment = .center
78
+
79
+ if let image = gmaAd.icon?.image {
80
+ let iconView = UIImageView(image: image)
81
+ iconView.translatesAutoresizingMaskIntoConstraints = false
82
+ NSLayoutConstraint.activate([
83
+ iconView.widthAnchor.constraint(equalToConstant: 40),
84
+ iconView.heightAnchor.constraint(equalToConstant: 40),
85
+ ])
86
+ headerRow.addArrangedSubview(iconView)
87
+ adView.iconView = iconView
88
+ }
89
+
90
+ let textColumn = UIStackView()
91
+ textColumn.axis = .vertical
92
+
93
+ if let headline = gmaAd.headline {
94
+ let label = UILabel()
95
+ label.text = headline
96
+ label.font = .boldSystemFont(ofSize: 16)
97
+ label.numberOfLines = 0
98
+ textColumn.addArrangedSubview(label)
99
+ adView.headlineView = label
100
+ }
101
+
102
+ if let advertiser = gmaAd.advertiser {
103
+ let label = UILabel()
104
+ label.text = advertiser
105
+ label.font = .systemFont(ofSize: 12)
106
+ textColumn.addArrangedSubview(label)
107
+ adView.advertiserView = label
108
+ }
109
+
110
+ headerRow.addArrangedSubview(textColumn)
111
+ mainStack.addArrangedSubview(headerRow)
112
+
113
+ let mediaView = MediaView()
114
+ mediaView.mediaContent = gmaAd.mediaContent
115
+ mediaView.translatesAutoresizingMaskIntoConstraints = false
116
+ // Priority 999 so a caller-supplied style shorter than the template's
117
+ // natural height breaks this constraint instead of spamming
118
+ // unsatisfiable-constraint logs.
119
+ let mediaHeight = mediaView.heightAnchor.constraint(equalToConstant: 175)
120
+ mediaHeight.priority = UILayoutPriority(999)
121
+ mediaHeight.isActive = true
122
+ mainStack.addArrangedSubview(mediaView)
123
+ adView.mediaView = mediaView
124
+
125
+ if let body = gmaAd.body {
126
+ let label = UILabel()
127
+ label.text = body
128
+ label.font = .systemFont(ofSize: 14)
129
+ label.numberOfLines = 0
130
+ mainStack.addArrangedSubview(label)
131
+ adView.bodyView = label
132
+ }
133
+
134
+ if let cta = gmaAd.callToAction {
135
+ let button = UIButton(type: .system)
136
+ button.setTitle(cta, for: .normal)
137
+ // The NativeAdView handles the tap; the button must not intercept it.
138
+ button.isUserInteractionEnabled = false
139
+ mainStack.addArrangedSubview(button)
140
+ adView.callToActionView = button
141
+ }
142
+
143
+ adView.addSubview(mainStack)
144
+ NSLayoutConstraint.activate([
145
+ mainStack.topAnchor.constraint(equalTo: adView.topAnchor, constant: 8),
146
+ mainStack.leadingAnchor.constraint(equalTo: adView.leadingAnchor, constant: 8),
147
+ mainStack.trailingAnchor.constraint(equalTo: adView.trailingAnchor, constant: -8),
148
+ mainStack.bottomAnchor.constraint(equalTo: adView.bottomAnchor, constant: -8),
149
+ ])
150
+
151
+ self.adView?.removeFromSuperview()
152
+ addSubview(adView)
153
+ NSLayoutConstraint.activate([
154
+ adView.topAnchor.constraint(equalTo: topAnchor),
155
+ adView.leadingAnchor.constraint(equalTo: leadingAnchor),
156
+ adView.trailingAnchor.constraint(equalTo: trailingAnchor),
157
+ adView.bottomAnchor.constraint(equalTo: bottomAnchor),
158
+ ])
159
+
160
+ adView.nativeAd = gmaAd
161
+ self.adView = adView
162
+ }
163
+
164
+ // MARK: - EzoicNativeAdDelegate
165
+ public func nativeAdDidRecordImpression(_ nativeAd: EzoicNativeAd) {
166
+ hostDelegate?.nativeAdDidRecordImpression()
167
+ }
168
+ public func nativeAdDidRecordClick(_ nativeAd: EzoicNativeAd) {
169
+ hostDelegate?.nativeAdDidRecordClick()
170
+ }
171
+ public func nativeAdWillPresentScreen(_ nativeAd: EzoicNativeAd) {
172
+ hostDelegate?.nativeAdWillPresentScreen()
173
+ }
174
+ public func nativeAdDidDismissScreen(_ nativeAd: EzoicNativeAd) {
175
+ hostDelegate?.nativeAdDidDismissScreen()
176
+ }
177
+
178
+ deinit {
179
+ ezoicNativeAd?.destroy()
180
+ }
181
+ }
@@ -0,0 +1,99 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <react/renderer/components/EzoicReactNativeSdkSpec/ComponentDescriptors.h>
3
+ #import <react/renderer/components/EzoicReactNativeSdkSpec/EventEmitters.h>
4
+ #import <react/renderer/components/EzoicReactNativeSdkSpec/Props.h>
5
+ #import <react/renderer/components/EzoicReactNativeSdkSpec/RCTComponentViewHelpers.h>
6
+ #import <EzoicReactNativeSdk/EzoicReactNativeSdk-Swift.h>
7
+
8
+ using namespace facebook::react;
9
+
10
+ @interface EzoicNativeAdView : RCTViewComponentView <EzoicNativeAdHostViewDelegate>
11
+ @end
12
+
13
+ @implementation EzoicNativeAdView {
14
+ EzoicNativeAdHostView *_host;
15
+ NSString *_lastAdUnit;
16
+ BOOL _loadStarted;
17
+ }
18
+
19
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
20
+ return concreteComponentDescriptorProvider<EzoicNativeAdViewComponentDescriptor>();
21
+ }
22
+
23
+ - (instancetype)initWithFrame:(CGRect)frame {
24
+ if (self = [super initWithFrame:frame]) {
25
+ _host = [EzoicNativeAdHostView new];
26
+ _host.hostDelegate = self;
27
+ self.contentView = _host;
28
+ }
29
+ return self;
30
+ }
31
+
32
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
33
+ const auto &newProps = *std::static_pointer_cast<EzoicNativeAdViewProps const>(props);
34
+ NSString *adUnit = [NSString stringWithUTF8String:newProps.adUnitIdentifier.c_str()];
35
+ if (![adUnit isEqualToString:_lastAdUnit]) {
36
+ // Ad unit changed after a load already started: swap in a fresh host
37
+ // (mirrors prepareForRecycle) and reset the load guard so
38
+ // finalizeUpdates starts a new load for the new id.
39
+ if (_loadStarted) {
40
+ [_host removeFromSuperview];
41
+ _host = [EzoicNativeAdHostView new];
42
+ _host.hostDelegate = self;
43
+ self.contentView = _host;
44
+ _loadStarted = NO;
45
+ }
46
+ _lastAdUnit = adUnit;
47
+ [_host configureWithAdUnitIdentifier:adUnit];
48
+ }
49
+ [super updateProps:props oldProps:oldProps];
50
+ }
51
+
52
+ // RCTMountingManager mounts on Insert as updateProps → updateEventEmitter →
53
+ // finalizeUpdates. Starting the load here (not in updateProps) guarantees the
54
+ // event emitter is attached before the native SDK can fail synchronously and
55
+ // emit onError, which would otherwise be dropped while _eventEmitter is nil.
56
+ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
57
+ [super finalizeUpdates:updateMask];
58
+ if (!_loadStarted && _lastAdUnit.length > 0) {
59
+ _loadStarted = YES;
60
+ [_host startLoad];
61
+ }
62
+ }
63
+
64
+ - (void)nativeAdDidLoad {
65
+ if (_eventEmitter) std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)->onLoad({});
66
+ }
67
+ - (void)nativeAdDidFail:(NSString *)message code:(NSInteger)code {
68
+ if (_eventEmitter)
69
+ std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)
70
+ ->onError({.message = std::string([message UTF8String]), .code = (int)code});
71
+ }
72
+ - (void)nativeAdDidRecordImpression {
73
+ if (_eventEmitter) std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)->onImpression({});
74
+ }
75
+ - (void)nativeAdDidRecordClick {
76
+ if (_eventEmitter) std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)->onAdClick({});
77
+ }
78
+ - (void)nativeAdWillPresentScreen {
79
+ if (_eventEmitter) std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)->onOpen({});
80
+ }
81
+ - (void)nativeAdDidDismissScreen {
82
+ if (_eventEmitter) std::static_pointer_cast<EzoicNativeAdViewEventEmitter const>(_eventEmitter)->onClose({});
83
+ }
84
+
85
+ - (void)prepareForRecycle {
86
+ // Recycled views are reused for a new ad unit; rebuild the host (which owns
87
+ // the loaded EzoicNativeAd, destroyed on deinit) and reset the load guards.
88
+ [_host removeFromSuperview];
89
+ _host = [EzoicNativeAdHostView new];
90
+ _host.hostDelegate = self;
91
+ self.contentView = _host;
92
+ _lastAdUnit = nil;
93
+ _loadStarted = NO;
94
+ [super prepareForRecycle];
95
+ }
96
+
97
+ Class<RCTComponentViewProtocol> EzoicNativeAdViewCls(void) { return EzoicNativeAdView.class; }
98
+
99
+ @end
@@ -0,0 +1,22 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
3
+
4
+ type LoadEvent = Readonly<{}>;
5
+ type ErrorEvent = Readonly<{ message: string; code: CodegenTypes.Int32 }>;
6
+
7
+ export interface NativeProps extends ViewProps {
8
+ adUnitIdentifier: string;
9
+ onLoad?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
10
+ onError?: CodegenTypes.BubblingEventHandler<ErrorEvent> | null;
11
+ onImpression?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
12
+ // `onClick` is reserved by core ViewProps (a gesture handler), so the native
13
+ // native-ad-click event is exposed as `onAdClick`. The public
14
+ // `EzoicNativeAdView` component maps the user-facing `onClick` prop onto this.
15
+ onAdClick?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
16
+ onOpen?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
17
+ onClose?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
18
+ }
19
+
20
+ export default codegenNativeComponent<NativeProps>(
21
+ 'EzoicNativeAdView'
22
+ ) as HostComponent<NativeProps>;
@@ -1 +1 @@
1
- {"version":3,"names":["normalizeConfig","config","domain","Error","out","autoReadConsent","undefined","subjectToCOPPA","requestATTBeforeAds","debugEnabled","testMode","normalizeSize","size","split","map","s","trim","filter","length","join","coerceAdUnitId","adUnitIdentifier","String","mapRewardResult","result","earned","type","amount"],"sourceRoot":"../../src","sources":["helpers.ts"],"mappings":";;AAEA,OAAO,SAASA,eAAeA,CAACC,MAAmB,EAAe;EAChE,IAAI,CAACA,MAAM,IAAI,CAACA,MAAM,CAACC,MAAM,EAAE;IAC7B,MAAM,IAAIC,KAAK,CAAC,oDAAoD,CAAC;EACvE;EACA,MAAMC,GAAgB,GAAG;IAAEF,MAAM,EAAED,MAAM,CAACC;EAAO,CAAC;EAClD,IAAID,MAAM,CAACI,eAAe,KAAKC,SAAS,EACtCF,GAAG,CAACC,eAAe,GAAGJ,MAAM,CAACI,eAAe;EAC9C,IAAIJ,MAAM,CAACM,cAAc,KAAKD,SAAS,EACrCF,GAAG,CAACG,cAAc,GAAGN,MAAM,CAACM,cAAc;EAC5C,IAAIN,MAAM,CAACO,mBAAmB,KAAKF,SAAS,EAC1CF,GAAG,CAACI,mBAAmB,GAAGP,MAAM,CAACO,mBAAmB;EACtD,IAAIP,MAAM,CAACQ,YAAY,KAAKH,SAAS,EAAEF,GAAG,CAACK,YAAY,GAAGR,MAAM,CAACQ,YAAY;EAC7E,IAAIR,MAAM,CAACS,QAAQ,KAAKJ,SAAS,EAAEF,GAAG,CAACM,QAAQ,GAAGT,MAAM,CAACS,QAAQ;EACjE,OAAON,GAAG;AACZ;AAEA,OAAO,SAASO,aAAaA,CAACC,IAAwB,EAAU;EAC9D,IAAI,CAACA,IAAI,EAAE,OAAO,EAAE;EACpB,OAAOA,IAAI,CACRC,KAAK,CAAC,GAAG,CAAC,CACVC,GAAG,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,CAAC,CAAC,CAAC,CACpBC,MAAM,CAAEF,CAAC,IAAKA,CAAC,CAACG,MAAM,GAAG,CAAC,CAAC,CAC3BC,IAAI,CAAC,GAAG,CAAC;AACd;AAEA,OAAO,SAASC,cAAcA,CAACC,gBAAwB,EAAU;EAC/D,OAAOC,MAAM,CAACD,gBAAgB,CAAC;AACjC;AAQA;AACA;AACA;AACA;AACA,OAAO,SAASE,eAAeA,CAC7BC,MAA2C,EACF;EACzC,IAAIA,MAAM,IAAIA,MAAM,CAACC,MAAM,EAAE;IAC3B,OAAO;MAAEC,IAAI,EAAEF,MAAM,CAACE,IAAI;MAAEC,MAAM,EAAEH,MAAM,CAACG;IAAO,CAAC;EACrD;EACA,OAAO,IAAI;AACb","ignoreList":[]}
1
+ {"version":3,"names":["normalizeConfig","config","domain","Error","out","autoReadConsent","undefined","subjectToCOPPA","requestATTBeforeAds","debugEnabled","testMode","normalizeSize","size","split","map","s","trim","filter","length","join","coerceAdUnitId","adUnitIdentifier","String","mapRewardResult","result","earned","type","amount"],"sourceRoot":"../../src","sources":["helpers.ts"],"mappings":";;AAEA,OAAO,SAASA,eAAeA,CAACC,MAAmB,EAAe;EAChE,IAAI,CAACA,MAAM,IAAI,CAACA,MAAM,CAACC,MAAM,EAAE;IAC7B,MAAM,IAAIC,KAAK,CAAC,oDAAoD,CAAC;EACvE;EACA,MAAMC,GAAgB,GAAG;IAAEF,MAAM,EAAED,MAAM,CAACC;EAAO,CAAC;EAClD,IAAID,MAAM,CAACI,eAAe,KAAKC,SAAS,EACtCF,GAAG,CAACC,eAAe,GAAGJ,MAAM,CAACI,eAAe;EAC9C,IAAIJ,MAAM,CAACM,cAAc,KAAKD,SAAS,EACrCF,GAAG,CAACG,cAAc,GAAGN,MAAM,CAACM,cAAc;EAC5C,IAAIN,MAAM,CAACO,mBAAmB,KAAKF,SAAS,EAC1CF,GAAG,CAACI,mBAAmB,GAAGP,MAAM,CAACO,mBAAmB;EACtD,IAAIP,MAAM,CAACQ,YAAY,KAAKH,SAAS,EAAEF,GAAG,CAACK,YAAY,GAAGR,MAAM,CAACQ,YAAY;EAC7E,IAAIR,MAAM,CAACS,QAAQ,KAAKJ,SAAS,EAAEF,GAAG,CAACM,QAAQ,GAAGT,MAAM,CAACS,QAAQ;EACjE,OAAON,GAAG;AACZ;AAEA,OAAO,SAASO,aAAaA,CAACC,IAAwB,EAAU;EAC9D,IAAI,CAACA,IAAI,EAAE,OAAO,EAAE;EACpB,OAAOA,IAAI,CACRC,KAAK,CAAC,GAAG,CAAC,CACVC,GAAG,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,CAAC,CAAC,CAAC,CACpBC,MAAM,CAAEF,CAAC,IAAKA,CAAC,CAACG,MAAM,GAAG,CAAC,CAAC,CAC3BC,IAAI,CAAC,GAAG,CAAC;AACd;AAEA,OAAO,SAASC,cAAcA,CAACC,gBAAiC,EAAU;EACxE,OAAOC,MAAM,CAACD,gBAAgB,CAAC;AACjC;AAQA;AACA;AACA;AACA;AACA,OAAO,SAASE,eAAeA,CAC7BC,MAA2C,EACF;EACzC,IAAIA,MAAM,IAAIA,MAAM,CAACC,MAAM,EAAE;IAC3B,OAAO;MAAEC,IAAI,EAAEF,MAAM,CAACE,IAAI;MAAEC,MAAM,EAAEH,MAAM,CAACG;IAAO,CAAC;EACrD;EACA,OAAO,IAAI;AACb","ignoreList":[]}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import NativeEzoicAds from "./NativeEzoicAds.js";
4
4
  import EzoicBannerNative from './EzoicBannerViewNativeComponent';
5
+ import EzoicNativeAdNative from './EzoicNativeAdViewNativeComponent';
5
6
  import { coerceAdUnitId, normalizeConfig, normalizeSize } from "./helpers.js";
6
7
  import { jsx as _jsx } from "react/jsx-runtime";
7
8
  export { EzoicRewardedAd } from "./EzoicRewardedAd.js";
@@ -47,4 +48,31 @@ export function EzoicBannerView(props) {
47
48
  onClose: onClose ? () => onClose() : undefined
48
49
  });
49
50
  }
51
+ /**
52
+ * Renders a native ad in an SDK-built template `NativeAdView`. The component
53
+ * fills the bounds it is given by its RN style, so size it with `style` (e.g.
54
+ * `{ width: '100%', height: 300 }`); the template lays out its assets inside.
55
+ */
56
+ export function EzoicNativeAdView(props) {
57
+ const {
58
+ adUnitIdentifier,
59
+ onLoad,
60
+ onError,
61
+ onImpression,
62
+ onClick,
63
+ onOpen,
64
+ onClose,
65
+ ...rest
66
+ } = props;
67
+ return /*#__PURE__*/_jsx(EzoicNativeAdNative, {
68
+ ...rest,
69
+ adUnitIdentifier: coerceAdUnitId(adUnitIdentifier),
70
+ onLoad: onLoad ? () => onLoad() : undefined,
71
+ onError: onError ? e => onError(e.nativeEvent) : undefined,
72
+ onImpression: onImpression ? () => onImpression() : undefined,
73
+ onAdClick: onClick ? () => onClick() : undefined,
74
+ onOpen: onOpen ? () => onOpen() : undefined,
75
+ onClose: onClose ? () => onClose() : undefined
76
+ });
77
+ }
50
78
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["NativeEzoicAds","EzoicBannerNative","coerceAdUnitId","normalizeConfig","normalizeSize","jsx","_jsx","EzoicRewardedAd","EzoicInterstitialAd","EzoicAds","initialize","config","setGDPRConsent","applies","consentString","setGPPConsent","gppString","sectionIds","setSubjectToCOPPA","value","trackPageview","EzoicBannerView","props","adUnitIdentifier","size","onLoad","onError","onImpression","onClick","onOpen","onClose","rest","undefined","e","nativeEvent","onAdClick"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AACA,OAAOA,cAAc,MAA4B,qBAAkB;AACnE,OAAOC,iBAAiB,MAAM,kCAAkC;AAChE,SAASC,cAAc,EAAEC,eAAe,EAAEC,aAAa,QAAQ,cAAW;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAG3E,SACEC,eAAe,QAGV,sBAAmB;AAC1B,SACEC,mBAAmB,QAEd,0BAAuB;AAE9B,OAAO,MAAMC,QAAQ,GAAG;EACtBC,UAAUA,CAACC,MAAmB,EAAiB;IAC7C,OAAOX,cAAc,CAACU,UAAU,CAACP,eAAe,CAACQ,MAAM,CAAC,CAAC;EAC3D,CAAC;EACDC,cAAcA,CAACC,OAAgB,EAAEC,aAAsB,EAAQ;IAC7Dd,cAAc,CAACY,cAAc,CAACC,OAAO,EAAEC,aAAa,CAAC;EACvD,CAAC;EACDC,aAAaA,CAACC,SAAkB,EAAEC,UAAmB,EAAQ;IAC3DjB,cAAc,CAACe,aAAa,CAACC,SAAS,EAAEC,UAAU,CAAC;EACrD,CAAC;EACDC,iBAAiBA,CAACC,KAAc,EAAQ;IACtCnB,cAAc,CAACkB,iBAAiB,CAACC,KAAK,CAAC;EACzC,CAAC;EACDC,aAAaA,CAAA,EAAqB;IAChC,OAAOpB,cAAc,CAACoB,aAAa,CAAC,CAAC;EACvC;AACF,CAAC;AAmBD,OAAO,SAASC,eAAeA,CAACC,KAA2B,EAAE;EAC3D,MAAM;IACJC,gBAAgB;IAChBC,IAAI;IACJC,MAAM;IACNC,OAAO;IACPC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,OAAO;IACP,GAAGC;EACL,CAAC,GAAGT,KAAK;EACT,oBACEhB,IAAA,CAACL,iBAAiB;IAAA,GACZ8B,IAAI;IACRR,gBAAgB,EAAErB,cAAc,CAACqB,gBAAgB,CAAE;IACnDC,IAAI,EAAEpB,aAAa,CAACoB,IAAI,CAAE;IAC1BC,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGO,SAAU;IAC5CN,OAAO,EAAEA,OAAO,GAAIO,CAAC,IAAKP,OAAO,CAACO,CAAC,CAACC,WAAW,CAAC,GAAGF,SAAU;IAC7DL,YAAY,EAAEA,YAAY,GAAG,MAAMA,YAAY,CAAC,CAAC,GAAGK,SAAU;IAC9DG,SAAS,EAAEP,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGI,SAAU;IACjDH,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGG,SAAU;IAC5CF,OAAO,EAAEA,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGE;EAAU,CAChD,CAAC;AAEN","ignoreList":[]}
1
+ {"version":3,"names":["NativeEzoicAds","EzoicBannerNative","EzoicNativeAdNative","coerceAdUnitId","normalizeConfig","normalizeSize","jsx","_jsx","EzoicRewardedAd","EzoicInterstitialAd","EzoicAds","initialize","config","setGDPRConsent","applies","consentString","setGPPConsent","gppString","sectionIds","setSubjectToCOPPA","value","trackPageview","EzoicBannerView","props","adUnitIdentifier","size","onLoad","onError","onImpression","onClick","onOpen","onClose","rest","undefined","e","nativeEvent","onAdClick","EzoicNativeAdView"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AACA,OAAOA,cAAc,MAA4B,qBAAkB;AACnE,OAAOC,iBAAiB,MAAM,kCAAkC;AAChE,OAAOC,mBAAmB,MAAM,oCAAoC;AACpE,SAASC,cAAc,EAAEC,eAAe,EAAEC,aAAa,QAAQ,cAAW;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAG3E,SACEC,eAAe,QAGV,sBAAmB;AAC1B,SACEC,mBAAmB,QAEd,0BAAuB;AAE9B,OAAO,MAAMC,QAAQ,GAAG;EACtBC,UAAUA,CAACC,MAAmB,EAAiB;IAC7C,OAAOZ,cAAc,CAACW,UAAU,CAACP,eAAe,CAACQ,MAAM,CAAC,CAAC;EAC3D,CAAC;EACDC,cAAcA,CAACC,OAAgB,EAAEC,aAAsB,EAAQ;IAC7Df,cAAc,CAACa,cAAc,CAACC,OAAO,EAAEC,aAAa,CAAC;EACvD,CAAC;EACDC,aAAaA,CAACC,SAAkB,EAAEC,UAAmB,EAAQ;IAC3DlB,cAAc,CAACgB,aAAa,CAACC,SAAS,EAAEC,UAAU,CAAC;EACrD,CAAC;EACDC,iBAAiBA,CAACC,KAAc,EAAQ;IACtCpB,cAAc,CAACmB,iBAAiB,CAACC,KAAK,CAAC;EACzC,CAAC;EACDC,aAAaA,CAAA,EAAqB;IAChC,OAAOrB,cAAc,CAACqB,aAAa,CAAC,CAAC;EACvC;AACF,CAAC;AAmBD,OAAO,SAASC,eAAeA,CAACC,KAA2B,EAAE;EAC3D,MAAM;IACJC,gBAAgB;IAChBC,IAAI;IACJC,MAAM;IACNC,OAAO;IACPC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,OAAO;IACP,GAAGC;EACL,CAAC,GAAGT,KAAK;EACT,oBACEhB,IAAA,CAACN,iBAAiB;IAAA,GACZ+B,IAAI;IACRR,gBAAgB,EAAErB,cAAc,CAACqB,gBAAgB,CAAE;IACnDC,IAAI,EAAEpB,aAAa,CAACoB,IAAI,CAAE;IAC1BC,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGO,SAAU;IAC5CN,OAAO,EAAEA,OAAO,GAAIO,CAAC,IAAKP,OAAO,CAACO,CAAC,CAACC,WAAW,CAAC,GAAGF,SAAU;IAC7DL,YAAY,EAAEA,YAAY,GAAG,MAAMA,YAAY,CAAC,CAAC,GAAGK,SAAU;IAC9DG,SAAS,EAAEP,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGI,SAAU;IACjDH,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGG,SAAU;IAC5CF,OAAO,EAAEA,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGE;EAAU,CAChD,CAAC;AAEN;AAkBA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,iBAAiBA,CAACd,KAA6B,EAAE;EAC/D,MAAM;IACJC,gBAAgB;IAChBE,MAAM;IACNC,OAAO;IACPC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,OAAO;IACP,GAAGC;EACL,CAAC,GAAGT,KAAK;EACT,oBACEhB,IAAA,CAACL,mBAAmB;IAAA,GACd8B,IAAI;IACRR,gBAAgB,EAAErB,cAAc,CAACqB,gBAAgB,CAAE;IACnDE,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGO,SAAU;IAC5CN,OAAO,EAAEA,OAAO,GAAIO,CAAC,IAAKP,OAAO,CAACO,CAAC,CAACC,WAAW,CAAC,GAAGF,SAAU;IAC7DL,YAAY,EAAEA,YAAY,GAAG,MAAMA,YAAY,CAAC,CAAC,GAAGK,SAAU;IAC9DG,SAAS,EAAEP,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGI,SAAU;IACjDH,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGG,SAAU;IAC5CF,OAAO,EAAEA,OAAO,GAAG,MAAMA,OAAO,CAAC,CAAC,GAAGE;EAAU,CAChD,CAAC;AAEN","ignoreList":[]}
@@ -0,0 +1,18 @@
1
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
2
+ type LoadEvent = Readonly<{}>;
3
+ type ErrorEvent = Readonly<{
4
+ message: string;
5
+ code: CodegenTypes.Int32;
6
+ }>;
7
+ export interface NativeProps extends ViewProps {
8
+ adUnitIdentifier: string;
9
+ onLoad?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
10
+ onError?: CodegenTypes.BubblingEventHandler<ErrorEvent> | null;
11
+ onImpression?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
12
+ onAdClick?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
13
+ onOpen?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
14
+ onClose?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
15
+ }
16
+ declare const _default: HostComponent<NativeProps>;
17
+ export default _default;
18
+ //# sourceMappingURL=EzoicNativeAdViewNativeComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EzoicNativeAdViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/EzoicNativeAdViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3E,KAAK,SAAS,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC9B,KAAK,UAAU,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC,KAAK,CAAA;CAAE,CAAC,CAAC;AAE1E,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAC7D,OAAO,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAInE,SAAS,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAChE,MAAM,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAC7D,OAAO,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;CAC/D;wBAII,aAAa,CAAC,WAAW,CAAC;AAF/B,wBAEgC"}
@@ -1,7 +1,7 @@
1
1
  import type { EzoicConfig } from './NativeEzoicAds.js';
2
2
  export declare function normalizeConfig(config: EzoicConfig): EzoicConfig;
3
3
  export declare function normalizeSize(size: string | undefined): string;
4
- export declare function coerceAdUnitId(adUnitIdentifier: string): string;
4
+ export declare function coerceAdUnitId(adUnitIdentifier: string | number): string;
5
5
  export interface RewardResultLike {
6
6
  earned: boolean;
7
7
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAkB,CAAC;AAEpD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAchE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAO9D;AAED,wBAAgB,cAAc,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAkB,CAAC;AAEpD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAchE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAO9D;AAED,wBAAgB,cAAc,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAExE;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzC"}
@@ -26,4 +26,24 @@ export interface EzoicBannerViewProps {
26
26
  onClose?: () => void;
27
27
  }
28
28
  export declare function EzoicBannerView(props: EzoicBannerViewProps): import("react").JSX.Element;
29
+ export interface EzoicNativeAdError {
30
+ message: string;
31
+ code: number;
32
+ }
33
+ export interface EzoicNativeAdViewProps {
34
+ adUnitIdentifier: string | number;
35
+ style?: StyleProp<ViewStyle>;
36
+ onLoad?: () => void;
37
+ onError?: (error: EzoicNativeAdError) => void;
38
+ onImpression?: () => void;
39
+ onClick?: () => void;
40
+ onOpen?: () => void;
41
+ onClose?: () => void;
42
+ }
43
+ /**
44
+ * Renders a native ad in an SDK-built template `NativeAdView`. The component
45
+ * fills the bounds it is given by its RN style, so size it with `style` (e.g.
46
+ * `{ width: '100%', height: 300 }`); the template lays out its assets inside.
47
+ */
48
+ export declare function EzoicNativeAdView(props: EzoicNativeAdViewProps): import("react").JSX.Element;
29
49
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAkB,CAAC;AAIpE,YAAY,EAAE,WAAW,EAAE,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAC9B,MAAM,sBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,KAAK,4BAA4B,GAClC,MAAM,0BAAuB,CAAC;AAE/B,eAAO,MAAM,QAAQ;uBACA,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;4BAGtB,OAAO,kBAAkB,MAAM,GAAG,IAAI;8BAGpC,MAAM,eAAe,MAAM,GAAG,IAAI;6BAGnC,OAAO,GAAG,IAAI;qBAGtB,OAAO,CAAC,OAAO,CAAC;CAGlC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAyB1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAkB,CAAC;AAKpE,YAAY,EAAE,WAAW,EAAE,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAC9B,MAAM,sBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,KAAK,4BAA4B,GAClC,MAAM,0BAAuB,CAAC;AAE/B,eAAO,MAAM,QAAQ;uBACA,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;4BAGtB,OAAO,kBAAkB,MAAM,GAAG,IAAI;8BAGpC,MAAM,eAAe,MAAM,GAAG,IAAI;6BAGnC,OAAO,GAAG,IAAI;qBAGtB,OAAO,CAAC,OAAO,CAAC;CAGlC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAyB1D;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAuB9D"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ezoic/react-native-sdk",
3
- "version": "1.2.0",
4
- "description": "Ezoic Ads SDK for React Native (Prebid + Google Ad Manager banner, interstitial and rewarded ads).",
3
+ "version": "1.3.0",
4
+ "description": "Ezoic Ads SDK for React Native (Prebid + Google Ad Manager banner, native, interstitial and rewarded ads).",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
7
7
  "exports": {
@@ -0,0 +1,22 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
3
+
4
+ type LoadEvent = Readonly<{}>;
5
+ type ErrorEvent = Readonly<{ message: string; code: CodegenTypes.Int32 }>;
6
+
7
+ export interface NativeProps extends ViewProps {
8
+ adUnitIdentifier: string;
9
+ onLoad?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
10
+ onError?: CodegenTypes.BubblingEventHandler<ErrorEvent> | null;
11
+ onImpression?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
12
+ // `onClick` is reserved by core ViewProps (a gesture handler), so the native
13
+ // native-ad-click event is exposed as `onAdClick`. The public
14
+ // `EzoicNativeAdView` component maps the user-facing `onClick` prop onto this.
15
+ onAdClick?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
16
+ onOpen?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
17
+ onClose?: CodegenTypes.BubblingEventHandler<LoadEvent> | null;
18
+ }
19
+
20
+ export default codegenNativeComponent<NativeProps>(
21
+ 'EzoicNativeAdView'
22
+ ) as HostComponent<NativeProps>;
package/src/helpers.ts CHANGED
@@ -25,7 +25,7 @@ export function normalizeSize(size: string | undefined): string {
25
25
  .join(',');
26
26
  }
27
27
 
28
- export function coerceAdUnitId(adUnitIdentifier: string): string {
28
+ export function coerceAdUnitId(adUnitIdentifier: string | number): string {
29
29
  return String(adUnitIdentifier);
30
30
  }
31
31
 
package/src/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { StyleProp, ViewStyle } from 'react-native';
2
2
  import NativeEzoicAds, { type EzoicConfig } from './NativeEzoicAds';
3
3
  import EzoicBannerNative from './EzoicBannerViewNativeComponent';
4
+ import EzoicNativeAdNative from './EzoicNativeAdViewNativeComponent';
4
5
  import { coerceAdUnitId, normalizeConfig, normalizeSize } from './helpers';
5
6
 
6
7
  export type { EzoicConfig };
@@ -75,3 +76,49 @@ export function EzoicBannerView(props: EzoicBannerViewProps) {
75
76
  />
76
77
  );
77
78
  }
79
+
80
+ export interface EzoicNativeAdError {
81
+ message: string;
82
+ code: number;
83
+ }
84
+
85
+ export interface EzoicNativeAdViewProps {
86
+ adUnitIdentifier: string | number;
87
+ style?: StyleProp<ViewStyle>;
88
+ onLoad?: () => void;
89
+ onError?: (error: EzoicNativeAdError) => void;
90
+ onImpression?: () => void;
91
+ onClick?: () => void;
92
+ onOpen?: () => void;
93
+ onClose?: () => void;
94
+ }
95
+
96
+ /**
97
+ * Renders a native ad in an SDK-built template `NativeAdView`. The component
98
+ * fills the bounds it is given by its RN style, so size it with `style` (e.g.
99
+ * `{ width: '100%', height: 300 }`); the template lays out its assets inside.
100
+ */
101
+ export function EzoicNativeAdView(props: EzoicNativeAdViewProps) {
102
+ const {
103
+ adUnitIdentifier,
104
+ onLoad,
105
+ onError,
106
+ onImpression,
107
+ onClick,
108
+ onOpen,
109
+ onClose,
110
+ ...rest
111
+ } = props;
112
+ return (
113
+ <EzoicNativeAdNative
114
+ {...rest}
115
+ adUnitIdentifier={coerceAdUnitId(adUnitIdentifier)}
116
+ onLoad={onLoad ? () => onLoad() : undefined}
117
+ onError={onError ? (e) => onError(e.nativeEvent) : undefined}
118
+ onImpression={onImpression ? () => onImpression() : undefined}
119
+ onAdClick={onClick ? () => onClick() : undefined}
120
+ onOpen={onOpen ? () => onOpen() : undefined}
121
+ onClose={onClose ? () => onClose() : undefined}
122
+ />
123
+ );
124
+ }