@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.
Files changed (39) hide show
  1. package/Package.swift +1 -1
  2. package/QuquplayAdmob.podspec +1 -1
  3. package/README.md +178 -0
  4. package/android/build.gradle +1 -1
  5. package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +26 -0
  6. package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdExecutor.java +336 -0
  7. package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdPluginEvents.kt +9 -0
  8. package/android/src/main/res/layout/admob_native_ad_strip.xml +112 -0
  9. package/android/src/main/res/values/strings.xml +4 -0
  10. package/android/src/main/res/values-pl/strings.xml +4 -0
  11. package/dist/docs.json +483 -0
  12. package/dist/esm/definitions.d.ts +2 -1
  13. package/dist/esm/definitions.js.map +1 -1
  14. package/dist/esm/index.d.ts +1 -0
  15. package/dist/esm/index.js +1 -0
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/native-ad/index.d.ts +3 -0
  18. package/dist/esm/native-ad/index.js +4 -0
  19. package/dist/esm/native-ad/index.js.map +1 -0
  20. package/dist/esm/native-ad/native-ad-definitions.interface.d.ts +68 -0
  21. package/dist/esm/native-ad/native-ad-definitions.interface.js +2 -0
  22. package/dist/esm/native-ad/native-ad-definitions.interface.js.map +1 -0
  23. package/dist/esm/native-ad/native-ad-options.interface.d.ts +35 -0
  24. package/dist/esm/native-ad/native-ad-options.interface.js +2 -0
  25. package/dist/esm/native-ad/native-ad-options.interface.js.map +1 -0
  26. package/dist/esm/native-ad/native-ad-plugin-events.enum.d.ts +10 -0
  27. package/dist/esm/native-ad/native-ad-plugin-events.enum.js +13 -0
  28. package/dist/esm/native-ad/native-ad-plugin-events.enum.js.map +1 -0
  29. package/dist/esm/web.d.ts +4 -0
  30. package/dist/esm/web.js +9 -0
  31. package/dist/esm/web.js.map +1 -1
  32. package/dist/plugin.cjs.js +22 -0
  33. package/dist/plugin.cjs.js.map +1 -1
  34. package/dist/plugin.js +22 -0
  35. package/dist/plugin.js.map +1 -1
  36. package/ios/Sources/AdMobPlugin/AdMobPlugin.swift +30 -0
  37. package/ios/Sources/AdMobPlugin/NativeAd/NativeAdExecutor.swift +306 -0
  38. package/ios/Sources/AdMobPlugin/NativeAd/NativeAdPluginEvents.swift +7 -0
  39. 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
+ }
@@ -0,0 +1,7 @@
1
+ public enum NativeAdPluginEvents: String {
2
+ case SizeChanged = "nativeAdSizeChanged"
3
+ case Loaded = "nativeAdLoaded"
4
+ case FailedToLoad = "nativeAdFailedToLoad"
5
+ case Clicked = "nativeAdClicked"
6
+ case AdImpression = "nativeAdImpression"
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ququplay/admob",
3
- "version": "8.0.8",
3
+ "version": "8.1.0",
4
4
  "description": "A native plugin for AdMob",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",