From 3ab5157871abbc5fd1b1adc1f4854d60e2f3292d Mon Sep 17 00:00:00 2001 From: Woojae Kim Date: Thu, 21 Nov 2024 00:14:18 +0900 Subject: [PATCH] feat: add android media view --- .../ReactNativeGoogleMobileAdsMediaView.kt | 48 ++++++++++++++++++ ...ctNativeGoogleMobileAdsMediaViewManager.kt | 50 +++++++++++++++++++ .../ReactNativeGoogleMobileAdsNativeAdView.kt | 22 +++++++- .../ReactNativeGoogleMobileAdsNativeModule.kt | 22 +++++++- .../ReactNativeGoogleMobileAdsPackage.kt | 11 +--- .../RNGoogleMobileAdsNativeModule.mm | 33 ++++++++++++ 6 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaView.kt create mode 100644 android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaViewManager.kt diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaView.kt b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaView.kt new file mode 100644 index 00000000..bf5670b9 --- /dev/null +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaView.kt @@ -0,0 +1,48 @@ +package io.invertase.googlemobileads + +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import android.annotation.SuppressLint +import com.facebook.react.bridge.ReactContext +import com.google.android.gms.ads.nativead.MediaView + +@SuppressLint("ViewConstructor") +class ReactNativeGoogleMobileAdsMediaView( + private val context: ReactContext +): MediaView(context) { + fun setResponseId(responseId: String?) { + val nativeModule = context.getNativeModule(ReactNativeGoogleMobileAdsNativeModule.NAME) as ReactNativeGoogleMobileAdsNativeModule? + nativeModule?.getNativeAd(responseId ?: "")?.let { + this.mediaContent = it.mediaContent + requestLayout() + } + } + + override fun requestLayout() { + super.requestLayout() + post(measureAndLayout) + } + + private val measureAndLayout = Runnable { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ) + layout(left, top, right, bottom) + } +} diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaViewManager.kt b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaViewManager.kt new file mode 100644 index 00000000..c617e5de --- /dev/null +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsMediaViewManager.kt @@ -0,0 +1,50 @@ +package io.invertase.googlemobileads + +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNGoogleMobileAdsMediaViewManagerDelegate +import com.facebook.react.viewmanagers.RNGoogleMobileAdsMediaViewManagerInterface + +@ReactModule(name = ReactNativeGoogleMobileAdsMediaViewManager.NAME) +class ReactNativeGoogleMobileAdsMediaViewManager( + reactContext: ReactApplicationContext +) : ViewGroupManager(reactContext), + RNGoogleMobileAdsMediaViewManagerInterface { + private val delegate: ViewManagerDelegate = RNGoogleMobileAdsMediaViewManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate = delegate + + override fun getName(): String = NAME + + override fun createViewInstance(context: ThemedReactContext): ReactNativeGoogleMobileAdsMediaView = ReactNativeGoogleMobileAdsMediaView(context) + + @ReactProp(name = "responseId") + override fun setResponseId(mediaView: ReactNativeGoogleMobileAdsMediaView, responseId: String?) { + mediaView.setResponseId(responseId) + } + + companion object { + const val NAME = "RNGoogleMobileAdsMediaView" + } +} diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeAdView.kt b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeAdView.kt index 80e30b61..1ecd9848 100644 --- a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeAdView.kt +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeAdView.kt @@ -20,7 +20,10 @@ package io.invertase.googlemobileads import android.annotation.SuppressLint import android.widget.FrameLayout import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.common.UIManagerType import com.facebook.react.views.view.ReactViewGroup +import com.google.android.gms.ads.nativead.MediaView import com.google.android.gms.ads.nativead.NativeAd import com.google.android.gms.ads.nativead.NativeAdView import kotlinx.coroutines.CoroutineScope @@ -29,6 +32,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch + @SuppressLint("ViewConstructor") class ReactNativeGoogleMobileAdsNativeAdView( private val context: ReactContext @@ -55,7 +59,8 @@ class ReactNativeGoogleMobileAdsNativeAdView( } fun registerAsset(assetKey: String, reactTag: Int) { - val assetView = context.fabricUIManager?.resolveView(reactTag) ?: return + val uiManager = UIManagerHelper.getUIManager(context, UIManagerType.FABRIC) + val assetView = uiManager?.resolveView(reactTag) ?: return when (assetKey) { "advertiser" -> nativeAdView.advertiserView = assetView "body" -> nativeAdView.bodyView = assetView @@ -66,6 +71,7 @@ class ReactNativeGoogleMobileAdsNativeAdView( "starRating" -> nativeAdView.starRatingView = assetView "icon" -> nativeAdView.iconView = assetView "image" -> nativeAdView.imageView = assetView + "media" -> nativeAdView.mediaView = assetView as MediaView } reloadAd() } @@ -74,7 +80,21 @@ class ReactNativeGoogleMobileAdsNativeAdView( reloadJob?.cancel() reloadJob = CoroutineScope(Dispatchers.Main).launch { delay(100) + requestLayout() nativeAd?.let { nativeAdView.setNativeAd(it) } } } + + override fun requestLayout() { + super.requestLayout() + post(measureAndLayout) + } + + private val measureAndLayout = Runnable { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ) + layout(left, top, right, bottom) + } } diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeModule.kt b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeModule.kt index c1f55605..5b06fed3 100644 --- a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeModule.kt +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsNativeModule.kt @@ -22,6 +22,7 @@ import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import com.google.android.gms.ads.AdLoader +import com.google.android.gms.ads.MediaAspectRatio import com.google.android.gms.ads.nativead.NativeAd import com.google.android.gms.ads.nativead.NativeAdOptions @@ -33,8 +34,20 @@ class ReactNativeGoogleMobileAdsNativeModule( override fun getName() = NAME override fun load(adUnitId: String, requestOptions: ReadableMap, promise: Promise) { + val mediaAspectRatio = if (requestOptions.hasKey("aspectRatio")) { + when (requestOptions.getInt("aspectRatio")) { + 1 -> MediaAspectRatio.ANY + 2 -> MediaAspectRatio.PORTRAIT + 3 -> MediaAspectRatio.LANDSCAPE + 4 -> MediaAspectRatio.SQUARE + else -> MediaAspectRatio.UNKNOWN + } + } else { + MediaAspectRatio.ANY + } val nativeAdOptions = NativeAdOptions.Builder() - .setReturnUrlsForImageAssets(true) +// .setReturnUrlsForImageAssets(true) + .setMediaAspectRatio(mediaAspectRatio) .build() val adLoader = AdLoader.Builder(reactApplicationContext, adUnitId) .withNativeAdOptions(nativeAdOptions) @@ -63,6 +76,13 @@ class ReactNativeGoogleMobileAdsNativeModule( } ?: run { data.putNull("icon") } + val mediaContent = Arguments.createMap() + nativeAd.mediaContent?.let { + mediaContent.putDouble("aspectRatio", it.aspectRatio.toDouble()) + mediaContent.putBoolean("hasVideoContent", it.hasVideoContent()) + mediaContent.putDouble("duration", it.duration.toDouble()) + data.putMap("mediaContent", mediaContent) + } promise.resolve(data) } diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsPackage.kt b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsPackage.kt index a34bdcfd..23e2882c 100644 --- a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsPackage.kt +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsPackage.kt @@ -31,7 +31,8 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { ): List> { return listOf( ReactNativeGoogleMobileAdsBannerAdViewManager(), - ReactNativeGoogleMobileAdsNativeAdViewManager(reactContext) + ReactNativeGoogleMobileAdsNativeAdViewManager(reactContext), + ReactNativeGoogleMobileAdsMediaViewManager(reactContext) ) } @@ -59,7 +60,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { ReactNativeAppModule.NAME, false, // canOverrideExistingModule false, // needsEagerInit - false, // hasConstants false, // isCxxModule false, // isTurboModule ) @@ -70,7 +70,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, isTurboModule, ) moduleInfos[ReactNativeGoogleMobileAdsConsentModule.NAME] = @@ -81,7 +80,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, ) moduleInfos[ReactNativeGoogleMobileAdsAppOpenModule.NAME] = ReactModuleInfo( @@ -91,7 +89,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, ) moduleInfos[ReactNativeGoogleMobileAdsInterstitialModule.NAME] = ReactModuleInfo( @@ -101,7 +98,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, ) moduleInfos[ReactNativeGoogleMobileAdsRewardedModule.NAME] = ReactModuleInfo( @@ -111,7 +107,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, ) moduleInfos[ReactNativeGoogleMobileAdsRewardedInterstitialModule.NAME] = ReactModuleInfo( @@ -121,7 +116,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, ) moduleInfos[ReactNativeGoogleMobileAdsNativeModule.NAME] = ReactModuleInfo( @@ -130,7 +124,6 @@ class ReactNativeGoogleMobileAdsPackage : TurboReactPackage() { false, false, false, - false, isTurboModule, ) moduleInfos diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeModule.mm b/ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeModule.mm index 0917b7f2..0f941fe3 100644 --- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeModule.mm +++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeModule.mm @@ -93,6 +93,7 @@ - (void)load:(NSString *)adUnitId @"mediaContent" : @{ @"aspectRatio" : @(nativeAd.mediaContent.aspectRatio), @"hasVideoContent" : @(nativeAd.mediaContent.hasVideoContent), + @"duration" : @(nativeAd.mediaContent.duration) } }); }]; @@ -104,6 +105,8 @@ - (GADNativeAd *)nativeAdForResponseId:(NSString *)responseId { @end +#pragma mark - RNGMANativeAdHolder + @implementation RNGMANativeAdHolder { GADAdLoader *_adLoader; GAMRequest *_adRequest; @@ -149,9 +152,12 @@ - (void)loadWithCompletionHandler:(RNGMANativeAdLoadCompletionHandler)completion [_adLoader loadRequest:_adRequest]; } +#pragma mark - GADNativeAdLoaderDelegate + - (void)adLoader:(nonnull GADAdLoader *)adLoader didReceiveNativeAd:(nonnull GADNativeAd *)nativeAd { _nativeAd = nativeAd; + _nativeAd.delegate = self; _completionHandler(nativeAd, nil); } @@ -160,6 +166,33 @@ - (void)adLoader:(nonnull GADAdLoader *)adLoader _completionHandler(nil, error); } +#pragma mark - GADNativeAdDelegate + +- (void)nativeAdDidRecordImpression:(GADNativeAd *)nativeAd { + // The native ad was shown. +} + +- (void)nativeAdDidRecordClick:(GADNativeAd *)nativeAd { + // The native ad was clicked on. +} + +- (void)nativeAdWillPresentScreen:(GADNativeAd *)nativeAd { + // The native ad will present a full screen view. +} + +- (void)nativeAdWillDismissScreen:(GADNativeAd *)nativeAd { + // The native ad will dismiss a full screen view. +} + +- (void)nativeAdDidDismissScreen:(GADNativeAd *)nativeAd { + // The native ad did dismiss a full screen view. +} + +- (void)nativeAdWillLeaveApplication:(GADNativeAd *)nativeAd { + // The native ad will cause the app to become inactive and + // open a new app. +} + @end #endif