react-native-buzzvil-ad 0.1.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 (62) hide show
  1. package/Buzzvil.podspec +26 -0
  2. package/LICENSE +20 -0
  3. package/README.md +58 -0
  4. package/android/build.gradle +76 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/buzzvil/BuzzvilModule.kt +90 -0
  7. package/android/src/main/java/com/buzzvil/BuzzvilNativeAdView.kt +250 -0
  8. package/android/src/main/java/com/buzzvil/BuzzvilNativeAdViewManager.kt +62 -0
  9. package/android/src/main/java/com/buzzvil/BuzzvilPackage.kt +37 -0
  10. package/android/src/main/res/layout/buzzvil_native_ad_banner.xml +68 -0
  11. package/android/src/main/res/layout/buzzvil_native_ad_card.xml +39 -0
  12. package/ios/Buzzvil.h +5 -0
  13. package/ios/Buzzvil.mm +100 -0
  14. package/ios/BuzzvilNativeAdView.h +14 -0
  15. package/ios/BuzzvilNativeAdView.mm +423 -0
  16. package/lib/module/BuzzvilNativeAdView.js +16 -0
  17. package/lib/module/BuzzvilNativeAdView.js.map +1 -0
  18. package/lib/module/BuzzvilNativeAdView.native.js +42 -0
  19. package/lib/module/BuzzvilNativeAdView.native.js.map +1 -0
  20. package/lib/module/BuzzvilNativeAdViewNativeComponent.ts +22 -0
  21. package/lib/module/NativeBuzzvil.js +32 -0
  22. package/lib/module/NativeBuzzvil.js.map +1 -0
  23. package/lib/module/buzzvil.js +25 -0
  24. package/lib/module/buzzvil.js.map +1 -0
  25. package/lib/module/buzzvil.native.js +64 -0
  26. package/lib/module/buzzvil.native.js.map +1 -0
  27. package/lib/module/index.js +5 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/module/layout.js +39 -0
  30. package/lib/module/layout.js.map +1 -0
  31. package/lib/module/package.json +1 -0
  32. package/lib/module/types.js +2 -0
  33. package/lib/module/types.js.map +1 -0
  34. package/lib/typescript/package.json +1 -0
  35. package/lib/typescript/src/BuzzvilNativeAdView.d.ts +3 -0
  36. package/lib/typescript/src/BuzzvilNativeAdView.d.ts.map +1 -0
  37. package/lib/typescript/src/BuzzvilNativeAdView.native.d.ts +196 -0
  38. package/lib/typescript/src/BuzzvilNativeAdView.native.d.ts.map +1 -0
  39. package/lib/typescript/src/BuzzvilNativeAdViewNativeComponent.d.ts +25 -0
  40. package/lib/typescript/src/BuzzvilNativeAdViewNativeComponent.d.ts.map +1 -0
  41. package/lib/typescript/src/NativeBuzzvil.d.ts +72 -0
  42. package/lib/typescript/src/NativeBuzzvil.d.ts.map +1 -0
  43. package/lib/typescript/src/buzzvil.d.ts +7 -0
  44. package/lib/typescript/src/buzzvil.d.ts.map +1 -0
  45. package/lib/typescript/src/buzzvil.native.d.ts +25 -0
  46. package/lib/typescript/src/buzzvil.native.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +5 -0
  48. package/lib/typescript/src/index.d.ts.map +1 -0
  49. package/lib/typescript/src/layout.d.ts +6 -0
  50. package/lib/typescript/src/layout.d.ts.map +1 -0
  51. package/lib/typescript/src/types.d.ts +44 -0
  52. package/lib/typescript/src/types.d.ts.map +1 -0
  53. package/package.json +159 -0
  54. package/src/BuzzvilNativeAdView.native.tsx +48 -0
  55. package/src/BuzzvilNativeAdView.tsx +12 -0
  56. package/src/BuzzvilNativeAdViewNativeComponent.ts +22 -0
  57. package/src/NativeBuzzvil.ts +76 -0
  58. package/src/buzzvil.native.tsx +65 -0
  59. package/src/buzzvil.tsx +29 -0
  60. package/src/index.tsx +10 -0
  61. package/src/layout.ts +21 -0
  62. package/src/types.ts +45 -0
@@ -0,0 +1,26 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "Buzzvil"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/AndrewDongminYoo/react-native-buzzvil.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ s.dependency "BuzzvilSDK", "~> 6.7.5"
20
+ # Buzzvil.mm imports <BuzzAdBenefitSDK/BuzzAdBenefitSDK-Swift.h> directly for
21
+ # BuzzBenefitHub, so declare it explicitly rather than relying on transitive
22
+ # header reachability.
23
+ s.dependency "BuzzAdBenefitSDK", "~> 6.7.5"
24
+
25
+ install_modules_dependencies(s)
26
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dongmin, Yu
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # react-native-buzzvil-ad
2
+
3
+ > [!IMPORTANT]
4
+ > **This is an unofficial, community-maintained package — NOT an official Buzzvil product.**
5
+ > It is not affiliated with, endorsed by, or supported by Buzzvil. "Buzzvil" and "BuzzBenefit"
6
+ > are trademarks of their respective owners. Use at your own risk: neither the maintainer nor
7
+ > Buzzvil is responsible for any issues arising from its use.
8
+
9
+ An unofficial React Native wrapper for the Buzzvil **BuzzBenefit v6** SDK (Android & iOS).
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install react-native-buzzvil-ad
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```js
20
+ import {
21
+ initialize,
22
+ login,
23
+ logout,
24
+ isLoggedIn,
25
+ showBenefitHub,
26
+ } from 'react-native-buzzvil-ad';
27
+
28
+ // Call once at app startup, before any other method:
29
+ initialize('YOUR_BUZZVIL_APP_ID');
30
+
31
+ // Log a user in (gender / birthYear are optional):
32
+ await login({ userId: 'user-123', gender: 'MALE', birthYear: 1990 });
33
+
34
+ // Present the BenefitHub (offerwall):
35
+ showBenefitHub();
36
+
37
+ // Session helpers:
38
+ const loggedIn = await isLoggedIn();
39
+ logout();
40
+ ```
41
+
42
+ ## Contributing
43
+
44
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
45
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
46
+ - [Code of conduct](CODE_OF_CONDUCT.md)
47
+
48
+ ## Disclaimer
49
+
50
+ This is an unofficial, community-maintained project and is **not** affiliated with, endorsed by, or sponsored by Buzzvil. All product names, logos, and brands — including "Buzzvil" and "BuzzBenefit" — are the property of their respective owners. This package is provided "as is", without warranty of any kind; the maintainer and Buzzvil accept no liability for any damages or issues arising from its use. For official SDKs and support, refer to [Buzzvil's official documentation](https://docs.buzzvil.com).
51
+
52
+ ## License
53
+
54
+ MIT
55
+
56
+ ---
57
+
58
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,76 @@
1
+ buildscript {
2
+ ext.Buzzvil = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return Buzzvil[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ gradlePluginPortal()
21
+ maven { url "https://dl.buzzvil.com/public/maven" }
22
+ }
23
+
24
+ dependencies {
25
+ classpath "com.android.tools.build:gradle:8.7.2"
26
+ // noinspection DifferentKotlinGradleVersion
27
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
28
+ }
29
+ }
30
+
31
+
32
+ apply plugin: "com.android.library"
33
+ apply plugin: "kotlin-android"
34
+
35
+ apply plugin: "com.facebook.react"
36
+
37
+ android {
38
+ namespace "com.buzzvil"
39
+
40
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
41
+
42
+ defaultConfig {
43
+ minSdkVersion getExtOrDefault("minSdkVersion")
44
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
45
+ }
46
+
47
+ buildFeatures {
48
+ buildConfig true
49
+ }
50
+
51
+ buildTypes {
52
+ release {
53
+ minifyEnabled false
54
+ }
55
+ }
56
+
57
+ lint {
58
+ disable "GradleCompatible"
59
+ }
60
+
61
+ compileOptions {
62
+ sourceCompatibility JavaVersion.VERSION_17
63
+ targetCompatibility JavaVersion.VERSION_17
64
+ }
65
+ }
66
+
67
+ dependencies {
68
+ implementation "com.facebook.react:react-android"
69
+
70
+ // Buzzvil BuzzBenefit v6 SDK. The buzzvil-bom pins versions for buzzvil-sdk
71
+ // and its sub-modules. The Maven repo (dl.buzzvil.com/public/maven) is
72
+ // declared in the consumer's settings.gradle, not here — RNGP runs in
73
+ // FAIL_ON_PROJECT_REPOS mode, which rejects module-level repositories {}.
74
+ api platform("com.buzzvil:buzzvil-bom:6.7.+")
75
+ implementation "com.buzzvil:buzzvil-sdk"
76
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,90 @@
1
+ package com.buzzvil
2
+
3
+ import android.app.Application
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.UiThreadUtil
7
+
8
+ // The Buzzvil class names and import paths below are verified against the
9
+ // resolved `com.buzzvil:buzzvil-sdk` AAR (buzzvil-bom 6.7.x) — this module
10
+ // compiles cleanly via `:dongminyu_react-native-buzzvil:compileDebugKotlin`.
11
+ import com.buzzvil.buzzbenefit.BuzzBenefitConfig
12
+ import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHub
13
+ import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHubConfig
14
+ import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHubPage
15
+ import com.buzzvil.sdk.BuzzvilSdk
16
+ import com.buzzvil.sdk.BuzzvilSdkLoginListener
17
+ import com.buzzvil.sdk.BuzzvilSdkUser
18
+
19
+ class BuzzvilModule(
20
+ reactContext: ReactApplicationContext,
21
+ ) : NativeBuzzvilSpec(reactContext) {
22
+ override fun initialize(appId: String) {
23
+ val application = reactApplicationContext.applicationContext as Application
24
+ val config = BuzzBenefitConfig.Builder(appId).build()
25
+ BuzzvilSdk.initialize(application, config)
26
+ }
27
+
28
+ override fun login(
29
+ userId: String,
30
+ gender: String,
31
+ birthYear: Double,
32
+ promise: Promise,
33
+ ) {
34
+ // Sentinel contract (see NativeBuzzvil.ts): "" gender / 0 birthYear → unset.
35
+ val user =
36
+ BuzzvilSdkUser(
37
+ userId = userId,
38
+ gender =
39
+ when (gender) {
40
+ "MALE" -> BuzzvilSdkUser.Gender.MALE
41
+ "FEMALE" -> BuzzvilSdkUser.Gender.FEMALE
42
+ else -> BuzzvilSdkUser.Gender.UNKNOWN // sentinel "" → unspecified
43
+ },
44
+ birthYear = if (birthYear > 0) birthYear.toInt() else null,
45
+ )
46
+ BuzzvilSdk.login(
47
+ user,
48
+ object : BuzzvilSdkLoginListener {
49
+ override fun onSuccess() {
50
+ promise.resolve(null)
51
+ }
52
+
53
+ override fun onFailure(errorType: BuzzvilSdkLoginListener.ErrorType) {
54
+ promise.reject("buzzvil_login_failed", "Buzzvil login failed: $errorType")
55
+ }
56
+ },
57
+ )
58
+ }
59
+
60
+ override fun logout() {
61
+ BuzzvilSdk.logout()
62
+ }
63
+
64
+ override fun isLoggedIn(promise: Promise) {
65
+ promise.resolve(BuzzvilSdk.isLoggedIn)
66
+ }
67
+
68
+ override fun showBenefitHub(
69
+ routePath: String,
70
+ showHistory: Boolean,
71
+ ) {
72
+ // BenefitHub launches an Activity — must run on the main thread (parity
73
+ // with the iOS dispatch_async(main) path).
74
+ UiThreadUtil.runOnUiThread {
75
+ val activity = currentActivity ?: return@runOnUiThread
76
+ val configBuilder = BuzzBenefitHubConfig.Builder()
77
+ if (routePath.isNotEmpty()) {
78
+ configBuilder.routePath(routePath)
79
+ }
80
+ if (showHistory) {
81
+ configBuilder.queryParams(BuzzBenefitHubPage.HISTORY.toRedirectQueryParams())
82
+ }
83
+ BuzzBenefitHub.show(activity, configBuilder.build())
84
+ }
85
+ }
86
+
87
+ companion object {
88
+ const val NAME = NativeBuzzvilSpec.NAME
89
+ }
90
+ }
@@ -0,0 +1,250 @@
1
+ package com.buzzvil
2
+
3
+ import android.view.LayoutInflater
4
+ import android.view.ViewGroup
5
+ import android.widget.FrameLayout
6
+ import android.widget.ImageView
7
+ import android.widget.TextView
8
+ import androidx.core.view.doOnLayout
9
+ import com.facebook.react.bridge.Arguments
10
+ import com.facebook.react.bridge.ReactContext
11
+ import com.facebook.react.bridge.UiThreadUtil
12
+ import com.facebook.react.bridge.WritableMap
13
+ import com.facebook.react.uimanager.ThemedReactContext
14
+ import com.facebook.react.uimanager.UIManagerHelper
15
+ import com.facebook.react.uimanager.events.Event
16
+
17
+ // Buzzvil native-ad API. Class names / signatures verified against the resolved
18
+ // com.buzzvil:buzzad-benefit-base 6.7.7 AAR via `javap` (transitive through
19
+ // buzzvil-sdk / buzzvil-bom). Notably: BuzzNativeViewBinder.bind() takes the
20
+ // BuzzNative loader (NOT the loaded BuzzNativeAd), and the binder exposes
21
+ // unbind()/dispose() for cleanup.
22
+ import com.buzzvil.buzzbenefit.buzznative.BuzzMediaView
23
+ import com.buzzvil.buzzbenefit.DefaultBuzzCtaView
24
+ import com.buzzvil.buzzbenefit.buzznative.BuzzNative
25
+ import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAd
26
+ import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdEventsListener
27
+ import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdView as BuzzAdView
28
+ import com.buzzvil.buzzbenefit.buzznative.BuzzNativeViewBinder
29
+ import com.buzzvil.buzzbenefit.buzznative.BuzzRewardResult
30
+
31
+ class BuzzvilNativeAdView(
32
+ context: ThemedReactContext,
33
+ ) : FrameLayout(context) {
34
+ // Plain private fields + explicit setters. Do NOT use `var x; private set` —
35
+ // its generated setX(String) would clash with the fun setX(String) below.
36
+ private var unitId: String? = null
37
+ private var layoutVariant: String = "300x250"
38
+
39
+ @Volatile private var disposed = false
40
+
41
+ private var buzzNative: BuzzNative? = null
42
+ private var binder: BuzzNativeViewBinder? = null
43
+
44
+ // The unit id of the in-flight / loaded ad. Doubles as the reload guard: an
45
+ // in-place `unitId` prop change must (re)load (mirrors iOS's `_loadedUnitId`
46
+ // comparison). Cleared in cleanup() so a recycled view can load again — never
47
+ // latched permanently.
48
+ private var loadedUnitId: String? = null
49
+
50
+ fun setUnitId(id: String) {
51
+ unitId = id.ifEmpty { null }
52
+ loadIfReady()
53
+ }
54
+
55
+ fun setLayoutVariant(v: String) {
56
+ layoutVariant = v
57
+ }
58
+
59
+ override fun onAttachedToWindow() {
60
+ super.onAttachedToWindow()
61
+ loadIfReady()
62
+ }
63
+
64
+ // Fabric sets props in any order, and the view may not be attached when the
65
+ // unitId arrives — both entry points call this. Guard on an id comparison
66
+ // (mirrors iOS): (re)load only when a non-null unitId actually changes, so an
67
+ // in-place `unitId` prop change on a mounted view reloads instead of being
68
+ // ignored.
69
+ private fun loadIfReady() {
70
+ val id = unitId
71
+ if (id == null || !isAttachedToWindow || id == loadedUnitId) return
72
+ loadedUnitId = id
73
+
74
+ // Tear down any previous ad before the new load (mirrors cleanup()'s
75
+ // teardown); a unitId change on a mounted view reuses this same object.
76
+ binder?.unbind()
77
+ binder?.dispose()
78
+ binder = null
79
+ buzzNative = null
80
+ removeAllViews()
81
+ // Re-arm the in-flight guard so the new load's callbacks (and the
82
+ // doOnLayout size emit) are not blocked by a previous cleanup()/load.
83
+ disposed = false
84
+
85
+ val buzz = BuzzNative(id)
86
+ buzzNative = buzz
87
+
88
+ buzz.setAdEventsListener(
89
+ object : BuzzNativeAdEventsListener {
90
+ override fun onImpressed(ad: BuzzNativeAd) {
91
+ emit("topImpressed", Arguments.createMap())
92
+ }
93
+
94
+ override fun onClicked(ad: BuzzNativeAd) {
95
+ emit("topAdClicked", Arguments.createMap())
96
+ }
97
+
98
+ override fun onRewardRequested(ad: BuzzNativeAd) {
99
+ // No matching JS event.
100
+ }
101
+
102
+ override fun onParticipated(ad: BuzzNativeAd) {
103
+ // No matching JS event.
104
+ }
105
+
106
+ override fun onRewarded(
107
+ ad: BuzzNativeAd,
108
+ result: BuzzRewardResult,
109
+ ) {
110
+ val payload = Arguments.createMap()
111
+ payload.putBoolean("success", result == BuzzRewardResult.SUCCESS)
112
+ emit("topRewarded", payload)
113
+ }
114
+ },
115
+ )
116
+
117
+ buzz.load(
118
+ { _ ->
119
+ // Inflate + bind must touch views on the UI thread. Re-check the reload
120
+ // guard on the UI thread (mirrors iOS): a late callback from a previous
121
+ // unitId must bail when `loadedUnitId` has since changed, otherwise it
122
+ // would bind a stale ad and double-emit.
123
+ UiThreadUtil.runOnUiThread {
124
+ if (disposed || id != loadedUnitId) return@runOnUiThread
125
+ bindLoadedAd(buzz)
126
+ }
127
+ },
128
+ { error ->
129
+ // Marshal to the UI thread for symmetry with the success path; the SDK
130
+ // gives no thread guarantee for these callbacks.
131
+ UiThreadUtil.runOnUiThread {
132
+ if (disposed || id != loadedUnitId) return@runOnUiThread
133
+ val payload = Arguments.createMap()
134
+ payload.putString("code", error.type.name)
135
+ payload.putString("message", error.message ?: error.type.name)
136
+ emit("topAdFailed", payload)
137
+ }
138
+ },
139
+ )
140
+ }
141
+
142
+ // Two layout families: compact horizontal banner (no media) for the small
143
+ // inventory sizes, the vertical media-on-top card for the large ones.
144
+ private fun layoutResFor(variant: String): Int =
145
+ when (variant) {
146
+ "320x50", "320x100", "320x130" -> R.layout.buzzvil_native_ad_banner
147
+ else -> R.layout.buzzvil_native_ad_card
148
+ }
149
+
150
+ // Fixed inventory-box height (dp) per exact size. Width comes from the JS
151
+ // `style`. NOTE: under Fabric the host frame is ultimately driven by the
152
+ // shadow node, so this height is a best-effort hint — see CLAUDE notes / PR.
153
+ private fun heightDpFor(variant: String): Int =
154
+ when (variant) {
155
+ "320x50" -> 50
156
+ "320x100" -> 100
157
+ "320x130" -> 130
158
+ "300x250" -> 250
159
+ "320x480" -> 480
160
+ else -> 250
161
+ }
162
+
163
+ private fun bindLoadedAd(buzz: BuzzNative) {
164
+ val adView =
165
+ LayoutInflater
166
+ .from(context)
167
+ .inflate(layoutResFor(layoutVariant), this, false) as BuzzAdView
168
+ removeAllViews()
169
+ addView(adView)
170
+
171
+ // Pin the host to the inventory-box height (px from dp); width stays from style.
172
+ val density = resources.displayMetrics.density
173
+ val heightPx = (heightDpFor(layoutVariant) * density).toInt()
174
+ layoutParams =
175
+ (
176
+ layoutParams ?: FrameLayout.LayoutParams(
177
+ ViewGroup.LayoutParams.MATCH_PARENT,
178
+ heightPx,
179
+ )
180
+ ).apply { height = heightPx }
181
+
182
+ val media = adView.findViewById<BuzzMediaView>(R.id.buzz_media)
183
+ val icon = adView.findViewById<ImageView>(R.id.buzz_icon)
184
+ val title = adView.findViewById<TextView>(R.id.buzz_title)
185
+ val desc = adView.findViewById<TextView>(R.id.buzz_desc)
186
+ val cta = adView.findViewById<DefaultBuzzCtaView>(R.id.buzz_cta)
187
+
188
+ binder =
189
+ BuzzNativeViewBinder
190
+ .Builder()
191
+ .buzzNativeAdView(adView)
192
+ .buzzMediaView(media)
193
+ .iconImageView(icon)
194
+ .titleTextView(title)
195
+ .descriptionTextView(desc)
196
+ .buzzCtaView(cta)
197
+ .build()
198
+ // bind() takes the BuzzNative loader, not the loaded BuzzNativeAd.
199
+ binder?.bind(buzz)
200
+
201
+ // Emit the REAL measured size, not the pre-layout {0,0}. doOnLayout fires
202
+ // once on the next layout pass (it self-removes), so this emits exactly once
203
+ // per load with non-zero dimensions.
204
+ doOnLayout {
205
+ if (disposed) return@doOnLayout
206
+ // getWidth()/getHeight() are physical pixels; RN's coordinate system is
207
+ // DP/points, so divide by display density to match iOS (which emits
208
+ // UIKit points). Same `displayMetrics.density` used for the fixed-height
209
+ // calc above.
210
+ val density = resources.displayMetrics.density
211
+ val payload = Arguments.createMap()
212
+ payload.putDouble("width", width / density.toDouble())
213
+ payload.putDouble("height", height / density.toDouble())
214
+ emit("topAdLoaded", payload)
215
+ }
216
+ }
217
+
218
+ private fun emit(
219
+ eventName: String,
220
+ payload: WritableMap,
221
+ ) {
222
+ val reactContext = context as ReactContext
223
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
224
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) ?: return
225
+ dispatcher.dispatchEvent(BuzzvilAdEvent(surfaceId, id, eventName, payload))
226
+ }
227
+
228
+ fun cleanup() {
229
+ disposed = true
230
+ binder?.unbind()
231
+ binder?.dispose()
232
+ binder = null
233
+ buzzNative = null
234
+ // Clear the reload guard for recycle symmetry with iOS (prepareForRecycle):
235
+ // a reused view must be able to load again.
236
+ loadedUnitId = null
237
+ removeAllViews()
238
+ }
239
+
240
+ private class BuzzvilAdEvent(
241
+ surfaceId: Int,
242
+ viewTag: Int,
243
+ private val name: String,
244
+ private val payload: WritableMap,
245
+ ) : Event<BuzzvilAdEvent>(surfaceId, viewTag) {
246
+ override fun getEventName() = name
247
+
248
+ override fun getEventData() = payload
249
+ }
250
+ }
@@ -0,0 +1,62 @@
1
+ package com.buzzvil
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+ import com.facebook.react.viewmanagers.BuzzvilNativeAdViewManagerDelegate
9
+ import com.facebook.react.viewmanagers.BuzzvilNativeAdViewManagerInterface
10
+
11
+ @ReactModule(name = BuzzvilNativeAdViewManager.NAME)
12
+ class BuzzvilNativeAdViewManager :
13
+ SimpleViewManager<BuzzvilNativeAdView>(),
14
+ BuzzvilNativeAdViewManagerInterface<BuzzvilNativeAdView> {
15
+ private val mDelegate: ViewManagerDelegate<BuzzvilNativeAdView>
16
+
17
+ init {
18
+ mDelegate = BuzzvilNativeAdViewManagerDelegate(this)
19
+ }
20
+
21
+ override fun getDelegate(): ViewManagerDelegate<BuzzvilNativeAdView>? = mDelegate
22
+
23
+ override fun getName(): String = NAME
24
+
25
+ public override fun createViewInstance(context: ThemedReactContext): BuzzvilNativeAdView = BuzzvilNativeAdView(context)
26
+
27
+ @ReactProp(name = "unitId")
28
+ override fun setUnitId(
29
+ view: BuzzvilNativeAdView,
30
+ value: String?,
31
+ ) {
32
+ view.setUnitId(value ?: "")
33
+ }
34
+
35
+ @ReactProp(name = "layout")
36
+ override fun setLayout(
37
+ view: BuzzvilNativeAdView,
38
+ value: String?,
39
+ ) {
40
+ view.setLayoutVariant(value ?: "300x250")
41
+ }
42
+
43
+ // Map the bubbling event names dispatched by the view to the JS prop handlers
44
+ // declared in BuzzvilNativeAdViewNativeComponent.ts.
45
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
46
+ mutableMapOf(
47
+ "topAdLoaded" to mapOf("registrationName" to "onAdLoaded"),
48
+ "topAdFailed" to mapOf("registrationName" to "onAdFailed"),
49
+ "topAdClicked" to mapOf("registrationName" to "onAdClicked"),
50
+ "topImpressed" to mapOf("registrationName" to "onImpressed"),
51
+ "topRewarded" to mapOf("registrationName" to "onRewarded"),
52
+ )
53
+
54
+ override fun onDropViewInstance(view: BuzzvilNativeAdView) {
55
+ view.cleanup()
56
+ super.onDropViewInstance(view)
57
+ }
58
+
59
+ companion object {
60
+ const val NAME = "BuzzvilNativeAdView"
61
+ }
62
+ }
@@ -0,0 +1,37 @@
1
+ package com.buzzvil
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+
10
+ class BuzzvilPackage : BaseReactPackage() {
11
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = listOf(BuzzvilNativeAdViewManager())
12
+
13
+ override fun getModule(
14
+ name: String,
15
+ reactContext: ReactApplicationContext,
16
+ ): NativeModule? =
17
+ if (name == BuzzvilModule.NAME) {
18
+ BuzzvilModule(reactContext)
19
+ } else {
20
+ null
21
+ }
22
+
23
+ override fun getReactModuleInfoProvider() =
24
+ ReactModuleInfoProvider {
25
+ mapOf(
26
+ BuzzvilModule.NAME to
27
+ ReactModuleInfo(
28
+ name = BuzzvilModule.NAME,
29
+ className = BuzzvilModule.NAME,
30
+ canOverrideExistingModule = false,
31
+ needsEagerInit = false,
32
+ isCxxModule = false,
33
+ isTurboModule = true,
34
+ ),
35
+ )
36
+ }
37
+ }