@capacitor-community/admob 8.2.0 → 8.2.1-beta.pr490.shaf4a3cab095f4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorCommunityAdmob.podspec +1 -1
- package/Package.swift +1 -1
- package/README.md +8 -7
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +88 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/NativeAdExecutor.kt +626 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/NativeAdFeedSessions.kt +35 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/NativeAdPlacementValue.kt +44 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/NativeAdPluginEvents.kt +11 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/PluginNativeAdView.kt +187 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/native-ads/index.d.ts +6 -0
- package/dist/esm/native-ads/index.js +4 -0
- package/dist/esm/native-ads/index.js.map +1 -0
- package/dist/esm/native-ads/native-ad-definitions.interface.d.ts +20 -0
- package/dist/esm/native-ads/native-ad-definitions.interface.js +2 -0
- package/dist/esm/native-ads/native-ad-definitions.interface.js.map +1 -0
- package/dist/esm/native-ads/native-ad-element.d.ts +9 -0
- package/dist/esm/native-ads/native-ad-element.js +53 -0
- package/dist/esm/native-ads/native-ad-element.js.map +1 -0
- package/dist/esm/native-ads/native-ad-event.interface.d.ts +5 -0
- package/dist/esm/native-ads/native-ad-event.interface.js +2 -0
- package/dist/esm/native-ads/native-ad-event.interface.js.map +1 -0
- package/dist/esm/native-ads/native-ad-feed.d.ts +72 -0
- package/dist/esm/native-ads/native-ad-feed.js +643 -0
- package/dist/esm/native-ads/native-ad-feed.js.map +1 -0
- package/dist/esm/native-ads/native-ad-geometry.d.ts +8 -0
- package/dist/esm/native-ads/native-ad-geometry.js +92 -0
- package/dist/esm/native-ads/native-ad-geometry.js.map +1 -0
- package/dist/esm/native-ads/native-ad-options.interface.d.ts +40 -0
- package/dist/esm/native-ads/native-ad-options.interface.js +2 -0
- package/dist/esm/native-ads/native-ad-options.interface.js.map +1 -0
- package/dist/esm/native-ads/native-ad-placement.interface.d.ts +28 -0
- package/dist/esm/native-ads/native-ad-placement.interface.js +2 -0
- package/dist/esm/native-ads/native-ad-placement.interface.js.map +1 -0
- package/dist/esm/native-ads/native-ad-plugin-events.enum.d.ts +9 -0
- package/dist/esm/native-ads/native-ad-plugin-events.enum.js +11 -0
- package/dist/esm/native-ads/native-ad-plugin-events.enum.js.map +1 -0
- package/dist/esm/native-ads/native-ad-style.interface.d.ts +30 -0
- package/dist/esm/native-ads/native-ad-style.interface.js +2 -0
- package/dist/esm/native-ads/native-ad-style.interface.js.map +1 -0
- package/dist/esm/native-ads/native-ad-template.enum.d.ts +11 -0
- package/dist/esm/native-ads/native-ad-template.enum.js +13 -0
- package/dist/esm/native-ads/native-ad-template.enum.js.map +1 -0
- package/dist/esm/web.d.ts +9 -1
- package/dist/esm/web.js +15 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +883 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +883 -1
- package/dist/plugin.js.map +1 -1
- package/docs/configuration.md +2 -2
- package/docs/consent.md +6 -6
- package/docs/events.md +3 -2
- package/docs/migration.md +22 -2
- package/docs/native-ads.md +187 -0
- package/docs/rewarded.md +13 -0
- package/ios/Sources/AdMobPlugin/AdMobPlugin.swift +103 -41
- package/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerAdSizeEnum.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerExecutor.swift +4 -4
- package/ios/Sources/AdMobPlugin/Helper/AuthorizationStatusEnum.swift +3 -0
- package/ios/Sources/AdMobPlugin/Interstitial/AdInterstitialExecutor.swift +3 -0
- package/ios/Sources/AdMobPlugin/Interstitial/InterstitialAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdExecutor.swift +354 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdFeedSessions.swift +34 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdPlacementValue.swift +50 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdPluginEvents.swift +11 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdScrollTracker.swift +66 -0
- package/ios/Sources/AdMobPlugin/NativeAds/NativeAdValues.swift +27 -0
- package/ios/Sources/AdMobPlugin/NativeAds/PluginNativeAdView.swift +187 -0
- package/ios/Sources/AdMobPlugin/Rewarded/AdRewardExecutor.swift +3 -0
- package/ios/Sources/AdMobPlugin/Rewarded/RewardAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/RewardedInterstitial/AdRewardInterstitialExecutor.swift +3 -0
- package/ios/Sources/AdMobPlugin/RewardedInterstitial/RewardInterstitialAdPluginEvents.swift +3 -0
- package/ios/Tests/AdMobPluginTests/AdMobPluginTests.swift +82 -10
- package/ios/Tests/AdMobPluginTests/NativeAdScrollTrackerTests.swift +47 -0
- package/package.json +6 -4
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import GoogleMobileAds
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
final class PluginNativeAdView: NativeAdView {
|
|
6
|
+
private let isSmall: Bool
|
|
7
|
+
private let attributionLabel = UILabel()
|
|
8
|
+
private let headlineLabel = UILabel()
|
|
9
|
+
private let bodyLabel = UILabel()
|
|
10
|
+
private let advertiserLabel = UILabel()
|
|
11
|
+
private let iconImageView = UIImageView()
|
|
12
|
+
private let actionButton = UIButton(type: .system)
|
|
13
|
+
private let nativeMediaView = MediaView()
|
|
14
|
+
|
|
15
|
+
init(nativeAd: NativeAd, template: String, style: [String: Any]) {
|
|
16
|
+
self.isSmall = template == "small"
|
|
17
|
+
super.init(frame: .zero)
|
|
18
|
+
clipsToBounds = true
|
|
19
|
+
configure(nativeAd: nativeAd, style: style)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
required init?(coder: NSCoder) {
|
|
23
|
+
fatalError("init(coder:) has not been implemented")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override func layoutSubviews() {
|
|
27
|
+
super.layoutSubviews()
|
|
28
|
+
let padding: CGFloat = 12
|
|
29
|
+
let available = bounds.insetBy(dx: padding, dy: padding)
|
|
30
|
+
|
|
31
|
+
attributionLabel.frame = CGRect(x: padding, y: padding, width: 28, height: 20)
|
|
32
|
+
|
|
33
|
+
if isSmall {
|
|
34
|
+
layoutDetails(
|
|
35
|
+
in: CGRect(
|
|
36
|
+
x: available.minX,
|
|
37
|
+
y: attributionLabel.frame.maxY + 4,
|
|
38
|
+
width: available.width,
|
|
39
|
+
height: max(0, available.maxY - attributionLabel.frame.maxY - 4)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
} else {
|
|
43
|
+
let mediaHeight = max(120, available.height * 0.58)
|
|
44
|
+
nativeMediaView.frame = CGRect(
|
|
45
|
+
x: available.minX,
|
|
46
|
+
y: available.minY,
|
|
47
|
+
width: available.width,
|
|
48
|
+
height: min(mediaHeight, available.height)
|
|
49
|
+
)
|
|
50
|
+
layoutDetails(
|
|
51
|
+
in: CGRect(
|
|
52
|
+
x: available.minX,
|
|
53
|
+
y: nativeMediaView.frame.maxY + 8,
|
|
54
|
+
width: available.width,
|
|
55
|
+
height: max(0, available.maxY - nativeMediaView.frame.maxY - 8)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
bringSubviewToFront(attributionLabel)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private func layoutDetails(in rect: CGRect) {
|
|
63
|
+
let headlineHeight = min(42, max(22, rect.height * 0.27))
|
|
64
|
+
headlineLabel.frame = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: headlineHeight)
|
|
65
|
+
let bodyHeight = bodyLabel.isHidden ? 0 : min(38, max(18, rect.height * 0.22))
|
|
66
|
+
bodyLabel.frame = CGRect(x: rect.minX, y: headlineLabel.frame.maxY, width: rect.width, height: bodyHeight)
|
|
67
|
+
|
|
68
|
+
let footerY = bodyLabel.frame.maxY + 4
|
|
69
|
+
let footerHeight = max(0, rect.maxY - footerY)
|
|
70
|
+
let iconSize = min(32, footerHeight)
|
|
71
|
+
iconImageView.frame = CGRect(x: rect.minX, y: footerY, width: iconSize, height: iconSize)
|
|
72
|
+
let actionWidth = actionButton.isHidden ? 0 : min(128, rect.width * 0.4)
|
|
73
|
+
actionButton.frame = CGRect(x: rect.maxX - actionWidth, y: footerY, width: actionWidth, height: min(40, footerHeight))
|
|
74
|
+
advertiserLabel.frame = CGRect(
|
|
75
|
+
x: iconImageView.isHidden ? rect.minX : iconImageView.frame.maxX + 6,
|
|
76
|
+
y: footerY,
|
|
77
|
+
width: max(0, actionButton.frame.minX - (iconImageView.isHidden ? rect.minX : iconImageView.frame.maxX + 6) - 6),
|
|
78
|
+
height: min(32, footerHeight)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private func configure(nativeAd: NativeAd, style: [String: Any]) {
|
|
83
|
+
backgroundColor = color(style["backgroundColor"] as? String) ?? .white
|
|
84
|
+
layer.cornerRadius = number(style["cornerRadius"], fallback: 0)
|
|
85
|
+
layer.borderWidth = number(style["borderWidth"], fallback: 0)
|
|
86
|
+
layer.borderColor = (color(style["borderColor"] as? String) ?? .clear).cgColor
|
|
87
|
+
|
|
88
|
+
configureAttribution()
|
|
89
|
+
|
|
90
|
+
headlineLabel.text = nativeAd.headline
|
|
91
|
+
headlineLabel.numberOfLines = 2
|
|
92
|
+
headlineLabel.font = .boldSystemFont(
|
|
93
|
+
ofSize: fontSize(style["headlineFontSize"], fallback: 16, range: 12 ... 24)
|
|
94
|
+
)
|
|
95
|
+
headlineLabel.textColor = color(style["headlineColor"] as? String) ?? .black
|
|
96
|
+
addSubview(headlineLabel)
|
|
97
|
+
headlineView = headlineLabel
|
|
98
|
+
|
|
99
|
+
bodyLabel.text = nativeAd.body
|
|
100
|
+
bodyLabel.numberOfLines = isSmall ? 1 : 2
|
|
101
|
+
bodyLabel.font = .systemFont(ofSize: fontSize(style["bodyFontSize"], fallback: 13, range: 10 ... 18))
|
|
102
|
+
bodyLabel.textColor = color(style["bodyColor"] as? String) ?? .darkGray
|
|
103
|
+
bodyLabel.isHidden = isSmall || nativeAd.body == nil
|
|
104
|
+
addSubview(bodyLabel)
|
|
105
|
+
bodyView = bodyLabel
|
|
106
|
+
|
|
107
|
+
advertiserLabel.text = nativeAd.advertiser
|
|
108
|
+
advertiserLabel.font = .systemFont(ofSize: 11)
|
|
109
|
+
advertiserLabel.numberOfLines = 1
|
|
110
|
+
advertiserLabel.isHidden = nativeAd.advertiser == nil
|
|
111
|
+
addSubview(advertiserLabel)
|
|
112
|
+
advertiserView = advertiserLabel
|
|
113
|
+
|
|
114
|
+
iconImageView.image = nativeAd.icon?.image
|
|
115
|
+
iconImageView.contentMode = .scaleAspectFill
|
|
116
|
+
iconImageView.clipsToBounds = true
|
|
117
|
+
iconImageView.isHidden = nativeAd.icon == nil
|
|
118
|
+
addSubview(iconImageView)
|
|
119
|
+
iconView = iconImageView
|
|
120
|
+
|
|
121
|
+
actionButton.setTitle(nativeAd.callToAction, for: .normal)
|
|
122
|
+
actionButton.titleLabel?.font = .systemFont(
|
|
123
|
+
ofSize: fontSize(style["callToActionFontSize"], fallback: 13, range: 12 ... 18)
|
|
124
|
+
)
|
|
125
|
+
actionButton.setTitleColor(color(style["callToActionTextColor"] as? String) ?? .white, for: .normal)
|
|
126
|
+
actionButton.backgroundColor = color(style["callToActionBackgroundColor"] as? String) ?? UIColor(
|
|
127
|
+
red: 33 / 255,
|
|
128
|
+
green: 150 / 255,
|
|
129
|
+
blue: 243 / 255,
|
|
130
|
+
alpha: 1
|
|
131
|
+
)
|
|
132
|
+
actionButton.isUserInteractionEnabled = false
|
|
133
|
+
actionButton.isHidden = nativeAd.callToAction == nil
|
|
134
|
+
addSubview(actionButton)
|
|
135
|
+
callToActionView = actionButton
|
|
136
|
+
|
|
137
|
+
configureMedia(nativeAd)
|
|
138
|
+
|
|
139
|
+
self.nativeAd = nativeAd
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private func configureAttribution() {
|
|
143
|
+
attributionLabel.text = "Ad"
|
|
144
|
+
attributionLabel.textAlignment = .center
|
|
145
|
+
attributionLabel.font = .boldSystemFont(ofSize: 10)
|
|
146
|
+
attributionLabel.textColor = .white
|
|
147
|
+
attributionLabel.backgroundColor = .darkGray
|
|
148
|
+
addSubview(attributionLabel)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private func configureMedia(_ nativeAd: NativeAd) {
|
|
152
|
+
guard !isSmall else { return }
|
|
153
|
+
nativeMediaView.mediaContent = nativeAd.mediaContent
|
|
154
|
+
nativeMediaView.contentMode = .scaleAspectFit
|
|
155
|
+
nativeMediaView.clipsToBounds = true
|
|
156
|
+
addSubview(nativeMediaView)
|
|
157
|
+
mediaView = nativeMediaView
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func color(_ value: String?) -> UIColor? {
|
|
161
|
+
guard var value = value?.trimmingCharacters(in: .whitespacesAndNewlines) else { return nil }
|
|
162
|
+
if value.hasPrefix("#") { value.removeFirst() }
|
|
163
|
+
guard value.count == 6 || value.count == 8, let number = UInt64(value, radix: 16) else { return nil }
|
|
164
|
+
if value.count == 8 {
|
|
165
|
+
return UIColor(
|
|
166
|
+
red: CGFloat((number >> 24) & 0xff) / 255,
|
|
167
|
+
green: CGFloat((number >> 16) & 0xff) / 255,
|
|
168
|
+
blue: CGFloat((number >> 8) & 0xff) / 255,
|
|
169
|
+
alpha: CGFloat(number & 0xff) / 255
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
return UIColor(
|
|
173
|
+
red: CGFloat((number >> 16) & 0xff) / 255,
|
|
174
|
+
green: CGFloat((number >> 8) & 0xff) / 255,
|
|
175
|
+
blue: CGFloat(number & 0xff) / 255,
|
|
176
|
+
alpha: 1
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private func number(_ value: Any?, fallback: CGFloat) -> CGFloat {
|
|
181
|
+
max(0, CGFloat((value as? NSNumber)?.doubleValue ?? Double(fallback)))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private func fontSize(_ value: Any?, fallback: CGFloat, range: ClosedRange<CGFloat>) -> CGFloat {
|
|
185
|
+
min(range.upperBound, max(range.lowerBound, number(value, fallback: fallback)))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -97,6 +97,7 @@ class AdRewardExecutor: NSObject, FullScreenContentDelegate {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
|
|
100
|
+
self.plugin?.resumeWebMedia(for: ad)
|
|
100
101
|
removeCurrentlyShowingAd()
|
|
101
102
|
NSLog("RewardFullScreenDelegate Ad failed to present full screen content with error \(error.localizedDescription).")
|
|
102
103
|
self.plugin?.notifyListeners(RewardAdPluginEvents.FailedToShow.rawValue, data: [
|
|
@@ -106,6 +107,7 @@ class AdRewardExecutor: NSObject, FullScreenContentDelegate {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
|
|
110
|
+
self.plugin?.suspendWebMedia(for: ad)
|
|
109
111
|
NSLog("RewardFullScreenDelegate Ad did present full screen content.")
|
|
110
112
|
self.plugin?.notifyListeners(RewardAdPluginEvents.Showed.rawValue, data: [:])
|
|
111
113
|
}
|
|
@@ -115,6 +117,7 @@ class AdRewardExecutor: NSObject, FullScreenContentDelegate {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
|
|
120
|
+
self.plugin?.resumeWebMedia(for: ad)
|
|
118
121
|
removeCurrentlyShowingAd()
|
|
119
122
|
NSLog("RewardFullScreenDelegate Ad did dismiss full screen content.")
|
|
120
123
|
self.plugin?.notifyListeners(RewardAdPluginEvents.Dismissed.rawValue, data: [:])
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Preserve existing public Swift API names.
|
|
2
|
+
// swiftlint:disable identifier_name
|
|
1
3
|
public enum RewardAdPluginEvents: String {
|
|
2
4
|
case adClicked = "onRewardedVideoAdClicked"
|
|
3
5
|
case Loaded = "onRewardedVideoAdLoaded"
|
|
@@ -8,3 +10,4 @@ public enum RewardAdPluginEvents: String {
|
|
|
8
10
|
case Rewarded = "onRewardedVideoAdReward"
|
|
9
11
|
case AdImpression = "onRewardedVideoAdImpression"
|
|
10
12
|
}
|
|
13
|
+
// swiftlint:enable identifier_name
|
|
@@ -97,6 +97,7 @@ class AdRewardInterstitialExecutor: NSObject, FullScreenContentDelegate {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
|
|
100
|
+
self.plugin?.resumeWebMedia(for: ad)
|
|
100
101
|
removeCurrentlyShowingAd()
|
|
101
102
|
NSLog("RewardFullScreenDelegate Ad failed to present full screen content with error \(error.localizedDescription).")
|
|
102
103
|
self.plugin?.notifyListeners(RewardInterstitialAdPluginEvents.FailedToShow.rawValue, data: [
|
|
@@ -106,11 +107,13 @@ class AdRewardInterstitialExecutor: NSObject, FullScreenContentDelegate {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
|
|
110
|
+
self.plugin?.suspendWebMedia(for: ad)
|
|
109
111
|
NSLog("RewardFullScreenDelegate Ad did present full screen content.")
|
|
110
112
|
self.plugin?.notifyListeners(RewardInterstitialAdPluginEvents.Showed.rawValue, data: [:])
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
|
|
116
|
+
self.plugin?.resumeWebMedia(for: ad)
|
|
114
117
|
removeCurrentlyShowingAd()
|
|
115
118
|
NSLog("RewardFullScreenDelegate Ad did dismiss full screen content.")
|
|
116
119
|
self.plugin?.notifyListeners(RewardInterstitialAdPluginEvents.Dismissed.rawValue, data: [:])
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Preserve existing public Swift API names.
|
|
2
|
+
// swiftlint:disable identifier_name
|
|
1
3
|
public enum RewardInterstitialAdPluginEvents: String {
|
|
2
4
|
case Loaded = "onRewardedInterstitialAdLoaded"
|
|
3
5
|
case FailedToLoad = "onRewardedInterstitialAdFailedToLoad"
|
|
@@ -7,3 +9,4 @@ public enum RewardInterstitialAdPluginEvents: String {
|
|
|
7
9
|
case Rewarded = "onRewardedInterstitialAdReward"
|
|
8
10
|
case AdImpression = "onRewardedInterstitialAdImpression"
|
|
9
11
|
}
|
|
12
|
+
// swiftlint:enable identifier_name
|
|
@@ -1,15 +1,87 @@
|
|
|
1
1
|
import XCTest
|
|
2
2
|
@testable import AdMobPlugin
|
|
3
3
|
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
final class AdMobPluginTests: XCTestCase {
|
|
5
|
+
@MainActor
|
|
6
|
+
func testNormalSuspensionAndResume() async {
|
|
7
|
+
await checkSuspensions([(0, true, true), (0, false, false)])
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@MainActor
|
|
11
|
+
func testFailedAdCannotReleaseAnotherAdsSuspension() async {
|
|
12
|
+
await checkSuspensions([(0, true, true), (1, false, nil), (0, false, false)])
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@MainActor
|
|
16
|
+
func testOnlyLastAdResumesMedia() async {
|
|
17
|
+
await checkSuspensions([(0, true, true), (1, true, nil), (0, false, nil), (1, false, false)])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@MainActor
|
|
21
|
+
private func checkSuspensions(_ steps: [(Int, Bool, Bool?)]) async {
|
|
22
|
+
let plugin = MediaTestPlugin()
|
|
23
|
+
let owners = [NSObject(), NSObject()]
|
|
24
|
+
|
|
25
|
+
for (owner, suspend, expected) in steps {
|
|
26
|
+
let called = expectation(description: "WebKit suspension: \(String(describing: expected))")
|
|
27
|
+
called.isInverted = expected == nil
|
|
28
|
+
plugin.onSuspension = { actual in
|
|
29
|
+
XCTAssertEqual(actual, expected)
|
|
30
|
+
called.fulfill()
|
|
31
|
+
}
|
|
32
|
+
if suspend {
|
|
33
|
+
plugin.suspendWebMedia(for: owners[owner])
|
|
34
|
+
} else {
|
|
35
|
+
plugin.resumeWebMedia(for: owners[owner])
|
|
36
|
+
}
|
|
37
|
+
await fulfillment(of: [called], timeout: expected == nil ? 0.1 : 2)
|
|
38
|
+
}
|
|
39
|
+
plugin.onSuspension = nil
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func testNativeFeedSessionsEnforceCapacityAndAllowReplacement() {
|
|
43
|
+
let sessions = NativeAdFeedSessions()
|
|
44
|
+
|
|
45
|
+
XCTAssertTrue(sessions.start(feedId: "first", sessionId: "session-1"))
|
|
46
|
+
XCTAssertTrue(sessions.start(feedId: "second", sessionId: "session-2"))
|
|
47
|
+
XCTAssertFalse(sessions.start(feedId: "third", sessionId: "session-3"))
|
|
48
|
+
XCTAssertTrue(sessions.start(feedId: "first", sessionId: "replacement"))
|
|
49
|
+
XCTAssertTrue(sessions.isCurrent(feedId: "first", sessionId: "replacement"))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func testNativeFeedSessionsRejectStalePlacementSequences() {
|
|
53
|
+
let sessions = NativeAdFeedSessions()
|
|
54
|
+
XCTAssertTrue(sessions.start(feedId: "feed", sessionId: "session"))
|
|
55
|
+
|
|
56
|
+
XCTAssertTrue(sessions.accepts(feedId: "feed", sequence: 1))
|
|
57
|
+
XCTAssertFalse(sessions.accepts(feedId: "feed", sequence: 1))
|
|
58
|
+
XCTAssertFalse(sessions.accepts(feedId: "feed", sequence: 0))
|
|
59
|
+
XCTAssertTrue(sessions.accepts(feedId: "feed", sequence: 2))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func testNativePlacementRejectsWrongFeedAndMalformedRect() {
|
|
63
|
+
let valid: [String: Any] = [
|
|
64
|
+
"visible": true,
|
|
65
|
+
"feedId": "feed",
|
|
66
|
+
"slotKey": "slot",
|
|
67
|
+
"generation": 1,
|
|
68
|
+
"rect": ["x": 0, "y": 0, "width": 144, "height": 300],
|
|
69
|
+
"clipRect": ["x": 0, "y": 0, "width": 144, "height": 300]
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
XCTAssertNotNil(NativeAdPlacementValue(valid, expectedFeedId: "feed"))
|
|
73
|
+
XCTAssertNil(NativeAdPlacementValue(valid, expectedFeedId: "another-feed"))
|
|
74
|
+
var malformed = valid
|
|
75
|
+
malformed["rect"] = ["x": 0, "y": 0, "width": 0, "height": 300]
|
|
76
|
+
XCTAssertNil(NativeAdPlacementValue(malformed, expectedFeedId: "feed"))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private final class MediaTestPlugin: AdMobPlugin {
|
|
81
|
+
var onSuspension: ((Bool) -> Void)?
|
|
82
|
+
|
|
83
|
+
@MainActor
|
|
84
|
+
override func setWebMediaPlaybackSuspended(_ suspended: Bool) {
|
|
85
|
+
onSuspension?(suspended)
|
|
14
86
|
}
|
|
15
87
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import WebKit
|
|
3
|
+
@testable import AdMobPlugin
|
|
4
|
+
|
|
5
|
+
final class NativeAdScrollTrackerTests: XCTestCase {
|
|
6
|
+
@MainActor
|
|
7
|
+
func testScrollClipsPlacementsAndReleasingTrackerStopsUpdates() throws {
|
|
8
|
+
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 640))
|
|
9
|
+
let web = WKWebView(frame: window.bounds)
|
|
10
|
+
window.addSubview(web)
|
|
11
|
+
let scroll = UIScrollView(frame: CGRect(x: 0, y: 80, width: 320, height: 500))
|
|
12
|
+
scroll.contentSize = CGSize(width: 320, height: 1200)
|
|
13
|
+
web.addSubview(scroll)
|
|
14
|
+
let placement = try XCTUnwrap(NativeAdPlacementValue([
|
|
15
|
+
"feedId": "feed", "slotKey": "ad", "generation": 1, "visible": true,
|
|
16
|
+
"rect": ["x": 10, "y": 400, "width": 300, "height": 320],
|
|
17
|
+
"clipRect": ["x": 10, "y": 400, "width": 300, "height": 320]
|
|
18
|
+
], expectedFeedId: "feed"))
|
|
19
|
+
let container: [String: Any] = [
|
|
20
|
+
"rect": ["x": 0, "y": 80, "width": 320, "height": 500],
|
|
21
|
+
"clipRect": ["x": 0, "y": 100, "width": 320, "height": 480],
|
|
22
|
+
"contentWidth": 320.0, "contentHeight": 1200.0
|
|
23
|
+
]
|
|
24
|
+
var rendered: [NativeAdPlacementValue] = []
|
|
25
|
+
var updates = 0
|
|
26
|
+
var tracker: NativeAdScrollTracker? = try XCTUnwrap(NativeAdScrollTracker(
|
|
27
|
+
container: container, webView: web, placements: [placement],
|
|
28
|
+
render: { rendered = $0; updates += 1 }
|
|
29
|
+
))
|
|
30
|
+
tracker?.update()
|
|
31
|
+
XCTAssertEqual(rendered.first?.clip, CGRect(x: 10, y: 480, width: 300, height: 100))
|
|
32
|
+
scroll.contentOffset.y = 450
|
|
33
|
+
XCTAssertEqual(rendered.first?.rect, CGRect(x: 10, y: 30, width: 300, height: 320))
|
|
34
|
+
XCTAssertEqual(rendered.first?.clip, CGRect(x: 10, y: 100, width: 300, height: 250))
|
|
35
|
+
scroll.contentOffset.y = 800
|
|
36
|
+
XCTAssertTrue(rendered.isEmpty)
|
|
37
|
+
tracker = nil
|
|
38
|
+
let previousUpdates = updates
|
|
39
|
+
scroll.contentOffset.y = 0
|
|
40
|
+
XCTAssertEqual(updates, previousUpdates)
|
|
41
|
+
|
|
42
|
+
let duplicate = UIScrollView(frame: scroll.frame)
|
|
43
|
+
duplicate.contentSize = scroll.contentSize
|
|
44
|
+
web.addSubview(duplicate)
|
|
45
|
+
XCTAssertNil(NativeAdScrollTracker(container: container, webView: web, placements: [placement], render: { _ in }))
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor-community/admob",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "8.2.
|
|
4
|
+
"version": "8.2.1-beta.pr490.shaf4a3cab095f4",
|
|
5
5
|
"description": "A native plugin for AdMob",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
7
7
|
"module": "dist/esm/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
35
35
|
"verify:ios": "xcodebuild -scheme CapacitorCommunityAdmob -destination generic/platform=iOS",
|
|
36
|
+
"test:ios": "xcodebuild test -scheme CapacitorCommunityAdmob",
|
|
36
37
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
37
38
|
"verify:web": "npm run build",
|
|
38
39
|
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
@@ -67,11 +68,13 @@
|
|
|
67
68
|
"rimraf": "^6.1.0",
|
|
68
69
|
"rollup": "^4.53.2",
|
|
69
70
|
"swiftlint": "^2.0.0",
|
|
70
|
-
"typescript": "^5.9.3"
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"@rollup/plugin-node-resolve": "^16.0.3"
|
|
71
73
|
},
|
|
72
74
|
"dependencies": {
|
|
73
75
|
"@capacitor/core": "^8.0.0",
|
|
74
|
-
"@rdlabo/capacitor-docgen": "^0.4.1"
|
|
76
|
+
"@rdlabo/capacitor-docgen": "^0.4.1",
|
|
77
|
+
"@webview-native-ui/engine": "1.0.0"
|
|
75
78
|
},
|
|
76
79
|
"lint-staged": {
|
|
77
80
|
"*.{ts}": [
|
|
@@ -84,7 +87,6 @@
|
|
|
84
87
|
]
|
|
85
88
|
},
|
|
86
89
|
"prettier": "@ionic/prettier-config",
|
|
87
|
-
"swiftlint": "@ionic/swiftlint-config",
|
|
88
90
|
"eslintConfig": {
|
|
89
91
|
"extends": "@ionic/eslint-config/recommended"
|
|
90
92
|
},
|