From 2a5465db49a748360ab4ea0ad3ce2e36b0931de3 Mon Sep 17 00:00:00 2001 From: Astrovic Date: Thu, 1 Jun 2023 17:22:13 +0200 Subject: [PATCH] - Added support for App Open Ad type - Events added and renamed - Updated documentation --- apidoc/Admob.yml | 8 ++ apidoc/View.yml | 70 ++++++----- ios/Classes/TiAdmobModule.h | 2 - ios/Classes/TiAdmobModule.m | 65 ++++++++++- ios/Classes/TiAdmobModuleAssets.m | 24 ---- ios/Classes/TiAdmobTypes.h | 3 +- ios/Classes/TiAdmobView.h | 5 + ios/Classes/TiAdmobView.m | 131 ++++++++++++++++++--- ios/Classes/TiAdmobViewProxy.h | 4 + ios/Classes/TiAdmobViewProxy.m | 10 ++ ios/documentation/changelog.md | 12 ++ ios/documentation/index.md | 124 ++++++++++++++------ ios/documentation/view.md | 33 ++---- ios/example/app.js | 188 +++++++++++++++++++++++------- ios/manifest | 2 +- 15 files changed, 506 insertions(+), 175 deletions(-) delete mode 100644 ios/Classes/TiAdmobModuleAssets.m diff --git a/apidoc/Admob.yml b/apidoc/Admob.yml index 27cf951e..8c19cb15 100644 --- a/apidoc/Admob.yml +++ b/apidoc/Admob.yml @@ -112,6 +112,12 @@ properties: summary: Used when creating a to determine ad type of rewarded video permission: read-only platforms: [iphone, ipad] + + - name: AD_TYPE_APP_OPEN + type: Number + summary: Used when creating a to determine ad type of app open + permission: read-only + platforms: [iphone, ipad] - name: AD_SIZE_BANNER type: Number @@ -361,6 +367,8 @@ methods: type: void summary: Disables automated in app purchase (IAP) reporting. Must be called before any IAP transaction is initiated. IAP reporting is used to track IAP ad conversions. Do not disable reporting if you use IAP ads. platforms: [iphone, ipad] + deleted: + since: "6.2.0" - name: getAndroidAdId returns: diff --git a/apidoc/View.yml b/apidoc/View.yml index 4ab6d970..b783a092 100644 --- a/apidoc/View.yml +++ b/apidoc/View.yml @@ -135,11 +135,7 @@ events: properties: - name: adUnitId type: String - summary: Id for the add - - name: isReady - type: Boolean - summary: Whether the ad is ready. Populated only for interstitial ads - optional: true + summary: Id for the ad - name: didFailToReceiveAd summary: Sent when an ad request failed. Normally this is because no network connection was available or no ads were available (i.e. no fill). @@ -147,7 +143,18 @@ events: properties: - name: adUnitId type: String - summary: add id that failed to load + summary: ad id that failed to load + - name: error + type: String + summary: error message + + - name: didFailToShowAd + summary: Sent when App Open Ad failed to show. + platforms: [iphone, ipad] + properties: + - name: adUnitId + type: String + summary: ad id that failed to show - name: error type: String summary: error message @@ -163,11 +170,7 @@ events: properties: - name: adUnitId type: String - summary: Id for the add - - name: isReady - type: Boolean - summary: Whether the ad is ready. Populated only for interstitial ads - optional: true + summary: Id for the ad - name: willDismissScreen summary: Sent just before dismissing a full screen view. @@ -175,11 +178,7 @@ events: properties: - name: adUnitId type: String - summary: Id for the add - - name: isReady - type: Boolean - summary: Whether the ad is ready. Populated only for interstitial ads - optional: true + summary: Id for the ad - name: didDismissScreen summary: Sent just after dismissing a full screen view. Use this opportunity to restart anything you may have stopped as part of `willPresentScreen`. @@ -187,11 +186,7 @@ events: properties: - name: adUnitId type: String - summary: Id for the add - - name: isReady - type: Boolean - summary: Whether the ad is ready. Populated only for interstitial ads - optional: true + summary: Id for the ad - name: willLeaveApplication summary: Sent just before the application will background or terminate because the user clicked on an ad that will launch another application (such as the App Store). @@ -199,11 +194,7 @@ events: properties: - name: adUnitId type: String - summary: Id for the add - - name: isReady - type: Boolean - summary: Whether the ad is ready. Populated only for interstitial ads - optional: true + summary: Id for the ad - name: didReceiveInAppPurchase summary: Called when the user clicks on the buy button of an in-app purchase ad. @@ -218,6 +209,33 @@ events: deprecated: since: "2.2.0" + - name: didRecordClick + summary: Called when the user clicks on the ad. + platforms: [iphone, ipad] + properties: + - name: adUnitId + type: String + summary: Id for the ad + + - name: didRecordImpression + summary: Called when an ad is impressed + platforms: [iphone, ipad] + properties: + - name: adUnitId + type: String + summary: Id for the ad + + - name: didRewardUser + summary: Fired when the reward based video ad has rewarded the user. + platforms: [iphone, ipad] + properties: + - name: type + type: String + summary: Type of the reward. + - name: amount + type: Number + summary: Amount rewarded to the user. + - name: ad_received summary: Fired when ad is loaded platforms: [android] diff --git a/ios/Classes/TiAdmobModule.h b/ios/Classes/TiAdmobModule.h index f3c35e9c..2bc9931c 100644 --- a/ios/Classes/TiAdmobModule.h +++ b/ios/Classes/TiAdmobModule.h @@ -11,8 +11,6 @@ - (void)disableSDKCrashReporting:(id)unused; -- (void)disableAutomatedInAppPurchaseReporting:(id)unused; // REMOVED - - (void)requestConsentInfoUpdateWithParameters:(id)args; - (void)loadForm:(id)args; diff --git a/ios/Classes/TiAdmobModule.m b/ios/Classes/TiAdmobModule.m index ce343112..59e75a57 100644 --- a/ios/Classes/TiAdmobModule.m +++ b/ios/Classes/TiAdmobModule.m @@ -37,16 +37,65 @@ - (NSString *)moduleId #pragma mark Public API's -- (void)disableSDKCrashReporting:(id)unused -{ - [GADMobileAds.sharedInstance disableSDKCrashReporting]; + +// Check if the TC string last updated date was more than 13 months ago +// https://developers.google.com/admob/ios/privacy/gdpr#troubleshooting +/** + * This function checks the date of last consent, which is base64-encoded in digits 1..7 of a string that is stored + * in userDefaults under the key "IABTCF_TCString". + * + * If this date is older than 365 days, the entry with that key will be removed from userDefaults. With the IABTCF + * configuration now being invalid, the CMP should re-display the consent dialog the next time it is instantiated. + * + * This should avoid errors of any used ad solution, which is supposed to consider consent older than 13 months "outdated". + * + * Inspired by @bocops code: https://github.com/bocops/UMP-workarounds/blob/main/detect_outdated_consent/android/java/detect_outdated_consent.java + */ +- (void)deleteTCStringIfOutdated { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + // IABTCF string is stored in userDefaults + NSString *tcString = [userDefaults stringForKey:@"IABTCF_TCString"] ?: @""; + + // Check if the TCF string is empty + if (tcString.length > 0) { + + // base64 alphabet used to store data in IABTCF string + NSString *base64 = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Date is stored in digits 1..7 of the IABTCF string + NSString *dateSubstring = [tcString substringWithRange:NSMakeRange(1, 6)]; + + // Interpret date substring as base64-encoded integer value + long timestamp = 0; + for (int i = 0; i < dateSubstring.length; i++) { + unichar c = [dateSubstring characterAtIndex:i]; + + NSInteger value = [base64 rangeOfString:[NSString stringWithFormat:@"%C", c]].location; + timestamp = timestamp * 64 + value; + } + + // Timestamp is given is deci-seconds, convert to milliseconds + timestamp *= 100; + + // Compare with current timestamp to get age in days + long daysAgo = (long)(([[NSDate date] timeIntervalSince1970] * 1000) - timestamp) / (1000 * 60 * 60 * 24); + NSLog(@"[DEBUG] Ti.AdMob: TC string last updated date was %ld days ago", daysAgo); + // Delete TC string if age is over a year + if (daysAgo > 365) { + [userDefaults removeObjectForKey:@"IABTCF_TCString"]; + [userDefaults synchronize]; + NSLog(@"[DEBUG] Ti.AdMob: TC string removed"); + } + } else { + NSLog(@"[DEBUG] The TCF string does not exist or is empty."); + } } -- (void)disableAutomatedInAppPurchaseReporting:(id)unused +- (void)disableSDKCrashReporting:(id)unused { - DEPRECATED_REMOVED(@"Admob.disableAutomatedInAppPurchaseReporting", @"4.0.0", @"4.0.0 (removed by Google)"); + [GADMobileAds.sharedInstance disableSDKCrashReporting]; } - - (void)requestConsentInfoUpdateWithParameters:(id)args { ENSURE_SINGLE_ARG(args, NSDictionary); @@ -66,6 +115,9 @@ - (void)requestConsentInfoUpdateWithParameters:(id)args // Set tag for under age of consent. Here NO means users are not under age. parameters.tagForUnderAgeOfConsent = [TiUtils boolValue:@"tagForUnderAgeOfConsent" properties:args def:NO];; + // The TCF string may be outdated, so proceed with removal if needed. + [self deleteTCStringIfOutdated]; + // Request an update to the consent information. [[UMPConsentInformation sharedInstance] requestConsentInfoUpdateWithParameters:parameters completionHandler:^(NSError *_Nullable error) { @@ -367,5 +419,6 @@ - (void)setInMobi_updateGDPRConsent:(id)updateGDPRConsent MAKE_SYSTEM_PROP(AD_TYPE_BANNER, TiAdmobAdTypeBanner); MAKE_SYSTEM_PROP(AD_TYPE_INTERSTITIAL, TiAdmobAdTypeInterstitial); MAKE_SYSTEM_PROP(AD_TYPE_REWARDED_VIDEO, TiAdmobAdTypeRewardedVideo); +MAKE_SYSTEM_PROP(AD_TYPE_APP_OPEN, TiAdmobAdTypeAppOpen); @end diff --git a/ios/Classes/TiAdmobModuleAssets.m b/ios/Classes/TiAdmobModuleAssets.m deleted file mode 100644 index afd2cde2..00000000 --- a/ios/Classes/TiAdmobModuleAssets.m +++ /dev/null @@ -1,24 +0,0 @@ -/** - * This is a generated file. Do not edit or your changes will be lost - */ -#import "TiAdmobModuleAssets.h" - -extern NSData* filterDataInRange(NSData* thedata, NSRange range); - -@implementation TiAdmobModuleAssets - -- (NSData *)moduleAsset -{ - - - return nil; -} - -- (NSData *)resolveModuleAsset:(NSString *)path -{ - - - return nil; -} - -@end diff --git a/ios/Classes/TiAdmobTypes.h b/ios/Classes/TiAdmobTypes.h index b86ae71a..e322beac 100644 --- a/ios/Classes/TiAdmobTypes.h +++ b/ios/Classes/TiAdmobTypes.h @@ -10,5 +10,6 @@ typedef NS_ENUM(NSUInteger, TiAdmobAdType){ TiAdmobAdTypeBanner = 0, TiAdmobAdTypeInterstitial, - TiAdmobAdTypeRewardedVideo + TiAdmobAdTypeRewardedVideo, + TiAdmobAdTypeAppOpen }; diff --git a/ios/Classes/TiAdmobView.h b/ios/Classes/TiAdmobView.h index 2ad5a5e6..95d61024 100644 --- a/ios/Classes/TiAdmobView.h +++ b/ios/Classes/TiAdmobView.h @@ -12,6 +12,7 @@ GADBannerView *bannerView; GADRewardedAd *rewardedAd; GADInterstitialAd *interstitialAd; + GADAppOpenAd *appOpenAd; NSString *adUnitId; } @@ -22,6 +23,10 @@ - (void)showRewardedVideo; +- (void)requestAppOpenAd; + +- (void)showAppOpenAd; + - (void)setAdUnitId_:(id)value; - (void)setKeywords_:(id)value; diff --git a/ios/Classes/TiAdmobView.m b/ios/Classes/TiAdmobView.m index 459a8428..df569c83 100644 --- a/ios/Classes/TiAdmobView.m +++ b/ios/Classes/TiAdmobView.m @@ -10,7 +10,10 @@ #import "TiApp.h" #import "TiUtils.h" -@implementation TiAdmobView +@implementation TiAdmobView { + BOOL _isLoadingAd; + BOOL _isShowingAd; +} #pragma mark - Ad Lifecycle @@ -60,9 +63,14 @@ - (void)dealloc rewardedAd.fullScreenContentDelegate = nil; } + if (appOpenAd) { + appOpenAd.fullScreenContentDelegate = nil; + } + RELEASE_TO_NIL(bannerView); RELEASE_TO_NIL(interstitialAd); RELEASE_TO_NIL(rewardedAd); + RELEASE_TO_NIL(appOpenAd); [super dealloc]; } @@ -98,6 +106,8 @@ - (void)initialize [self loadInterstitial]; } else if ([TiUtils intValue:adType def:TiAdmobAdTypeBanner] == TiAdmobAdTypeRewardedVideo) { [self loadRewardedVideoWithAdUnitID:adUnitId]; + } else if ([TiUtils intValue:adType def:TiAdmobAdTypeBanner] == TiAdmobAdTypeAppOpen) { + [self requestAppOpenAd]; } }]; } @@ -187,14 +197,14 @@ - (void)loadRewardedVideoWithAdUnitID:(NSString *)adUnitID // calling "loadRewardedVideo(adUnitId)" again [GADRewardedAd loadWithAdUnitID:adUnitID request:[GADRequest request] completionHandler:^(GADRewardedAd * _Nullable _rewardedAd, NSError * _Nullable error) { if (error) { - [self.proxy fireEvent:@"adfailedtoload" withObject:@{ @"message": error.localizedDescription }]; + [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error": error.localizedDescription }]; return; } rewardedAd = [_rewardedAd retain]; rewardedAd.fullScreenContentDelegate = self; - - [[self proxy] fireEvent:@"adloaded"]; + + [self.proxy fireEvent:@"didReceiveAd" withObject:@{ @"adUnitId": adUnitId }]; }]; } @@ -204,14 +214,14 @@ - (void)loadInterstitial request:[GADRequest request] completionHandler:^(GADInterstitialAd *ad, NSError *error) { if (error) { - [self.proxy fireEvent:@"adfailedtoload" withObject:@{ @"message": error.localizedDescription }]; + [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error": error.localizedDescription }]; return; } interstitialAd = [ad retain]; interstitialAd.fullScreenContentDelegate = self; - [[self proxy] fireEvent:@"adloaded"]; + [self.proxy fireEvent:@"didReceiveAd" withObject:@{ @"adUnitId": adUnitId }]; }]; } @@ -239,13 +249,91 @@ - (void)showRewardedVideo @"type": rewardedAd.adReward.type }; - [[self proxy] fireEvent:@"adrewarded" withObject:event]; + [[self proxy] fireEvent:@"didRewardUser" withObject:event]; }]; } else { NSLog(@"[WARN] Cannot show rewarded video: %@", error.localizedDescription); } } +// App Open Ad + +- (BOOL)isAdAvailable { + // Check if ad exists. + return appOpenAd != nil; +} + +- (void)requestAppOpenAd +{ + // Do not load ad if one is already loading. + if(_isLoadingAd){ + NSLog(@"[DEBUG] Ad is loading. No need to rush!"); + return; + } + + //GADRequest *request = [GADRequest request]; + _isLoadingAd = YES; + // + [GADAppOpenAd loadWithAdUnitID:adUnitId + request:[GADRequest request] + orientation:UIInterfaceOrientationPortrait + completionHandler:^(GADAppOpenAd *_Nullable _appOpenAd, NSError *_Nullable error) { + _isLoadingAd = NO; + if (error) { + [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error": error.localizedDescription }]; + appOpenAd = nil; + return; + } + + appOpenAd = [_appOpenAd retain]; + appOpenAd.fullScreenContentDelegate = self; + + [self.proxy fireEvent:@"didReceiveAd" withObject:@{ @"adUnitId": adUnitId }]; + + _isLoadingAd = NO; + }]; +} + +- (void)showAppOpenAd +{ + // If the app open ad is already showing, do not show the ad again. + if (_isShowingAd) { + NSLog(@"[WARN] The App Open Ad is already showing."); + return; + } + + // If the app open ad is still loading, you can not show the ad. + if (_isLoadingAd){ + NSLog(@"[WARN] The App Open Ad is still loading. Hang on!"); + return; + } + + // If the app open ad is not available yet, invoke the callback then load the ad. + if (![self isAdAvailable]) { + NSLog(@"[ERROR] The App Open Ad is not available. Did you call load() method?"); + [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error": [NSString stringWithFormat:@"The App Open Ad is not available. Did you call load() method?"]}]; + appOpenAd = nil; + return; + } + + NSError *error; + BOOL canPresent = appOpenAd && [appOpenAd canPresentFromRootViewController:[[[TiApp app] controller] topPresentedController] error:&error]; + + if (canPresent) { + _isShowingAd = YES; + //[appOpenAd presentFromRootViewController:[[[TiApp app] controller] topPresentedController]]; + + UIViewController *rootViewController = [[[TiApp app] controller] topPresentedController]; + [appOpenAd presentFromRootViewController:rootViewController]; + + } else { + NSLog(@"[WARN] Cannot show App Open ad: %@", error.localizedDescription); + [self.proxy fireEvent:@"didFailToShowAd" withObject:@{ @"adUnitId" : adUnitId, @"error": error.localizedDescription }]; + _isShowingAd = NO; + appOpenAd = nil; + } +} + #pragma mark - Deprecated / removed API's - (void)setPublisherId_:(id)value @@ -329,7 +417,12 @@ - (void)bannerView:(nonnull GADBannerView *)bannerView - (void)bannerViewDidRecordImpression:(nonnull GADBannerView *)bannerView { - [self.proxy fireEvent:@"didRecordImpression" withObject:adUnitId]; + [self.proxy fireEvent:@"didRecordImpression" withObject:@{ @"adUnitId": adUnitId }]; +} + +- (void)bannerViewDidRecordClick:(nonnull GADBannerView *)bannerView +{ + [self.proxy fireEvent:@"didRecordClick" withObject:@{ @"adUnitId": adUnitId }]; } // These three are only called if the banner ad triggers an in-app fullscreen view @@ -352,9 +445,6 @@ - (void)bannerViewDidDismissScreen:(nonnull GADBannerView *)bannerView #pragma mark - GADFullScreenContentDelegate -// NOTE: I tried to map them as best as possible, but for example "adDidRecordImpression" is new -// and "willPresentScreen" is not available anymore - - (void)adDidRecordImpression:(id)ad { NSMutableDictionary *event = [NSMutableDictionary dictionaryWithDictionary:@{ @"adUnitId": adUnitId }]; @@ -368,14 +458,23 @@ - (void)adDidRecordImpression:(id)ad [self.proxy fireEvent:@"didRecordImpression" withObject:event]; } +- (void)adDidRecordClick:(id)ad +{ + [self.proxy fireEvent:@"didRecordClick" withObject:@{ @"adUnitId": adUnitId }]; +} + - (void)ad:(id)ad didFailToPresentFullScreenContentWithError:(NSError *)error { - [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error" : error.localizedDescription }]; + if ([ad isKindOfClass:[GADAppOpenAd class]]) { + _isShowingAd = NO; + appOpenAd = nil; + } + [self.proxy fireEvent:@"didFailToReceiveAd" withObject:@{ @"adUnitId" : adUnitId, @"error" : error.localizedDescription }]; } -- (void)adDidPresentFullScreenContent:(id)ad +- (void)adWillPresentFullScreenContent:(id)ad { - [self.proxy fireEvent:@"didPresentScreen" withObject:@{ @"adUnitId": adUnitId }]; + [self.proxy fireEvent:@"willPresentScreen" withObject:@{ @"adUnitId": adUnitId }]; } - (void)adWillDismissFullScreenContent:(id)ad @@ -385,6 +484,10 @@ - (void)adWillDismissFullScreenContent:(id)ad - (void)adDidDismissFullScreenContent:(id)ad { + if ([ad isKindOfClass:[GADAppOpenAd class]]) { + _isShowingAd = NO; + appOpenAd = nil; + } [self.proxy fireEvent:@"didDismissScreen" withObject:@{ @"adUnitId": adUnitId }]; } diff --git a/ios/Classes/TiAdmobViewProxy.h b/ios/Classes/TiAdmobViewProxy.h index c71e36e4..369ac82d 100644 --- a/ios/Classes/TiAdmobViewProxy.h +++ b/ios/Classes/TiAdmobViewProxy.h @@ -21,4 +21,8 @@ - (void)loadRewardedVideo:(id)adUnitId; +- (void)requestAppOpenAd:(id)args; + +- (void)showAppOpenAd:(id)args; + @end diff --git a/ios/Classes/TiAdmobViewProxy.m b/ios/Classes/TiAdmobViewProxy.m index 450f2932..a13198ff 100644 --- a/ios/Classes/TiAdmobViewProxy.m +++ b/ios/Classes/TiAdmobViewProxy.m @@ -36,4 +36,14 @@ - (void)loadRewardedVideo:(id)adUnitId [[self admobView] loadRewardedVideoWithAdUnitID:adUnitId]; } +- (void)requestAppOpenAd:(id)args +{ + [[self admobView] requestAppOpenAd]; +} + +- (void)showAppOpenAd:(id)args +{ + [[self admobView] showAppOpenAd]; +} + @end diff --git a/ios/documentation/changelog.md b/ios/documentation/changelog.md index 241a89f5..2edcaf56 100644 --- a/ios/documentation/changelog.md +++ b/ios/documentation/changelog.md @@ -1,5 +1,17 @@ # Change Log +### v6.2.0 +- Added support for App Open Ad (https://developers.google.com/admob/android/app-open) +- Reset TC string if last updated date was more than 13 months ago (https://developers.google.com/admob/ios/privacy/gdpr#troubleshooting) +- Renamed Interstitial and Rewarded Video event names: +-- `adloaded` becomes `didReceiveAd` +-- `adfailedtoload` becomes `didFailToReceiveAd` +-- `adrewarded` becomes `didRewardUser` +- Added `didRecordImpression` and `didRecordClick` events to all ad types +- Added `didFailToShowAd` event to Open App ad type +- Removed deprecated `didPresentScreen` event +- Updated documentation and iOS example + ### v6.1.0 - Update InMobi SDK 10.5.4 (https://support.inmobi.com/monetize/sdk-documentation/download-sdk) - Update InMobiAdapter 10.5.4.0 (https://developers.google.com/admob/ios/mediation/inmobi#version-10.5.4.0_1) diff --git a/ios/documentation/index.md b/ios/documentation/index.md index 0a34bb2b..c86e3a0a 100644 --- a/ios/documentation/index.md +++ b/ios/documentation/index.md @@ -163,6 +163,8 @@ reports SDK related exceptions and calls the recorded original exception handler ### `disableAutomatedInAppPurchaseReporting()` +⚠️ Removed since Ti.Admob 6.2.0. + Disables automated in app purchase (IAP) reporting. Must be called before any IAP transaction is initiated. IAP reporting is used to track IAP ad conversions. Do not disable reporting if you use IAP ads. @@ -299,6 +301,10 @@ Used when creating a to determine ad type of interstitial Used when creating a to determine ad type of rewarded video +### Number `AD_TYPE_APP_OPEN` + +Used when creating a to determine ad type of app open + ### Number `CONSENT_FORM_STATUS_AVAILABLE` Consent form is available. @@ -406,34 +412,39 @@ var bannerAdView = Admob.createView({ win.add(bannerAdView); bannerAdView.addEventListener('didReceiveAd', function (e) { - Ti.API.info('BannerAdView - Did receive ad: ' + e.adUnitId + '!'); + Ti.API.info('BannerAdView - Did receive ad: ' + e.adUnitId); }); bannerAdView.addEventListener('didFailToReceiveAd', function (e) { Ti.API.error('BannerAdView - Failed to receive ad: ' + e.error); }); +bannerAdView.addEventListener('didRecordImpression', function (e) { + Ti.API.info('BannerAdView - didRecordImpression: ' + e.adUnitId); +}); + +bannerAdView.addEventListener('didRecordClick', function (e) { + Ti.API.info('BannerAdView - didRecordClick: ' + e.adUnitId); +}); + bannerAdView.addEventListener('willPresentScreen', function (e) { - Ti.API.error('BannerAdView - willPresentScreen'); + Ti.API.error('BannerAdView - willPresentScreen: ' + e.adUnitId); }); -bannerAdView.addEventListener('willDismissScreen', function () { - Ti.API.info('BannerAdView - willDismissScreen!'); +bannerAdView.addEventListener('willDismissScreen', function (e) { + Ti.API.info('BannerAdView - willDismissScreen: ' + e.adUnitId); }); bannerAdView.addEventListener('didDismissScreen', function () { - Ti.API.info('BannerAdView - Dismissed screen!'); + Ti.API.info('BannerAdView - Dismissed screen: ' + e.adUnitId); }); -bannerAdView.addEventListener('didPresentScreen', function (e) { - Ti.API.info('BannerAdView - Presenting screen!' + e.adUnitId); -}); ``` ### Interstitials To receive an interstitional ad, you need to add it to the view hierarchy. -It fires the `adloaded` event if the ad was successfully received, the `didFailToReceiveAd` event otherwise. When ad is loaded, then you can use `ad.showInterstitial()` to show. +It fires the `didReceiveAd` event if the ad was successfully received, the `didFailToReceiveAd` event otherwise. When ad is loaded, then you can use `ad.showInterstitial()` to show. ```js var Admob = require('ti.admob'); @@ -450,23 +461,14 @@ var interstitialAd = Admob.createView({ }); win.add(interstitialAd); -interstitialAd.addEventListener('adloaded', function (e) { - Ti.API.info('interstitialAd - adloaded: Did receive ad: ' + e.source.adUnitId); - interstitialAd.showInterstitial(); -}); - interstitialAd.addEventListener('didReceiveAd', function (e) { - Ti.API.info('interstitialAd - Did receive ad: ' + e.source.adUnitId); + Ti.API.info('interstitialAd - Did receive ad: ' + e.adUnitId); }); interstitialAd.addEventListener('didFailToReceiveAd', function (e) { Ti.API.error('interstitialAd - Failed to receive ad: ' + e.error); }); -interstitialAd.addEventListener('didPresentScreen', function (e) { - Ti.API.info('interstitialAd - didPresentScreen: ' + e.adUnitId); -}); - interstitialAd.addEventListener('didDismissScreen', function (e) { Ti.API.info('interstitialAd - Dismissed screen: ' + e.adUnitId); }); @@ -476,7 +478,11 @@ interstitialAd.addEventListener('willDismissScreen', function (e) { }); interstitialAd.addEventListener('didRecordImpression', function (e) { - Ti.API.info('interstitialAd- didRecordImpression: ' + e.source.adUnitId); + Ti.API.info('interstitialAd- didRecordImpression: ' + e.adUnitId); +}); + +interstitialAd.addEventListener('didRecordClick', function (e) { + Ti.API.info('interstitialAd - didRecordClick: ' + e.adUnitId); }); ``` @@ -486,7 +492,7 @@ Please see the example for a complete implementation. Since version 2.4.2 you can use Admob Rewarded Video ads. This is similar to interstitials with the addition of getting a reward after watching an ad video. -You create a rewarded video ad by specifying `Admob.AD_TYPE_REWARDED_VIDEO` as the `adType`. The first video will be automatically pre-loaded after creating the view and calling `ad.receive()`. To know when a video is completely loaded you can use the `adloaded` event. To show a rewarded video add call the `ad.showRewardedVideo()` method. Loading another video can be started with the `loadRewardedVideo(adUnitId)` method on the same instance. +You create a rewarded video ad by specifying `Admob.AD_TYPE_REWARDED_VIDEO` as the `adType`. The first video will be automatically pre-loaded after creating the view and calling `ad.receive()`. To know when a video is completely loaded you can use the `didReceiveAd` event. To show a rewarded video add call the `ad.showRewardedVideo()` method. Loading another video can be started with the `loadRewardedVideo(adUnitId)` method on the same instance. ```js var Admob = require('ti.admob'); @@ -502,32 +508,23 @@ var rewardedVideo = Admob.createView({ rewardedVideo.receive() -rewardedVideo.addEventListener('adloaded', function(e) { - Ti.API.info('rewardedVideo - adloaded: Did receive ad: ' + e.source.adUnitId); - rewardedVideo.showRewardedVideo(); -}); - -rewardedVideo.addEventListener('adrewarded', function (reward) { - Ti.API.debug(`rewardedVideo -adrewarded: Received reward! type: ${reward.type}, amount: ${reward.amount}`); +rewardedVideo.addEventListener('didRewardUser', function (reward) { + Ti.API.debug(`rewardedVideo - didRewardUser: Received reward! type: ${reward.type}, amount: ${reward.amount}`); console.log(reward); }); -rewardedVideo.addEventListener('adfailedtoload', function (error) { - Ti.API.debug('rewardedVideo - Rewarded video ad failed to load: ' + error.message); +rewardedVideo.addEventListener('didFailToReceiveAd', function (er) { + Ti.API.debug('rewardedVideo - Rewarded video ad failed to load: ' + e.error); }); rewardedVideo.addEventListener('didReceiveAd', function (e) { - Ti.API.info('rewardedVideo - Did receive ad!'); + Ti.API.info('rewardedVideo - Did receive ad: ' + e.adUnitId); }); rewardedVideo.addEventListener('didFailToReceiveAd', function (e) { Ti.API.error('rewardedVideo - Failed to receive ad: ' + e.error); }); -rewardedVideo.addEventListener('didPresentScreen', function (e) { - Ti.API.info('rewardedVideo - didPresentScreen: ' + e.adUnitId); -}); - rewardedVideo.addEventListener('didDismissScreen', function (e) { Ti.API.info('rewardedVideo - Dismissed screen: ' + e.adUnitId); }); @@ -537,12 +534,67 @@ rewardedVideo.addEventListener('willDismissScreen', function (e) { }); rewardedVideo.addEventListener('didRecordImpression', function (e) { - Ti.API.info('rewardedVideo - didRecordImpression'); + Ti.API.info('rewardedVideo - didRecordImpression: ' + e.adUnitId); +}); + +rewardedVideo.addEventListener('didRecordClick', function (e) { + Ti.API.info('rewardedVideo - didRecordClick: ' + e.adUnitId); }); ``` Please see the example for a complete implementation. +### Open App + +Since version 6.2.0 you can use Admob Open App Ads, a special ad format intended for publishers wishing to monetize their app load screens. App open ads can be closed by your users at any time. App open ads can be shown when users bring your app to the foreground. + +You create a rewarded video ad by specifying `Admob.AD_TYPE_APP_OPEN` as the `adType`. The first ad will be automatically pre-loaded after creating the view and calling `appOpenAd.receive()`. To know when the ad is completely loaded you can use the `didReceiveAd` event. To show an Open Ad call the `appOpenAd.showAppOpenAd()` method. Loading another ad can be started with the `appOpenAd.requestAppOpenAd();` method on the same instance. +App open ads will time out after four hours. Ads rendered more than four hours after request time will no longer be valid and may not earn revenue, so you should request a new ad. See the example for a complete implementation or read the official documentation: https://developers.google.com/admob/ios/app-open + +```js +var Admob = require('ti.admob'); + +appOpenAd = Admob.createView({ + adType: Admob.AD_TYPE_APP_OPEN, + adUnitId: 'ca-app-pub-3940256099942544/5662855259', // You can get your own at http: //www.admob.com/ + extras: { + 'version': 1.0, + 'name': 'My App' + } // Object of additional infos +}); + +// appOpenAd custom events +appOpenAd.addEventListener('didReceiveAd', function (e) { + console.debug('appOpenAd - didReceiveAd: Did receive ad!'); +}); +appOpenAd.addEventListener('didFailToShowAd', function (e) { + console.error('appOpenAd - Failed to show: ' + e.error); +}); + +// appOpenAd AdMob avents +appOpenAd.addEventListener('didRecordClick', function (e) { + console.debug('appOpenAd - didRecordClick: ' + e.adUnitId); +}); +appOpenAd.addEventListener('didFailToReceiveAd', function (e) { + console.error('appOpenAd - Failed to receive ad: ' + e.error); +}); +appOpenAd.addEventListener('didDismissScreen', function (e) { + console.debug('appOpenAd - Dismissed screen: ' + e.adUnitId); +}); +appOpenAd.addEventListener('willPresentScreen', function (e) { + console.debug('appOpenAd - willPresentScreen: ' + e.adUnitId); +}); +appOpenAd.addEventListener('willDismissScreen', function (e) { + console.debug('appOpenAd - willDismissScreen: ' + e.adUnitId); +}); +appOpenAd.addEventListener('didRecordImpression', function (e) { + console.debug('appOpenAd- didRecordImpression: ' + e.adUnitId); +}); + +``` + +Please see the example for a complete implementation. + ### iAd ⚠️ Removed by the Admob SDK 7.x and Ti.Admob 2.2.0 diff --git a/ios/documentation/view.md b/ios/documentation/view.md index dbfe5cb3..eb218a7e 100644 --- a/ios/documentation/view.md +++ b/ios/documentation/view.md @@ -117,6 +117,10 @@ Sets a testing value for `adUnitId` to test ads without an admob account. Sent when an ad request failed. Normally this is because no network connection was available or no ads were available (i.e. no fill). +### didFailToShowAd + +Sent when App Open Ad failed to show. + ### willPresentScreen Sent just before presenting the user a full screen view, such as a browser, @@ -150,7 +154,12 @@ Store). Called when the user clicks on the buy button of an in-app purchase ad. After the receiver handles the purchase, it must call the GADInAppPurchase object's reportPurchaseStatus: method. +### didRewardUser + +Fired when the reward based video ad has rewarded the user. + ### adrewarded +⚠️ Removed since Ti.Admob 6.2.0. You can use `didRewardUser` Fired when the reward based video ad has rewarded the user. @@ -162,30 +171,12 @@ Fired when the reward based video ad has rewarded the user. | `reward.type` | `String` | Type of the reward. | | `reward.amount` | `Number` | Amount rewarded to the user. | -### adloaded +### adloaded +⚠️ Removed since Ti.Admob 6.2.0. You can use `didReceiveAd` Fired when a reward based video ad was received. From this point on you can open the video using `showRewardedVideo()`. -### adopened - -Fired when the reward based video ad opened. - -### videostarted - -Fired when the reward based video ad started playing. - -### videocompleted - -Fired when the reward based video ad completed playing. - -### adclosed - -Fired when the reward based video ad closed. - -### adleftapplication - -Fires when the reward based video ad will leave the application. - ### adfailedtoload +⚠️ Removed since Ti.Admob 6.2.0. You can use `didFailToReceiveAd` Fired when the reward based video ad failed to load. diff --git a/ios/example/app.js b/ios/example/app.js index fb2c93d3..dd26d16b 100644 --- a/ios/example/app.js +++ b/ios/example/app.js @@ -180,13 +180,15 @@ function openTestAdsWin() { var view = Ti.UI.createView({ layout: "vertical", - top: 20 + top: 50 }); var label = Ti.UI.createLabel({ - text: 'Loading the ads now! Note that there may be a several minute delay if you have not viewed an ad in over 24 hours.', + text: 'Loading the ads now! Note that there may be a several minute delay if you have not viewed an ad in over 24 hours.\n\n' + + 'Resume the app to show OpenApp ad', font: { fontSize: "20sp" }, + textAlign: 'center', color: "black", width: "90%", top: 20 @@ -257,23 +259,26 @@ function openTestAdsWin() { bannerAdView.addEventListener('didReceiveAd', function (e) { console.log(e) - Ti.API.info('BannerAdView - Did receive ad: ' + e.adUnitId + '!'); + Ti.API.info('BannerAdView - Did receive ad: ' + e.adUnitId); }); bannerAdView.addEventListener('didFailToReceiveAd', function (e) { Ti.API.error('BannerAdView - Failed to receive ad: ' + e.error); }); + bannerAdView.addEventListener('didRecordImpression', function (e) { + Ti.API.info('BannerAdView - didRecordImpression: ' + e.adUnitId); + }); - bannerAdView.addEventListener('willPresentScreen', function (e) { - Ti.API.error('BannerAdView - willPresentScreen'); + bannerAdView.addEventListener('didRecordClick', function (e) { + Ti.API.info('BannerAdView - didRecordClick: ' + e.adUnitId); }); - bannerAdView.addEventListener('willDismissScreen', function () { - Ti.API.info('BannerAdView - willDismissScreen!'); + bannerAdView.addEventListener('willPresentScreen', function (e) { + Ti.API.error('BannerAdView - willPresentScreen: ' + e.adUnitId); }); - bannerAdView.addEventListener('didDismissScreen', function () { - Ti.API.info('BannerAdView - Dismissed screen!'); + bannerAdView.addEventListener('willDismissScreen', function (e) { + Ti.API.info('BannerAdView - willDismissScreen: ' + e.adUnitId); }); - bannerAdView.addEventListener('didPresentScreen', function (e) { - Ti.API.info('BannerAdView - Presenting screen!' + e.adUnitId); + bannerAdView.addEventListener('didDismissScreen', function (e) { + Ti.API.info('BannerAdView - Dismissed screen: ' + e.adUnitId); }); @@ -289,17 +294,11 @@ function openTestAdsWin() { }, // Object of additional infos visible: false // If true, covers the win when added and can't tap nothing }); - interstitialAd.addEventListener('adloaded', function (e) { - Ti.API.info('interstitialAd - adloaded: Did receive ad!'); - console.log(e); - interstitialButton.title = "Show interstitial Ad"; - enableInterstitialButton(); - }); + interstitialAd.addEventListener('didReceiveAd', function (e) { - Ti.API.info('interstitialAd - Did receive ad!'); + Ti.API.info('interstitialAd - Did receive ad: ' + e.adUnitId); interstitialButton.title = "Show interstitial Ad"; - console.log(e); enableInterstitialButton(); }); interstitialAd.addEventListener('didFailToReceiveAd', function (e) { @@ -307,11 +306,7 @@ function openTestAdsWin() { interstitialButton.title = "Load interstitial Ad"; testAdsWin.remove(interstitialAd); enableInterstitialButton(); - }); - interstitialAd.addEventListener('didPresentScreen', function (e) { - Ti.API.info('interstitialAd - didPresentScreen: ' + e.adUnitId); - enableInterstitialButton(); - }); + }); interstitialAd.addEventListener('didDismissScreen', function (e) { Ti.API.info('interstitialAd - Dismissed screen: ' + e.adUnitId); testAdsWin.remove(interstitialAd); @@ -322,10 +317,12 @@ function openTestAdsWin() { enableInterstitialButton(); }); interstitialAd.addEventListener('didRecordImpression', function (e) { - Ti.API.info('interstitialAd- didRecordImpression'); - console.log(e); + Ti.API.info('interstitialAd- didRecordImpression: ' + e.adUnitId); enableInterstitialButton(); }); + interstitialAd.addEventListener('didRecordClick', function (e) { + Ti.API.info('interstitialAd - didRecordClick: ' + e.adUnitId); + }); function showInterstitial() { @@ -359,36 +356,28 @@ function openTestAdsWin() { 'name': 'My App' } // Object of additional infos }); - - rewardedVideo.addEventListener('adloaded', function (e) { - Ti.API.debug('rewardedVideo - Rewarded video loaded!'); - console.log(e); - enableRewardedVideoButton(); - }); - rewardedVideo.addEventListener('adrewarded', function (reward) { - Ti.API.debug('rewardedVideo -adrewarded'); + + rewardedVideo.addEventListener('didRewardUser', function (reward) { + Ti.API.debug('rewardedVideo - didRewardUser'); Ti.API.debug(`Received reward! type: ${reward.type}, amount: ${reward.amount}`); console.log(reward); disableRewardedVideoButton(); alert("Well! Amount earned: " + reward.amount); }); - rewardedVideo.addEventListener('adfailedtoload', function (error) { - Ti.API.debug('rewardedVideo - Rewarded video ad failed to load: ' + error.message); - disableRewardedVideoButton(); - }); + rewardedVideo.addEventListener('didReceiveAd', function (e) { - Ti.API.info('rewardedVideo - Did receive ad!'); + Ti.API.info('rewardedVideo - Did receive ad: ' + e.adUnitId); console.log(e); enableRewardedVideoButton(); }); + rewardedVideo.addEventListener('didFailToReceiveAd', function (e) { + Ti.API.debug('rewardedVideo - Rewarded video ad failed to load: ' + e.error); + disableRewardedVideoButton(); + }); rewardedVideo.addEventListener('didFailToReceiveAd', function (e) { Ti.API.error('rewardedVideo - Failed to receive ad: ' + e.error); disableRewardedVideoButton(); - }); - rewardedVideo.addEventListener('didPresentScreen', function (e) { - Ti.API.info('rewardedVideo - didPresentScreen: ' + e.adUnitId); - enableRewardedVideoButton(); - }); + }); rewardedVideo.addEventListener('didDismissScreen', function (e) { Ti.API.info('rewardedVideo - Dismissed screen: ' + e.adUnitId); disableRewardedVideoButton(); @@ -402,6 +391,10 @@ function openTestAdsWin() { console.log(e); disableRewardedVideoButton(); }); + rewardedVideo.addEventListener('didRecordClick', function (e) { + Ti.API.info('rewardedVideo - didRecordClick: ' + e.adUnitId); + }); + function showRewarded() { if (rewardedVideoButton.title === "Load Rewarded Video Ad") { @@ -427,6 +420,113 @@ function openTestAdsWin() { }, 10); } + /* OpenApp Ad */ + + let appOpenAd; + + function loadOpenAd() { + const reload_max_tries_case_error = 4; + let reload_max_tries = 0; + + function reloadAppOpenAd() { + if (reload_max_tries < reload_max_tries_case_error) { + setTimeout(() => { + appOpenAd.requestAppOpenAd(); + }, 10000); + } + reload_max_tries += 1; + } + + appOpenAd = Admob.createView({ + debugEnabled: false, + adType: Admob.AD_TYPE_APP_OPEN, + adUnitId: 'ca-app-pub-3940256099942544/5662855259', // You can get your own at http: //www.admob.com/ + extras: { + 'version': 1.0, + 'name': 'My App' + } // Object of additional infos + }); + + // appOpenAd custom events + appOpenAd.addEventListener('didReceiveAd', function (e) { + Ti.API.debug('appOpenAd - didReceiveAd: Did receive ad: ' + e.adUnitId); + Ti.API.debug(e); + reload_max_tries = 0; + Titanium.App.Properties.setDouble('appOpenAdLoadTime', (new Date().getTime())); + }); + appOpenAd.addEventListener('didFailToShowAd', function (e) { + Ti.API.error('appOpenAd - Failed to show: ' + e.error); + reloadAppOpenAd(); + }); + + // appOpenAd AdMob avents + appOpenAd.addEventListener('didRecordClick', function (e) { + Ti.API.debug('appOpenAd - didRecordClick: ' + e.adUnitId); + }); + appOpenAd.addEventListener('didFailToReceiveAd', function (e) { + Ti.API.error('appOpenAd - Failed to receive ad: ' + e.error); + reloadAppOpenAd(); + }); + appOpenAd.addEventListener('didDismissScreen', function (e) { + Ti.API.debug('appOpenAd - Dismissed screen: ' + e.adUnitId); + Titanium.App.Properties.setDouble('lastTimeAppOpenAdWasShown', (new Date().getTime())); + appOpenAd.requestAppOpenAd(); + }); + appOpenAd.addEventListener('willPresentScreen', function (e) { + Ti.API.debug('appOpenAd - willPresentScreen: ' + e.adUnitId); + }); + appOpenAd.addEventListener('willDismissScreen', function (e) { + Ti.API.debug('appOpenAd - willDismissScreen: ' + e.adUnitId); + }); + appOpenAd.addEventListener('didRecordImpression', function (e) { + Ti.API.debug('appOpenAd- didRecordImpression: ' + e.adUnitId); + }); + + console.log("appOpenAd.receive();") + appOpenAd.receive(); + + } + + function resumeOpenAd() { + let currentTime = (new Date().getTime()); + let loadTime = Titanium.App.Properties.getDouble('appOpenAdLoadTime', currentTime); + let lastTimeAppOpenAdWasShown = Titanium.App.Properties.getDouble('lastTimeAppOpenAdWasShown', 1); + + if ((currentTime - loadTime) < 14400000) { // then less than 4 hours elapsed. + if ((currentTime - lastTimeAppOpenAdWasShown) > 600000) { // then more than 10 minutes elapsed after the last Ad showed. + console.log("appOpenAd.showAppOpenAd()!") + setTimeout(() => { + try { + appOpenAd.showAppOpenAd(); + } catch (error) { + Ti.API.error(error); + Titanium.App.removeEventListener('resume', resumeOpenAd); + setTimeout(() => { + loadOpenAd(); + Titanium.App.addEventListener('resume', resumeOpenAd); + }, 500); + } + }, 500); + } else { + Titanium.API.warn("You have showned an AppOpenAd less than 10 minutes ago. You should wait!"); + } + } else { + Titanium.API.warn("The AppOpenAd was requested more than 4 hours ago and has expired! You should load another one."); + Titanium.App.removeEventListener('resume', resumeOpenAd); + setTimeout(() => { + loadOpenAd(); + Titanium.App.addEventListener('resume', resumeOpenAd); + }, 500); + } + } + + loadOpenAd(); + Titanium.App.addEventListener('resume', resumeOpenAd); + + testAdsWin.addEventListener('close', () => { + Titanium.App.removeEventListener('resume', resumeOpenAd); + }); + testAdsWin.open(); setTimeout(() => { console.log("Add banner!!!!") diff --git a/ios/manifest b/ios/manifest index 8d209ea9..51134333 100644 --- a/ios/manifest +++ b/ios/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 5.1.0 +version: 6.2.0 architectures: arm64 x86_64 mac: false description: AdMob module for ad delivery via AdMob