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.
- package/Buzzvil.podspec +26 -0
- package/LICENSE +20 -0
- package/README.md +58 -0
- package/android/build.gradle +76 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/buzzvil/BuzzvilModule.kt +90 -0
- package/android/src/main/java/com/buzzvil/BuzzvilNativeAdView.kt +250 -0
- package/android/src/main/java/com/buzzvil/BuzzvilNativeAdViewManager.kt +62 -0
- package/android/src/main/java/com/buzzvil/BuzzvilPackage.kt +37 -0
- package/android/src/main/res/layout/buzzvil_native_ad_banner.xml +68 -0
- package/android/src/main/res/layout/buzzvil_native_ad_card.xml +39 -0
- package/ios/Buzzvil.h +5 -0
- package/ios/Buzzvil.mm +100 -0
- package/ios/BuzzvilNativeAdView.h +14 -0
- package/ios/BuzzvilNativeAdView.mm +423 -0
- package/lib/module/BuzzvilNativeAdView.js +16 -0
- package/lib/module/BuzzvilNativeAdView.js.map +1 -0
- package/lib/module/BuzzvilNativeAdView.native.js +42 -0
- package/lib/module/BuzzvilNativeAdView.native.js.map +1 -0
- package/lib/module/BuzzvilNativeAdViewNativeComponent.ts +22 -0
- package/lib/module/NativeBuzzvil.js +32 -0
- package/lib/module/NativeBuzzvil.js.map +1 -0
- package/lib/module/buzzvil.js +25 -0
- package/lib/module/buzzvil.js.map +1 -0
- package/lib/module/buzzvil.native.js +64 -0
- package/lib/module/buzzvil.native.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/layout.js +39 -0
- package/lib/module/layout.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/BuzzvilNativeAdView.d.ts +3 -0
- package/lib/typescript/src/BuzzvilNativeAdView.d.ts.map +1 -0
- package/lib/typescript/src/BuzzvilNativeAdView.native.d.ts +196 -0
- package/lib/typescript/src/BuzzvilNativeAdView.native.d.ts.map +1 -0
- package/lib/typescript/src/BuzzvilNativeAdViewNativeComponent.d.ts +25 -0
- package/lib/typescript/src/BuzzvilNativeAdViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/NativeBuzzvil.d.ts +72 -0
- package/lib/typescript/src/NativeBuzzvil.d.ts.map +1 -0
- package/lib/typescript/src/buzzvil.d.ts +7 -0
- package/lib/typescript/src/buzzvil.d.ts.map +1 -0
- package/lib/typescript/src/buzzvil.native.d.ts +25 -0
- package/lib/typescript/src/buzzvil.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/layout.d.ts +6 -0
- package/lib/typescript/src/layout.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +44 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +159 -0
- package/src/BuzzvilNativeAdView.native.tsx +48 -0
- package/src/BuzzvilNativeAdView.tsx +12 -0
- package/src/BuzzvilNativeAdViewNativeComponent.ts +22 -0
- package/src/NativeBuzzvil.ts +76 -0
- package/src/buzzvil.native.tsx +65 -0
- package/src/buzzvil.tsx +29 -0
- package/src/index.tsx +10 -0
- package/src/layout.ts +21 -0
- package/src/types.ts +45 -0
package/Buzzvil.podspec
ADDED
|
@@ -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,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
|
+
}
|