diff --git a/packages/interactive_media_ads/CHANGELOG.md b/packages/interactive_media_ads/CHANGELOG.md index 456b0b506d4..983429cbba7 100644 --- a/packages/interactive_media_ads/CHANGELOG.md +++ b/packages/interactive_media_ads/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.8 + +* Adds support for accessing data for an ad. See `AdEvent.ad`. + ## 0.2.7 * Adds support to retrieve content time offsets at which ad breaks are scheduled. See diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt index fb74ed55930..2853cdf5d19 100644 --- a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt @@ -21,7 +21,7 @@ class AdsRequestProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : * * This must match the version in pubspec.yaml. */ - const val pluginVersion = "0.2.7" + const val pluginVersion = "0.2.8" } override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) { diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt index 43a794255d3..ed643e3419d 100644 --- a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt @@ -5839,7 +5839,7 @@ abstract class PigeonApiCompanionAd( /** The URL for the static resource of this companion. */ abstract fun resourceValue( pigeon_instance: com.google.ads.interactivemedia.v3.api.CompanionAd - ): String + ): String? /** * The width of the companion in pixels. diff --git a/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift index 6ac61aa981e..ea5e0d43096 100644 --- a/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift +++ b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift @@ -13,7 +13,7 @@ class AdsRequestProxyAPIDelegate: PigeonApiDelegateIMAAdsRequest { /// The current version of the `interactive_media_ads` plugin. /// /// This must match the version in pubspec.yaml. - static let pluginVersion = "0.2.7" + static let pluginVersion = "0.2.8" func pigeonDefaultConstructor( pigeonApi: PigeonApiIMAAdsRequest, adTagUrl: String, adDisplayContainer: IMAAdDisplayContainer, diff --git a/packages/interactive_media_ads/lib/interactive_media_ads.dart b/packages/interactive_media_ads/lib/interactive_media_ads.dart index bb8047af3ef..e86e00f1995 100644 --- a/packages/interactive_media_ads/lib/interactive_media_ads.dart +++ b/packages/interactive_media_ads/lib/interactive_media_ads.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +export 'src/ad.dart'; export 'src/ad_display_container.dart'; export 'src/ads_loader.dart'; export 'src/ads_manager_delegate.dart'; @@ -19,7 +20,6 @@ export 'src/platform_interface/platform_interface.dart' AdErrorCode, AdErrorEvent, AdErrorType, - AdEvent, AdEventType, AdUIElement, AdsLoadErrorData, diff --git a/packages/interactive_media_ads/lib/src/ad.dart b/packages/interactive_media_ads/lib/src/ad.dart new file mode 100644 index 00000000000..32454800bae --- /dev/null +++ b/packages/interactive_media_ads/lib/src/ad.dart @@ -0,0 +1,203 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'platform_interface/platform_interface.dart'; + +/// Data object representing a single ad. +class Ad { + /// Constructs an [Ad] from a specific platform implementation. + Ad.fromPlatform(this.platform); + + /// Implementation of [PlatformAd] for the current platform. + final PlatformAd platform; + + /// The ad ID as specified in the VAST response. + String get adId => platform.adId; + + /// The pod metadata object. + AdPodInfo get adPodInfo => AdPodInfo.fromPlatform(platform.adPodInfo); + + /// The ad system as specified in the VAST response. + String get adSystem => platform.adSystem; + + /// The IDs of the ads' creatives, starting with the first wrapper ad. + List get wrapperCreativeIds => platform.wrapperCreativeIds; + + /// The wrapper ad IDs as specified in the VAST response. + List get wrapperIds => platform.wrapperIds; + + /// The wrapper ad systems as specified in the VAST response. + List get wrapperSystems => platform.wrapperSystems; + + /// The advertiser name as defined by the serving party. + String get advertiserName => platform.advertiserName; + + /// The companions for the current ad while using DAI. + /// + /// Returns an empty list in any other scenario. + List get companionAds => List.unmodifiable( + platform.companionAds.map(CompanionAd.fromPlatform), + ); + + /// The content type of the currently selected creative, or null if no + /// creative is selected or the content type is unavailable. + String? get contentType => platform.contentType; + + /// The ISCI (Industry Standard Commercial Identifier) code for an ad. + String get creativeAdId => platform.creativeAdId; + + /// The ID of the selected creative for the ad, + String get creativeId => platform.creativeId; + + /// The first deal ID present in the wrapper chain for the current ad, + /// starting from the top. + String get dealId => platform.dealId; + + /// The description of this ad from the VAST response. + String? get description => platform.description; + + /// The duration of the ad. + Duration? get duration => platform.duration; + + /// The width of the selected creative if non-linear, else returns 0. + int get width => platform.width; + + /// The height of the selected creative if non-linear, else returns 0. + int get height => platform.height; + + /// The playback time before the ad becomes skippable. + /// + /// The value is null for non-skippable ads, or if the value is unavailable. + Duration? get skipTimeOffset => platform.skipTimeOffset; + + /// The URL associated with the survey for the given ad. + String? get surveyUrl => platform.surveyUrl; + + /// The title of this ad from the VAST response. + String? get title => platform.title; + + /// The custom parameters associated with the ad at the time of ad + /// trafficking. + String get traffickingParameters => platform.traffickingParameters; + + /// The set of ad UI elements rendered by the IMA SDK for this ad. + Set get uiElements => platform.uiElements; + + /// The list of all universal ad IDs for this ad. + List get universalAdIds => List.unmodifiable( + platform.universalAdIds.map(UniversalAdId.fromPlatform), + ); + + /// The VAST bitrate in Kbps of the selected creative. + int get vastMediaBitrate => platform.vastMediaBitrate; + + /// The VAST media height in pixels of the selected creative. + int get vastMediaHeight => platform.vastMediaHeight; + + /// The VAST media width in pixels of the selected creative. + int get vastMediaWidth => platform.vastMediaWidth; + + /// Indicates whether the ad’s current mode of operation is linear or + /// non-linear. + bool get isLinear => platform.isLinear; + + /// Indicates whether the ad can be skipped by the user. + bool get isSkippable => platform.isSkippable; +} + +/// Simple data object containing podding metadata. +class AdPodInfo { + /// Constructs an [AdPodInfo] from a specific platform implementation. + AdPodInfo.fromPlatform(this.platform); + + /// Implementation of [PlatformAdPodInfo] for the current platform. + final PlatformAdPodInfo platform; + + /// The position of the ad within the pod. + /// + /// The value returned is one-based, for example, 1 of 2, 2 of 2, etc. If the + /// ad is not part of a pod, this will return 1. + int get adPosition => platform.adPosition; + + /// The maximum duration of the pod. + /// + /// For unknown duration, null. + Duration? get maxDuration => platform.maxDuration; + + /// Returns the index of the ad pod. + /// + /// Client side: For a preroll pod, returns 0. For midrolls, returns 1, 2,…, + /// N. For a postroll pod, returns -1. Defaults to 0 if this ad is not part of + /// a pod, or this pod is not part of a playlist. + /// + /// DAI VOD: Returns the index of the ad pod. For a preroll pod, returns 0. + /// For midrolls, returns 1, 2,…,N. For a postroll pod, returns N+1…N+X. + /// Defaults to 0 if this ad is not part of a pod, or this pod is not part of + /// a playlist. + /// + /// DAI live stream: For a preroll pod, returns 0. For midrolls, returns the + /// break ID. Returns -2 if pod index cannot be determined (internal error). + int get podIndex => platform.podIndex; + + /// The content time offset at which the current ad pod was scheduled. + /// + /// For preroll pod, 0 is returned. For midrolls, the scheduled time is + /// returned. For postroll, -1 is returned. Defaults to 0 if this ad is not + /// part of a pod, or the pod is not part of an ad playlist. + Duration get timeOffset => platform.timeOffset; + + /// Total number of ads in the pod this ad belongs to, including bumpers. + /// + /// Will be 1 for standalone ads. + int get totalAds => platform.totalAds; + + /// Specifies whether the ad is a bumper. + /// + /// Bumpers are short videos used to open and close ad breaks. + bool get isBumper => platform.isBumper; +} + +/// An object that holds data corresponding to the companion Ad. +class CompanionAd { + /// Constructs a [CompanionAd] from a specific platform implementation. + CompanionAd.fromPlatform(this.platform); + + /// Implementation of [PlatformCompanionAd] for the current platform. + final PlatformCompanionAd platform; + + /// The width of the companion in pixels. + /// + /// `null` if unavailable. + int? get width => platform.width; + + /// The height of the companion in pixels. + /// + /// `null` if unavailable. + int? get height => platform.height; + + /// The API needed to execute this ad, or null if unavailable. + String? get apiFramework => platform.apiFramework; + + /// The URL for the static resource of this companion. + String? get resourceValue => platform.resourceValue; +} + +/// Simple data object containing universal ad ID information. +class UniversalAdId { + /// Constructs an [UniversalAdId] from a specific platform implementation. + UniversalAdId.fromPlatform(this.platform); + + /// Implementation of [PlatformUniversalAdId] for the current platform. + final PlatformUniversalAdId platform; + + /// The universal ad ID value. + /// + /// This will be null if it isn’t defined by the ad. + String? get adIdValue => platform.adIdValue; + + /// The universal ad ID registry with which the value is registered. + /// + /// This will be null if it isn’t defined by the ad. + String? get adIdRegistry => platform.adIdRegistry; +} diff --git a/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart index e676b355829..55f45ed0dc1 100644 --- a/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart +++ b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'ad.dart'; import 'platform_interface/platform_interface.dart'; /// Delegate for ad events or errors that occur during ad or stream @@ -37,7 +38,9 @@ class AdsManagerDelegate { void Function(AdErrorEvent event)? onAdErrorEvent, }) : this.fromPlatformCreationParams( PlatformAdsManagerDelegateCreationParams( - onAdEvent: onAdEvent, + onAdEvent: + (PlatformAdEvent event) => + onAdEvent?.call(AdEvent._fromPlatform(event)), onAdErrorEvent: onAdErrorEvent, ), ); @@ -80,10 +83,28 @@ class AdsManagerDelegate { final PlatformAdsManagerDelegate platform; /// Invoked when there is an [AdEvent]. - void Function(AdEvent event)? get onAdEvent => platform.params.onAdEvent; + void Function(AdEvent event)? get onAdEvent => + (AdEvent event) => platform.params.onAdEvent?.call(event.platform); /// Invoked when there was an error playing the ad. Log the error and resume /// playing content. void Function(AdErrorEvent event)? get onAdErrorEvent => platform.params.onAdErrorEvent; } + +/// Simple data class used to transport ad playback information. +class AdEvent { + AdEvent._fromPlatform(this.platform); + + /// Implementation of [PlatformAdEvent] for the current platform. + final PlatformAdEvent platform; + + /// The type of event that occurred. + AdEventType get type => platform.type; + + /// The ad with which this event is associated. + Ad? get ad => platform.ad != null ? Ad.fromPlatform(platform.ad!) : null; + + /// A map containing any extra ad data for the event, if needed. + Map get adData => platform.adData; +} diff --git a/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart b/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart index c0a61fbec6c..dd1abee41a5 100644 --- a/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart +++ b/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart @@ -96,10 +96,11 @@ class AndroidAdsManager extends PlatformAdsManager { proxy.newAdEventListener( onAdEvent: (_, ima.AdEvent event) { weakThis.target?._managerDelegate?.params.onAdEvent?.call( - AdEvent( + PlatformAdEvent( type: toInterfaceEventType(event.type), adData: event.adData?.cast() ?? {}, + ad: event.ad != null ? _asPlatformAd(event.ad!) : null, ), ); }, @@ -123,3 +124,102 @@ class AndroidAdsManager extends PlatformAdsManager { ); } } + +PlatformAd _asPlatformAd(ima.Ad ad) { + return PlatformAd( + adId: ad.adId, + adPodInfo: _asPlatformAdInfo(ad.adPodInfo), + adSystem: ad.adSystem, + wrapperCreativeIds: ad.adWrapperCreativeIds, + wrapperIds: ad.adWrapperIds, + wrapperSystems: ad.adWrapperSystems, + advertiserName: ad.advertiserName, + companionAds: List.unmodifiable( + ad.companionAds.map(_asPlatformCompanionAd), + ), + contentType: ad.contentType, + creativeAdId: ad.creativeAdId, + creativeId: ad.creativeId, + dealId: ad.dealId, + description: ad.description, + duration: + ad.duration == -1 + ? null + : Duration( + milliseconds: + (ad.duration * Duration.millisecondsPerSecond).round(), + ), + height: ad.height, + skipTimeOffset: + ad.skipTimeOffset == -1 + ? null + : Duration( + milliseconds: + (ad.skipTimeOffset * Duration.millisecondsPerSecond).round(), + ), + surveyUrl: ad.surveyUrl, + title: ad.title, + traffickingParameters: ad.traffickingParameters, + uiElements: + ad.uiElements + .map((ima.UiElement element) { + return switch (element) { + ima.UiElement.adAttribution => AdUIElement.adAttribution, + ima.UiElement.countdown => AdUIElement.countdown, + ima.UiElement.unknown => null, + }; + }) + .whereType() + .toSet(), + universalAdIds: ad.universalAdIds.map(_asPlatformUniversalAdId).toList(), + vastMediaBitrate: ad.vastMediaBitrate, + vastMediaHeight: ad.vastMediaHeight, + vastMediaWidth: ad.vastMediaWidth, + width: ad.width, + isLinear: ad.isLinear, + isSkippable: ad.isSkippable, + ); +} + +PlatformAdPodInfo _asPlatformAdInfo(ima.AdPodInfo adPodInfo) { + return PlatformAdPodInfo( + adPosition: adPodInfo.adPosition, + maxDuration: + adPodInfo.maxDuration == -1 + ? null + : Duration( + milliseconds: + (adPodInfo.maxDuration * Duration.millisecondsPerSecond) + .round(), + ), + podIndex: adPodInfo.podIndex, + timeOffset: Duration( + milliseconds: + (adPodInfo.timeOffset * Duration.millisecondsPerSecond).round(), + ), + totalAds: adPodInfo.totalAds, + isBumper: adPodInfo.isBumper, + ); +} + +PlatformCompanionAd _asPlatformCompanionAd(ima.CompanionAd ad) { + return PlatformCompanionAd( + apiFramework: ad.apiFramework, + height: ad.height == 0 ? null : ad.height, + resourceValue: ad.resourceValue, + width: ad.width == 0 ? null : ad.width, + ); +} + +PlatformUniversalAdId _asPlatformUniversalAdId( + ima.UniversalAdId universalAdId, +) { + return PlatformUniversalAdId( + adIdValue: + universalAdId.adIdValue == 'unknown' ? null : universalAdId.adIdValue, + adIdRegistry: + universalAdId.adIdRegistry == 'unknown' + ? null + : universalAdId.adIdRegistry, + ); +} diff --git a/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart b/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart index dfb02c675b2..21474a69797 100644 --- a/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart +++ b/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart @@ -7510,7 +7510,7 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { super.pigeon_instanceManager, this.apiFramework, required this.height, - required this.resourceValue, + this.resourceValue, required this.width, }); @@ -7523,7 +7523,7 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { final int height; /// The URL for the static resource of this companion. - final String resourceValue; + final String? resourceValue; /// The width of the companion in pixels. /// @@ -7537,7 +7537,7 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { CompanionAd Function( String? apiFramework, int height, - String resourceValue, + String? resourceValue, int width, )? pigeon_newInstance, @@ -7575,10 +7575,6 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { 'Argument for dev.flutter.pigeon.interactive_media_ads.CompanionAd.pigeon_newInstance was null, expected non-null int.', ); final String? arg_resourceValue = (args[3] as String?); - assert( - arg_resourceValue != null, - 'Argument for dev.flutter.pigeon.interactive_media_ads.CompanionAd.pigeon_newInstance was null, expected non-null String.', - ); final int? arg_width = (args[4] as int?); assert( arg_width != null, @@ -7590,7 +7586,7 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { pigeon_newInstance?.call( arg_apiFramework, arg_height!, - arg_resourceValue!, + arg_resourceValue, arg_width!, ) ?? CompanionAd.pigeon_detached( @@ -7598,7 +7594,7 @@ class CompanionAd extends PigeonInternalProxyApiBaseClass { pigeon_instanceManager: pigeon_instanceManager, apiFramework: arg_apiFramework, height: arg_height!, - resourceValue: arg_resourceValue!, + resourceValue: arg_resourceValue, width: arg_width!, ), arg_pigeon_instanceIdentifier!, diff --git a/packages/interactive_media_ads/lib/src/ios/ios_ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/ios/ios_ads_manager_delegate.dart index 6eec2a21c19..841a1441588 100644 --- a/packages/interactive_media_ads/lib/src/ios/ios_ads_manager_delegate.dart +++ b/packages/interactive_media_ads/lib/src/ios/ios_ads_manager_delegate.dart @@ -66,13 +66,14 @@ final class IOSAdsManagerDelegate extends PlatformAdsManagerDelegate { return interfaceDelegate.target!._iosParams._proxy.newIMAAdsManagerDelegate( didReceiveAdEvent: (_, __, ima.IMAAdEvent event) { interfaceDelegate.target?.params.onAdEvent?.call( - AdEvent( + PlatformAdEvent( type: toInterfaceEventType(event.type), adData: event.adData?.map((String? key, Object? value) { return MapEntry(key!, value.toString()); }) ?? {}, + ad: event.ad != null ? _asPlatformAd(event.ad!) : null, ), ); }, @@ -89,14 +90,113 @@ final class IOSAdsManagerDelegate extends PlatformAdsManagerDelegate { }, didRequestContentPause: (_, __) { interfaceDelegate.target?.params.onAdEvent?.call( - const AdEvent(type: AdEventType.contentPauseRequested), + const PlatformAdEvent(type: AdEventType.contentPauseRequested), ); }, didRequestContentResume: (_, __) { interfaceDelegate.target?.params.onAdEvent?.call( - const AdEvent(type: AdEventType.contentResumeRequested), + const PlatformAdEvent(type: AdEventType.contentResumeRequested), ); }, ); } } + +PlatformAd _asPlatformAd(ima.IMAAd ad) { + return PlatformAd( + adId: ad.adId, + adPodInfo: _asPlatformAdInfo(ad.adPodInfo), + adSystem: ad.adSystem, + wrapperCreativeIds: ad.wrapperCreativeIDs, + wrapperIds: ad.wrapperAdIDs, + wrapperSystems: ad.wrapperSystems, + advertiserName: ad.advertiserName, + companionAds: List.unmodifiable( + ad.companionAds.map(_asPlatformCompanionAd), + ), + contentType: ad.contentType, + creativeAdId: ad.creativeAdID, + creativeId: ad.creativeID, + dealId: ad.dealID, + description: ad.adDescription, + duration: + ad.duration == -1 + ? null + : Duration( + milliseconds: + (ad.duration * Duration.millisecondsPerSecond).round(), + ), + height: ad.height, + skipTimeOffset: + ad.skipTimeOffset == -1 + ? null + : Duration( + milliseconds: + (ad.skipTimeOffset * Duration.millisecondsPerSecond).round(), + ), + surveyUrl: ad.surveyURL, + title: ad.adTitle, + traffickingParameters: ad.traffickingParameters, + uiElements: + ad.uiElements + .map((ima.UIElementType element) { + return switch (element) { + ima.UIElementType.adAttribution => AdUIElement.adAttribution, + ima.UIElementType.countdown => AdUIElement.countdown, + ima.UIElementType.unknown => null, + }; + }) + .whereType() + .toSet(), + universalAdIds: ad.universalAdIDs.map(_asPlatformUniversalAdId).toList(), + vastMediaBitrate: ad.vastMediaBitrate, + vastMediaHeight: ad.vastMediaHeight, + vastMediaWidth: ad.vastMediaWidth, + width: ad.width, + isLinear: ad.isLinear, + isSkippable: ad.isSkippable, + ); +} + +PlatformAdPodInfo _asPlatformAdInfo(ima.IMAAdPodInfo adPodInfo) { + return PlatformAdPodInfo( + adPosition: adPodInfo.adPosition, + maxDuration: + adPodInfo.maxDuration == -1 + ? null + : Duration( + milliseconds: + (adPodInfo.maxDuration * Duration.millisecondsPerSecond) + .round(), + ), + podIndex: adPodInfo.podIndex, + timeOffset: Duration( + milliseconds: + (adPodInfo.timeOffset * Duration.millisecondsPerSecond).round(), + ), + totalAds: adPodInfo.totalAds, + isBumper: adPodInfo.isBumper, + ); +} + +PlatformCompanionAd _asPlatformCompanionAd(ima.IMACompanionAd ad) { + return PlatformCompanionAd( + apiFramework: ad.apiFramework, + height: ad.height == 0 ? null : ad.height, + resourceValue: ad.resourceValue, + width: ad.width == 0 ? null : ad.width, + ); +} + +PlatformUniversalAdId _asPlatformUniversalAdId( + ima.IMAUniversalAdID universalAdId, +) { + return PlatformUniversalAdId( + adIdValue: + universalAdId.adIDValue == 'unknown' ? null : universalAdId.adIDValue, + adIdRegistry: + universalAdId.adIDRegistry == 'unknown' + ? null + : universalAdId.adIDRegistry, + ); +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ad.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad.dart new file mode 100644 index 00000000000..b86f2c4fe6a --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad.dart @@ -0,0 +1,129 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'ad_ui_element.dart'; +import 'platform_ad_pod_info.dart'; +import 'platform_companion_ad.dart'; +import 'platform_universal_ad_id.dart'; + +/// Data object representing a single ad. +base class PlatformAd { + /// Constructs a [PlatformAd]. + PlatformAd({ + required this.adId, + required this.adPodInfo, + required this.adSystem, + required this.wrapperCreativeIds, + required this.wrapperIds, + required this.wrapperSystems, + required this.advertiserName, + required this.companionAds, + required this.contentType, + required this.creativeAdId, + required this.creativeId, + required this.dealId, + required this.description, + required this.duration, + required this.width, + required this.height, + required this.skipTimeOffset, + required this.surveyUrl, + required this.title, + required this.traffickingParameters, + required this.uiElements, + required this.universalAdIds, + required this.vastMediaBitrate, + required this.vastMediaWidth, + required this.vastMediaHeight, + required this.isLinear, + required this.isSkippable, + }); + + /// The ad ID as specified in the VAST response. + final String adId; + + /// The pod metadata object. + final PlatformAdPodInfo adPodInfo; + + /// The ad system as specified in the VAST response. + final String adSystem; + + /// The IDs of the ads' creatives, starting with the first wrapper ad. + final List wrapperCreativeIds; + + /// The wrapper ad IDs as specified in the VAST response. + final List wrapperIds; + + /// The wrapper ad systems as specified in the VAST response. + final List wrapperSystems; + + /// The advertiser name as defined by the serving party. + final String advertiserName; + + /// The companions for the current ad while using DAI. + /// + /// Returns an empty list in any other scenario. + final List companionAds; + + /// The content type of the currently selected creative, or null if no + /// creative is selected or the content type is unavailable. + final String? contentType; + + /// The ISCI (Industry Standard Commercial Identifier) code for an ad. + final String creativeAdId; + + /// The ID of the selected creative for the ad, + final String creativeId; + + /// The first deal ID present in the wrapper chain for the current ad, + /// starting from the top. + final String dealId; + + /// The description of this ad from the VAST response. + final String? description; + + /// The duration of the ad. + final Duration? duration; + + /// The width of the selected creative if non-linear, else returns 0. + final int width; + + /// The height of the selected creative if non-linear, else returns 0. + final int height; + + /// The playback time before the ad becomes skippable. + final Duration? skipTimeOffset; + + /// The URL associated with the survey for the given ad. + final String? surveyUrl; + + /// The title of this ad from the VAST response. + final String? title; + + /// The custom parameters associated with the ad at the time of ad + /// trafficking. + final String traffickingParameters; + + /// The set of ad UI elements rendered by the IMA SDK for this ad. + final Set uiElements; + + /// The list of all universal ad IDs for this ad. + final List universalAdIds; + + /// The VAST bitrate in Kbps of the selected creative. + final int vastMediaBitrate; + + /// The VAST media width in pixels of the selected creative. + final int vastMediaWidth; + + /// The VAST media height in pixels of the selected creative. + final int vastMediaHeight; + + /// Indicates whether the ad’s current mode of operation is linear or + /// non-linear. + final bool isLinear; + + /// Indicates whether the ad can be skipped by the user. + final bool isSkippable; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_event.dart similarity index 91% rename from packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart rename to packages/interactive_media_ads/lib/src/platform_interface/platform_ad_event.dart index 2e4bc8860d5..c651c2138ec 100644 --- a/packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_event.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'ad_error.dart'; +import 'platform_ad.dart'; /// Types of events that can occur during ad playback. enum AdEventType { @@ -110,20 +111,27 @@ enum AdEventType { /// Simple data class used to transport ad playback information. @immutable -class AdEvent { - /// Creates an [AdEvent]. - const AdEvent({required this.type, this.adData = const {}}); +base class PlatformAdEvent { + /// Creates an [PlatformAdEvent]. + const PlatformAdEvent({ + required this.type, + this.ad, + this.adData = const {}, + }); /// The type of event that occurred. final AdEventType type; + /// The ad with which this event is associated. + final PlatformAd? ad; + /// A map containing any extra ad data for the event, if needed. final Map adData; } /// An event raised when there is an error loading or playing ads. @immutable -class AdErrorEvent { +base class AdErrorEvent { /// Creates an [AdErrorEvent]. const AdErrorEvent({required this.error}); diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_pod_info.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_pod_info.dart new file mode 100644 index 00000000000..ff6f0984901 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_pod_info.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Simple data object containing podding metadata. +base class PlatformAdPodInfo { + /// Constructs a [PlatformAdPodInfo]. + PlatformAdPodInfo({ + required this.adPosition, + required this.maxDuration, + required this.podIndex, + required this.timeOffset, + required this.totalAds, + required this.isBumper, + }); + + /// The position of the ad within the pod. + /// + /// The value returned is one-based, for example, 1 of 2, 2 of 2, etc. If the + /// ad is not part of a pod, this will return 1. + final int adPosition; + + /// The maximum duration of the pod. + /// + /// For unknown duration, null. + final Duration? maxDuration; + + /// Returns the index of the ad pod. + /// + /// Client side: For a preroll pod, returns 0. For midrolls, returns 1, 2,…, + /// N. For a postroll pod, returns -1. Defaults to 0 if this ad is not part of + /// a pod, or this pod is not part of a playlist. + /// + /// DAI VOD: Returns the index of the ad pod. For a preroll pod, returns 0. + /// For midrolls, returns 1, 2,…,N. For a postroll pod, returns N+1…N+X. + /// Defaults to 0 if this ad is not part of a pod, or this pod is not part of + /// a playlist. + /// + /// DAI live stream: For a preroll pod, returns 0. For midrolls, returns the + /// break ID. Returns -2 if pod index cannot be determined (internal error). + final int podIndex; + + /// The content time offset at which the current ad pod was scheduled. + /// + /// For preroll pod, 0 is returned. For midrolls, the scheduled time is + /// returned. For postroll, -1 is returned. Defaults to 0 if this ad is not + /// part of a pod, or the pod is not part of an ad playlist. + final Duration timeOffset; + + /// Total number of ads in the pod this ad belongs to, including bumpers. + /// + /// Will be 1 for standalone ads. + final int totalAds; + + /// Specifies whether the ad is a bumper. + /// + /// Bumpers are short videos used to open and close ad breaks. + final bool isBumper; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart index 9a5cc8bcaf9..f6ce589ab99 100644 --- a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart @@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart'; -import 'ad_event.dart'; import 'interactive_media_ads_platform.dart'; +import 'platform_ad_event.dart'; /// Object specifying creation parameters for creating a /// [PlatformAdsManagerDelegate]. @@ -47,8 +47,8 @@ base class PlatformAdsManagerDelegateCreationParams { this.onAdErrorEvent, }); - /// Invoked when there is an [AdEvent]. - final void Function(AdEvent event)? onAdEvent; + /// Invoked when there is an [PlatformAdEvent]. + final void Function(PlatformAdEvent event)? onAdEvent; /// Invoked when there was an error playing the ad. Log the error and resume /// playing content. diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_companion_ad.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_companion_ad.dart new file mode 100644 index 00000000000..3179e5f565e --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_companion_ad.dart @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// An object that holds data corresponding to the companion Ad. +base class PlatformCompanionAd { + /// Constructs a [PlatformCompanionAd]. + PlatformCompanionAd({ + required this.width, + required this.height, + required this.apiFramework, + required this.resourceValue, + }); + + /// The width of the companion in pixels. + /// + /// `null` if unavailable. + final int? width; + + /// The height of the companion in pixels. + /// + /// `null` if unavailable. + final int? height; + + /// The API needed to execute this ad, or null if unavailable. + final String? apiFramework; + + /// The URL for the static resource of this companion. + final String? resourceValue; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart index 3e9f0cb6609..245ba9416c7 100644 --- a/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart @@ -3,17 +3,21 @@ // found in the LICENSE file. export 'ad_error.dart'; -export 'ad_event.dart'; export 'ad_ui_element.dart'; export 'build_widget_creation_params.dart'; export 'companion_ad_slot_size.dart'; export 'interactive_media_ads_platform.dart'; +export 'platform_ad.dart'; export 'platform_ad_display_container.dart'; +export 'platform_ad_event.dart'; +export 'platform_ad_pod_info.dart'; export 'platform_ads_loader.dart'; export 'platform_ads_manager.dart'; export 'platform_ads_manager_delegate.dart'; export 'platform_ads_rendering_settings.dart'; export 'platform_ads_request.dart'; +export 'platform_companion_ad.dart'; export 'platform_companion_ad_slot.dart'; export 'platform_content_progress_provider.dart'; export 'platform_ima_settings.dart'; +export 'platform_universal_ad_id.dart'; diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_universal_ad_id.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_universal_ad_id.dart new file mode 100644 index 00000000000..334e1ba5076 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_universal_ad_id.dart @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Simple data object containing universal ad ID information. +base class PlatformUniversalAdId { + /// Constructs a [PlatformUniversalAdId]. + PlatformUniversalAdId({required this.adIdValue, required this.adIdRegistry}); + + /// The universal ad ID value. + /// + /// This will be null if it isn’t defined by the ad. + final String? adIdValue; + + /// The universal ad ID registry with which the value is registered. + /// + /// This will be null if it isn’t defined by the ad. + final String? adIdRegistry; +} diff --git a/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart b/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart index 5bd6231c56a..5c884ff5eda 100644 --- a/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart +++ b/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart @@ -1056,7 +1056,7 @@ abstract class CompanionAd { late final int height; /// The URL for the static resource of this companion. - late final String resourceValue; + late final String? resourceValue; /// The width of the companion in pixels. /// @@ -1155,7 +1155,7 @@ abstract class Ad { /// trafficking. late final String traffickingParameters; - /// Te set of ad UI elements rendered by the IMA SDK for this ad. + /// The set of ad UI elements rendered by the IMA SDK for this ad. late final List uiElements; /// The list of all universal ad IDs for this ad. diff --git a/packages/interactive_media_ads/pubspec.yaml b/packages/interactive_media_ads/pubspec.yaml index 385101a9c01..51d79c5a8b8 100644 --- a/packages/interactive_media_ads/pubspec.yaml +++ b/packages/interactive_media_ads/pubspec.yaml @@ -2,7 +2,7 @@ name: interactive_media_ads description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22 -version: 0.2.7 # This must match the version in +version: 0.2.8 # This must match the version in # `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt` and # `ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift` diff --git a/packages/interactive_media_ads/test/ads_manager_delegate_test.dart b/packages/interactive_media_ads/test/ads_manager_delegate_test.dart index a6f6a9cf510..5ff46a36fac 100644 --- a/packages/interactive_media_ads/test/ads_manager_delegate_test.dart +++ b/packages/interactive_media_ads/test/ads_manager_delegate_test.dart @@ -28,15 +28,57 @@ void main() { (_) => throw UnimplementedError(), ); - void onAdEvent(AdEvent event) {} void onAdErrorEvent(AdErrorEvent event) {} final AdsManagerDelegate delegate = AdsManagerDelegate( - onAdEvent: onAdEvent, + onAdEvent: expectAsync1((AdEvent event) { + expect(event.type, AdEventType.adBreakEnded); + expect(event.ad, isNotNull); + }), onAdErrorEvent: onAdErrorEvent, ); - expect(delegate.platform.params.onAdEvent, onAdEvent); + delegate.platform.params.onAdEvent!( + PlatformAdEvent( + type: AdEventType.adBreakEnded, + ad: PlatformAd( + adId: '', + adPodInfo: PlatformAdPodInfo( + adPosition: 0, + maxDuration: Duration.zero, + podIndex: 0, + timeOffset: Duration.zero, + totalAds: 0, + isBumper: true, + ), + adSystem: '', + wrapperCreativeIds: const [], + wrapperIds: const [], + wrapperSystems: const [], + advertiserName: '', + companionAds: const [], + contentType: '', + creativeAdId: '', + creativeId: '', + dealId: '', + description: '', + duration: Duration.zero, + height: 9, + skipTimeOffset: Duration.zero, + surveyUrl: '', + title: '', + traffickingParameters: '', + uiElements: const {}, + universalAdIds: const [], + vastMediaBitrate: 0, + vastMediaHeight: 0, + vastMediaWidth: 0, + width: 0, + isLinear: true, + isSkippable: false, + ), + ), + ); expect(delegate.platform.params.onAdErrorEvent, onAdErrorEvent); }); } diff --git a/packages/interactive_media_ads/test/android/ad_test.dart b/packages/interactive_media_ads/test/android/ad_test.dart new file mode 100644 index 00000000000..0569044608f --- /dev/null +++ b/packages/interactive_media_ads/test/android/ad_test.dart @@ -0,0 +1,217 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/src/android/android_ads_manager.dart'; +import 'package:interactive_media_ads/src/android/android_ads_manager_delegate.dart'; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as ima; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'ad_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + group('Ad', () { + setUp(() { + ima.PigeonOverrides.pigeon_reset(); + }); + + test('UniversalAdId sets unknown values to null', () async { + final MockAdsManager mockAdsManager = MockAdsManager(); + + late final void Function(ima.AdEventListener, ima.AdEvent) + onAdEventCallback; + + ima.PigeonOverrides.adEventListener_new = ({ + required void Function(ima.AdEventListener, ima.AdEvent) onAdEvent, + }) { + onAdEventCallback = onAdEvent; + return MockAdEventListener(); + }; + + ima.PigeonOverrides.adErrorListener_new = ({ + required void Function(ima.AdErrorListener, ima.AdErrorEvent) onAdError, + }) { + return MockAdErrorListener(); + }; + + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + + await adsManager.setAdsManagerDelegate( + AndroidAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.universalAdIds.single.adIdValue, isNull); + expect(event.ad!.universalAdIds.single.adIdRegistry, isNull); + }), + ), + ), + ); + + final MockAdEvent mockAdEvent = MockAdEvent(); + when(mockAdEvent.type).thenReturn(ima.AdEventType.allAdsCompleted); + when(mockAdEvent.ad).thenReturn( + createTestAd( + universalAdIds: [ + ima.UniversalAdId.pigeon_detached( + adIdRegistry: 'unknown', + adIdValue: 'unknown', + ), + ], + ), + ); + + onAdEventCallback(MockAdEventListener(), mockAdEvent); + }); + + test('CompanionAd sets 0 values for height/width to null', () async { + final MockAdsManager mockAdsManager = MockAdsManager(); + + late final void Function(ima.AdEventListener, ima.AdEvent) + onAdEventCallback; + + ima.PigeonOverrides.adEventListener_new = ({ + required void Function(ima.AdEventListener, ima.AdEvent) onAdEvent, + }) { + onAdEventCallback = onAdEvent; + return MockAdEventListener(); + }; + + ima.PigeonOverrides.adErrorListener_new = ({ + required void Function(ima.AdErrorListener, ima.AdErrorEvent) onAdError, + }) { + return MockAdErrorListener(); + }; + + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + + await adsManager.setAdsManagerDelegate( + AndroidAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.companionAds.single.width, isNull); + expect(event.ad!.companionAds.single.height, isNull); + }), + ), + ), + ); + + final MockAdEvent mockAdEvent = MockAdEvent(); + when(mockAdEvent.type).thenReturn(ima.AdEventType.allAdsCompleted); + when(mockAdEvent.ad).thenReturn( + createTestAd( + companionAds: [ + ima.CompanionAd.pigeon_detached(height: 0, width: 0), + ], + ), + ); + + onAdEventCallback(MockAdEventListener(), mockAdEvent); + }); + + test('Ad sets durations of -1 to null', () async { + final MockAdsManager mockAdsManager = MockAdsManager(); + + late final void Function(ima.AdEventListener, ima.AdEvent) + onAdEventCallback; + + ima.PigeonOverrides.adEventListener_new = ({ + required void Function(ima.AdEventListener, ima.AdEvent) onAdEvent, + }) { + onAdEventCallback = onAdEvent; + return MockAdEventListener(); + }; + + ima.PigeonOverrides.adErrorListener_new = ({ + required void Function(ima.AdErrorListener, ima.AdErrorEvent) onAdError, + }) { + return MockAdErrorListener(); + }; + + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + + await adsManager.setAdsManagerDelegate( + AndroidAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.duration, isNull); + expect(event.ad!.skipTimeOffset, isNull); + expect(event.ad!.adPodInfo.maxDuration, isNull); + }), + ), + ), + ); + + final MockAdEvent mockAdEvent = MockAdEvent(); + when(mockAdEvent.type).thenReturn(ima.AdEventType.allAdsCompleted); + when(mockAdEvent.ad).thenReturn( + createTestAd( + duration: -1, + skipTimeOffset: -1, + adPodInfo: ima.AdPodInfo.pigeon_detached( + adPosition: 0, + maxDuration: -1, + podIndex: 0, + timeOffset: 0, + totalAds: 0, + isBumper: true, + ), + ), + ); + + onAdEventCallback(MockAdEventListener(), mockAdEvent); + }); + }); +} + +ima.Ad createTestAd({ + List? universalAdIds, + List? companionAds, + ima.AdPodInfo? adPodInfo, + double? duration, + double? skipTimeOffset, +}) { + return ima.Ad.pigeon_detached( + adId: '', + adPodInfo: + adPodInfo ?? + ima.AdPodInfo.pigeon_detached( + adPosition: 0, + maxDuration: 0, + podIndex: 0, + timeOffset: 0, + totalAds: 0, + isBumper: false, + ), + adSystem: '', + adWrapperCreativeIds: const [], + adWrapperIds: const [], + adWrapperSystems: const [], + advertiserName: '', + companionAds: companionAds ?? const [], + creativeAdId: '', + creativeId: '', + dealId: '', + duration: duration ?? 0, + height: 0, + skipTimeOffset: skipTimeOffset ?? 0, + traffickingParameters: '', + uiElements: const [], + universalAdIds: universalAdIds ?? const [], + vastMediaBitrate: 0, + vastMediaHeight: 0, + vastMediaWidth: 0, + width: 0, + isLinear: true, + isSkippable: true, + ); +} diff --git a/packages/interactive_media_ads/test/android/ad_test.mocks.dart b/packages/interactive_media_ads/test/android/ad_test.mocks.dart new file mode 100644 index 00000000000..71f4f1b8d1a --- /dev/null +++ b/packages/interactive_media_ads/test/android/ad_test.mocks.dart @@ -0,0 +1,366 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in interactive_media_ads/test/android/ad_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeAdsManager_1 extends _i1.SmartFake implements _i2.AdsManager { + _FakeAdsManager_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeAdEvent_2 extends _i1.SmartFake implements _i2.AdEvent { + _FakeAdEvent_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeAdEventListener_3 extends _i1.SmartFake + implements _i2.AdEventListener { + _FakeAdEventListener_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeAdErrorListener_4 extends _i1.SmartFake + implements _i2.AdErrorListener { + _FakeAdErrorListener_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [AdsManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsManager extends _i1.Mock implements _i2.AdsManager { + @override + List get adCuePoints => + (super.noSuchMethod( + Invocation.getter(#adCuePoints), + returnValue: [], + returnValueForMissingStub: [], + ) + as List); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future discardAdBreak() => + (super.noSuchMethod( + Invocation.method(#discardAdBreak, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future pause() => + (super.noSuchMethod( + Invocation.method(#pause, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future start() => + (super.noSuchMethod( + Invocation.method(#start, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future resume() => + (super.noSuchMethod( + Invocation.method(#resume, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future skip() => + (super.noSuchMethod( + Invocation.method(#skip, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.AdsManager pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeAdsManager_1( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeAdsManager_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.AdsManager); + + @override + _i3.Future addAdErrorListener(_i2.AdErrorListener? errorListener) => + (super.noSuchMethod( + Invocation.method(#addAdErrorListener, [errorListener]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future addAdEventListener(_i2.AdEventListener? adEventListener) => + (super.noSuchMethod( + Invocation.method(#addAdEventListener, [adEventListener]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future destroy() => + (super.noSuchMethod( + Invocation.method(#destroy, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future init(_i2.AdsRenderingSettings? settings) => + (super.noSuchMethod( + Invocation.method(#init, [settings]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future focus() => + (super.noSuchMethod( + Invocation.method(#focus, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future<_i2.AdProgressInfo?> getAdProgressInfo() => + (super.noSuchMethod( + Invocation.method(#getAdProgressInfo, []), + returnValue: _i3.Future<_i2.AdProgressInfo?>.value(), + returnValueForMissingStub: _i3.Future<_i2.AdProgressInfo?>.value(), + ) + as _i3.Future<_i2.AdProgressInfo?>); + + @override + _i3.Future<_i2.Ad?> getCurrentAd() => + (super.noSuchMethod( + Invocation.method(#getCurrentAd, []), + returnValue: _i3.Future<_i2.Ad?>.value(), + returnValueForMissingStub: _i3.Future<_i2.Ad?>.value(), + ) + as _i3.Future<_i2.Ad?>); + + @override + _i3.Future removeAdErrorListener(_i2.AdErrorListener? errorListener) => + (super.noSuchMethod( + Invocation.method(#removeAdErrorListener, [errorListener]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future removeAdEventListener( + _i2.AdEventListener? adEventListener, + ) => + (super.noSuchMethod( + Invocation.method(#removeAdEventListener, [adEventListener]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); +} + +/// A class which mocks [AdEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdEvent extends _i1.Mock implements _i2.AdEvent { + @override + _i2.AdEventType get type => + (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _i2.AdEventType.adBreakEnded, + returnValueForMissingStub: _i2.AdEventType.adBreakEnded, + ) + as _i2.AdEventType); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.AdEvent pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeAdEvent_2( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeAdEvent_2( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.AdEvent); +} + +/// A class which mocks [AdEventListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdEventListener extends _i1.Mock implements _i2.AdEventListener { + @override + void Function(_i2.AdEventListener, _i2.AdEvent) get onAdEvent => + (super.noSuchMethod( + Invocation.getter(#onAdEvent), + returnValue: + (_i2.AdEventListener pigeon_instance, _i2.AdEvent event) {}, + returnValueForMissingStub: + (_i2.AdEventListener pigeon_instance, _i2.AdEvent event) {}, + ) + as void Function(_i2.AdEventListener, _i2.AdEvent)); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.AdEventListener pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeAdEventListener_3( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeAdEventListener_3( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.AdEventListener); +} + +/// A class which mocks [AdErrorListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdErrorListener extends _i1.Mock implements _i2.AdErrorListener { + @override + void Function(_i2.AdErrorListener, _i2.AdErrorEvent) get onAdError => + (super.noSuchMethod( + Invocation.getter(#onAdError), + returnValue: + ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + returnValueForMissingStub: + ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + ) + as void Function(_i2.AdErrorListener, _i2.AdErrorEvent)); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.AdErrorListener pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeAdErrorListener_4( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeAdErrorListener_4( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.AdErrorListener); +} diff --git a/packages/interactive_media_ads/test/android/ads_manager_test.dart b/packages/interactive_media_ads/test/android/ads_manager_test.dart index 56aba174591..2ab0959a7d4 100644 --- a/packages/interactive_media_ads/test/android/ads_manager_test.dart +++ b/packages/interactive_media_ads/test/android/ads_manager_test.dart @@ -142,7 +142,7 @@ void main() { await adsManager.setAdsManagerDelegate( AndroidAdsManagerDelegate( PlatformAdsManagerDelegateCreationParams( - onAdEvent: expectAsync1((AdEvent event) { + onAdEvent: expectAsync1((PlatformAdEvent event) { expect(event.type, AdEventType.allAdsCompleted); expect(event.adData, {'hello': 'world'}); }), diff --git a/packages/interactive_media_ads/test/ios/ad_test.dart b/packages/interactive_media_ads/test/ios/ad_test.dart new file mode 100644 index 00000000000..1f949dfa860 --- /dev/null +++ b/packages/interactive_media_ads/test/ios/ad_test.dart @@ -0,0 +1,233 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/src/ios/interactive_media_ads.g.dart'; +import 'package:interactive_media_ads/src/ios/ios_ads_manager_delegate.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart' + hide AdEventType; + +void main() { + group('Ad', () { + setUp(() { + PigeonOverrides.pigeon_reset(); + }); + + test('UniversalAdID sets unknown values to null', () { + late final void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEventCallback; + + late final IMAAdsManagerDelegate delegate; + PigeonOverrides.iMAAdsManagerDelegate_new = ({ + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEvent, + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdError) + didReceiveAdError, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentPause, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentResume, + }) { + didReceiveAdEventCallback = didReceiveAdEvent; + delegate = IMAAdsManagerDelegate.pigeon_detached( + didReceiveAdEvent: didReceiveAdEvent, + didReceiveAdError: didReceiveAdError, + didRequestContentPause: didRequestContentPause, + didRequestContentResume: didRequestContentResume, + ); + return delegate; + }; + + final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( + IOSAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.universalAdIds.single.adIdValue, isNull); + expect(event.ad!.universalAdIds.single.adIdRegistry, isNull); + }), + ), + ); + + // Calls the field because the value is instantiated lazily. + // ignore: unnecessary_statements + adsManagerDelegate.delegate; + + didReceiveAdEventCallback( + delegate, + IMAAdsManager.pigeon_detached(adCuePoints: const []), + IMAAdEvent.pigeon_detached( + type: AdEventType.allAdsCompleted, + typeString: 'typeString', + ad: createTestAd( + universalAdIds: [ + IMAUniversalAdID.pigeon_detached( + adIDRegistry: 'unknown', + adIDValue: 'unknown', + ), + ], + ), + ), + ); + }); + + test('CompanionAd sets 0 values for height/width to null', () { + late final void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEventCallback; + + late final IMAAdsManagerDelegate delegate; + PigeonOverrides.iMAAdsManagerDelegate_new = ({ + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEvent, + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdError) + didReceiveAdError, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentPause, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentResume, + }) { + didReceiveAdEventCallback = didReceiveAdEvent; + delegate = IMAAdsManagerDelegate.pigeon_detached( + didReceiveAdEvent: didReceiveAdEvent, + didReceiveAdError: didReceiveAdError, + didRequestContentPause: didRequestContentPause, + didRequestContentResume: didRequestContentResume, + ); + return delegate; + }; + + final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( + IOSAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.companionAds.single.width, isNull); + expect(event.ad!.companionAds.single.height, isNull); + }), + ), + ); + + // Calls the field because the value is instantiated lazily. + // ignore: unnecessary_statements + adsManagerDelegate.delegate; + + didReceiveAdEventCallback( + delegate, + IMAAdsManager.pigeon_detached(adCuePoints: const []), + IMAAdEvent.pigeon_detached( + type: AdEventType.allAdsCompleted, + typeString: 'typeString', + ad: createTestAd( + companionAds: [ + IMACompanionAd.pigeon_detached(height: 0, width: 0), + ], + ), + ), + ); + }); + + test('Ad sets durations of -1 to null', () { + late final void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEventCallback; + + late final IMAAdsManagerDelegate delegate; + PigeonOverrides.iMAAdsManagerDelegate_new = ({ + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdEvent) + didReceiveAdEvent, + required void Function(IMAAdsManagerDelegate, IMAAdsManager, IMAAdError) + didReceiveAdError, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentPause, + required void Function(IMAAdsManagerDelegate, IMAAdsManager) + didRequestContentResume, + }) { + didReceiveAdEventCallback = didReceiveAdEvent; + delegate = IMAAdsManagerDelegate.pigeon_detached( + didReceiveAdEvent: didReceiveAdEvent, + didReceiveAdError: didReceiveAdError, + didRequestContentPause: didRequestContentPause, + didRequestContentResume: didRequestContentResume, + ); + return delegate; + }; + + final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( + IOSAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((PlatformAdEvent event) { + expect(event.ad!.duration, isNull); + expect(event.ad!.skipTimeOffset, isNull); + expect(event.ad!.adPodInfo.maxDuration, isNull); + }), + ), + ); + + // Calls the field because the value is instantiated lazily. + // ignore: unnecessary_statements + adsManagerDelegate.delegate; + + didReceiveAdEventCallback( + delegate, + IMAAdsManager.pigeon_detached(adCuePoints: const []), + IMAAdEvent.pigeon_detached( + type: AdEventType.allAdsCompleted, + typeString: 'typeString', + ad: createTestAd( + duration: -1, + skipTimeOffset: -1, + adPodInfo: IMAAdPodInfo.pigeon_detached( + adPosition: 0, + maxDuration: -1, + podIndex: 0, + timeOffset: 0, + totalAds: 0, + isBumper: true, + ), + ), + ), + ); + }); + }); +} + +IMAAd createTestAd({ + List? universalAdIds, + List? companionAds, + IMAAdPodInfo? adPodInfo, + double? duration, + double? skipTimeOffset, +}) { + return IMAAd.pigeon_detached( + adId: '', + adPodInfo: + adPodInfo ?? + IMAAdPodInfo.pigeon_detached( + adPosition: 0, + maxDuration: 0, + podIndex: 0, + timeOffset: 0, + totalAds: 0, + isBumper: false, + ), + adSystem: '', + adTitle: '', + adDescription: '', + contentType: '', + wrapperCreativeIDs: const [], + wrapperAdIDs: const [], + wrapperSystems: const [], + advertiserName: '', + companionAds: companionAds ?? const [], + creativeAdID: '', + creativeID: '', + dealID: '', + duration: duration ?? 0, + height: 0, + skipTimeOffset: skipTimeOffset ?? 0, + traffickingParameters: '', + uiElements: const [], + universalAdIDs: universalAdIds ?? const [], + vastMediaBitrate: 0, + vastMediaHeight: 0, + vastMediaWidth: 0, + width: 0, + isLinear: true, + isSkippable: true, + ); +} diff --git a/packages/interactive_media_ads/test/ios/ads_manager_delegate_tests.dart b/packages/interactive_media_ads/test/ios/ads_manager_delegate_test.dart similarity index 98% rename from packages/interactive_media_ads/test/ios/ads_manager_delegate_tests.dart rename to packages/interactive_media_ads/test/ios/ads_manager_delegate_test.dart index ddf9d08b40b..2777a9d27e4 100644 --- a/packages/interactive_media_ads/test/ios/ads_manager_delegate_tests.dart +++ b/packages/interactive_media_ads/test/ios/ads_manager_delegate_test.dart @@ -56,7 +56,7 @@ void main() { final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( IOSAdsManagerDelegateCreationParams( - onAdEvent: expectAsync1((AdEvent event) { + onAdEvent: expectAsync1((PlatformAdEvent event) { expect(event.type, AdEventType.allAdsCompleted); expect(event.adData, {'hello': 'world'}); }), @@ -124,7 +124,7 @@ void main() { final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( IOSAdsManagerDelegateCreationParams( - onAdEvent: expectAsync1((AdEvent event) { + onAdEvent: expectAsync1((PlatformAdEvent event) { expect(event.type, AdEventType.contentPauseRequested); }), proxy: imaProxy, @@ -185,7 +185,7 @@ void main() { final IOSAdsManagerDelegate adsManagerDelegate = IOSAdsManagerDelegate( IOSAdsManagerDelegateCreationParams( - onAdEvent: expectAsync1((AdEvent event) { + onAdEvent: expectAsync1((PlatformAdEvent event) { expect(event.type, AdEventType.contentResumeRequested); }), proxy: imaProxy,