diff --git a/CHANGELOG.md b/CHANGELOG.md index 49fb715..0f7b4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.0 + +- Android SDK: 8.4.0 +- iOS SDK: 8.4.0 + ## 2.0.0 , 2024 - Android SDK: 8.1.0 diff --git a/LICENSE b/LICENSE index bd749ec..f960c67 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ ironSource copyright © 2021 ironSource Ltd. + This software is subject to, and made available under, the Unity Advertising Terms of Service (available at https://unity.com/legal/one-operate-services-terms-of-service), and is a "Service Asset" as defined therein. + Your use of the Services constitutes your acceptance of such terms. Unless expressly provided otherwise, the software under this license is made available strictly on an "AS IS" BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the terms of service for details on these and other terms and conditions. \ No newline at end of file diff --git a/README.md b/README.md index 3f77115..614804e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ A bridge plugin for ironSource SDKs. -- [ironSource Knowledge Center](https://developers.is.com/developer-docs/flutter/) +- [ironSource Knowledge Center](https://developers.is.com/) - [Android SDK](https://developers.ironsrc.com/ironsource-mobile/android/android-sdk/) - [iOS SDK](https://developers.ironsrc.com/ironsource-mobile/ios/ios-sdk/) +- [Flutter Plugin](https://developers.is.com/ironsource-mobile/flutter/flutter-plugin/) # Getting Started @@ -97,113 +98,127 @@ Read more about Apple's ATT and user privacy guideline [here](https://developer. class LevelPlayRewardedVideoListenerClass with LevelPlayRewardedVideoListener { @override void onAdAvailable(IronSourceAdInfo? adInfo) { - // TODO: implement onAdAvailable + // Indicates that there's an available ad. } @override - void onAdClicked(IronSourceRewardedVideoPlacement? placement, IronSourceAdInfo? adInfo) { - // TODO: implement onAdClicked + void onAdUnavailable() { + // Indicates that no ads are available to be displayed } @override - void onAdClosed(IronSourceAdInfo? adInfo) { - // TODO: implement onAdClosed + void onAdOpened(IronSourceAdInfo? adInfo) { + // The Rewarded Video ad view has opened. Your activity will loose focus } - + @override - void onAdOpened(IronSourceAdInfo? adInfo) { - // TODO: implement onAdOpened + void onAdClosed(IronSourceAdInfo? adInfo) { + // The Rewarded Video ad view is about to be closed. Your activity will regain its focus } + @override void onAdRewarded(IronSourceRewardedVideoPlacement? placement, IronSourceAdInfo? adInfo) { - // TODO: implement onAdRewarded + // The user completed to watch the video, and should be rewarded. + // The placement parameter will include the reward data. + // When using server-to-server callbacks, you may ignore this event and wait for the ironSource server callback } @override void onAdShowFailed(IronSourceError? error, IronSourceAdInfo? adInfo) { - // TODO: implement onAdShowFailed + // The rewarded video ad was failed to show } @override - void onAdUnavailable() { - // TODO: implement onAdUnavailable + void onAdClicked(IronSourceRewardedVideoPlacement? placement, IronSourceAdInfo? adInfo) { + // Invoked when the video ad was clicked. + // This callback is not supported by all networks, and we recommend using it + // only if it's supported by all networks you included in your build } } ``` -#### LevelPlayInterstitialListener +#### LevelPlayInterstitialAdListener ```dart -class LevelPlayInterstitialListenerClass with LevelPlayInterstitialListener { +class LevelPlayInterstitialAdListenerClass with LevelPlayInterstitialAdListener { @override - void onAdClicked(IronSourceAdInfo? adInfo) { - // TODO: implement onAdClicked + void onAdLoaded(LevelPlayAdInfo adInfo) { + // Provided when the ad is successfully loaded } @override - void onAdClosed(IronSourceAdInfo? adInfo) { - // TODO: implement onAdClosed + void onAdLoadFailed(LevelPlayAdError error) { + // Provided when the ad fails to load. Ad Unit information is included } @override - void onAdLoadFailed(IronSourceError? error) { - // TODO: implement onAdLoadFailed + void onAdDisplayed(LevelPlayAdInfo adInfo) { + // Provided when the ad is displayed. This is equivalent to an impression } @override - void onAdOpened(IronSourceAdInfo? adInfo) { - // TODO: implement onAdOpened + void onAdDisplayFailed(LevelPlayAdError error, LevelPlayAdInfo adInfo) { + // Provided when the ad fails to be displayed } - + @override - void onAdReady(IronSourceAdInfo? adInfo) { - // TODO: implement onAdReady + void onAdClicked(LevelPlayAdInfo adInfo) { + // Provided when the user clicks on the ad } @override - void onAdShowFailed(IronSourceError? error, IronSourceAdInfo? adInfo) { - // TODO: implement onAdShowFailed + void onAdClosed(LevelPlayAdInfo adInfo) { + // Provided when the ad is closed } @override - void onAdShowSucceeded(IronSourceAdInfo? adInfo) { - // TODO: implement onAdShowSucceeded + void onAdInfoChanged(LevelPlayAdInfo adInfo) { + // Provided when the ad info is updated. Available when another ad has loaded, and includes a higher CPM/Rate } } ``` -#### LevelPlayBannerListener +#### LevelPlayBannerAdViewListener ```dart -class LevelPlayBannerListenerClass with LevelPlayBannerListener { +class LevelPlayBannerAdViewListenerClass with LevelPlayBannerAdViewListener { + @override + void onAdLoaded(LevelPlayAdInfo adInfo) { + // Ad was loaded successfully + } @override - void onAdClicked(IronSourceAdInfo? adInfo) { - // TODO: implement onAdClicked + void onAdLoadFailed(LevelPlayAdError error) { + // Ad load failed } @override - void onAdLeftApplication(IronSourceAdInfo? adInfo) { - // TODO: implement onAdLeftApplication + void onAdDisplayed(LevelPlayAdInfo adInfo) { + // Ad was displayed and visible on screen } @override - void onAdLoadFailed(IronSourceError? error) { - // TODO: implement onAdLoadFailed + void onAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error) { + // Ad failed to be displayed on screen + } + + @override + void onAdClicked(LevelPlayAdInfo adInfo) { + // Ad was clicked } @override - void onAdLoaded(IronSourceAdInfo? adInfo) { - // TODO: implement onAdLoaded + void onAdExpanded(LevelPlayAdInfo adInfo) { + // Ad is opened on full screen } @override - void onAdScreenDismissed(IronSourceAdInfo? adInfo) { - // TODO: implement onAdScreenDismissed + void onAdCollapsed(LevelPlayAdInfo adInfo) { + // Ad is restored to its original size } @override - void onAdScreenPresented(IronSourceAdInfo? adInfo) { - // TODO: implement onAdScreenPresented + void onAdLeftApplication(LevelPlayAdInfo adInfo) { + // User pressed on the ad and was navigated out of the app } } ``` @@ -212,50 +227,36 @@ class LevelPlayBannerListenerClass with LevelPlayBannerListener { ```dart class LevelPlayNativeAdListenerClass with LevelPlayNativeAdListener { @override - void onAdClicked(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { - // TODO: implement onAdClicked + void onAdLoaded(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { + // Invoked each time a native ad was loaded. } @override - void onAdImpression(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { - // TODO: implement onAdImpression + void onAdLoadFailed(LevelPlayNativeAd? nativeAd, IronSourceError? error) { + // Invoked when the native ad loading process has failed. } @override - void onAdLoadFailed(LevelPlayNativeAd? nativeAd, IronSourceError? error) { - // TODO: implement onAdLoadFailed + void onAdImpression(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { + // Invoked each time the first pixel is visible on the screen } - + @override - void onAdLoaded(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { - // TODO: implement onAdLoaded + void onAdClicked(LevelPlayNativeAd? nativeAd, IronSourceAdInfo? adInfo) { + // Invoked when end user clicked on the native ad } } ``` -### Initialize the plugin +### Initialize the plugin ```dart -Future initIronSource() async { - final appKey = Platform.isAndroid - ? ANDROID_APP_KEY - : Platform.isIOS - ? IOS_APP_KEY - : throw Exception("Unsupported Platform"); +Future init() async { + final appKey = '[YOUR_APP_KEY]'; try { - IronSource.setFlutterVersion('YOUR_FLUTTER_VERSION'); // must be called before init - IronSource.validateIntegration(); - // Set listeners - IronSource.setLevelPlayRewardedVideoListener(LevelPlayRewardedVideoListenerClass()); - IronSource.setLevelPlayInterstitialListener(LevelPlayInterstitialListenerClass()); - IronSource.setLevelPlayBannerListener(LevelPlayBannerListenerClass()); - - await IronSource.setAdaptersDebug(true); - await IronSource.shouldTrackNetworkState(true); - - // Do not use GAID or IDFA for this. - await IronSource.setUserId("unique-application-user-id"); - await IronSource.init(appKey: appKey, adUnits: [IronSourceAdUnit.RewardedVideo]); + List legacyAdFormats = [AdFormat.BANNER, AdFormat.REWARDED, AdFormat.INTERSTITIAL, AdFormat.NATIVE_AD]; + final initRequest = LevelPlayInitRequest(appKey: appKey, legacyAdFormats: legacyAdFormats); + await LevelPlay.init(initRequest: initRequest, initListener: this); } on PlatformException catch (e) { print(e); } @@ -274,26 +275,58 @@ Future _showRewardedVideoOnClick() async { ``` #### LevelPlayInterstitial + ```dart -void _loadInterstitialOnClick() { - IronSource.loadInterstitial(); +LevelPlayInterstitialAd? _interstitialAd; + +@override +void initState() { + super.initState(); + _createInterstitialAd(); } -Future _showInterstitialOnClick() async { - if (await IronSource.isInterstitialReady()) { - IronSource.showInterstitial(); +void _createInterstitialAd() { + _intersitialAd = LevelPlayInterstitialAd(adUnitId: [YOUR_AD_UNIT]); + _interstitialAd!.setListener([YOUR_LISTENER]); +} + +void _loadInterstitial() { + _interstitialAd?.loadAd(); +} + +Future _showInterstitial() async { + if (await __interstitialAd?.isAdReady()) { + _interstitialAd?.showAd(placement: [YOUR_PLACEMENT]); } } ``` #### LevelPlayBanner + ```dart -Future _loadBanner() async { // load will automatically show the ad - await IronSource.loadBanner( - size: size, - position: IronSourceBannerPosition.Bottom, - verticalOffset: -50, - placementName: 'YOUR_PLACEMENT'); +LevelPlayBannerAdView? _bannerAdView; + +@override +void initState() { + super.initState(); + _createBannerAdView(); +} + +void _createBannerAdView() { + final _bannerkey = GlobalKey(); + _bannerAdView = LevelPlayBannerAdView( + key: _bannerKey, + adUnitId: [YOUR_AD_UNIT_ID], + adSize:[YOUR_AD_SIZE], + listener: [YOUR_LISTENER], + placementName: [YOUR_PLACEMENT], + onPlatformViewCreated: _loadBanner + ); +} + +void _loadBanner() { + _bannerAdView?.loadAd(); + // or store and use key - _bannerKey.currentState?.loadAd(); } ``` @@ -321,11 +354,16 @@ class _LevelPlayNativeAdsSection extends State with L /// Initialize native ad view widget with native ad void _createNativeAdView() { _nativeAdView = LevelPlayNativeAdView( - key: GlobalKey(), // Unique key to force recreation of widget - height: 150, // Your chosen height - width: double.infinity, // Your chosen width - nativeAd: _nativeAd, // Native ad object - templateType: LevelPlayTemplateType.SMALL, // Built-in native ad template(not required when implementing custom template) + key: GlobalKey(), + // Unique key to force recreation of widget + height: 150, + // Your chosen height + width: double.infinity, + // Your chosen width + nativeAd: _nativeAd, + // Native ad object + templateType: LevelPlayTemplateType.SMALL, + // Built-in native ad template(not required when implementing custom template) templateStyle: LevelPlayNativeAdTemplateStyle( // Level play native ad styling(optional) callToActionStyle: LevelPlayNativeAdElementStyle( backgroundColor: Colors.white, @@ -339,8 +377,9 @@ class _LevelPlayNativeAdsSection extends State with L void _loadAd() { _nativeAd?.loadAd(); } - + // Rest of the class +} ``` Refer to the [example app](./example) for the more detailed implementation sample. @@ -348,48 +387,9 @@ Refer to the [example app](./example) for the more detailed implementation sampl Note: - Make sure to read the official documents at [ironSource Knowledge Center](TODO: replace with the real KC link) for proper usage. -- Some configurations must be done before `IronSource.init`. +- Some configurations must be done before initialization. +- LevelPlayBannerListener is deprecated - Please use LevelPlayBannerAdViewListener with LevelPlayBannerAdView instead. -### Banner Positioning - -For the native SDKs, a banner view must be implemented directly to the UI component. -This bridge takes care of native level view implementation. Therefore, positioning parameters are provided as below: - -#### Position - -```dart -enum IronSourceBannerPosition { - Top, - Center, - Bottom, -} -``` - -#### Offset - -This parameter represents the vertical offset of the banner: - -- Negative values: Upward offset -- Positive values: Downward offset - -Unit: - -- Android: dp -- iOS: point - -Note: - -- Offset in the same direction of the position will be ignored. e.g. Bottom & 50, Top & -50 -- However, the offsets in the opposite direction or both directions on the Center position can go beyond the screen boundaries. e.g. Bottom & -10000 -- Make sure that a banner presented will be visible - -```dart -IronSource.loadBanner( - size: IronSourceBannerSize.BANNER, - position: IronSourceBannerPosition.Bottom, - verticalOffset: -50, // adding 50dp/50point margin bottom - placementName: 'YOUR_PLACEMENT'); -``` # Mediation @@ -418,12 +418,3 @@ Note: ```ruby use_frameworks! :linkage => :static ``` - -## Version History -You can find a summary of the ironSouce SDK version history [here](https://developers.is.com/ironsource-mobile/flutter/sdk-change-log/) - -## Contact US -For any question please contact us [here](https://ironsrc.formtitan.com/knowledge-center#/) - -## License -The license can be viewed [here](https://github.com/ironsource-mobile/Flutter-SDK/blob/master/LICENSE) \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 940506f..ca1ccdd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 33 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,5 +39,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // ironSource SDK - implementation 'com.ironsource.sdk:mediationsdk:8.1.0' + implementation 'com.ironsource.sdk:mediationsdk:8.4.0' } diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/Extensions.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/Extensions.kt index e95c446..fe0b678 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/Extensions.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/Extensions.kt @@ -9,6 +9,11 @@ import com.ironsource.mediationsdk.adunit.adapter.utility.AdInfo import com.ironsource.mediationsdk.impressionData.ImpressionData import com.ironsource.mediationsdk.logger.IronSourceError import com.ironsource.mediationsdk.model.Placement +import com.unity3d.mediation.LevelPlayAdError +import com.unity3d.mediation.LevelPlayAdInfo +import com.unity3d.mediation.LevelPlayAdSize +import com.unity3d.mediation.LevelPlayConfiguration +import com.unity3d.mediation.LevelPlayInitError import java.io.ByteArrayOutputStream /** @@ -51,6 +56,9 @@ fun ImpressionData.toMap(): HashMap { return hashMapOf( "auctionId" to this.auctionId, "adUnit" to this.adUnit, + "adUnitName" to this.mediationAdUnitName, + "adUnitId" to this.mediationAdUnitId, + "adFormat" to this.adFormat, "country" to this.country, "ab" to this.ab, "segmentName" to this.segmentName, @@ -84,6 +92,7 @@ fun AdInfo.toMap(): HashMap { "segmentName" to this.segmentName, "revenue" to this.revenue, "precision" to this.precision, + "lifetimeRevenue" to this.lifetimeRevenue, "encryptedCPM" to this.encryptedCPM ) } @@ -101,8 +110,10 @@ fun LevelPlayNativeAd.toMap(): HashMap { "body" to this.body, "advertiser" to this.advertiser, "callToAction" to this.callToAction, - "iconUri" to this.icon?.uri.toString(), - "iconImageData" to this.icon?.drawable?.toBytes() + "icon" to hashMapOf( + "uri" to this.icon?.uri.toString(), + "imageData" to this.icon?.drawable?.toBytes() + ) ) } @@ -164,4 +175,72 @@ fun Bitmap.toBytes(): ByteArray { // Convert the ByteArrayOutputStream to a byte array and return the result return stream.toByteArray() +} + +/** + * Extension function to convert a LevelPlayInitError object to a Map. + * This function converts the LevelPlayInitError object's properties into a HashMap with String keys + * and nullable Any values, allowing easy serialization or mapping of the object. + * + * @return A HashMap representing the LevelPlayInitError object. + */ +fun LevelPlayInitError.toMap(): HashMap { + return hashMapOf( + "errorCode" to this.errorCode, + "errorMessage" to this.errorMessage + ) +} + +/** + * Extension function to convert a LevelPlayConfiguration object to a Map. + * This function converts the LevelPlayConfiguration object's properties into a HashMap with String keys + * and nullable Any values, allowing easy serialization or mapping of the object. + * + * @return A HashMap representing the LevelPlayConfiguration object. + */ +fun LevelPlayConfiguration.toMap(): HashMap { + return hashMapOf( + "isAdQualityEnabled" to this.isAdQualityEnabled + ) +} + +fun LevelPlayAdInfo.toMap(): HashMap { + return hashMapOf( + "adUnitId" to this.getAdUnitId(), + "adFormat" to this.getAdFormat(), + "adSize" to this.getAdSize().toMap(), + "impressionData" to hashMapOf( + "auctionId" to this.getAuctionId(), + "adUnitName" to this.getAdUnitName(), + "adUnitId" to this.getAdUnitId(), + "adFormat" to this.getAdFormat(), + "country" to this.getCountry(), + "ab" to this.getAb(), + "segmentName" to this.getSegmentName(), + "placement" to this.getPlacementName(), + "adNetwork" to this.getAdNetwork(), + "instanceName" to this.getInstanceName(), + "instanceId" to this.getInstanceId(), + "revenue" to this.getRevenue(), + "precision" to this.getPrecision(), + "encryptedCPM" to this.getEncryptedCPM(), + ), + ) +} + +fun LevelPlayAdSize?.toMap(): HashMap? { + return if (this != null) hashMapOf( + "width" to this.getWidth(), + "height" to this.getHeight(), + "adLabel" to this.getDescription(), + "isAdaptive" to this.isAdaptive + ) else null +} + +fun LevelPlayAdError.toMap(): HashMap { + return hashMapOf( + "adUnitId" to this.adUnitId, + "errorCode" to this.getErrorCode(), + "errorMessage" to this.getErrorMessage() + ) } \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/ImpressionDataListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/ImpressionDataListener.kt index 61bdb16..5121e9f 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/ImpressionDataListener.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/ImpressionDataListener.kt @@ -4,9 +4,8 @@ import com.ironsource.mediationsdk.impressionData.ImpressionData import com.ironsource.mediationsdk.impressionData.ImpressionDataListener import io.flutter.plugin.common.MethodChannel -class ImpressionDataListener(channel: MethodChannel) : IronSourceListener(channel), - ImpressionDataListener { +class ImpressionDataListener(channel: MethodChannel) : LevelPlayListener(channel), ImpressionDataListener { override fun onImpressionSuccess(impressionData: ImpressionData?) { - invokeMethod("onImpressionSuccess", impressionData?.toMap()) + invokeMethod( "onImpressionSuccess", impressionData?.toMap()) } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/InitializationListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/InitializationListener.kt index 8b81f9d..3a2a90a 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/InitializationListener.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/InitializationListener.kt @@ -3,8 +3,7 @@ package com.ironSource.ironsource_mediation import com.ironsource.mediationsdk.sdk.InitializationListener import io.flutter.plugin.common.MethodChannel -class InitializationListener(channel: MethodChannel) : IronSourceListener(channel), - InitializationListener { +class InitializationListener(channel: MethodChannel) : LevelPlayListener(channel), InitializationListener { override fun onInitializationComplete() { invokeMethod("onInitializationComplete") } diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceListener.kt deleted file mode 100644 index a755641..0000000 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceListener.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.ironSource.ironsource_mediation - -import android.app.Activity -import io.flutter.plugin.common.MethodChannel - -abstract class IronSourceListener(protected val channel: MethodChannel) { - var activity: Activity? = null - - protected fun invokeMethod(methodName: String, args: Any? = null) { - LevelPlayUtils.invokeChannelMethod(activity, channel, methodName, args) - } -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceMediationPlugin.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceMediationPlugin.kt index 8fbca79..72c3e03 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceMediationPlugin.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/IronSourceMediationPlugin.kt @@ -7,7 +7,6 @@ import android.util.Log import android.view.Gravity import android.view.View import android.widget.FrameLayout -import androidx.annotation.NonNull import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent @@ -21,6 +20,10 @@ import com.ironsource.mediationsdk.WaterfallConfiguration import com.ironsource.mediationsdk.config.ConfigFile import com.ironsource.mediationsdk.integration.IntegrationHelper import com.ironsource.mediationsdk.model.Placement +import com.unity3d.mediation.LevelPlay +import com.unity3d.mediation.LevelPlayAdSize +import com.unity3d.mediation.LevelPlayInitRequest +import com.unity3d.mediation.interstitial.LevelPlayInterstitialAd import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -33,7 +36,6 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.platform.PlatformViewFactory -import java.lang.IllegalStateException import java.util.concurrent.Executors import kotlin.math.abs @@ -57,6 +59,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar private var mInitializationListener: InitializationListener? = null // LevelPlay Listeners + private var mLevelPlayInitListener: LevelPlayInitListener? = null private var mLevelPlayRewardedVideoListener: LevelPlayRewardedVideoListener? = null private var mLevelPlayInterstitialListener: LevelPlayInterstitialListener? = null private var mLevelPlayBannerListener: LevelPlayBannerListener? = null @@ -65,22 +68,32 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar private var nativeAdViewFactories = hashMapOf() private var pluginBinding: FlutterPluginBinding? = null - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPluginBinding) { + // LevelPlay Ad Object Manager + private lateinit var levelPlayAdObjectManager: LevelPlayAdObjectManager + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ironsource_mediation") context = flutterPluginBinding.applicationContext pluginBinding = flutterPluginBinding - isPluginAttached = true channel?.setMethodCallHandler(this) initListeners() + // Banner ad view registry + val bannerAdViewFactory = LevelPlayBannerAdViewFactory(pluginBinding!!.binaryMessenger) + pluginBinding + ?.platformViewRegistry + ?.registerViewFactory("levelPlayBannerAdView", bannerAdViewFactory) + // Native ad view registry val nativeAdViewFactory = LevelPlayNativeAdViewFactoryTemplate(pluginBinding!!.binaryMessenger) - addNativeAdViewFactory("levelPlayNativeAdViewType", nativeAdViewFactory) + addNativeAdViewFactory("levelPlayNativeAdView", nativeAdViewFactory) + + // Ad object manager registry + levelPlayAdObjectManager = LevelPlayAdObjectManager(activity, channel!!) } - override fun onDetachedFromEngine(@NonNull binding: FlutterPluginBinding) { - isPluginAttached = false + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { channel?.setMethodCallHandler(null) channel = null detachListeners() @@ -100,6 +113,10 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar if (mInitializationListener == null) { mInitializationListener = InitializationListener(channel) } + // LevelPlay Init Listener + if (mLevelPlayInitListener == null) { + mLevelPlayInitListener = LevelPlayInitListener(channel) + } // LevelPlay RewardedVideo if (mLevelPlayRewardedVideoListener == null) { mLevelPlayRewardedVideoListener = LevelPlayRewardedVideoListener(channel) @@ -115,9 +132,6 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar mLevelPlayBannerListener = LevelPlayBannerListener(channel) } } - - // Set FlutterActivity - setActivityToListeners(activity) } /** @@ -129,7 +143,9 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar mImpressionDataListener = null // Init mInitializationListener = null - // LevelPlay ReawrdedVideo + // LevelPlay Init + mLevelPlayInitListener = null + // LevelPlay RewardedVideo mLevelPlayRewardedVideoListener = null // LevelPlay Interstitial mLevelPlayInterstitialListener = null @@ -137,7 +153,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar IronSource.setLevelPlayRewardedVideoListener(null) } - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { /** Base API ===============================================================================*/ "validateIntegration" -> validateIntegration(result) @@ -179,33 +195,43 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar "setClientSideCallbacks" -> setClientSideCallbacks(call, result) /** Internal Config API ====================================================================*/ "setPluginData" -> setPluginData(call, result) + + /** LevelPlay Init API ===============================================================================*/ + "initLevelPlay" -> initLevelPlay(call, result) + /** LevelPlayInterstitialAd API ===============================================================================*/ + "isInterstitialAdPlacementCapped" -> isInterstitialAdPlacementCapped(call, result) + "loadInterstitialAd" -> loadInterstitialAd(call, result) + "showInterstitialAd" -> showInterstitialAd(call, result) + "isInterstitialAdReady" -> isInterstitialAdReady(call, result) + "disposeAd" -> disposeAd(call, result) + "disposeAllAds" -> disposeAllAds(result) + /** LevelPlayAdSize API ===============================================================================*/ + "createAdaptiveAdSize" -> createAdaptiveAdSize(call, result) else -> result.notImplemented() } } /** region Base API ============================================================================*/ - //TODO: Implement with real error codes /** * Validates the integration of the SDK. * * @param result The result to be returned after validating the integration. */ - private fun validateIntegration(@NonNull result: Result) { + private fun validateIntegration(result: Result) { activity?.apply { IntegrationHelper.validateIntegration(this) return result.success(null) } ?: return result.error("ERROR", "Activity is null", null) } - //TODO: Implement with real error codes /** * Sets whether to track network state for IronSource SDK. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun shouldTrackNetworkState(@NonNull call: MethodCall, @NonNull result: Result) { + private fun shouldTrackNetworkState(call: MethodCall, result: Result) { if (activity == null) { return result.error("ERROR", "Activity is null", null) } @@ -215,28 +241,26 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar return result.success(null) } - //TODO: Implement with real error codes /** * Sets whether to enable debug mode for IronSource SDK adapters. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setAdaptersDebug(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setAdaptersDebug(call: MethodCall, result: Result) { val isEnabled = call.argument("isEnabled") as Boolean? ?: return result.error("ERROR", "isEnabled is null", null) IronSource.setAdaptersDebug(isEnabled) return result.success(null) } - //TODO: Implement with real error codes /** * Sets the dynamic user ID for IronSource SDK. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setDynamicUserId(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setDynamicUserId(call: MethodCall, result: Result) { val userId = call.argument("userId") as String? ?: return result.error("ERROR", "userId is null", null) @@ -244,13 +268,12 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar return result.success(null) } - //TODO: Implement with real error codes /** * Retrieves the advertiser ID asynchronously. * * @param result The result to be returned after processing. */ - private fun getAdvertiserId(@NonNull result: Result) { + private fun getAdvertiserId(result: Result) { activity?.apply { val executer = Executors.newSingleThreadExecutor() executer.execute { @@ -261,28 +284,26 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } ?: return result.error("ERROR", "getAdvertiserId called when activity is null", null) } - //TODO: Implement with real error codes /** * Sets the consent status for the user. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setConsent(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setConsent(call: MethodCall, result: Result) { val isConsent = call.argument("isConsent") as Boolean? ?: return result.error("ERROR", "isConsent is null", null) IronSource.setConsent(isConsent) return result.success(null) } - //TODO: Implement with real error codes /** * Sets the segment for the user. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setSegment(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setSegment(call: MethodCall, result: Result) { val segmentMap = call.argument("segment") as HashMap? ?: return result.error("ERROR", "segment is null", null) val iSSegment = IronSourceSegment() @@ -304,14 +325,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar return result.success(null) } - //TODO: Implement with real error codes /** * Sets meta data for IronSource. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setMetaData(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setMetaData(call: MethodCall, result: Result) { val metaDataMap = call.argument("metaData") as HashMap>? ?: return result.error("ERROR", "metaData is null", null) // internally overload function uses setMetaData(key: String, values:List) after all @@ -324,12 +344,11 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun launchTestSuite(@NonNull result: Result) { + private fun launchTestSuite(result: Result) { context?.let { IronSource.launchTestSuite(it) } return result.success(null) } - //TODO: Implement with real error codes /** * Sets the waterfall configuration for an ad unit. * @@ -368,14 +387,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar /** region Init API ============================================================================*/ - //TODO: Implement with real error codes /** * Sets the user ID for IronSource. * * @param call The method call containing the user ID as an argument. * @param result The result to be returned after processing. */ - private fun setUserId(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setUserId(call: MethodCall, result: Result) { val userId = call.argument("userId") as String? ?: return result.error("ERROR", "userId is null", null) @@ -383,14 +401,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar return result.success(null) } - //TODO: Implement with real error codes /** * Initializes IronSource SDK with the provided app key and ad units. * * @param call The method call containing the app key and ad units as arguments. * @param result The result to be returned after processing. */ - private fun initIronSource(@NonNull call: MethodCall, @NonNull result: Result) { + private fun initIronSource(call: MethodCall, result: Result) { if (activity == null) { return result.error("ERROR", "Activity is null", null) } @@ -420,14 +437,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar /** region RewardedVideo API ==============================================================================*/ - //TODO: Implement with real error codes /** * Shows a rewarded video. * * @param call The method call containing the placement name as an argument. * @param result The result to be returned after processing. */ - private fun showRewardedVideo(@NonNull call: MethodCall, @NonNull result: Result) { + private fun showRewardedVideo(call: MethodCall, result: Result) { activity?.apply { // Retrieve placement name from method call arguments val placementName = call.argument("placementName") as String? @@ -440,15 +456,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } ?: return result.error("ERROR", "showRewardedVideo called when activity is null", null) } - - //TODO: Implement with real error codes /** * Retrieves information about a rewarded video placement. * * @param call The method call containing the placement name as an argument. * @param result The result to be returned after processing. */ - private fun getRewardedVideoPlacementInfo(@NonNull call: MethodCall, @NonNull result: Result) { + private fun getRewardedVideoPlacementInfo(call: MethodCall, result: Result) { // Retrieve placement name from method call arguments val placementName = call.argument("placementName") as String? ?: return result.error("ERROR", "placementName is null", null) @@ -463,19 +477,18 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun isRewardedVideoAvailable(@NonNull result: Result) { + private fun isRewardedVideoAvailable(result: Result) { // Check if rewarded video is available and return the result return result.success(IronSource.isRewardedVideoAvailable()) } - //TODO: Implement with real error codes /** * Checks if a rewarded video placement is capped. * * @param call The method call containing the placement name as an argument. * @param result The result to be returned after processing. */ - private fun isRewardedVideoPlacementCapped(@NonNull call: MethodCall, @NonNull result: Result) { + private fun isRewardedVideoPlacementCapped(call: MethodCall, result: Result) { // Retrieve placement name from method call arguments val placementName = call.argument("placementName") as String? ?: return result.error("ERROR", "placementName is null", null) @@ -484,14 +497,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar return result.success(isCapped) } - //TODO: Implement with real error codes /** * Sets server parameters for rewarded video. * * @param call The method call containing the parameters as a hashmap. * @param result The result to be returned after processing. */ - private fun setRewardedVideoServerParams(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setRewardedVideoServerParams(call: MethodCall, result: Result) { // Retrieve parameters from method call arguments val parameters = call.argument("parameters") as HashMap? ?: return result.error("ERROR", "parameters is null", null) @@ -506,7 +518,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun clearRewardedVideoServerParams(@NonNull result: Result) { + private fun clearRewardedVideoServerParams(result: Result) { // Clear rewarded video server parameters IronSource.clearRewardedVideoServerParameters() // Return success @@ -519,7 +531,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun setLevelPlayRewardedVideoManual(@NonNull result: Result) { + private fun setLevelPlayRewardedVideoManual(result: Result) { // Remove the auto load LevelPlay RewardedVideo listener IronSource.setLevelPlayRewardedVideoListener(null) // Set the LevelPlay RewardedVideo manual @@ -533,7 +545,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun loadRewardedVideo(@NonNull result: Result) { + private fun loadRewardedVideo(result: Result) { IronSource.loadRewardedVideo() return result.success(null) } @@ -546,19 +558,18 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun loadInterstitial(@NonNull result: Result) { + private fun loadInterstitial(result: Result) { IronSource.loadInterstitial() return result.success(null) } - //TODO: Implement with real error codes /** * Shows an Interstitial ad. * * @param call The method call containing arguments, such as placementName. * @param result The result to be returned after processing. */ - private fun showInterstitial(@NonNull call: MethodCall, @NonNull result: Result) { + private fun showInterstitial(call: MethodCall, result: Result) { activity?.apply { val placementName = call.argument("placementName") as String? placementName?.let { name -> IronSource.showInterstitial(name) } @@ -573,23 +584,23 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * * @param result The result to be returned after processing. */ - private fun isInterstitialReady(@NonNull result: Result) { + private fun isInterstitialReady(result: Result) { return result.success(IronSource.isInterstitialReady()) } - //TODO: Implement with real error codes /** * Checks if the specified Interstitial placement is capped. * * @param call The method call containing arguments, such as placementName. * @param result The result to be returned after processing. */ - private fun isInterstitialPlacementCapped(@NonNull call: MethodCall, @NonNull result: Result) { + private fun isInterstitialPlacementCapped(call: MethodCall, result: Result) { val placementName = call.argument("placementName") as String? ?: return result.error("ERROR", "placementName is null", null) val isCapped = IronSource.isInterstitialPlacementCapped(placementName) return result.success(isCapped) } + // endregion /** region Banner API ==============================================================================*/ @@ -601,7 +612,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun loadBanner(@NonNull call: MethodCall, @NonNull result: Result) { + private fun loadBanner(call: MethodCall, result: Result) { // fallback to BANNER in the case of invalid descriptions fun getBannerSize(description: String, width: Int, height: Int): ISBannerSize { return when (description) { @@ -614,7 +625,6 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } } - //TODO: Implement with real error codes activity?.apply { // args // Dart int is 64bits, so if the value is over 32bits, it is parsed into Long @@ -706,24 +716,22 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } else { IronSource.loadBanner(mBanner) } - //TODO: Implement with real error codes result.success(null) } catch (e: Throwable) { Log.e(TAG, e.toString()) result.error("ERROR", "Failed to load banner", e) } } - }//TODO: Implement with real error codes + } } ?: result.error("ERROR", "loadBanner called when activity is null", null) } - //TODO: Implement with real error codes /** * Destroys the banner ad. * * @param result The result to be returned after processing. */ - private fun destroyBanner(@NonNull result: Result) { + private fun destroyBanner(result: Result) { activity?.apply { runOnUiThread { synchronized(this@IronSourceMediationPlugin) { @@ -739,13 +747,12 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } ?: result.error("ERROR", "destroyBanner called when activity is null", null) } - //TODO: Implement with real error codes /** * Displays the banner ad. * * @param result The result to be returned after processing. */ - private fun displayBanner(@NonNull result: Result) { + private fun displayBanner(result: Result) { activity?.apply { runOnUiThread { synchronized(this@IronSourceMediationPlugin) { @@ -758,13 +765,12 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } ?: result.error("ERROR", "displayBanner called when activity is null", null) } - //TODO: Implement with real error codes /** * Hides the banner ad. * * @param result The result to be returned after processing. */ - private fun hideBanner(@NonNull result: Result) { + private fun hideBanner(result: Result) { activity?.apply { runOnUiThread { synchronized(this@IronSourceMediationPlugin) { @@ -777,14 +783,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } ?: result.error("ERROR", "hideBanner called when activity is null", null) } - //TODO: Implement with real error codes /** * Checks if a banner placement is capped. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun isBannerPlacementCapped(@NonNull call: MethodCall, @NonNull result: Result) { + private fun isBannerPlacementCapped(call: MethodCall, result: Result) { val placementName = call.argument("placementName") as String? ?: return result.error("ERROR", "placementName is null", null) val isCapped = IronSource.isBannerPlacementCapped(placementName) @@ -837,14 +842,13 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar /** region Config API ==========================================================================*/ - //TODO: Implement with real error codes /** * Enables or disables client-side callbacks. * * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setClientSideCallbacks(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setClientSideCallbacks(call: MethodCall, result: Result) { val isEnabled = call.argument("isEnabled") as Boolean? ?: return result.error("ERROR", "isEnabled is null", null) SupersonicConfig.getConfigObj().clientSideCallbacks = isEnabled @@ -854,7 +858,6 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar /** region Internal Config API =================================================================*/ - //TODO: Implement with real error codes /** * Sets plugin data for IronSource mediation. * Only called internally in the process of init on the Flutter plugin @@ -862,7 +865,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar * @param call The method call containing arguments. * @param result The result to be returned after processing. */ - private fun setPluginData(@NonNull call: MethodCall, @NonNull result: Result) { + private fun setPluginData(call: MethodCall, result: Result) { val pluginType = call.argument("pluginType") as String? ?: return result.error("ERROR", "pluginType is null", null) @@ -876,9 +879,90 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar // endregion + /** region LevelPlay Init API =================================================================*/ + + private fun initLevelPlay(call: MethodCall, result: Result) { + if (context == null) { + return result.error("ERROR", "Context is null", null) + } + val appKey = call.argument("appKey") as String? ?: return result.error("ERROR", "appKey is null", null) + val adFormats = call.argument("adFormats") as List? ?: listOf() + val legacyAdFormats: List = adFormats.map { + when (it) { + "REWARDED" -> LevelPlay.AdFormat.REWARDED + "INTERSTITIAL" -> LevelPlay.AdFormat.INTERSTITIAL + "BANNER" -> LevelPlay.AdFormat.BANNER + "NATIVE_AD" -> LevelPlay.AdFormat.NATIVE_AD + else -> return@initLevelPlay result.error("ERROR", "Unsupported ad format: $it", null) + } + }.toList() + val initRequest = LevelPlayInitRequest.Builder(appKey) + .withLegacyAdFormats(legacyAdFormats) + .build() + + LevelPlay.init(context!!, initRequest, mLevelPlayInitListener!!) + + return result.success(null) + } + + // endregion + + /** region LevelPlayInterstitialAd API =================================================================*/ + private fun isInterstitialAdPlacementCapped(call: MethodCall, result: Result) { + val placementName: String = call.argument("placementName")!! + val isCapped = LevelPlayInterstitialAd.isPlacementCapped(placementName) + result.success(isCapped) + } + + private fun loadInterstitialAd(call: MethodCall, result: Result) { + val adObjectId: Int = call.argument("adObjectId")!! + val adUnitId: String = call.argument("adUnitId")!! + levelPlayAdObjectManager.loadInterstitialAd(adObjectId, adUnitId) + result.success(null) + } + + private fun showInterstitialAd(call: MethodCall, result: Result) { + val adObjectId: Int = call.argument("adObjectId")!! + val placementName: String? = call.argument("placementName") + levelPlayAdObjectManager.showInterstitialAd(adObjectId, placementName) + result.success(null) + } + + private fun isInterstitialAdReady(call: MethodCall, result: Result) { + val adObjectId: Int = call.argument("adObjectId")!! + val isReady = levelPlayAdObjectManager.isInterstitialAdReady(adObjectId) + result.success(isReady) + } + + private fun disposeAd(call: MethodCall, result: Result) { + val adObjectId: Int = call.argument("adObjectId")!! + levelPlayAdObjectManager.disposeAd(adObjectId) + result.success(null) + } + + private fun disposeAllAds(result: Result) { + levelPlayAdObjectManager.disposeAllAds() + result.success(null) + } + + // endregion + + /** region LevelPlayAdSize API =================================================================*/ + + private fun createAdaptiveAdSize(call: MethodCall, result: Result) { + val width = call.argument("width") as Int? + val size = context?.let { + LevelPlayAdSize.createAdaptiveAdSize(it, width) + } + return result.success(size.toMap()) + } + + // endregion + /** region ActivityAware =======================================================================*/ override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity + levelPlayAdObjectManager.activity = binding.activity if (activity is FlutterActivity) { (activity as FlutterActivity).lifecycle.addObserver(this) @@ -887,7 +971,6 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar { (activity as FlutterFragmentActivity).lifecycle.addObserver(this) } - setActivityToListeners(activity) } override fun onDetachedFromActivityForConfigChanges() { @@ -900,7 +983,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar (activity as FlutterFragmentActivity).lifecycle.removeObserver(this) } activity = null - setActivityToListeners(null) + levelPlayAdObjectManager.activity = null } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { @@ -914,7 +997,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar activity = binding.activity as FlutterFragmentActivity (activity as FlutterFragmentActivity).lifecycle.addObserver(this) } - setActivityToListeners(activity) + levelPlayAdObjectManager.activity = activity } override fun onDetachedFromActivity() { if (activity is FlutterActivity) @@ -926,22 +1009,11 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar (activity as FlutterFragmentActivity).lifecycle.removeObserver(this) } activity = null - setActivityToListeners(null) + levelPlayAdObjectManager.activity = null } // endregion - /** - * Set FlutterActivity to listener instances - */ - private fun setActivityToListeners(activity: Activity?) { - mImpressionDataListener?.activity = activity - mInitializationListener?.activity = activity - mLevelPlayRewardedVideoListener?.activity = activity - mLevelPlayInterstitialListener?.activity = activity - mLevelPlayBannerListener?.activity = activity - } - /** region LifeCycleObserver ==================================================================*/ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { @@ -954,9 +1026,7 @@ class IronSourceMediationPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar // endregion companion object { - val TAG = IronSourceMediationPlugin::class.java.simpleName - var isPluginAttached: Boolean = false - + val TAG: String = IronSourceMediationPlugin::class.java.simpleName /** * Registers a native ad view factory with the specified factory ID to be used within the Flutter engine. diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayAdObjectManager.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayAdObjectManager.kt new file mode 100644 index 0000000..febc819 --- /dev/null +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayAdObjectManager.kt @@ -0,0 +1,95 @@ +package com.ironSource.ironsource_mediation + +import android.app.Activity +import com.ironSource.ironsource_mediation.LevelPlayUtils.Companion.invokeMethodOnUiThread +import com.unity3d.mediation.LevelPlayAdError +import com.unity3d.mediation.LevelPlayAdInfo +import com.unity3d.mediation.interstitial.LevelPlayInterstitialAd +import com.unity3d.mediation.interstitial.LevelPlayInterstitialAdListener +import io.flutter.plugin.common.MethodChannel + +class LevelPlayAdObjectManager( + var activity: Activity?, + private val channel: MethodChannel +) { + + private val interstitialAdsMap = hashMapOf() + + fun loadInterstitialAd(adObjectId: Int, adUnitId: String) { + // Check if an interstitial ad already exists for this adObjectId + val existingAd = interstitialAdsMap[adObjectId] + + if (existingAd != null) { + // Ad exists, load the existing ad + existingAd.loadAd() + return + } + + // Ad doesn't exist, create a new one + val interstitialAd = LevelPlayInterstitialAd(adUnitId) + interstitialAd.setListener(object : LevelPlayInterstitialAdListener { + override fun onAdLoaded(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adObjectId" to adObjectId, "adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdLoaded", args) + } + + override fun onAdLoadFailed(error: LevelPlayAdError) { + val args = hashMapOf("adObjectId" to adObjectId, "error" to error.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdLoadFailed", args) + } + + override fun onAdInfoChanged(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adObjectId" to adObjectId, "adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdInfoChanged", args) + } + + override fun onAdDisplayed(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adObjectId" to adObjectId, "adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdDisplayed", args) + } + + override fun onAdDisplayFailed(error: LevelPlayAdError, adInfo: LevelPlayAdInfo) { + val args = hashMapOf( + "adObjectId" to adObjectId, + "error" to error.toMap(), + "adInfo" to adInfo.toMap() + ) + invokeMethodOnUiThread(channel, "onInterstitialAdDisplayFailed", args) + } + + override fun onAdClicked(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adObjectId" to adObjectId, "adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdClicked", args) + } + + override fun onAdClosed(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adObjectId" to adObjectId, "adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(channel, "onInterstitialAdClosed", args) + } + }) + + // Store the new ad instance in the map and load it + interstitialAdsMap[adObjectId] = interstitialAd + interstitialAd.loadAd() + } + + fun showInterstitialAd(adObjectId: Int, placementName: String?) { + activity?.let { + interstitialAdsMap[adObjectId]?.showAd(it, placementName) + } + } + + fun isInterstitialAdReady(adObjectId: Int): Boolean { + return interstitialAdsMap[adObjectId]?.isAdReady() ?: false + } + + fun disposeAd(adObjectId: Int) { + interstitialAdsMap.remove(adObjectId) + } + + fun disposeAllAds() { + interstitialAdsMap.clear() + channel.setMethodCallHandler(null) + activity = null + } +} diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdView.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdView.kt new file mode 100644 index 0000000..9bc7a11 --- /dev/null +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdView.kt @@ -0,0 +1,118 @@ +package com.ironSource.ironsource_mediation + +import com.ironSource.ironsource_mediation.LevelPlayUtils.Companion.invokeMethodOnUiThread +import com.unity3d.mediation.LevelPlayAdError +import com.unity3d.mediation.LevelPlayAdInfo +import com.unity3d.mediation.banner.LevelPlayBannerAdView +import com.unity3d.mediation.banner.LevelPlayBannerAdViewListener +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformView + +internal class LevelPlayBannerAdView( + viewId: Int?, + levelPlayBinaryMessenger: BinaryMessenger, + viewType: String, + private var levelPlayBanner: LevelPlayBannerAdView? +) : PlatformView, LevelPlayBannerAdViewListener { + private var methodChannel: MethodChannel? = null + + init { + methodChannel = MethodChannel(levelPlayBinaryMessenger, "${viewType}_$viewId") + methodChannel!!.setMethodCallHandler { call, result -> handleMethodCall(call, result)} + levelPlayBanner?.setBannerListener(this) + } + + /** + * Handles method calls from Flutter. + * This method is invoked when a method call is received from Flutter, and it delegates the call to the appropriate handler method. + * + * @param call The method call from Flutter. + * @param result The result to be returned to Flutter. + */ + private fun handleMethodCall(call: MethodCall, result: MethodChannel.Result) { + when(call.method) { + "loadAd" -> loadAd(result) + "destroyBanner" -> destroyBanner(result) + "pauseAutoRefresh" -> pauseAutoRefresh(result) + "resumeAutoRefresh" -> resumeAutoRefresh(result) + else -> result.error("ERROR", "Method ${call.method} unknown", null) + } + } + + private fun loadAd(result: MethodChannel.Result) { + levelPlayBanner?.loadAd() + // Return success result to Flutter + result.success(null) + } + + private fun destroyBanner(result: MethodChannel.Result) { + levelPlayBanner?.destroy() + // Return success result to Flutter + result.success(null) + } + + + private fun pauseAutoRefresh(result: MethodChannel.Result) { + levelPlayBanner?.pauseAutoRefresh() + // Return success result to Flutter + result.success(null) + } + + private fun resumeAutoRefresh(result: MethodChannel.Result) { + levelPlayBanner?.resumeAutoRefresh() + // Return success result to Flutter + result.success(null) + } + + override fun getView(): LevelPlayBannerAdView? = levelPlayBanner + + override fun dispose() { + levelPlayBanner?.destroy() + // Set method call handler to nil + methodChannel?.setMethodCallHandler(null) + // Set method channel to nil + methodChannel = null + } + + override fun onAdLoaded(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdLoaded", args) + } + + override fun onAdLoadFailed(error: LevelPlayAdError) { + val args = hashMapOf("error" to error.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdLoadFailed", args) + } + + override fun onAdDisplayed(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdDisplayed", args) + } + + override fun onAdDisplayFailed(adInfo: LevelPlayAdInfo, error: LevelPlayAdError) { + val args = hashMapOf("adInfo" to adInfo.toMap(), "error" to error.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdDisplayFailed", args) + } + + override fun onAdClicked(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdClicked", args) + } + + override fun onAdExpanded(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdExpanded", args) + } + + override fun onAdCollapsed(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdCollapsed", args) + } + + override fun onAdLeftApplication(adInfo: LevelPlayAdInfo) { + val args = hashMapOf("adInfo" to adInfo.toMap()) + invokeMethodOnUiThread(methodChannel!!, "onAdLeftApplication", args) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdViewFactory.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdViewFactory.kt new file mode 100644 index 0000000..dc15958 --- /dev/null +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerAdViewFactory.kt @@ -0,0 +1,56 @@ +package com.ironSource.ironsource_mediation + +import android.content.Context +import com.unity3d.mediation.LevelPlayAdSize +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory +import com.unity3d.mediation.banner.LevelPlayBannerAdView + +class LevelPlayBannerAdViewFactory( + private var levelPlayBinaryMessenger: BinaryMessenger +) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { + // Extract map from arguments + val creationParams = args as Map + // Extract variables from map + val adUnitId = creationParams["adUnitId"] as String + val viewType = creationParams["viewType"] as String + val placementName = creationParams["placementName"] as String? + val adSize = getLevelPlayAdSize(context, creationParams["adSize"] as Map) + val levelPlayBanner = context?.let { LevelPlayBannerAdView(it, adUnitId) } + if (adSize != null) { + levelPlayBanner?.setAdSize(adSize) + } + levelPlayBanner?.setPlacementName(placementName) + return LevelPlayBannerAdView(viewId, levelPlayBinaryMessenger, viewType, levelPlayBanner) + } + + private fun getLevelPlayAdSize(context: Context?, adSizeMap: Map): LevelPlayAdSize? { + if (context == null) return null + + val width = adSizeMap["width"] as Int + val height = adSizeMap["height"] as Int + val adLabel = adSizeMap["adLabel"] as String? + val isAdaptive = adSizeMap["isAdaptive"] as Boolean + + // At this point, developer has provided ad size, which means checks for + // width and height already performed by the sdk and no need to check again. + return if (isAdaptive) { + // Valid width provided as adaptive already called if entered here + LevelPlayAdSize.createAdaptiveAdSize(context, width) + } else if (adLabel.equals("BANNER", true)) { + LevelPlayAdSize.BANNER + } else if (adLabel.equals("LARGE", true)) { + LevelPlayAdSize.LARGE + } else if (adLabel.equals("MEDIUM_RECTANGLE", true)) { + LevelPlayAdSize.MEDIUM_RECTANGLE + } else if (adLabel.equals("CUSTOM", true)) { + LevelPlayAdSize.createCustomSize(width, height) + } else { + null + } + } +} diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerListener.kt index cde564f..2b5d5a7 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerListener.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayBannerListener.kt @@ -5,11 +5,7 @@ import com.ironsource.mediationsdk.logger.IronSourceError import com.ironsource.mediationsdk.sdk.LevelPlayBannerListener import io.flutter.plugin.common.MethodChannel -/** - * LevelPlay Banner Listener - */ -class LevelPlayBannerListener(channel: MethodChannel) : IronSourceListener(channel), - LevelPlayBannerListener { +class LevelPlayBannerListener(channel: MethodChannel) : LevelPlayListener(channel), LevelPlayBannerListener { override fun onAdLoaded(adInfo: AdInfo) { invokeMethod("LevelPlay_Banner:onAdLoaded", adInfo.toMap()) diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInitListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInitListener.kt new file mode 100644 index 0000000..7db40a9 --- /dev/null +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInitListener.kt @@ -0,0 +1,17 @@ +package com.ironSource.ironsource_mediation + +import com.unity3d.mediation.LevelPlayConfiguration +import com.unity3d.mediation.LevelPlayInitError +import com.unity3d.mediation.LevelPlayInitListener +import io.flutter.plugin.common.MethodChannel + +class LevelPlayInitListener(channel: MethodChannel) : LevelPlayListener(channel), LevelPlayInitListener { + + override fun onInitFailed(error: LevelPlayInitError) { + invokeMethod("onInitFailed", error.toMap()) + } + + override fun onInitSuccess(configuration: LevelPlayConfiguration) { + invokeMethod("onInitSuccess", configuration.toMap()) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInterstitialListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInterstitialListener.kt index ba3d337..3dae66e 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInterstitialListener.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayInterstitialListener.kt @@ -5,11 +5,7 @@ import com.ironsource.mediationsdk.logger.IronSourceError import com.ironsource.mediationsdk.sdk.LevelPlayInterstitialListener import io.flutter.plugin.common.MethodChannel -/** - * LevelPlay Interstitial Listener - */ -class LevelPlayInterstitialListener(channel: MethodChannel) : IronSourceListener(channel), - LevelPlayInterstitialListener { +class LevelPlayInterstitialListener(channel: MethodChannel) : LevelPlayListener(channel), LevelPlayInterstitialListener { override fun onAdReady(adInfo: AdInfo) { invokeMethod("LevelPlay_Interstitial:onAdReady", adInfo.toMap()) diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayListener.kt new file mode 100644 index 0000000..ce64432 --- /dev/null +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayListener.kt @@ -0,0 +1,10 @@ +package com.ironSource.ironsource_mediation + +import io.flutter.plugin.common.MethodChannel + +abstract class LevelPlayListener(protected val channel: MethodChannel) { + + protected fun invokeMethod(methodName: String, args: Map? = null) { + LevelPlayUtils.invokeMethodOnUiThread(channel, methodName, args) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdTemplateStyle.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdTemplateStyle.kt index af699bd..e17b745 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdTemplateStyle.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdTemplateStyle.kt @@ -1,6 +1,7 @@ package com.ironSource.ironsource_mediation data class LevelPlayNativeAdTemplateStyle( + val mainBackgroundColor: Int?, val titleStyle: LevelPlayNativeAdElementStyle?, val bodyStyle: LevelPlayNativeAdElementStyle?, val advertiserStyle: LevelPlayNativeAdElementStyle?, diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdView.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdView.kt index 70abd37..8546f87 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdView.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdView.kt @@ -4,9 +4,8 @@ import android.graphics.Typeface import android.graphics.drawable.GradientDrawable import android.view.View import android.widget.Button -import android.widget.ImageView import android.widget.TextView -import com.ironsource.mediationsdk.ads.nativead.LevelPlayMediaView +import com.ironSource.ironsource_mediation.LevelPlayUtils.Companion.invokeMethodOnUiThread import com.ironsource.mediationsdk.ads.nativead.LevelPlayNativeAd import com.ironsource.mediationsdk.ads.nativead.LevelPlayNativeAdListener import com.ironsource.mediationsdk.ads.nativead.NativeAdLayout @@ -25,7 +24,7 @@ import io.flutter.plugin.platform.PlatformView * @property placement The placement name associated with the native ad. * @property levelPlayBinaryMessenger The binary messenger used for communication between Flutter and native code. */ -class LevelPlayNativeAdView( +internal class LevelPlayNativeAdView( viewId: Int?, private var placement: String? = "", levelPlayBinaryMessenger: BinaryMessenger, @@ -40,17 +39,11 @@ class LevelPlayNativeAdView( init { methodChannel = MethodChannel(levelPlayBinaryMessenger, "${viewType}_$viewId") methodChannel!!.setMethodCallHandler { call, result -> handleMethodCall(call, result)} - - // Apply styles before ad loaded - applyStyles( - nativeAdLayout.findViewById(R.id.adTitle), - nativeAdLayout.findViewById(R.id.adBody), - nativeAdLayout.findViewById(R.id.adAdvertiser), - nativeAdLayout.findViewById(R.id.adCallToAction)) } private fun applyStyles(titleView: TextView, bodyView: TextView, advertiserView: TextView, callToActionView: Button) { templateStyles?.let { styles -> + styles.mainBackgroundColor?.let { nativeAdLayout.setBackgroundColor(it) } applyStyle(titleView, styles.titleStyle) applyStyle(bodyView, styles.bodyStyle) applyStyle(advertiserView, styles.advertiserStyle) @@ -159,7 +152,7 @@ class LevelPlayNativeAdView( result.success(null) } - override fun getView(): View = nativeAdLayout + override fun getView(): NativeAdLayout = nativeAdLayout override fun dispose() { // Remove any views @@ -175,17 +168,17 @@ class LevelPlayNativeAdView( override fun onAdClicked(nativeAd: LevelPlayNativeAd?, adInfo: AdInfo?) { // Notify Flutter that the ad has been clicked - methodChannel?.invokeMethod("onAdClicked", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) + invokeMethodOnUiThread(methodChannel!!, "onAdClicked", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) } override fun onAdImpression(nativeAd: LevelPlayNativeAd?, adInfo: AdInfo?) { // Notify Flutter that the ad has been shown - methodChannel?.invokeMethod("onAdImpression", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) + invokeMethodOnUiThread(methodChannel!!, "onAdImpression", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) } override fun onAdLoadFailed(nativeAd: LevelPlayNativeAd?, error: IronSourceError?) { // Notify Flutter that the ad load has been failed - methodChannel?.invokeMethod("onAdLoadFailed", LevelPlayUtils.hashMapOfIronSourceNativeAdAndError(nativeAd, error)) + invokeMethodOnUiThread(methodChannel!!, "onAdLoadFailed", LevelPlayUtils.hashMapOfIronSourceNativeAdAndError(nativeAd, error)) } override fun onAdLoaded(nativeAd: LevelPlayNativeAd?, adInfo: AdInfo?) { @@ -196,6 +189,16 @@ class LevelPlayNativeAdView( onBindNativeAdView.invoke(nativeAd) // Notify Flutter that the ad has been loaded - methodChannel?.invokeMethod("onAdLoaded", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) + invokeMethodOnUiThread(methodChannel!!, "onAdLoaded", LevelPlayUtils.hashMapOfIronSourceNativeAdAndAdInfo(nativeAd, adInfo)) + + // Apply styles + applyStyles( + nativeAdLayout.findViewById(R.id.adTitle), + nativeAdLayout.findViewById(R.id.adBody), + nativeAdLayout.findViewById(R.id.adAdvertiser), + nativeAdLayout.findViewById(R.id.adCallToAction)) + + // Visible the ad + nativeAdLayout.visibility = View.VISIBLE } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdViewFactory.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdViewFactory.kt index 2f18d68..70f17c6 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdViewFactory.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayNativeAdViewFactory.kt @@ -31,12 +31,13 @@ abstract class LevelPlayNativeAdViewFactory( val viewType = if (creationParams?.containsKey("viewType") == true) creationParams["viewType"] as String else null val styleMap = creationParams?.get("templateStyle") as? Map // Parse LevelPlayNativeAdElementStyle objects + val mainBackgroundColor = (styleMap?.get("mainBackgroundColor") as? Long)?.toInt() val titleElementStyle = parseElementStyle(styleMap?.get("titleStyle") as? Map) val bodyElementStyle = parseElementStyle(styleMap?.get("bodyStyle") as? Map) val advertiserElementStyle = parseElementStyle(styleMap?.get("advertiserStyle") as? Map) val callToActionElementStyle = parseElementStyle(styleMap?.get("callToActionStyle") as? Map) // Create the template style from parsed element styles(if exist) - val levelPlayNativeAdTemplateStyle = LevelPlayNativeAdTemplateStyle(titleElementStyle, bodyElementStyle, advertiserElementStyle, callToActionElementStyle) + val levelPlayNativeAdTemplateStyle = LevelPlayNativeAdTemplateStyle(mainBackgroundColor, titleElementStyle, bodyElementStyle, advertiserElementStyle, callToActionElementStyle) // Create the native ad layout val layoutInflater = LayoutInflater.from(context) val nativeAdLayout = if (layoutId != null && layoutId > 0) { diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayRewardedVideoListener.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayRewardedVideoListener.kt index 3d668a3..6966ff1 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayRewardedVideoListener.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayRewardedVideoListener.kt @@ -7,11 +7,7 @@ import com.ironsource.mediationsdk.sdk.LevelPlayRewardedVideoListener import com.ironsource.mediationsdk.sdk.LevelPlayRewardedVideoManualListener import io.flutter.plugin.common.MethodChannel -/** - * LevelPlay ReawrdedVideo Listener - */ -class LevelPlayRewardedVideoListener(channel: MethodChannel) : IronSourceListener(channel), - LevelPlayRewardedVideoListener, LevelPlayRewardedVideoManualListener { +class LevelPlayRewardedVideoListener(channel: MethodChannel) : LevelPlayListener(channel), LevelPlayRewardedVideoListener, LevelPlayRewardedVideoManualListener { override fun onAdAvailable(adInfo: AdInfo) { invokeMethod("LevelPlay_RewardedVideo:onAdAvailable", adInfo.toMap()) diff --git a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayUtils.kt b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayUtils.kt index 5523468..cf17059 100644 --- a/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayUtils.kt +++ b/android/src/main/kotlin/com/ironSource/ironsource_mediation/LevelPlayUtils.kt @@ -1,7 +1,7 @@ package com.ironSource.ironsource_mediation -import android.app.Activity -import android.util.Log +import android.os.Handler +import android.os.Looper import com.ironsource.mediationsdk.IronSource import com.ironsource.mediationsdk.adunit.adapter.utility.AdInfo import com.ironsource.mediationsdk.logger.IronSourceError @@ -14,29 +14,21 @@ import io.flutter.plugin.common.MethodChannel */ class LevelPlayUtils { companion object { + /** - * Invokes a method on the given method channel from the UI thread. - * This method is used to invoke Flutter methods from non-UI threads. + * Invokes a method on a Flutter MethodChannel on the UI thread. + * + * This function ensures that the call to `invokeMethod` on the + * MethodChannel is performed on the main (UI) thread. This is essential + * for thread-safety and to prevent crashes due to improper thread access. * - * @param activity The current activity. - * @param channel The method channel. - * @param methodName The name of the method to invoke. - * @param args The arguments to pass to the method. + * @param channel The [MethodChannel] on which the method is to be invoked. + * @param methodName The name of the method to invoke on the channel. + * @param arguments The arguments to pass to the method. This parameter is optional and defaults to null. */ - fun invokeChannelMethod(activity: Activity?, channel: MethodChannel, methodName: String, args: Any? = null) { - activity?.runOnUiThread { - channel.invokeMethod(methodName, args, object : MethodChannel.Result { - override fun success(result: Any?) {} - override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { - Log.e(IronSourceMediationPlugin.TAG, "Error: invokeMethod $methodName failed " - + "errorCode: $errorCode, message: $errorMessage, details: $errorDetails") - } - - override fun notImplemented() { - throw Error("Critical Error: invokeMethod $methodName notImplemented ") - } - }) - } + fun invokeMethodOnUiThread(channel: MethodChannel, methodName: String, arguments: Map? = null) { + Handler(Looper.getMainLooper()) + .post { channel.invokeMethod(methodName, arguments) } } /** diff --git a/android/src/main/res/layout/medium_level_play_native_ad_template.xml b/android/src/main/res/layout/medium_level_play_native_ad_template.xml index 3e11172..ea5f9ac 100644 --- a/android/src/main/res/layout/medium_level_play_native_ad_template.xml +++ b/android/src/main/res/layout/medium_level_play_native_ad_template.xml @@ -3,21 +3,23 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="350dp" + android:background="@android:color/background_light" + android:visibility="invisible" + tools:visibility="visible" android:contentDescription="com.ironsource.mediationsdk.ads.NativeAdLayout"> + android:layout_height="match_parent" + android:padding="8dp"> + android:layout_height="wrap_content"> @@ -57,6 +59,7 @@ android:id="@+id/adTextLayout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@android:color/transparent" android:paddingHorizontal="4dp" android:orientation="vertical"> @@ -65,6 +68,7 @@ android:id="@+id/adTitle" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@android:color/transparent" tools:text="Title" android:textSize="14sp" android:textStyle="bold" @@ -76,6 +80,7 @@ android:id="@+id/adAdvertiser" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@android:color/transparent" tools:text="Advertiser" android:textSize="14sp" android:textStyle="bold" @@ -90,14 +95,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/stackTopView" - android:background="@android:color/background_light" - android:padding="8dp" + android:background="@android:color/transparent" android:orientation="vertical"> + + + + android:background="@android:color/transparent" + android:layout_marginTop="4dp"/> @@ -127,6 +138,7 @@ android:id="@+id/adPrice" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:background="@android:color/background_light" tools:text="Price" android:textSize="12sp" android:textColor="@android:color/black" @@ -137,6 +149,7 @@ android:id="@+id/adStore" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:background="@android:color/background_light" tools:text="Store" android:textSize="12sp" android:textColor="@android:color/black" @@ -146,7 +159,7 @@