blob: 30954e6db89dcffa42a9e27ab4a3d381256cd450 [file] [log] [blame]
// Copyright 2017, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#import "MobileAd.h"
#import "FirebaseAdmobPlugin.h"
static NSMutableDictionary *allAds = nil;
static NSDictionary *statusToString = nil;
static void logWarning(NSString *format, ...) {
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(@"FirebaseAdMobPlugin <Warning> %@", message);
}
@implementation MobileAd
NSNumber *_mobileAdId;
FlutterMethodChannel *_channel;
MobileAdStatus _status;
+ (void)initialize {
if (allAds == nil) {
allAds = [[NSMutableDictionary alloc] init];
}
if (statusToString == nil) {
statusToString = @{
@(CREATED) : @"CREATED",
@(LOADING) : @"LOADING",
@(FAILED) : @"FAILED",
@(PENDING) : @"PENDING",
@(LOADED) : @"LOADED"
};
}
}
+ (void)configureWithAppId:(NSString *)appId {
[GADMobileAds configureWithApplicationID:appId];
}
+ (MobileAd *)getAdForId:(NSNumber *)mobileAdId {
return allAds[mobileAdId];
}
+ (UIViewController *)rootViewController {
return [UIApplication sharedApplication].delegate.window.rootViewController;
}
- (instancetype)initWithId:(NSNumber *)mobileAdId channel:(FlutterMethodChannel *)channel {
self = [super init];
if (self) {
_mobileAdId = mobileAdId;
_channel = channel;
_status = CREATED;
allAds[mobileAdId] = self;
}
return self;
}
- (MobileAdStatus)status {
return _status;
}
- (void)loadWithUnitId:(NSString *)unitId targetingInfo:(NSDictionary *)targetingInfo {
// Implemented by the Banner and Interstitial subclasses
}
- (void)show {
// Implemented by the Banner and Interstitial subclasses
}
- (void)dispose {
[allAds removeObjectForKey:_mobileAdId];
}
- (NSArray *)targetingInfoArrayForKey:(NSString *)key info:(NSDictionary *)info {
NSObject *value = info[key];
if (value == NULL) {
return nil;
}
if (![value isKindOfClass:[NSArray class]]) {
logWarning(@"targeting info %@: expected an array (MobileAd %@)", key, self);
return nil;
}
return (NSArray *)value;
}
- (NSString *)targetingInfoStringForKey:(NSString *)key info:(NSDictionary *)info {
NSObject *value = info[key];
if (value == NULL) {
return nil;
}
if (![value isKindOfClass:[NSString class]]) {
logWarning(@"targeting info %@: expected a string (MobileAd %@)", key, self);
return nil;
}
NSString *stringValue = (NSString *)value;
if ([stringValue length] == 0) {
logWarning(@"targeting info %@: expected a non-empty string (MobileAd %@)", key, self);
return nil;
}
return stringValue;
}
- (NSNumber *)targetingInfoBoolForKey:(NSString *)key info:(NSDictionary *)info {
NSObject *value = info[key];
if (value == NULL) {
return nil;
}
if (![value isKindOfClass:[NSNumber class]]) {
logWarning(@"targeting info %@: expected a boolean, (MobileAd %@)", key, self);
return nil;
}
return (NSNumber *)value;
}
- (GADRequest *)createLoadRequest:(NSDictionary *)targetingInfo {
GADRequest *request = [GADRequest request];
if (targetingInfo == nil) {
return request;
}
NSArray *testDevices = [self targetingInfoArrayForKey:@"testDevices" info:targetingInfo];
if (testDevices != nil) {
request.testDevices = testDevices;
}
NSArray *keywords = [self targetingInfoArrayForKey:@"keywords" info:targetingInfo];
if (keywords != nil) {
request.keywords = keywords;
}
NSString *contentURL = [self targetingInfoStringForKey:@"contentUrl" info:targetingInfo];
if (contentURL != nil) {
request.contentURL = contentURL;
}
NSObject *birthday = targetingInfo[@"birthday"];
if (birthday != NULL) {
if (![birthday isKindOfClass:[NSNumber class]]) {
logWarning(@"targeting info birthday: expected a long integer (MobileAd %@)", self);
} else {
// Incoming time value is milliseconds since the epoch, NSDate uses
// seconds.
request.birthday =
[NSDate dateWithTimeIntervalSince1970:((NSNumber *)birthday).longValue / 1000.0];
}
}
NSObject *gender = targetingInfo[@"gender"];
if (gender != NULL) {
if (![gender isKindOfClass:[NSNumber class]]) {
logWarning(@"targeting info gender: expected an integer (MobileAd %@)", self);
} else {
int genderValue = ((NSNumber *)gender).intValue;
switch (genderValue) {
case 0: // MobileAdGender.unknown
case 1: // MobileAdGender.male
case 2: // MobileAdGender.female
request.gender = genderValue;
break;
default:
logWarning(@"targeting info gender: not one of 0, 1, or 2 (MobileAd %@)", self);
}
}
}
NSNumber *childDirected = [self targetingInfoBoolForKey:@"childDirected" info:targetingInfo];
if (childDirected != nil) {
[request tagForChildDirectedTreatment:childDirected.boolValue];
}
NSString *requestAgent = [self targetingInfoStringForKey:@"requestAgent" info:targetingInfo];
if (requestAgent != nil) {
request.requestAgent = requestAgent;
}
return request;
}
- (NSDictionary *)argumentsMap {
return @{@"id" : _mobileAdId};
}
- (NSString *)description {
NSString *statusString = (NSString *)statusToString[[NSNumber numberWithInt:_status]];
return [NSString
stringWithFormat:@"%@ %@ mobileAdId:%@", super.description, statusString, _mobileAdId];
}
@end
@implementation BannerAd
GADBannerView *_banner;
+ (instancetype)withId:(NSNumber *)mobileAdId channel:(FlutterMethodChannel *)channel {
MobileAd *ad = [MobileAd getAdForId:mobileAdId];
return ad != nil ? (BannerAd *)ad : [[BannerAd alloc] initWithId:mobileAdId channel:channel];
}
- (void)loadWithUnitId:(NSString *)unitId targetingInfo:(NSDictionary *)targetingInfo {
if (_status != CREATED) return;
_status = LOADING;
_banner = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];
_banner.delegate = self;
_banner.adUnitID = unitId;
_banner.rootViewController = [MobileAd rootViewController];
[_banner loadRequest:[self createLoadRequest:targetingInfo]];
}
- (void)show {
if (_status == LOADING) {
_status = PENDING;
return;
}
if (_status != LOADED) return;
UIView *screen = [MobileAd rootViewController].view;
CGFloat x = screen.frame.size.width / 2 - _banner.frame.size.width / 2;
CGFloat y = screen.frame.size.height - _banner.frame.size.height;
_banner.frame = (CGRect){{x, y}, _banner.frame.size};
[screen addSubview:_banner];
}
- (void)adViewDidReceiveAd:(GADBannerView *)adView {
bool statusWasPending = _status == PENDING;
_status = LOADED;
[_channel invokeMethod:@"onAdLoaded" arguments:[self argumentsMap]];
if (statusWasPending) [self show];
}
- (void)adView:(GADBannerView *)adView didFailToReceiveAdWithError:(GADRequestError *)error {
logWarning(@"adView:didFailToReceiveAdWithError: %@ (MobileAd %@)", [error localizedDescription],
self);
[_channel invokeMethod:@"onAdFailedToLoad" arguments:[self argumentsMap]];
}
- (void)adViewWillPresentScreen:(GADBannerView *)adView {
[_channel invokeMethod:@"onAdClicked" arguments:[self argumentsMap]];
}
- (void)adViewWillDismissScreen:(GADBannerView *)adView {
[_channel invokeMethod:@"onAdImpression" arguments:[self argumentsMap]];
}
- (void)adViewDidDismissScreen:(GADBannerView *)adView {
[_channel invokeMethod:@"onAdClosed" arguments:[self argumentsMap]];
}
- (void)adViewWillLeaveApplication:(GADBannerView *)adView {
[_channel invokeMethod:@"onAdLeftApplication" arguments:[self argumentsMap]];
}
- (void)dispose {
if (_banner.superview) [_banner removeFromSuperview];
_banner = nil;
[super dispose];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ for: %@", super.description, _banner];
}
@end
@implementation InterstitialAd
GADInterstitial *_interstitial;
+ (instancetype)withId:(NSNumber *)mobileAdId channel:(FlutterMethodChannel *)channel {
MobileAd *ad = [MobileAd getAdForId:mobileAdId];
return ad != nil ? (InterstitialAd *)ad
: [[InterstitialAd alloc] initWithId:mobileAdId channel:channel];
}
- (void)loadWithUnitId:(NSString *)unitId targetingInfo:(NSDictionary *)targetingInfo {
if (_status != CREATED) return;
_status = LOADING;
_interstitial = [[GADInterstitial alloc] initWithAdUnitID:unitId];
_interstitial.delegate = self;
[_interstitial loadRequest:[self createLoadRequest:targetingInfo]];
}
- (void)show {
if (_status == LOADING) {
_status = PENDING;
return;
}
if (_status != LOADED) return;
[_interstitial presentFromRootViewController:[MobileAd rootViewController]];
}
- (void)interstitialDidReceiveAd:(GADInterstitial *)ad {
bool statusWasPending = _status == PENDING;
_status = LOADED;
[_channel invokeMethod:@"onAdLoaded" arguments:[self argumentsMap]];
if (statusWasPending) [self show];
}
- (void)interstitial:(GADInterstitial *)ad didFailToReceiveAdWithError:(GADRequestError *)error {
logWarning(@"interstitial:didFailToReceiveAdWithError: %@ (MobileAd %@)",
[error localizedDescription], self);
[_channel invokeMethod:@"onAdFailedToLoad" arguments:[self argumentsMap]];
}
- (void)interstitialWillPresentScreen:(GADInterstitial *)ad {
[_channel invokeMethod:@"onAdClicked" arguments:[self argumentsMap]];
}
- (void)interstitialWillDismissScreen:(GADInterstitial *)ad {
[_channel invokeMethod:@"onAdImpression" arguments:[self argumentsMap]];
}
- (void)interstitialDidDismissScreen:(GADInterstitial *)ad {
[_channel invokeMethod:@"onAdClosed" arguments:[self argumentsMap]];
}
- (void)interstitialWillLeaveApplication:(GADInterstitial *)ad {
[_channel invokeMethod:@"onAdLeftApplication" arguments:[self argumentsMap]];
}
- (void)dispose {
// It is not possible to hide/remove/destroy an AdMob interstitial Ad.
_interstitial = nil;
[super dispose];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ for: %@", super.description, _interstitial];
}
@end