diff --git a/android/build.gradle b/android/build.gradle index b6f6f33..7dafa6a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -12,8 +12,8 @@ buildscript { apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 16 diff --git a/android/src/main/java/de/callosumSw/RNGoogleAdManager/BannerViewManager.java b/android/src/main/java/de/callosumSw/RNGoogleAdManager/BannerViewManager.java index 05e0184..08cd223 100644 --- a/android/src/main/java/de/callosumSw/RNGoogleAdManager/BannerViewManager.java +++ b/android/src/main/java/de/callosumSw/RNGoogleAdManager/BannerViewManager.java @@ -5,8 +5,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.util.Log; +import android.view.Choreographer; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.LinearLayout; +import android.widget.Toast; +import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; @@ -20,7 +27,6 @@ import com.facebook.react.views.view.ReactViewGroup; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdSize; -import com.google.android.gms.ads.MobileAds; import com.google.android.gms.ads.doubleclick.AppEventListener; import com.google.android.gms.ads.doubleclick.PublisherAdRequest; import com.google.android.gms.ads.doubleclick.PublisherAdView; @@ -32,7 +38,10 @@ import org.prebid.mobile.addendum.PbFindSizeError; import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; class BannerView extends ReactViewGroup { public static final String LOG_TAG = "RNGoogleAdManager"; @@ -45,39 +54,33 @@ class BannerView extends ReactViewGroup { public static final String NATIVE_ERROR = "NATIVE_ERROR"; public static final String PROPS_SET = "PROPS_SET"; - public static final String BANNER = "BANNER"; - public static final String FULL_BANNER = "FULL_BANNER"; - public static final String LARGE_BANNER = "LARGE_BANNER"; - public static final String LEADERBOARD = "LEADERBOARD"; - public static final String MEDIUM_RECTANGLE = "MEDIUM_RECTANGLE"; - - protected Integer adHeight = null; - protected Integer adHeightInPixel = null; + protected PublisherAdView adView; protected String adId = null; protected ArrayList adSizes = null; - protected String adType = null; - protected PublisherAdView adView; - protected Integer adWidth = null; - protected Integer adWidthInPixel = null; - protected Context context = null; + protected Boolean isFluid = false; protected String prebidAdId = null; protected ArrayList testDeviceIds = null; protected Map targeting = null; + private LinearLayout fluidLayout = null; + protected Boolean sizeHasSettled = false; public BannerView (final Context context) { super(context); } - public void sendEvent(String type, @Nullable WritableMap event) { - try { + public BannerView(Context context, Activity activity) { + super(context); + } + + private void sendIfPropsSet(){ + if(adId != null && adSizes != null){ + WritableMap event = Arguments.createMap(); + ReactContext reactContext = (ReactContext)getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( getId(), - type, - event - ); - } catch (Exception err) { - Log.d(LOG_TAG, Log.getStackTraceString(err)); + PROPS_SET, + event); } } @@ -89,18 +92,35 @@ public void logAndSendError(Exception e){ WritableMap event = Arguments.createMap(); event.putString("errorMessage", errorMessage); - sendEvent(NATIVE_ERROR, event); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + NATIVE_ERROR, + event); } catch (Exception err) { Log.d(LOG_TAG, Log.getStackTraceString(err)); } } - private void sendIfPropsSet(){ - if(adId != null && (adSizes != null || adType != null)){ - sendEvent(PROPS_SET, null); - } + @Override + public void requestLayout() { + super.requestLayout(); + post(measureAndLayout); } + private final Runnable measureAndLayout = new Runnable() { + @Override + public void run() { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + + // do the manual child layout when running our measure-and-layout + manuallyLayoutChildren(); + } + }; + private void addAdView(){ try { this.addView(this.adView); @@ -111,41 +131,18 @@ private void addAdView(){ private void createAdView(){ try { - context = getContext(); + final Context context = getContext(); this.adView = new PublisherAdView(context); this.adView.setAdUnitId(adId); - if (adType != null) { - switch (adType) { - case BANNER: - this.adView.setAdSizes(new AdSize[]{AdSize.BANNER}); - break; - case FULL_BANNER: - this.adView.setAdSizes(new AdSize[]{AdSize.FULL_BANNER}); - break; - case LARGE_BANNER: - this.adView.setAdSizes(new AdSize[]{AdSize.LARGE_BANNER}); - break; - case LEADERBOARD: - this.adView.setAdSizes(new AdSize[]{AdSize.LEADERBOARD}); - break; - case MEDIUM_RECTANGLE: - default: - this.adView.setAdSizes(new AdSize[]{AdSize.MEDIUM_RECTANGLE}); - break; - } - - AdSize adSize = this.adView.getAdSize(); - adWidth = adSize.getWidth(); - adHeight = adSize.getHeight(); - adWidthInPixel = adSize.getWidthInPixels(context); - adHeightInPixel = adSize.getHeightInPixels(context); - } else if (adSizes != null) { - AdSize []sizes = adSizes.toArray(new AdSize[0]); - this.adView.setAdSizes(sizes); + if(isFluid){ + adView.setAdSizes(AdSize.FLUID); + } else { + AdSize []arr = this.adSizes.toArray(new AdSize[0]); + this.adView.setAdSizes(arr); } } catch (Exception e) { - logAndSendError(e); + System.out.println(e); } } @@ -192,7 +189,11 @@ public void onAppEvent(String name, String info) { WritableMap event = Arguments.createMap(); event.putString("url", info); - BannerView.this.sendEvent(AD_CLICKED, event); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + AD_CLICKED, + event); break; } @@ -202,7 +203,12 @@ public void onAppEvent(String name, String info) { destroyAdView(); - BannerView.this.sendEvent(AD_CLOSED, null); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + AD_CLOSED, + null); + break; } } @@ -214,43 +220,84 @@ private void sendLoadEvent(int width, int height) { event.putInt("width", width); event.putInt("height", height); - sendEvent(AD_LOADED, event); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + AD_LOADED, + event); } private void handleLoad(String adServer) { try { Log.d(LOG_TAG, "Ad loaded. Server: " + adServer); - if (adType == null) { - AdSize adSize = adView.getAdSize(); - adWidth = adSize.getWidth(); - adHeight = adSize.getHeight(); - adWidthInPixel = adSize.getWidthInPixels(context); - adHeightInPixel = adSize.getHeightInPixels(context); - } + final Context context = getContext(); + + AdSize size = adView.getAdSize(); - adView.measure(adWidth, adHeight); - adView.layout(0, 0, adWidthInPixel, adHeightInPixel); + int widthInPixel = size.getWidthInPixels(context); + int width = size.getWidth(); + int heightInPixel = size.getHeightInPixels(context); + int height = size.getHeight(); + int left = adView.getLeft(); + int top = adView.getTop(); - sendLoadEvent(adWidth, adHeight); + + adView.measure(width, height); + adView.layout(left, top, left + widthInPixel, top + heightInPixel); + sendLoadEvent(width, height); } catch (Exception e) { logAndSendError(e); } } + void manuallyLayoutChildren() { +// Log.d(LOG_TAG, "laying out children"); + if (!isFluid) { return; } + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + // UNSPECIFIED Height allows the ad view to find a size + child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED)); + + int childWidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + + // divide out the screen density before sending it over to React + int reactWidth = Math.round((float) childWidth / getResources().getDisplayMetrics().density); + int reachHeight = Math.round((float) childHeight / getResources().getDisplayMetrics().density); + +// Log.d(LOG_TAG, "measure height: " + childHeight); + + child.layout(0, 0, childWidth, childHeight); + sendLoadEvent(reactWidth, reachHeight); + } + } + + private void setListeners(){ this.adView.setAppEventListener(new BannerAppEventListener()); this.adView.setAdListener(new AdListener() { + + private void showToast(String message) { + final Context context = getContext(); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + @Override public void onAdLoaded() { super.onAdLoaded(); - try { if(!"".equals(prebidAdId)){ AdViewUtils.findPrebidCreativeSize(adView, new AdViewUtils.PbFindSizeListener() { @Override public void success(int width, int height) { - adView.setAdSizes(new AdSize(width, height)); + if(isFluid){ + adView.setAdSizes(AdSize.FLUID); + } else { + adView.setAdSizes(new AdSize(width, height)); + } handleLoad("Prebid"); } @@ -263,7 +310,7 @@ public void failure(@NonNull PbFindSizeError error) { handleLoad("GAM"); } } catch (Exception e) { - BannerView.this.logAndSendError(e); + } } @@ -274,19 +321,24 @@ public void onAdFailedToLoad(int errorCode) { Log.d(LOG_TAG, "Ad failed to load. Reason: " + errorMessage); + destroyAdView(); + WritableMap event = Arguments.createMap(); event.putString("errorMessage", errorMessage); - - BannerView.this.sendEvent(AD_FAILED, event); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + AD_FAILED, + event); } catch (Exception e) { - BannerView.this.logAndSendError(e); + } } }); } private void loadAd(){ - try { + try{ PublisherAdRequest.Builder adRequestBuilder = new PublisherAdRequest.Builder(); for(String testId : testDeviceIds){ @@ -295,7 +347,7 @@ private void loadAd(){ for (Map.Entry entry : targeting.entrySet()) { String key = entry.getKey(); - ArrayList value = (ArrayList) entry.getValue(); + String value = (String) entry.getValue(); adRequestBuilder.addCustomTargeting(key, value); } @@ -306,6 +358,9 @@ private void loadAd(){ WritableMap event = Arguments.createMap(); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), AD_REQUEST, event); + if(!"".equals(prebidAdId)){ final String prebidAdUnitId = this.prebidAdId; @@ -325,14 +380,13 @@ public void onComplete(ResultCode resultCode) { Log.d(LOG_TAG, "GAM Banner request with adunit id " + adUnitId + " with size " + adSize); this.adView.loadAd(adRequest); } - sendEvent(AD_REQUEST, event); } catch (Exception e) { logAndSendError(e); } } protected void addBannerView() { - try { + try{ if(this.adView == null ){ this.createAdView(); this.setListeners(); @@ -354,24 +408,20 @@ protected void destroyBanner() { } protected void loadBanner() { - try { - if(this.adView != null) { - final String adUnitId = this.adView.getAdUnitId(); + if(this.adView != null) { + final String _adUnitId = this.adView.getAdUnitId(); - if (!adId.equals(adUnitId) && adUnitId != null) { - this.destroyAdView(); - } - } - - if(this.adView == null) { - this.createAdView(); - this.setListeners(); + if (!adId.equals(_adUnitId) && _adUnitId != null) { + this.destroyAdView(); } + } - this.loadAd(); - } catch (Exception e) { - logAndSendError(e); + if(this.adView == null) { + this.createAdView(); + this.setListeners(); } + + this.loadAd(); } protected void removeBannerView() { @@ -384,12 +434,15 @@ protected void removeBannerView() { } } - protected void openDebugMenu() { - try { - ReactContext reactContext = (ReactContext)getContext(); - MobileAds.openDebugMenu(reactContext.getCurrentActivity(), adId); - } catch (Exception e) { - logAndSendError(e); + protected void setIsFluid(Boolean fluid) { + isFluid = fluid; + if(this.adView != null) { + if (isFluid) { + adView.setAdSizes(AdSize.FLUID); + } else { + AdSize[] arr = adSizes.toArray(new AdSize[0]); + this.adView.setAdSizes(arr); + } } } @@ -402,8 +455,12 @@ protected void setAdUnitId() { protected void setAdSizes() { try { if(this.adView != null) { - AdSize[] arr = adSizes.toArray(new AdSize[0]); - this.adView.setAdSizes(arr); + if(isFluid){ + adView.setAdSizes(AdSize.FLUID); + } else { + AdSize[] arr = adSizes.toArray(new AdSize[0]); + this.adView.setAdSizes(arr); + } } sendIfPropsSet(); } catch (Exception e) { @@ -414,24 +471,25 @@ protected void setAdSizes() { public class BannerViewManager extends ViewGroupManager { private static final String REACT_CLASS = "RNGAMBannerView"; - public static final String LOG_TAG = "RNGoogleAdManager"; public static final int COMMAND_ADD_BANNER_VIEW = 1; public static final int COMMAND_DESTROY_BANNER = 2; public static final int COMMAND_LOAD_BANNER = 3; public static final int COMMAND_REMOVE_BANNER_VIEW = 4; - public static final int COMMAND_PAUSE_BANNER_VIEW = 5; - public static final int COMMAND_RESUME_BANNER_VIEW = 6; - public static final int COMMAND_OPEN_DEBUG_MENU = 7; @Override public String getName() { return REACT_CLASS; } + @Override + public boolean needsCustomLayoutForChildren() { + return true; + } + @Override protected BannerView createViewInstance(ThemedReactContext context) { - return new BannerView(context); + return new BannerView(context, context.getCurrentActivity()); } @Override @@ -454,6 +512,10 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onAdFailedToLoad"))) + .put(BannerView.NATIVE_ERROR, + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onNativeError"))) .put(BannerView.AD_LOADED, MapBuilder.of( "phasedRegistrationNames", @@ -462,10 +524,6 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onAdRequest"))) - .put(BannerView.NATIVE_ERROR, - MapBuilder.of( - "phasedRegistrationNames", - MapBuilder.of("bubbled", "onNativeError"))) .put(BannerView.PROPS_SET, MapBuilder.of( "phasedRegistrationNames", @@ -475,12 +533,8 @@ public Map getExportedCustomBubblingEventTypeConstants() { @ReactProp(name = "adId") public void setAdId(BannerView view, @Nullable String adId) { - try { - view.adId = adId; - view.setAdUnitId(); - } catch (Exception e) { - Log.d(LOG_TAG, Log.getStackTraceString(e)); - } + view.adId = adId; + view.setAdUnitId(); } @ReactProp(name = "adSizes") @@ -499,14 +553,13 @@ public void setSize(BannerView view, @Nullable ReadableArray adSizes) { view.adSizes = list; view.setAdSizes(); } catch (Exception e) { - Log.d(LOG_TAG, Log.getStackTraceString(e)); + } } - @ReactProp(name = "adType") - public void setAdSize(BannerView view, @Nullable String adType) { - Log.d(LOG_TAG, String.valueOf(adType)); - view.adType = adType; + @ReactProp(name = "fluid") + public void setFluid(BannerView view, @Nullable Boolean fluid) { + view.setIsFluid(fluid); } @ReactProp(name = "prebidAdId") @@ -516,18 +569,14 @@ public void setPrebidAdId(BannerView view, @Nullable String prebidAdId) { @ReactProp(name = "testDeviceIds") public void setTestDeviceIds(BannerView view, ReadableArray testDeviceIds) { - try { - ArrayList list = new ArrayList<>(); - - for(int i = 0; i < testDeviceIds.size(); i++){ - String item = testDeviceIds.getString(i); - list.add(item); - } + ArrayList list = new ArrayList<>(); - view.testDeviceIds = list; - } catch (Exception e) { - Log.d(LOG_TAG, Log.getStackTraceString(e)); + for(int i = 0; i < testDeviceIds.size(); i++){ + String item = testDeviceIds.getString(i); + list.add(item); } + + view.testDeviceIds = list; } @ReactProp(name = "targeting") @@ -542,8 +591,7 @@ public Map getCommandsMap() { "addBannerView", COMMAND_ADD_BANNER_VIEW, "destroyBanner", COMMAND_DESTROY_BANNER, "loadBanner", COMMAND_LOAD_BANNER, - "removeBannerView", COMMAND_REMOVE_BANNER_VIEW, - "openDebugMenu", COMMAND_OPEN_DEBUG_MENU + "removeBannerView", COMMAND_REMOVE_BANNER_VIEW ); } @@ -565,10 +613,6 @@ public void receiveCommand(BannerView view, int commandId, @Nullable ReadableArr case COMMAND_REMOVE_BANNER_VIEW: view.removeBannerView(); break; - - case COMMAND_OPEN_DEBUG_MENU: - view.openDebugMenu(); - break; } } } diff --git a/ios/BannerView.swift b/ios/BannerView.swift index c66a240..8c4d1c8 100644 --- a/ios/BannerView.swift +++ b/ios/BannerView.swift @@ -14,6 +14,7 @@ class BannerView: UIView, GADAppEventDelegate, GADBannerViewDelegate, GADAdSizeD var width: Int?, height: Int? = nil var isAdUnitSet: Bool = false + var isFluid: Bool = true @objc var onAdClicked: RCTDirectEventBlock? @objc var onAdClosed: RCTDirectEventBlock? @@ -44,6 +45,13 @@ class BannerView: UIView, GADAppEventDelegate, GADBannerViewDelegate, GADAdSizeD @objc var targeting: NSDictionary? = [:] @objc var testDeviceIds: Array? = [""] + @objc var fluid: Bool = false { + didSet { + sendIfPropsSet() + isFluid = fluid + } + } + override init(frame: CGRect) { super.init(frame: frame) } @@ -69,25 +77,33 @@ class BannerView: UIView, GADAppEventDelegate, GADBannerViewDelegate, GADAdSizeD } func createAdView(){ - bannerView = DFPBannerView.init() - + if(isFluid){ + bannerView = DFPBannerView(adSize: kGADAdSizeFluid) + } + else{ + bannerView = DFPBannerView.init() + } + let rootViewController = UIApplication.shared.delegate?.window??.rootViewController - + var validAdSizes = [NSValue]() - - let adSizesArray = adSizes ?? [] - - for sizes in adSizesArray { + + for sizes in adSizes! { let width = sizes[0] let height = sizes[1] let customGADAdSize = GADAdSizeFromCGSize(CGSize(width: width, height: height)) validAdSizes.append(NSValueFromGADAdSize(customGADAdSize)) } + if(isFluid){ + validAdSizes.append(NSValueFromGADAdSize(kGADAdSizeFluid)) + } else{ + bannerView?.translatesAutoresizingMaskIntoConstraints = false + } + bannerView?.rootViewController = rootViewController bannerView?.delegate = self bannerView?.adSizeDelegate = self - bannerView?.translatesAutoresizingMaskIntoConstraints = false bannerView?.appEventDelegate = self bannerView?.validAdSizes = validAdSizes } @@ -136,12 +152,18 @@ class BannerView: UIView, GADAppEventDelegate, GADBannerViewDelegate, GADAdSizeD func adView(_ bannerView: GADBannerView, willChangeAdSizeTo size: GADAdSize) { print("\(LOG_TAG): Ad size changed") - if let adSize = adSizes?.first(where: { - let customGADAdSize = GADAdSizeFromCGSize(CGSize(width: $0[0], height: $0[1])) - return GADAdSizeEqualToSize(customGADAdSize, size) - }) { - width = adSize[0] - height = adSize[1] + if(isFluid){ + width = Int(size.size.width) + height = Int(size.size.height) + } + else{ + if let adSize = adSizes?.first(where: { + let customGADAdSize = GADAdSizeFromCGSize(CGSize(width: $0[0], height: $0[1])) + return GADAdSizeEqualToSize(customGADAdSize, size) + }) { + width = adSize[0] + height = adSize[1] + } } } diff --git a/ios/RNGoogleAdManager.m b/ios/RNGoogleAdManager.m index 947d9ec..5b3370d 100644 --- a/ios/RNGoogleAdManager.m +++ b/ios/RNGoogleAdManager.m @@ -18,6 +18,7 @@ @interface RCT_EXTERN_REMAP_MODULE(RNGAMBannerView, BannerViewManager, RCTViewMa RCT_EXPORT_VIEW_PROPERTY(adId, NSString) RCT_EXPORT_VIEW_PROPERTY(adSizes, NSArray) +RCT_EXPORT_VIEW_PROPERTY(fluid, BOOL) RCT_EXPORT_VIEW_PROPERTY(targeting, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(testDeviceIds, NSArray) diff --git a/package.json b/package.json index 6125336..b495a49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@callosum/react-native-google-ad-manager", - "version": "2.1.0-rc.7", + "name": "@axioscode/react-native-google-ad-manager", + "version": "2.1.0-rc.11", "description": "Google Ad Manager for react-native", "main": "index.js", "scripts": { @@ -50,10 +50,13 @@ }, "repository": { "type": "git", - "url": "git+ssh://git@gitlab.com/cmsw/react-native-google-ad-manager.git" + "url": "https://github.com/axioscode/react-native-google-ad-manager" }, "bugs": { - "url": "https://gitlab.com/cmsw/react-native-google-ad-manager/issues" + "url": "https://github.com/axioscode/react-native-google-ad-manager/issues" }, - "homepage": "https://gitlab.com/cmsw/react-native-google-ad-manager#readme" + "homepage": "https://github.com/axioscode/react-native-google-ad-manager#readme", + "directories": { + "example": "example" + } } diff --git a/src/RNGAMBanner.js b/src/RNGAMBanner.js index 2b9ab78..578c7b1 100644 --- a/src/RNGAMBanner.js +++ b/src/RNGAMBanner.js @@ -152,6 +152,7 @@ class RNGAMBanner extends React.PureComponent { adId={this.props.adId} adSizes={this.props.adSizes} adType={this.props.adType} + fluid={this.props.fluid} onAdClicked={this._onAdClicked} onAdClosed={this._onAdClosed} onAdFailedToLoad={this._onAdFailedToLoad} @@ -173,6 +174,7 @@ RNGAMBanner.propTypes = { adId: P.string.isRequired, adSizes: P.arrayOf(P.arrayOf(P.number)), adType: P.string, + fluid: P.bool, onAdClicked: P.func, onAdClosed: P.func, onAdFailedToLoad: P.func, @@ -189,6 +191,7 @@ RNGAMBanner.propTypes = { RNGAMBanner.defaultProps = { adSizes: undefined, adType: undefined, + fluid: false, onAdClicked: noop, onAdClosed: noop, onAdFailedToLoad: noop,