react-native-levelplayads 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/LICENSE +20 -0
- package/NitroLevelPlayAds.podspec +28 -0
- package/README.md +52 -0
- package/android/CMakeLists.txt +20 -0
- package/android/build.gradle +130 -0
- package/android/consumer-rules.pro +3 -0
- package/android/fix-prefab.gradle +46 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +7 -0
- package/android/src/main/java/com/margelo/nitro/levelplayads/HybridLevelPlayAds.kt +287 -0
- package/android/src/main/java/com/margelo/nitro/levelplayads/LevelPlayAndroidUtils.kt +30 -0
- package/android/src/main/java/com/margelo/nitro/levelplayads/NitroLevelPlayAdsPackage.java +27 -0
- package/ios/HybridLevelPlayAds.swift +274 -0
- package/ios/LevelPlayAdsSupport.swift +74 -0
- package/lib/module/index.js +31 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.test.js +78 -0
- package/lib/module/index.test.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/LevelPlayAds.nitro.js +4 -0
- package/lib/module/specs/LevelPlayAds.nitro.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.test.d.ts +2 -0
- package/lib/typescript/src/index.test.d.ts.map +1 -0
- package/lib/typescript/src/specs/LevelPlayAds.nitro.d.ts +18 -0
- package/lib/typescript/src/specs/LevelPlayAds.nitro.d.ts.map +1 -0
- package/nitro.json +18 -0
- package/nitrogen/generated/android/NitroLevelPlayAds+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroLevelPlayAds+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroLevelPlayAdsOnLoad.cpp +44 -0
- package/nitrogen/generated/android/NitroLevelPlayAdsOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JHybridLevelPlayAdsSpec.cpp +112 -0
- package/nitrogen/generated/android/c++/JHybridLevelPlayAdsSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JLevelPlayAdShowResult.hpp +54 -0
- package/nitrogen/generated/android/c++/JLevelPlayAdShowState.hpp +59 -0
- package/nitrogen/generated/android/c++/JLevelPlayAdType.hpp +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/levelplayads/HybridLevelPlayAdsSpec.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/levelplayads/LevelPlayAdShowResult.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/levelplayads/LevelPlayAdShowState.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/levelplayads/LevelPlayAdType.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/levelplayads/NitroLevelPlayAdsOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroLevelPlayAds+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroLevelPlayAds-Swift-Cxx-Bridge.cpp +56 -0
- package/nitrogen/generated/ios/NitroLevelPlayAds-Swift-Cxx-Bridge.hpp +168 -0
- package/nitrogen/generated/ios/NitroLevelPlayAds-Swift-Cxx-Umbrella.hpp +54 -0
- package/nitrogen/generated/ios/NitroLevelPlayAdsAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroLevelPlayAdsAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridLevelPlayAdsSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLevelPlayAdsSpecSwift.hpp +116 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_LevelPlayAdShowResult.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridLevelPlayAdsSpec.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLevelPlayAdsSpec_cxx.swift +201 -0
- package/nitrogen/generated/ios/swift/LevelPlayAdShowResult.swift +35 -0
- package/nitrogen/generated/ios/swift/LevelPlayAdShowState.swift +40 -0
- package/nitrogen/generated/ios/swift/LevelPlayAdType.swift +40 -0
- package/nitrogen/generated/shared/c++/HybridLevelPlayAdsSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridLevelPlayAdsSpec.hpp +73 -0
- package/nitrogen/generated/shared/c++/LevelPlayAdShowResult.hpp +68 -0
- package/nitrogen/generated/shared/c++/LevelPlayAdShowState.hpp +76 -0
- package/nitrogen/generated/shared/c++/LevelPlayAdType.hpp +76 -0
- package/package.json +170 -0
- package/react-native.config.js +8 -0
- package/src/index.test.ts +119 -0
- package/src/index.ts +71 -0
- package/src/specs/LevelPlayAds.nitro.ts +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 exzos
|
|
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.
|
|
@@ -0,0 +1,28 @@
|
|
|
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 = "NitroLevelPlayAds"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = "https://github.com/exzos28/react-native-levelplayads"
|
|
10
|
+
s.license = { :type => "MIT" }
|
|
11
|
+
s.authors = { "exzos" => "oleksandr.kurinnyi.work@gmail.com" }
|
|
12
|
+
s.platforms = { :ios => "15.1" }
|
|
13
|
+
s.source = { :path => "." }
|
|
14
|
+
|
|
15
|
+
s.source_files = ["ios/**/*.{swift,m,mm}"]
|
|
16
|
+
|
|
17
|
+
load "nitrogen/generated/ios/NitroLevelPlayAds+autolinking.rb"
|
|
18
|
+
add_nitrogen_files(s)
|
|
19
|
+
|
|
20
|
+
s.dependency "React-Core"
|
|
21
|
+
s.dependency "React-jsi"
|
|
22
|
+
s.dependency "React-callinvoker"
|
|
23
|
+
# Verified against CocoaPods trunk (https://trunk.cocoapods.org/api/v1/pods/IronSourceSDK) —
|
|
24
|
+
# the real pod name is "IronSourceSDK" (module name `IronSource`), not "IronSourceMediation".
|
|
25
|
+
s.dependency "IronSourceSDK", "~> 9.5.0.0"
|
|
26
|
+
|
|
27
|
+
install_modules_dependencies(s)
|
|
28
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# react-native-levelplayads
|
|
2
|
+
|
|
3
|
+
[Nitro Modules](https://nitro.margelo.com/) bridge for the LevelPlay
|
|
4
|
+
(ironSource) mediation SDK — interstitial and rewarded ads.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install react-native-levelplayads react-native-nitro-modules
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
There's no `InterstitialAd`/`RewardedAd` class or event listeners here —
|
|
15
|
+
just `load`/`show`, keyed by ad type and ad unit ID, resolving through
|
|
16
|
+
promises.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import LevelPlayAds from 'react-native-levelplayads';
|
|
20
|
+
|
|
21
|
+
await LevelPlayAds().initialize('YOUR_LEVELPLAY_APP_KEY', {testMode: true});
|
|
22
|
+
LevelPlayAds().setGDPRConsent(true);
|
|
23
|
+
LevelPlayAds().setCCPAConsent(true);
|
|
24
|
+
LevelPlayAds().setCOPPA(false);
|
|
25
|
+
|
|
26
|
+
await LevelPlayAds().load('interstitial', 'YOUR_AD_UNIT_ID');
|
|
27
|
+
const result = await LevelPlayAds().show('interstitial', 'YOUR_AD_UNIT_ID');
|
|
28
|
+
// result.state: 'completed' | 'skipped'
|
|
29
|
+
|
|
30
|
+
await LevelPlayAds().load('rewarded', 'YOUR_REWARDED_AD_UNIT_ID');
|
|
31
|
+
const rewardResult = await LevelPlayAds().show(
|
|
32
|
+
'rewarded',
|
|
33
|
+
'YOUR_REWARDED_AD_UNIT_ID',
|
|
34
|
+
);
|
|
35
|
+
if (rewardResult.state === 'completed') {
|
|
36
|
+
// grant the reward
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
- `load()`/`show()` reject on failure — no separate `ERROR` event.
|
|
43
|
+
- `result.state === 'completed'` is also how you know a rewarded ad earned
|
|
44
|
+
its reward; LevelPlay doesn't send a distinct "reward earned" signal.
|
|
45
|
+
- Call `load()` again after each `show()` to prepare the next impression.
|
|
46
|
+
|
|
47
|
+
See [`src/specs/LevelPlayAds.nitro.ts`](src/specs/LevelPlayAds.nitro.ts) for
|
|
48
|
+
the full native interface and [`example`](example) for a runnable demo.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
project(NitroLevelPlayAds)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME NitroLevelPlayAds)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
9
|
+
src/main/cpp/cpp-adapter.cpp
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroLevelPlayAds+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
find_library(LOG_LIB log)
|
|
15
|
+
|
|
16
|
+
target_link_libraries(
|
|
17
|
+
${PACKAGE_NAME}
|
|
18
|
+
${LOG_LIB}
|
|
19
|
+
android
|
|
20
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.12.1"
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def reactNativeArchitectures() {
|
|
14
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
15
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def isNewArchitectureEnabled() {
|
|
19
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
apply plugin: "com.android.library"
|
|
23
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
24
|
+
|
|
25
|
+
def nitroAutolinkingFile = file("../nitrogen/generated/android/NitroLevelPlayAds+autolinking.gradle")
|
|
26
|
+
if (nitroAutolinkingFile.exists()) {
|
|
27
|
+
apply from: nitroAutolinkingFile
|
|
28
|
+
}
|
|
29
|
+
apply from: "./fix-prefab.gradle"
|
|
30
|
+
|
|
31
|
+
def getExtOrDefault(name) {
|
|
32
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroLevelPlayAds_" + name]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def getExtOrIntegerDefault(name) {
|
|
36
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroLevelPlayAds_" + name]).toInteger()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
android {
|
|
40
|
+
namespace "com.margelo.nitro.levelplayads"
|
|
41
|
+
|
|
42
|
+
ndkVersion getExtOrDefault("ndkVersion")
|
|
43
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
44
|
+
|
|
45
|
+
defaultConfig {
|
|
46
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
47
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
48
|
+
consumerProguardFiles "consumer-rules.pro"
|
|
49
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
50
|
+
|
|
51
|
+
externalNativeBuild {
|
|
52
|
+
cmake {
|
|
53
|
+
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
|
|
54
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
55
|
+
abiFilters (*reactNativeArchitectures())
|
|
56
|
+
|
|
57
|
+
buildTypes {
|
|
58
|
+
debug {
|
|
59
|
+
cppFlags "-O1 -g"
|
|
60
|
+
}
|
|
61
|
+
release {
|
|
62
|
+
cppFlags "-O2"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
externalNativeBuild {
|
|
70
|
+
cmake {
|
|
71
|
+
path "CMakeLists.txt"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
packagingOptions {
|
|
76
|
+
excludes = [
|
|
77
|
+
"META-INF",
|
|
78
|
+
"META-INF/**",
|
|
79
|
+
"**/libc++_shared.so",
|
|
80
|
+
"**/libfbjni.so",
|
|
81
|
+
"**/libjsi.so",
|
|
82
|
+
"**/libfolly_json.so",
|
|
83
|
+
"**/libfolly_runtime.so",
|
|
84
|
+
"**/libglog.so",
|
|
85
|
+
"**/libhermes.so",
|
|
86
|
+
"**/libreactnative.so",
|
|
87
|
+
"**/libreactnativejni.so"
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
buildFeatures {
|
|
92
|
+
buildConfig true
|
|
93
|
+
prefab true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
buildTypes {
|
|
97
|
+
release {
|
|
98
|
+
minifyEnabled false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
lintOptions {
|
|
103
|
+
disable "GradleCompatible"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
compileOptions {
|
|
107
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
108
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
kotlinOptions {
|
|
112
|
+
jvmTarget = "17"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
repositories {
|
|
118
|
+
google()
|
|
119
|
+
mavenCentral()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
dependencies {
|
|
123
|
+
implementation "com.facebook.react:react-android"
|
|
124
|
+
implementation project(":react-native-nitro-modules")
|
|
125
|
+
|
|
126
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
127
|
+
// android-sdk.is.com was deprecated 2025-06-30 and frozen at 9.2.0, which drops
|
|
128
|
+
// onAdClosed/onAdRewarded on Android; LevelPlay now ships on Maven Central.
|
|
129
|
+
implementation "com.unity3d.ads-mediation:mediation-sdk:9.5.0"
|
|
130
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
tasks.configureEach { task ->
|
|
2
|
+
def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
|
|
3
|
+
def matcher = task.name =~ prefabConfigurePattern
|
|
4
|
+
if (matcher.matches()) {
|
|
5
|
+
def variantName = matcher[0][1]
|
|
6
|
+
task.outputs.upToDateWhen { false }
|
|
7
|
+
task.dependsOn("externalNativeBuild${variantName}")
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
afterEvaluate {
|
|
12
|
+
def abis = reactNativeArchitectures()
|
|
13
|
+
rootProject.allprojects.each { proj ->
|
|
14
|
+
if (proj === rootProject) return
|
|
15
|
+
|
|
16
|
+
def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
|
|
17
|
+
config.dependencies.any { dep ->
|
|
18
|
+
dep.group == project.group && dep.name == project.name
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!dependsOnThisLib && proj != project) return
|
|
22
|
+
|
|
23
|
+
if (!proj.plugins.hasPlugin("com.android.application") &&
|
|
24
|
+
!proj.plugins.hasPlugin("com.android.library")) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def variants = proj.android.hasProperty("applicationVariants") ?
|
|
29
|
+
proj.android.applicationVariants : proj.android.libraryVariants
|
|
30
|
+
variants.all { variant ->
|
|
31
|
+
def variantName = variant.name
|
|
32
|
+
abis.each { abi ->
|
|
33
|
+
def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
|
|
34
|
+
if (!searchDir.exists()) return
|
|
35
|
+
def matches = []
|
|
36
|
+
searchDir.eachDir { randomDir ->
|
|
37
|
+
def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
|
|
38
|
+
if (prefabFile.exists()) matches << prefabFile
|
|
39
|
+
}
|
|
40
|
+
matches.each { prefabConfig ->
|
|
41
|
+
prefabConfig.setLastModified(System.currentTimeMillis())
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
package com.margelo.nitro.levelplayads
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import androidx.annotation.Keep
|
|
5
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.margelo.nitro.NitroModules
|
|
8
|
+
import com.margelo.nitro.core.Promise
|
|
9
|
+
import com.unity3d.mediation.LevelPlay
|
|
10
|
+
import com.unity3d.mediation.LevelPlayAdError
|
|
11
|
+
import com.unity3d.mediation.LevelPlayAdInfo
|
|
12
|
+
import com.unity3d.mediation.LevelPlayInitError
|
|
13
|
+
import com.unity3d.mediation.LevelPlayInitRequest
|
|
14
|
+
import com.unity3d.mediation.interstitial.LevelPlayInterstitialAd
|
|
15
|
+
import com.unity3d.mediation.interstitial.LevelPlayInterstitialAdListener
|
|
16
|
+
import com.unity3d.mediation.rewarded.LevelPlayReward
|
|
17
|
+
import com.unity3d.mediation.rewarded.LevelPlayRewardedAd
|
|
18
|
+
import com.unity3d.mediation.rewarded.LevelPlayRewardedAdListener
|
|
19
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
20
|
+
import kotlinx.coroutines.Dispatchers
|
|
21
|
+
import kotlinx.coroutines.withContext
|
|
22
|
+
|
|
23
|
+
// Verified against the real com.ironsource.sdk:mediationsdk:9.2.0 AAR (javap on
|
|
24
|
+
// the downloaded classes.jar, https://android-sdk.is.com/) — the unified LevelPlay
|
|
25
|
+
// API lives under `com.unity3d.mediation`. LevelPlayInitRequest.Builder has no
|
|
26
|
+
// withLegacyAdFormats() in 9.2.0 (removed since the previous 8.6.0 generation),
|
|
27
|
+
// and setConsent/setMetaData/setAdaptersDebug now live on `LevelPlay` itself
|
|
28
|
+
// rather than the legacy `com.ironsource.mediationsdk.IronSource` facade, whose
|
|
29
|
+
// surface was stripped down in this release.
|
|
30
|
+
@DoNotStrip
|
|
31
|
+
@Keep
|
|
32
|
+
class HybridLevelPlayAds : HybridLevelPlayAdsSpec() {
|
|
33
|
+
private companion object {
|
|
34
|
+
const val TAG = "LevelPlayAds"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private val context: ReactApplicationContext by lazy {
|
|
38
|
+
NitroModules.applicationContext as? ReactApplicationContext
|
|
39
|
+
?: error("LevelPlay requires a ReactApplicationContext")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private val stateLock = Any()
|
|
43
|
+
private var isInitialized = false
|
|
44
|
+
private var initializedAppKey: String? = null
|
|
45
|
+
private var initializingAppKey: String? = null
|
|
46
|
+
private var initialization: CompletableDeferred<Unit>? = null
|
|
47
|
+
|
|
48
|
+
// LevelPlay requires the SDK listener to be set exactly once, before the first
|
|
49
|
+
// loadAd() call, and reused for the ad's whole lifetime. Re-registering a
|
|
50
|
+
// different listener object right before showAd() (as this module used to do)
|
|
51
|
+
// silently drops onAdClosed on Android: the internal IronSource event fires
|
|
52
|
+
// (confirmed via logcat, tag "IronSource"), but the unified
|
|
53
|
+
// LevelPlayInterstitialAdListener/LevelPlayRewardedAdListener never sees it
|
|
54
|
+
// once its listener has been swapped. So the SDK listener is installed once
|
|
55
|
+
// in the holder's init{} block, and load()/show() only swap out our own
|
|
56
|
+
// plain-Kotlin callback fields, never the SDK listener.
|
|
57
|
+
private abstract class AdHolder {
|
|
58
|
+
var onLoaded: (() -> Unit)? = null
|
|
59
|
+
var onLoadFailed: ((LevelPlayAdError) -> Unit)? = null
|
|
60
|
+
var onDisplayed: (() -> Unit)? = null
|
|
61
|
+
var onDisplayFailed: ((LevelPlayAdError) -> Unit)? = null
|
|
62
|
+
var onClosed: (() -> Unit)? = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private class InterstitialHolder(adUnitId: String) : AdHolder() {
|
|
66
|
+
val ad = LevelPlayInterstitialAd(adUnitId)
|
|
67
|
+
|
|
68
|
+
init {
|
|
69
|
+
ad.setListener(object : LevelPlayInterstitialAdListener {
|
|
70
|
+
override fun onAdLoaded(adInfo: LevelPlayAdInfo) = onLoaded?.invoke() ?: Unit
|
|
71
|
+
override fun onAdLoadFailed(error: LevelPlayAdError) = onLoadFailed?.invoke(error) ?: Unit
|
|
72
|
+
override fun onAdDisplayed(adInfo: LevelPlayAdInfo) = onDisplayed?.invoke() ?: Unit
|
|
73
|
+
override fun onAdDisplayFailed(error: LevelPlayAdError, adInfo: LevelPlayAdInfo) =
|
|
74
|
+
onDisplayFailed?.invoke(error) ?: Unit
|
|
75
|
+
override fun onAdClosed(adInfo: LevelPlayAdInfo) = onClosed?.invoke() ?: Unit
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private class RewardedHolder(adUnitId: String) : AdHolder() {
|
|
81
|
+
val ad = LevelPlayRewardedAd(adUnitId)
|
|
82
|
+
var onRewarded: (() -> Unit)? = null
|
|
83
|
+
|
|
84
|
+
init {
|
|
85
|
+
ad.setListener(object : LevelPlayRewardedAdListener {
|
|
86
|
+
override fun onAdLoaded(adInfo: LevelPlayAdInfo) = onLoaded?.invoke() ?: Unit
|
|
87
|
+
override fun onAdLoadFailed(error: LevelPlayAdError) = onLoadFailed?.invoke(error) ?: Unit
|
|
88
|
+
override fun onAdDisplayed(adInfo: LevelPlayAdInfo) = onDisplayed?.invoke() ?: Unit
|
|
89
|
+
override fun onAdDisplayFailed(error: LevelPlayAdError, adInfo: LevelPlayAdInfo) =
|
|
90
|
+
onDisplayFailed?.invoke(error) ?: Unit
|
|
91
|
+
override fun onAdRewarded(reward: LevelPlayReward, adInfo: LevelPlayAdInfo) = onRewarded?.invoke() ?: Unit
|
|
92
|
+
override fun onAdClosed(adInfo: LevelPlayAdInfo) = onClosed?.invoke() ?: Unit
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Keyed by adUnitId: load() and show() are separate calls in this module's
|
|
98
|
+
// spec, so the loaded ad object has to be held onto between the two calls.
|
|
99
|
+
private val loadedInterstitials = mutableMapOf<String, InterstitialHolder>()
|
|
100
|
+
private val loadedRewarded = mutableMapOf<String, RewardedHolder>()
|
|
101
|
+
|
|
102
|
+
override fun initialize(appKey: String, testMode: Boolean): Promise<Unit> = Promise.async {
|
|
103
|
+
require(appKey.isNotBlank()) { "LevelPlay app key is empty" }
|
|
104
|
+
|
|
105
|
+
val (deferred, shouldStart) = synchronized(stateLock) {
|
|
106
|
+
if (isInitialized) {
|
|
107
|
+
require(initializedAppKey == appKey) {
|
|
108
|
+
"LevelPlay SDK was already initialized with another app key"
|
|
109
|
+
}
|
|
110
|
+
return@async
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
val current = initialization
|
|
114
|
+
if (current != null) {
|
|
115
|
+
require(initializingAppKey == appKey) {
|
|
116
|
+
"LevelPlay SDK is being initialized with another app key"
|
|
117
|
+
}
|
|
118
|
+
current to false
|
|
119
|
+
} else {
|
|
120
|
+
CompletableDeferred<Unit>().also {
|
|
121
|
+
initialization = it
|
|
122
|
+
initializingAppKey = appKey
|
|
123
|
+
} to true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (shouldStart) {
|
|
128
|
+
withContext(Dispatchers.Main) {
|
|
129
|
+
if (testMode) {
|
|
130
|
+
LevelPlay.setAdaptersDebug(true)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
val initRequest = LevelPlayInitRequest.Builder(appKey).build()
|
|
134
|
+
|
|
135
|
+
LevelPlay.init(
|
|
136
|
+
context,
|
|
137
|
+
initRequest,
|
|
138
|
+
object : com.unity3d.mediation.LevelPlayInitListener {
|
|
139
|
+
override fun onInitSuccess(configuration: com.unity3d.mediation.LevelPlayConfiguration) {
|
|
140
|
+
synchronized(stateLock) {
|
|
141
|
+
isInitialized = true
|
|
142
|
+
initializedAppKey = appKey
|
|
143
|
+
initialization = null
|
|
144
|
+
initializingAppKey = null
|
|
145
|
+
}
|
|
146
|
+
deferred.complete(Unit)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
override fun onInitFailed(error: LevelPlayInitError) {
|
|
150
|
+
synchronized(stateLock) {
|
|
151
|
+
initialization = null
|
|
152
|
+
initializingAppKey = null
|
|
153
|
+
}
|
|
154
|
+
deferred.completeExceptionally(
|
|
155
|
+
levelPlayAdsException("initialize", error.errorCode.toString(), error.errorMessage),
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
deferred.await()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
override fun load(adType: LevelPlayAdType, adUnitId: String): Promise<Unit> = Promise.async {
|
|
167
|
+
require(adUnitId.isNotBlank()) { "LevelPlay ad unit ID is empty" }
|
|
168
|
+
val deferred = CompletableDeferred<Unit>()
|
|
169
|
+
|
|
170
|
+
withContext(Dispatchers.Main) {
|
|
171
|
+
when (adType) {
|
|
172
|
+
LevelPlayAdType.INTERSTITIAL -> {
|
|
173
|
+
val holder = InterstitialHolder(adUnitId)
|
|
174
|
+
holder.onLoaded = {
|
|
175
|
+
synchronized(stateLock) { loadedInterstitials[adUnitId] = holder }
|
|
176
|
+
deferred.complete(Unit)
|
|
177
|
+
}
|
|
178
|
+
holder.onLoadFailed = { error ->
|
|
179
|
+
deferred.completeExceptionally(
|
|
180
|
+
levelPlayAdsException("load", error.getErrorCode().toString(), error.getErrorMessage()),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
holder.ad.loadAd()
|
|
184
|
+
}
|
|
185
|
+
LevelPlayAdType.REWARDED -> {
|
|
186
|
+
val holder = RewardedHolder(adUnitId)
|
|
187
|
+
holder.onLoaded = {
|
|
188
|
+
synchronized(stateLock) { loadedRewarded[adUnitId] = holder }
|
|
189
|
+
deferred.complete(Unit)
|
|
190
|
+
}
|
|
191
|
+
holder.onLoadFailed = { error ->
|
|
192
|
+
deferred.completeExceptionally(
|
|
193
|
+
levelPlayAdsException("load", error.getErrorCode().toString(), error.getErrorMessage()),
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
holder.ad.loadAd()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
deferred.await()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun onShowDisplayFailed(
|
|
205
|
+
kind: String,
|
|
206
|
+
adUnitId: String,
|
|
207
|
+
error: LevelPlayAdError,
|
|
208
|
+
deferred: CompletableDeferred<*>,
|
|
209
|
+
) {
|
|
210
|
+
Log.d(TAG, "show($kind, $adUnitId): onAdDisplayFailed (${error.getErrorCode()}) ${error.getErrorMessage()}")
|
|
211
|
+
deferred.completeExceptionally(
|
|
212
|
+
levelPlayAdsException("show", error.getErrorCode().toString(), error.getErrorMessage()),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
override fun show(adType: LevelPlayAdType, adUnitId: String): Promise<LevelPlayAdShowResult> = Promise.async {
|
|
217
|
+
require(adUnitId.isNotBlank()) { "LevelPlay ad unit ID is empty" }
|
|
218
|
+
val activity = context.requireCurrentActivity()
|
|
219
|
+
val deferred = CompletableDeferred<LevelPlayAdShowResult>()
|
|
220
|
+
|
|
221
|
+
when (adType) {
|
|
222
|
+
LevelPlayAdType.INTERSTITIAL -> {
|
|
223
|
+
val holder = synchronized(stateLock) { loadedInterstitials.remove(adUnitId) }
|
|
224
|
+
?: error("LevelPlay interstitial ad is not ready")
|
|
225
|
+
// Reuse the same SDK listener that was registered in load() — swapping
|
|
226
|
+
// in a different listener object here is what used to drop onAdClosed.
|
|
227
|
+
holder.onLoaded = null
|
|
228
|
+
holder.onLoadFailed = null
|
|
229
|
+
holder.onDisplayed = {
|
|
230
|
+
Log.d(TAG, "show(interstitial, $adUnitId): onAdDisplayed")
|
|
231
|
+
}
|
|
232
|
+
holder.onDisplayFailed = { error -> onShowDisplayFailed("interstitial", adUnitId, error, deferred) }
|
|
233
|
+
holder.onClosed = {
|
|
234
|
+
Log.d(TAG, "show(interstitial, $adUnitId): onAdClosed")
|
|
235
|
+
deferred.complete(LevelPlayAdShowResult(LevelPlayAdShowState.COMPLETED))
|
|
236
|
+
}
|
|
237
|
+
Log.d(TAG, "show(interstitial, $adUnitId): calling showAd")
|
|
238
|
+
withContext(Dispatchers.Main) { holder.ad.showAd(activity) }
|
|
239
|
+
}
|
|
240
|
+
LevelPlayAdType.REWARDED -> {
|
|
241
|
+
val holder = synchronized(stateLock) { loadedRewarded.remove(adUnitId) }
|
|
242
|
+
?: error("LevelPlay rewarded ad is not ready")
|
|
243
|
+
// Rewarded ads don't carry a Unity-style "finish state" — LevelPlay fires a
|
|
244
|
+
// separate onAdRewarded event before onAdClosed, so completion is derived
|
|
245
|
+
// from whether that event happened at all before the ad closed.
|
|
246
|
+
var didReceiveReward = false
|
|
247
|
+
holder.onLoaded = null
|
|
248
|
+
holder.onLoadFailed = null
|
|
249
|
+
holder.onDisplayed = {
|
|
250
|
+
Log.d(TAG, "show(rewarded, $adUnitId): onAdDisplayed")
|
|
251
|
+
}
|
|
252
|
+
holder.onRewarded = {
|
|
253
|
+
Log.d(TAG, "show(rewarded, $adUnitId): onAdRewarded")
|
|
254
|
+
didReceiveReward = true
|
|
255
|
+
}
|
|
256
|
+
holder.onDisplayFailed = { error -> onShowDisplayFailed("rewarded", adUnitId, error, deferred) }
|
|
257
|
+
holder.onClosed = {
|
|
258
|
+
Log.d(TAG, "show(rewarded, $adUnitId): onAdClosed (didReceiveReward=$didReceiveReward)")
|
|
259
|
+
val state = if (didReceiveReward) {
|
|
260
|
+
LevelPlayAdShowState.COMPLETED
|
|
261
|
+
} else {
|
|
262
|
+
LevelPlayAdShowState.SKIPPED
|
|
263
|
+
}
|
|
264
|
+
deferred.complete(LevelPlayAdShowResult(state))
|
|
265
|
+
}
|
|
266
|
+
Log.d(TAG, "show(rewarded, $adUnitId): calling showAd")
|
|
267
|
+
withContext(Dispatchers.Main) { holder.ad.showAd(activity) }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
deferred.await()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override fun setGDPRConsent(optIn: Boolean) {
|
|
275
|
+
LevelPlay.setConsent(optIn)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// CCPA flag semantics are "opted out of sale", the inverse of our `optIn`
|
|
279
|
+
// (personalized-ads-allowed) flag.
|
|
280
|
+
override fun setCCPAConsent(optIn: Boolean) {
|
|
281
|
+
LevelPlay.setMetaData("do_not_sell", if (optIn) "false" else "true")
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
override fun setCOPPA(isCoppa: Boolean) {
|
|
285
|
+
LevelPlay.setMetaData("is_child_directed", if (isCoppa) "true" else "false")
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.margelo.nitro.levelplayads
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import java.util.concurrent.CountDownLatch
|
|
8
|
+
|
|
9
|
+
internal fun ReactApplicationContext.requireCurrentActivity(): Activity =
|
|
10
|
+
currentActivity ?: error("Cannot show LevelPlay ad without an active Activity")
|
|
11
|
+
|
|
12
|
+
internal fun levelPlayAdsException(stage: String, errorCode: String, message: String) =
|
|
13
|
+
IllegalStateException("LevelPlay $stage failed ($errorCode): $message")
|
|
14
|
+
|
|
15
|
+
/** Mirrors HybridLevelPlayAds.swift's `runOnMain` so consent metadata writes happen on the same thread on both platforms. */
|
|
16
|
+
internal fun runOnMain(body: () -> Unit) {
|
|
17
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
18
|
+
body()
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
val latch = CountDownLatch(1)
|
|
22
|
+
Handler(Looper.getMainLooper()).post {
|
|
23
|
+
try {
|
|
24
|
+
body()
|
|
25
|
+
} finally {
|
|
26
|
+
latch.countDown()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
latch.await()
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.margelo.nitro.levelplayads;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.TurboReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
9
|
+
|
|
10
|
+
import java.util.HashMap;
|
|
11
|
+
|
|
12
|
+
public class NitroLevelPlayAdsPackage extends TurboReactPackage {
|
|
13
|
+
@Nullable
|
|
14
|
+
@Override
|
|
15
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
21
|
+
return HashMap::new;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static {
|
|
25
|
+
NitroLevelPlayAdsOnLoad.initializeNative();
|
|
26
|
+
}
|
|
27
|
+
}
|