@ququplay/admob 8.0.8 → 8.1.0
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/Package.swift +1 -1
- package/QuquplayAdmob.podspec +1 -1
- package/README.md +178 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +26 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdExecutor.java +336 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdPluginEvents.kt +9 -0
- package/android/src/main/res/layout/admob_native_ad_strip.xml +112 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/main/res/values-pl/strings.xml +4 -0
- package/dist/docs.json +483 -0
- package/dist/esm/definitions.d.ts +2 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/native-ad/index.d.ts +3 -0
- package/dist/esm/native-ad/index.js +4 -0
- package/dist/esm/native-ad/index.js.map +1 -0
- package/dist/esm/native-ad/native-ad-definitions.interface.d.ts +68 -0
- package/dist/esm/native-ad/native-ad-definitions.interface.js +2 -0
- package/dist/esm/native-ad/native-ad-definitions.interface.js.map +1 -0
- package/dist/esm/native-ad/native-ad-options.interface.d.ts +35 -0
- package/dist/esm/native-ad/native-ad-options.interface.js +2 -0
- package/dist/esm/native-ad/native-ad-options.interface.js.map +1 -0
- package/dist/esm/native-ad/native-ad-plugin-events.enum.d.ts +10 -0
- package/dist/esm/native-ad/native-ad-plugin-events.enum.js +13 -0
- package/dist/esm/native-ad/native-ad-plugin-events.enum.js.map +1 -0
- package/dist/esm/web.d.ts +4 -0
- package/dist/esm/web.js +9 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +22 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +22 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/AdMobPlugin/AdMobPlugin.swift +30 -0
- package/ios/Sources/AdMobPlugin/NativeAd/NativeAdExecutor.swift +306 -0
- package/ios/Sources/AdMobPlugin/NativeAd/NativeAdPluginEvents.swift +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import GoogleMobileAds
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A native ad drawn as a strip across the top of the screen, below the status bar, over the WebView like a top banner.
|
|
7
|
+
* Hiding keeps the loaded ad, so showing it again brings back the same ad without a new request.
|
|
8
|
+
*/
|
|
9
|
+
class NativeAdExecutor: NSObject, NativeAdLoaderDelegate, NativeAdDelegate {
|
|
10
|
+
weak var plugin: AdMobPlugin?
|
|
11
|
+
|
|
12
|
+
// A loaded native ad may be shown for an hour; a new one is loaded a little before that.
|
|
13
|
+
private static let maxAdAge: TimeInterval = 55 * 60
|
|
14
|
+
|
|
15
|
+
private var adView: NativeAdView?
|
|
16
|
+
private var nativeAd: NativeAd?
|
|
17
|
+
private var adLoader: AdLoader?
|
|
18
|
+
private var loadedAdUnitID: String?
|
|
19
|
+
private var loadedAt = Date.distantPast
|
|
20
|
+
private var wantsVisible = false
|
|
21
|
+
|
|
22
|
+
private let iconView = UIImageView()
|
|
23
|
+
private let badgeLabel = PaddedLabel()
|
|
24
|
+
private let headlineLabel = UILabel()
|
|
25
|
+
private let bodyLabel = UILabel()
|
|
26
|
+
private let callToActionLabel = PaddedLabel()
|
|
27
|
+
private let divider = UIView()
|
|
28
|
+
private let adChoicesView = AdChoicesView()
|
|
29
|
+
|
|
30
|
+
func showNativeAd(_ call: CAPPluginCall, _ request: Request, _ adUnitID: String) {
|
|
31
|
+
guard let rootViewController = plugin?.getRootVC() else {
|
|
32
|
+
call.reject("AdMob: no view to show the native ad in")
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ensureAdView(in: rootViewController.view)
|
|
37
|
+
applyStyle(call.getObject("style") ?? [:])
|
|
38
|
+
wantsVisible = true
|
|
39
|
+
|
|
40
|
+
if nativeAd != nil && loadedAdUnitID == adUnitID && !isExpired() {
|
|
41
|
+
setVisible(true)
|
|
42
|
+
} else {
|
|
43
|
+
load(adUnitID, request, rootViewController)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
call.resolve([:])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func hideNativeAd(_ call: CAPPluginCall) {
|
|
50
|
+
wantsVisible = false
|
|
51
|
+
if adView != nil {
|
|
52
|
+
setVisible(false)
|
|
53
|
+
}
|
|
54
|
+
call.resolve([:])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func removeNativeAd(_ call: CAPPluginCall) {
|
|
58
|
+
wantsVisible = false
|
|
59
|
+
nativeAd = nil
|
|
60
|
+
loadedAdUnitID = nil
|
|
61
|
+
|
|
62
|
+
if let adView = adView {
|
|
63
|
+
adView.removeFromSuperview()
|
|
64
|
+
self.adView = nil
|
|
65
|
+
notifySize(0, 0)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
call.resolve([:])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private func load(_ adUnitID: String, _ request: Request, _ rootViewController: UIViewController) {
|
|
72
|
+
if adLoader != nil { return }
|
|
73
|
+
|
|
74
|
+
NSLog("AdMob: Native Ad ID: \(adUnitID)")
|
|
75
|
+
let options = NativeAdViewAdOptions()
|
|
76
|
+
options.preferredAdChoicesPosition = .topRightCorner
|
|
77
|
+
|
|
78
|
+
let loader = AdLoader(adUnitID: adUnitID, rootViewController: rootViewController, adTypes: [.native], options: [options])
|
|
79
|
+
loader.delegate = self
|
|
80
|
+
adLoader = loader
|
|
81
|
+
loader.load(request)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
|
|
85
|
+
self.adLoader = nil
|
|
86
|
+
|
|
87
|
+
// Removed while it loaded.
|
|
88
|
+
guard let adView = adView else { return }
|
|
89
|
+
|
|
90
|
+
nativeAd.delegate = self
|
|
91
|
+
self.nativeAd = nativeAd
|
|
92
|
+
loadedAdUnitID = adLoader.adUnitID
|
|
93
|
+
loadedAt = Date()
|
|
94
|
+
populate(adView, nativeAd)
|
|
95
|
+
plugin?.notifyListeners(NativeAdPluginEvents.Loaded.rawValue, data: [:])
|
|
96
|
+
|
|
97
|
+
if wantsVisible {
|
|
98
|
+
setVisible(true)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func adLoader(_ adLoader: AdLoader, didFailToReceiveAdWithError error: Error) {
|
|
103
|
+
self.adLoader = nil
|
|
104
|
+
NSLog("AdMob: native ad failed to load: \(error.localizedDescription)")
|
|
105
|
+
|
|
106
|
+
// An expired ad may not be shown again; a fresh one keeps showing.
|
|
107
|
+
if nativeAd != nil && isExpired() {
|
|
108
|
+
nativeAd = nil
|
|
109
|
+
loadedAdUnitID = nil
|
|
110
|
+
}
|
|
111
|
+
if nativeAd == nil && adView != nil {
|
|
112
|
+
setVisible(false)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
plugin?.notifyListeners(NativeAdPluginEvents.FailedToLoad.rawValue, data: [
|
|
116
|
+
"code": (error as NSError).code,
|
|
117
|
+
"message": error.localizedDescription
|
|
118
|
+
])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func nativeAdDidRecordClick(_ nativeAd: NativeAd) {
|
|
122
|
+
plugin?.notifyListeners(NativeAdPluginEvents.Clicked.rawValue, data: [:])
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func nativeAdDidRecordImpression(_ nativeAd: NativeAd) {
|
|
126
|
+
plugin?.notifyListeners(NativeAdPluginEvents.AdImpression.rawValue, data: [:])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Icon, "Ad" badge, headline and body, the call to action and AdChoices in the top right corner; a hairline along
|
|
130
|
+
// the bottom separates it from the screen below. The wider trailing padding keeps the button clear of AdChoices.
|
|
131
|
+
private func ensureAdView(in parent: UIView) {
|
|
132
|
+
if adView != nil { return }
|
|
133
|
+
|
|
134
|
+
let adView = NativeAdView()
|
|
135
|
+
adView.translatesAutoresizingMaskIntoConstraints = false
|
|
136
|
+
adView.isHidden = true
|
|
137
|
+
|
|
138
|
+
iconView.contentMode = .scaleAspectFit
|
|
139
|
+
badgeLabel.text = Locale.preferredLanguages.first?.hasPrefix("pl") == true ? "Reklama" : "Ad"
|
|
140
|
+
badgeLabel.font = .boldSystemFont(ofSize: 10)
|
|
141
|
+
badgeLabel.insets = UIEdgeInsets(top: 1, left: 4, bottom: 1, right: 4)
|
|
142
|
+
badgeLabel.layer.cornerRadius = 3
|
|
143
|
+
badgeLabel.layer.masksToBounds = true
|
|
144
|
+
headlineLabel.font = .boldSystemFont(ofSize: 14)
|
|
145
|
+
bodyLabel.font = .systemFont(ofSize: 12)
|
|
146
|
+
callToActionLabel.font = .boldSystemFont(ofSize: 13)
|
|
147
|
+
callToActionLabel.insets = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
|
|
148
|
+
callToActionLabel.layer.cornerRadius = 18
|
|
149
|
+
callToActionLabel.layer.masksToBounds = true
|
|
150
|
+
callToActionLabel.textAlignment = .center
|
|
151
|
+
// The SDK handles taps on the ad's views.
|
|
152
|
+
callToActionLabel.isUserInteractionEnabled = false
|
|
153
|
+
|
|
154
|
+
let headlineRow = UIStackView(arrangedSubviews: [badgeLabel, headlineLabel])
|
|
155
|
+
headlineRow.spacing = 6
|
|
156
|
+
headlineRow.alignment = .center
|
|
157
|
+
let texts = UIStackView(arrangedSubviews: [headlineRow, bodyLabel])
|
|
158
|
+
texts.axis = .vertical
|
|
159
|
+
texts.spacing = 2
|
|
160
|
+
|
|
161
|
+
badgeLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
162
|
+
badgeLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
163
|
+
callToActionLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
164
|
+
callToActionLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
165
|
+
|
|
166
|
+
for view in [iconView, texts, callToActionLabel, divider, adChoicesView] as [UIView] {
|
|
167
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
|
168
|
+
adView.addSubview(view)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let content = UILayoutGuide()
|
|
172
|
+
adView.addLayoutGuide(content)
|
|
173
|
+
|
|
174
|
+
NSLayoutConstraint.activate([
|
|
175
|
+
content.topAnchor.constraint(equalTo: adView.topAnchor, constant: 6),
|
|
176
|
+
content.leadingAnchor.constraint(equalTo: adView.leadingAnchor, constant: 12),
|
|
177
|
+
content.trailingAnchor.constraint(equalTo: adView.trailingAnchor, constant: -20),
|
|
178
|
+
content.bottomAnchor.constraint(equalTo: divider.topAnchor, constant: -7),
|
|
179
|
+
content.heightAnchor.constraint(greaterThanOrEqualToConstant: 41),
|
|
180
|
+
|
|
181
|
+
iconView.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
|
182
|
+
iconView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
|
|
183
|
+
iconView.widthAnchor.constraint(equalToConstant: 40),
|
|
184
|
+
iconView.heightAnchor.constraint(equalToConstant: 40),
|
|
185
|
+
|
|
186
|
+
texts.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 10),
|
|
187
|
+
texts.centerYAnchor.constraint(equalTo: content.centerYAnchor),
|
|
188
|
+
texts.topAnchor.constraint(greaterThanOrEqualTo: content.topAnchor),
|
|
189
|
+
texts.bottomAnchor.constraint(lessThanOrEqualTo: content.bottomAnchor),
|
|
190
|
+
|
|
191
|
+
callToActionLabel.leadingAnchor.constraint(equalTo: texts.trailingAnchor, constant: 10),
|
|
192
|
+
callToActionLabel.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
|
193
|
+
callToActionLabel.centerYAnchor.constraint(equalTo: content.centerYAnchor),
|
|
194
|
+
callToActionLabel.heightAnchor.constraint(equalToConstant: 36),
|
|
195
|
+
callToActionLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 64),
|
|
196
|
+
callToActionLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 120),
|
|
197
|
+
|
|
198
|
+
divider.leadingAnchor.constraint(equalTo: adView.leadingAnchor),
|
|
199
|
+
divider.trailingAnchor.constraint(equalTo: adView.trailingAnchor),
|
|
200
|
+
divider.bottomAnchor.constraint(equalTo: adView.bottomAnchor),
|
|
201
|
+
divider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale),
|
|
202
|
+
|
|
203
|
+
adChoicesView.topAnchor.constraint(equalTo: adView.topAnchor),
|
|
204
|
+
adChoicesView.trailingAnchor.constraint(equalTo: adView.trailingAnchor),
|
|
205
|
+
adChoicesView.widthAnchor.constraint(equalToConstant: 15),
|
|
206
|
+
adChoicesView.heightAnchor.constraint(equalToConstant: 15)
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
adView.iconView = iconView
|
|
210
|
+
adView.headlineView = headlineLabel
|
|
211
|
+
adView.bodyView = bodyLabel
|
|
212
|
+
adView.callToActionView = callToActionLabel
|
|
213
|
+
adView.adChoicesView = adChoicesView
|
|
214
|
+
|
|
215
|
+
// Below the status bar, like a banner at the top; it follows the safe area when that changes (rotation).
|
|
216
|
+
parent.addSubview(adView)
|
|
217
|
+
NSLayoutConstraint.activate([
|
|
218
|
+
adView.topAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor),
|
|
219
|
+
adView.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
|
|
220
|
+
adView.trailingAnchor.constraint(equalTo: parent.trailingAnchor)
|
|
221
|
+
])
|
|
222
|
+
|
|
223
|
+
self.adView = adView
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func populate(_ adView: NativeAdView, _ nativeAd: NativeAd) {
|
|
227
|
+
headlineLabel.text = nativeAd.headline
|
|
228
|
+
bodyLabel.text = nativeAd.body
|
|
229
|
+
bodyLabel.isHidden = nativeAd.body == nil
|
|
230
|
+
iconView.image = nativeAd.icon?.image
|
|
231
|
+
iconView.isHidden = nativeAd.icon == nil
|
|
232
|
+
callToActionLabel.text = nativeAd.callToAction
|
|
233
|
+
callToActionLabel.isHidden = nativeAd.callToAction == nil
|
|
234
|
+
|
|
235
|
+
adView.nativeAd = nativeAd
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private func applyStyle(_ style: JSObject) {
|
|
239
|
+
let textColor = color(style["textColor"], .black)
|
|
240
|
+
|
|
241
|
+
adView?.backgroundColor = color(style["backgroundColor"], .white)
|
|
242
|
+
headlineLabel.textColor = textColor
|
|
243
|
+
bodyLabel.textColor = color(style["secondaryTextColor"], textColor)
|
|
244
|
+
badgeLabel.backgroundColor = color(style["buttonColor"], UIColor(red: 26 / 255, green: 115 / 255, blue: 232 / 255, alpha: 1))
|
|
245
|
+
badgeLabel.textColor = color(style["buttonTextColor"], .white)
|
|
246
|
+
callToActionLabel.backgroundColor = badgeLabel.backgroundColor
|
|
247
|
+
callToActionLabel.textColor = badgeLabel.textColor
|
|
248
|
+
divider.backgroundColor = color(style["dividerColor"], .clear)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private func setVisible(_ visible: Bool) {
|
|
252
|
+
guard let adView = adView else { return }
|
|
253
|
+
|
|
254
|
+
adView.isHidden = !visible
|
|
255
|
+
|
|
256
|
+
if !visible {
|
|
257
|
+
notifySize(0, 0)
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
adView.superview?.layoutIfNeeded()
|
|
262
|
+
notifySize(adView.frame.width, adView.frame.height)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private func notifySize(_ width: CGFloat, _ height: CGFloat) {
|
|
266
|
+
plugin?.notifyListeners(NativeAdPluginEvents.SizeChanged.rawValue, data: [
|
|
267
|
+
"width": Int(width.rounded()),
|
|
268
|
+
"height": Int(height.rounded())
|
|
269
|
+
])
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private func isExpired() -> Bool {
|
|
273
|
+
return Date().timeIntervalSince(loadedAt) > NativeAdExecutor.maxAdAge
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// "#RRGGBB" or "#AARRGGBB", as on Android.
|
|
277
|
+
private func color(_ value: JSValue?, _ fallback: UIColor) -> UIColor {
|
|
278
|
+
guard let string = value as? String, string.hasPrefix("#"),
|
|
279
|
+
let number = UInt64(string.dropFirst(), radix: 16) else { return fallback }
|
|
280
|
+
|
|
281
|
+
switch string.count {
|
|
282
|
+
case 7:
|
|
283
|
+
return UIColor(red: CGFloat((number >> 16) & 0xFF) / 255, green: CGFloat((number >> 8) & 0xFF) / 255,
|
|
284
|
+
blue: CGFloat(number & 0xFF) / 255, alpha: 1)
|
|
285
|
+
case 9:
|
|
286
|
+
return UIColor(red: CGFloat((number >> 16) & 0xFF) / 255, green: CGFloat((number >> 8) & 0xFF) / 255,
|
|
287
|
+
blue: CGFloat(number & 0xFF) / 255, alpha: CGFloat((number >> 24) & 0xFF) / 255)
|
|
288
|
+
default:
|
|
289
|
+
return fallback
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// A label with padding around its text, for the badge and the call to action.
|
|
295
|
+
class PaddedLabel: UILabel {
|
|
296
|
+
var insets = UIEdgeInsets.zero
|
|
297
|
+
|
|
298
|
+
override func drawText(in rect: CGRect) {
|
|
299
|
+
super.drawText(in: rect.inset(by: insets))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
override var intrinsicContentSize: CGSize {
|
|
303
|
+
let size = super.intrinsicContentSize
|
|
304
|
+
return CGSize(width: size.width + insets.left + insets.right, height: size.height + insets.top + insets.bottom)
|
|
305
|
+
}
|
|
306
|
+
}
|