@ququplay/admob 8.0.9 → 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 (36) hide show
  1. package/README.md +178 -0
  2. package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +26 -0
  3. package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdExecutor.java +336 -0
  4. package/android/src/main/java/com/getcapacitor/community/admob/nativead/NativeAdPluginEvents.kt +9 -0
  5. package/android/src/main/res/layout/admob_native_ad_strip.xml +112 -0
  6. package/android/src/main/res/values/strings.xml +4 -0
  7. package/android/src/main/res/values-pl/strings.xml +4 -0
  8. package/dist/docs.json +483 -0
  9. package/dist/esm/definitions.d.ts +2 -1
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/index.d.ts +1 -0
  12. package/dist/esm/index.js +1 -0
  13. package/dist/esm/index.js.map +1 -1
  14. package/dist/esm/native-ad/index.d.ts +3 -0
  15. package/dist/esm/native-ad/index.js +4 -0
  16. package/dist/esm/native-ad/index.js.map +1 -0
  17. package/dist/esm/native-ad/native-ad-definitions.interface.d.ts +68 -0
  18. package/dist/esm/native-ad/native-ad-definitions.interface.js +2 -0
  19. package/dist/esm/native-ad/native-ad-definitions.interface.js.map +1 -0
  20. package/dist/esm/native-ad/native-ad-options.interface.d.ts +35 -0
  21. package/dist/esm/native-ad/native-ad-options.interface.js +2 -0
  22. package/dist/esm/native-ad/native-ad-options.interface.js.map +1 -0
  23. package/dist/esm/native-ad/native-ad-plugin-events.enum.d.ts +10 -0
  24. package/dist/esm/native-ad/native-ad-plugin-events.enum.js +13 -0
  25. package/dist/esm/native-ad/native-ad-plugin-events.enum.js.map +1 -0
  26. package/dist/esm/web.d.ts +4 -0
  27. package/dist/esm/web.js +9 -0
  28. package/dist/esm/web.js.map +1 -1
  29. package/dist/plugin.cjs.js +22 -0
  30. package/dist/plugin.cjs.js.map +1 -1
  31. package/dist/plugin.js +22 -0
  32. package/dist/plugin.js.map +1 -1
  33. package/ios/Sources/AdMobPlugin/AdMobPlugin.swift +30 -0
  34. package/ios/Sources/AdMobPlugin/NativeAd/NativeAdExecutor.swift +306 -0
  35. package/ios/Sources/AdMobPlugin/NativeAd/NativeAdPluginEvents.swift +7 -0
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -355,6 +355,14 @@ AdMob.addListener(RewardAdPluginEvents.Rewarded, async () => {
355
355
  * [`addListener(InterstitialAdPluginEvents.Dismissed, ...)`](#addlistenerinterstitialadplugineventsdismissed-)
356
356
  * [`addListener(InterstitialAdPluginEvents.FailedToShow, ...)`](#addlistenerinterstitialadplugineventsfailedtoshow-)
357
357
  * [`addListener(InterstitialAdPluginEvents.Showed, ...)`](#addlistenerinterstitialadplugineventsshowed-)
358
+ * [`showNativeAd(...)`](#shownativead)
359
+ * [`hideNativeAd()`](#hidenativead)
360
+ * [`removeNativeAd()`](#removenativead)
361
+ * [`addListener(NativeAdPluginEvents.SizeChanged, ...)`](#addlistenernativeadplugineventssizechanged-)
362
+ * [`addListener(NativeAdPluginEvents.Loaded, ...)`](#addlistenernativeadplugineventsloaded-)
363
+ * [`addListener(NativeAdPluginEvents.FailedToLoad, ...)`](#addlistenernativeadplugineventsfailedtoload-)
364
+ * [`addListener(NativeAdPluginEvents.Clicked, ...)`](#addlistenernativeadplugineventsclicked-)
365
+ * [`addListener(NativeAdPluginEvents.AdImpression, ...)`](#addlistenernativeadplugineventsadimpression-)
358
366
  * [`prepareRewardVideoAd(...)`](#preparerewardvideoad)
359
367
  * [`showRewardVideoAd()`](#showrewardvideoad)
360
368
  * [`addListener(RewardAdPluginEvents.FailedToLoad, ...)`](#addlistenerrewardadplugineventsfailedtoload-)
@@ -807,6 +815,141 @@ addListener(eventName: InterstitialAdPluginEvents.Showed, listenerFunc: () => vo
807
815
  --------------------
808
816
 
809
817
 
818
+ ### showNativeAd(...)
819
+
820
+ ```typescript
821
+ showNativeAd(options: NativeAdOptions) => Promise<void>
822
+ ```
823
+
824
+ Show a native ad as a strip across the top of the screen.
825
+ Shows the ad loaded before when there is one and it has not expired, otherwise loads a new one.
826
+ Android and iOS; resolves without showing anything on the web.
827
+
828
+ | Param | Type | Description |
829
+ | ------------- | ----------------------------------------------------------- | ---------------------------------------------- |
830
+ | **`options`** | <code><a href="#nativeadoptions">NativeAdOptions</a></code> | <a href="#nativeadoptions">NativeAdOptions</a> |
831
+
832
+ **Since:** 8.1.0
833
+
834
+ --------------------
835
+
836
+
837
+ ### hideNativeAd()
838
+
839
+ ```typescript
840
+ hideNativeAd() => Promise<void>
841
+ ```
842
+
843
+ Hide the native ad but keep it, so showNativeAd brings back the same ad.
844
+
845
+ **Since:** 8.1.0
846
+
847
+ --------------------
848
+
849
+
850
+ ### removeNativeAd()
851
+
852
+ ```typescript
853
+ removeNativeAd() => Promise<void>
854
+ ```
855
+
856
+ Destroy the native ad, remove it from screen.
857
+
858
+ **Since:** 8.1.0
859
+
860
+ --------------------
861
+
862
+
863
+ ### addListener(NativeAdPluginEvents.SizeChanged, ...)
864
+
865
+ ```typescript
866
+ addListener(eventName: NativeAdPluginEvents.SizeChanged, listenerFunc: (info: AdMobBannerSize) => void) => Promise<PluginListenerHandle>
867
+ ```
868
+
869
+ | Param | Type | Description |
870
+ | ------------------ | --------------------------------------------------------------------------------- | ------------------- |
871
+ | **`eventName`** | <code><a href="#nativeadpluginevents">NativeAdPluginEvents.SizeChanged</a></code> | nativeAdSizeChanged |
872
+ | **`listenerFunc`** | <code>(info: <a href="#admobbannersize">AdMobBannerSize</a>) =&gt; void</code> | |
873
+
874
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
875
+
876
+ **Since:** 8.1.0
877
+
878
+ --------------------
879
+
880
+
881
+ ### addListener(NativeAdPluginEvents.Loaded, ...)
882
+
883
+ ```typescript
884
+ addListener(eventName: NativeAdPluginEvents.Loaded, listenerFunc: () => void) => Promise<PluginListenerHandle>
885
+ ```
886
+
887
+ | Param | Type | Description |
888
+ | ------------------ | ---------------------------------------------------------------------------- | -------------- |
889
+ | **`eventName`** | <code><a href="#nativeadpluginevents">NativeAdPluginEvents.Loaded</a></code> | nativeAdLoaded |
890
+ | **`listenerFunc`** | <code>() =&gt; void</code> | |
891
+
892
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
893
+
894
+ **Since:** 8.1.0
895
+
896
+ --------------------
897
+
898
+
899
+ ### addListener(NativeAdPluginEvents.FailedToLoad, ...)
900
+
901
+ ```typescript
902
+ addListener(eventName: NativeAdPluginEvents.FailedToLoad, listenerFunc: (info: AdMobError) => void) => Promise<PluginListenerHandle>
903
+ ```
904
+
905
+ | Param | Type | Description |
906
+ | ------------------ | ---------------------------------------------------------------------------------- | -------------------- |
907
+ | **`eventName`** | <code><a href="#nativeadpluginevents">NativeAdPluginEvents.FailedToLoad</a></code> | nativeAdFailedToLoad |
908
+ | **`listenerFunc`** | <code>(info: <a href="#admoberror">AdMobError</a>) =&gt; void</code> | |
909
+
910
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
911
+
912
+ **Since:** 8.1.0
913
+
914
+ --------------------
915
+
916
+
917
+ ### addListener(NativeAdPluginEvents.Clicked, ...)
918
+
919
+ ```typescript
920
+ addListener(eventName: NativeAdPluginEvents.Clicked, listenerFunc: () => void) => Promise<PluginListenerHandle>
921
+ ```
922
+
923
+ | Param | Type | Description |
924
+ | ------------------ | ----------------------------------------------------------------------------- | --------------- |
925
+ | **`eventName`** | <code><a href="#nativeadpluginevents">NativeAdPluginEvents.Clicked</a></code> | nativeAdClicked |
926
+ | **`listenerFunc`** | <code>() =&gt; void</code> | |
927
+
928
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
929
+
930
+ **Since:** 8.1.0
931
+
932
+ --------------------
933
+
934
+
935
+ ### addListener(NativeAdPluginEvents.AdImpression, ...)
936
+
937
+ ```typescript
938
+ addListener(eventName: NativeAdPluginEvents.AdImpression, listenerFunc: () => void) => Promise<PluginListenerHandle>
939
+ ```
940
+
941
+ | Param | Type | Description |
942
+ | ------------------ | ---------------------------------------------------------------------------------- | ------------------ |
943
+ | **`eventName`** | <code><a href="#nativeadpluginevents">NativeAdPluginEvents.AdImpression</a></code> | nativeAdImpression |
944
+ | **`listenerFunc`** | <code>() =&gt; void</code> | |
945
+
946
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
947
+
948
+ **Since:** 8.1.0
949
+
950
+ --------------------
951
+
952
+
810
953
  ### prepareRewardVideoAd(...)
811
954
 
812
955
  ```typescript
@@ -1182,6 +1325,30 @@ https://developers.google.com/android/reference/com/google/android/gms/ads/AdErr
1182
1325
  | **`immersiveMode`** | <code>boolean</code> | Sets a flag that controls if this interstitial or reward object will be displayed in immersive mode. Call this method before show. During show, if this flag is on and immersive mode is supported, SYSTEM_UI_FLAG_IMMERSIVE_STICKY &SYSTEM_UI_FLAG_HIDE_NAVIGATION will be turned on for interstitial or reward ad. | | 7.0.3 |
1183
1326
 
1184
1327
 
1328
+ #### NativeAdOptions
1329
+
1330
+ | Prop | Type | Description | Default | Since |
1331
+ | ------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
1332
+ | **`style`** | <code><a href="#nativeadstyle">NativeAdStyle</a></code> | Colours of the native ad strip, as `#RRGGBB` (or `#AARRGGBB`). | | 8.1.0 |
1333
+ | **`adId`** | <code>string</code> | The ad unit ID that you want to request | | 1.1.2 |
1334
+ | **`isTesting`** | <code>boolean</code> | You can use test mode of ad. | <code>false</code> | 1.1.2 |
1335
+ | **`margin`** | <code>number</code> | Margin Banner. Default is 0px; If position is BOTTOM_CENTER, margin is be margin-bottom. If position is TOP_CENTER, margin is be margin-top. | <code>0</code> | 1.1.2 |
1336
+ | **`npa`** | <code>boolean</code> | The default behavior of the Google Mobile Ads SDK is to serve personalized ads. Set this to true to request Non-Personalized Ads | <code>false</code> | 1.2.0 |
1337
+ | **`immersiveMode`** | <code>boolean</code> | Sets a flag that controls if this interstitial or reward object will be displayed in immersive mode. Call this method before show. During show, if this flag is on and immersive mode is supported, SYSTEM_UI_FLAG_IMMERSIVE_STICKY &SYSTEM_UI_FLAG_HIDE_NAVIGATION will be turned on for interstitial or reward ad. | | 7.0.3 |
1338
+
1339
+
1340
+ #### NativeAdStyle
1341
+
1342
+ | Prop | Type | Description |
1343
+ | ------------------------ | ------------------- | ------------------------------------------------------------- |
1344
+ | **`backgroundColor`** | <code>string</code> | Background of the strip. Defaults to white. |
1345
+ | **`textColor`** | <code>string</code> | Headline text. Defaults to black. |
1346
+ | **`secondaryTextColor`** | <code>string</code> | Body text. Defaults to textColor. |
1347
+ | **`buttonColor`** | <code>string</code> | Call-to-action button and "Ad" badge background. |
1348
+ | **`buttonTextColor`** | <code>string</code> | Call-to-action button and "Ad" badge text. Defaults to white. |
1349
+ | **`dividerColor`** | <code>string</code> | Hairline along the bottom of the strip. Defaults to none. |
1350
+
1351
+
1185
1352
  #### RewardAdOptions
1186
1353
 
1187
1354
  | Prop | Type | Description | Default | Since |
@@ -1331,6 +1498,17 @@ From T, pick a set of properties whose keys are in the union K
1331
1498
  | **`Dismissed`** | <code>'interstitialAdDismissed'</code> | Emits when the Interstitial ad is not visible to the user anymore. |
1332
1499
 
1333
1500
 
1501
+ #### NativeAdPluginEvents
1502
+
1503
+ | Members | Value | Description |
1504
+ | ------------------ | ----------------------------------- | ------------------------------------------------------- |
1505
+ | **`SizeChanged`** | <code>'nativeAdSizeChanged'</code> | The strip's size in dp; 0 when it is hidden or removed. |
1506
+ | **`Loaded`** | <code>'nativeAdLoaded'</code> | |
1507
+ | **`FailedToLoad`** | <code>'nativeAdFailedToLoad'</code> | |
1508
+ | **`Clicked`** | <code>'nativeAdClicked'</code> | |
1509
+ | **`AdImpression`** | <code>'nativeAdImpression'</code> | |
1510
+
1511
+
1334
1512
  #### RewardAdPluginEvents
1335
1513
 
1336
1514
  | Members | Value | Description |
@@ -13,6 +13,7 @@ import com.getcapacitor.community.admob.consent.AdConsentExecutor;
13
13
  import com.getcapacitor.community.admob.helpers.AuthorizationStatusEnum;
14
14
  import com.getcapacitor.community.admob.interstitial.AdInterstitialExecutor;
15
15
  import com.getcapacitor.community.admob.interstitial.InterstitialAdCallbackAndListeners;
16
+ import com.getcapacitor.community.admob.nativead.NativeAdExecutor;
16
17
  import com.getcapacitor.community.admob.rewarded.AdRewardExecutor;
17
18
  import com.getcapacitor.community.admob.rewardedinterstitial.AdRewardInterstitialExecutor;
18
19
  import com.google.android.gms.ads.MobileAds;
@@ -34,6 +35,12 @@ public class AdMob extends Plugin {
34
35
  this::notifyListeners,
35
36
  getLogTag()
36
37
  );
38
+ private final NativeAdExecutor nativeAdExecutor = new NativeAdExecutor(
39
+ this::getContext,
40
+ this::getActivity,
41
+ this::notifyListeners,
42
+ getLogTag()
43
+ );
37
44
  private final AdRewardExecutor adRewardExecutor = new AdRewardExecutor(
38
45
  this::getContext,
39
46
  this::getActivity,
@@ -75,6 +82,7 @@ public class AdMob extends Plugin {
75
82
  }
76
83
  );
77
84
  bannerExecutor.initialize();
85
+ nativeAdExecutor.initialize();
78
86
  call.resolve();
79
87
  } catch (Exception ex) {
80
88
  call.reject(ex.getLocalizedMessage(), ex);
@@ -160,6 +168,24 @@ public class AdMob extends Plugin {
160
168
  bannerExecutor.removeBanner(call);
161
169
  }
162
170
 
171
+ // Show a native ad across the top of the screen, loading one when there is none or it expired
172
+ @PluginMethod
173
+ public void showNativeAd(final PluginCall call) {
174
+ nativeAdExecutor.showNativeAd(call);
175
+ }
176
+
177
+ // Hide the native ad but keep it, so showing it again brings back the same ad
178
+ @PluginMethod
179
+ public void hideNativeAd(final PluginCall call) {
180
+ nativeAdExecutor.hideNativeAd(call);
181
+ }
182
+
183
+ // Destroy the native ad, remove it from screen.
184
+ @PluginMethod
185
+ public void removeNativeAd(final PluginCall call) {
186
+ nativeAdExecutor.removeNativeAd(call);
187
+ }
188
+
163
189
  @PluginMethod
164
190
  public void prepareInterstitial(final PluginCall call) {
165
191
  adInterstitialExecutor.prepareInterstitial(call, this::notifyListeners);
@@ -0,0 +1,336 @@
1
+ package com.getcapacitor.community.admob.nativead;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Context;
5
+ import android.graphics.Color;
6
+ import android.graphics.drawable.GradientDrawable;
7
+ import android.os.Build;
8
+ import android.os.SystemClock;
9
+ import android.util.Log;
10
+ import android.view.Gravity;
11
+ import android.view.LayoutInflater;
12
+ import android.view.View;
13
+ import android.view.ViewGroup;
14
+ import android.view.ViewTreeObserver;
15
+ import android.widget.Button;
16
+ import android.widget.ImageView;
17
+ import android.widget.TextView;
18
+ import androidx.annotation.NonNull;
19
+ import androidx.coordinatorlayout.widget.CoordinatorLayout;
20
+ import androidx.core.util.Supplier;
21
+ import androidx.core.view.ViewCompat;
22
+ import androidx.core.view.WindowInsetsCompat;
23
+ import com.getcapacitor.JSObject;
24
+ import com.getcapacitor.PluginCall;
25
+ import com.getcapacitor.community.admob.admob.R;
26
+ import com.getcapacitor.community.admob.helpers.AdViewIdHelper;
27
+ import com.getcapacitor.community.admob.helpers.RequestHelper;
28
+ import com.getcapacitor.community.admob.models.AdMobPluginError;
29
+ import com.getcapacitor.community.admob.models.AdOptions;
30
+ import com.getcapacitor.community.admob.models.Executor;
31
+ import com.google.android.gms.ads.AdListener;
32
+ import com.google.android.gms.ads.AdLoader;
33
+ import com.google.android.gms.ads.AdRequest;
34
+ import com.google.android.gms.ads.LoadAdError;
35
+ import com.google.android.gms.ads.nativead.NativeAd;
36
+ import com.google.android.gms.ads.nativead.NativeAdView;
37
+ import com.google.android.gms.common.util.BiConsumer;
38
+
39
+ /**
40
+ * A native ad drawn as a strip across the top of the screen, over the WebView, like a top banner.
41
+ * Hiding keeps the loaded ad, so showing it again brings back the same ad without a new request.
42
+ */
43
+ public class NativeAdExecutor extends Executor {
44
+
45
+ public static final String NATIVE_TESTER_ID = "ca-app-pub-3940256099942544/2247696110";
46
+
47
+ // A loaded native ad may be shown for an hour; a new one is loaded a little before that.
48
+ private static final long MAX_AD_AGE_MS = 55 * 60 * 1000;
49
+
50
+ private final JSObject emptyObject = new JSObject();
51
+ private ViewGroup mViewGroup;
52
+ private NativeAdView mAdView;
53
+ private NativeAd mNativeAd;
54
+ private String mLoadedAdId;
55
+ private long mLoadedAt;
56
+ private boolean mLoading;
57
+ private boolean mWantsVisible;
58
+ // The status bar can change height while the ad is up (rotation, display size), so its margin follows it.
59
+ private final ViewTreeObserver.OnGlobalLayoutListener insetListener = () -> {
60
+ if (mAdView != null) applyTopInset();
61
+ };
62
+
63
+ public NativeAdExecutor(
64
+ Supplier<Context> contextSupplier,
65
+ Supplier<Activity> activitySupplier,
66
+ BiConsumer<String, JSObject> notifyListenersFunction,
67
+ String pluginLogTag
68
+ ) {
69
+ super(contextSupplier, activitySupplier, notifyListenersFunction, pluginLogTag, "NativeAdExecutor");
70
+ }
71
+
72
+ public void initialize() {
73
+ mViewGroup = (ViewGroup) ((ViewGroup) activitySupplier.get().findViewById(android.R.id.content)).getChildAt(0);
74
+ }
75
+
76
+ public void showNativeAd(final PluginCall call) {
77
+ final AdOptions adOptions = AdOptions.getFactory().createGenericOptions(call, NATIVE_TESTER_ID);
78
+ final JSObject style = call.getObject("style", new JSObject());
79
+
80
+ activitySupplier
81
+ .get()
82
+ .runOnUiThread(() -> {
83
+ try {
84
+ ensureAdView();
85
+ applyStyle(style);
86
+ applyTopInset();
87
+ mWantsVisible = true;
88
+
89
+ final AdRequest adRequest = RequestHelper.createRequest(adOptions);
90
+ final String adId = AdViewIdHelper.getFinalAdId(adOptions, adRequest, logTag, contextSupplier.get());
91
+
92
+ if (mNativeAd != null && adId.equals(mLoadedAdId) && !isExpired()) {
93
+ setVisible(true);
94
+ } else {
95
+ load(adId, adRequest);
96
+ }
97
+
98
+ call.resolve();
99
+ } catch (Exception ex) {
100
+ call.reject(ex.getLocalizedMessage(), ex);
101
+ }
102
+ });
103
+ }
104
+
105
+ public void hideNativeAd(final PluginCall call) {
106
+ activitySupplier
107
+ .get()
108
+ .runOnUiThread(() -> {
109
+ mWantsVisible = false;
110
+ if (mAdView != null) {
111
+ setVisible(false);
112
+ }
113
+ call.resolve();
114
+ });
115
+ }
116
+
117
+ public void removeNativeAd(final PluginCall call) {
118
+ activitySupplier
119
+ .get()
120
+ .runOnUiThread(() -> {
121
+ mWantsVisible = false;
122
+ destroyNativeAd();
123
+
124
+ if (mAdView != null) {
125
+ activitySupplier.get().getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(insetListener);
126
+ mAdView.destroy();
127
+ mViewGroup.removeView(mAdView);
128
+ mAdView = null;
129
+ notifySize(0, 0);
130
+ Log.d(logTag, "Native AD Removed");
131
+ }
132
+
133
+ call.resolve();
134
+ });
135
+ }
136
+
137
+ private void load(String adId, AdRequest adRequest) {
138
+ if (mLoading) return;
139
+
140
+ mLoading = true;
141
+ Log.d(logTag, "Native Ad ID: " + adId);
142
+
143
+ final AdLoader adLoader = new AdLoader.Builder(contextSupplier.get(), adId)
144
+ .forNativeAd((nativeAd) -> {
145
+ mLoading = false;
146
+ final Activity activity = activitySupplier.get();
147
+
148
+ // Removed, or the app went away, while it loaded.
149
+ if (mAdView == null || activity == null || activity.isDestroyed()) {
150
+ nativeAd.destroy();
151
+ return;
152
+ }
153
+
154
+ destroyNativeAd();
155
+ mNativeAd = nativeAd;
156
+ mLoadedAdId = adId;
157
+ mLoadedAt = SystemClock.elapsedRealtime();
158
+ populate(nativeAd);
159
+ notifyListeners(NativeAdPluginEvents.Loaded.getWebEventName(), emptyObject);
160
+
161
+ if (mWantsVisible) {
162
+ setVisible(true);
163
+ }
164
+ })
165
+ .withAdListener(
166
+ new AdListener() {
167
+ @Override
168
+ public void onAdFailedToLoad(@NonNull LoadAdError adError) {
169
+ mLoading = false;
170
+
171
+ // An expired ad may not be shown again; a fresh one keeps showing.
172
+ if (mNativeAd != null && isExpired()) {
173
+ destroyNativeAd();
174
+ }
175
+ if (mNativeAd == null && mAdView != null) {
176
+ setVisible(false);
177
+ }
178
+
179
+ notifyListeners(NativeAdPluginEvents.FailedToLoad.getWebEventName(), new AdMobPluginError(adError));
180
+ }
181
+
182
+ @Override
183
+ public void onAdClicked() {
184
+ notifyListeners(NativeAdPluginEvents.Clicked.getWebEventName(), emptyObject);
185
+ }
186
+
187
+ @Override
188
+ public void onAdImpression() {
189
+ notifyListeners(NativeAdPluginEvents.AdImpression.getWebEventName(), emptyObject);
190
+ }
191
+ }
192
+ )
193
+ .build();
194
+
195
+ adLoader.loadAd(adRequest);
196
+ }
197
+
198
+ private void ensureAdView() {
199
+ if (mAdView != null) return;
200
+
201
+ if (mViewGroup == null) {
202
+ initialize();
203
+ }
204
+
205
+ mAdView = (NativeAdView) LayoutInflater.from(activitySupplier.get()).inflate(R.layout.admob_native_ad_strip, mViewGroup, false);
206
+
207
+ final CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
208
+ ViewGroup.LayoutParams.MATCH_PARENT,
209
+ ViewGroup.LayoutParams.WRAP_CONTENT
210
+ );
211
+ params.gravity = Gravity.TOP;
212
+ mAdView.setLayoutParams(params);
213
+ mAdView.setVisibility(View.GONE);
214
+
215
+ mAdView.setHeadlineView(mAdView.findViewById(R.id.admob_native_ad_headline));
216
+ mAdView.setBodyView(mAdView.findViewById(R.id.admob_native_ad_body));
217
+ mAdView.setIconView(mAdView.findViewById(R.id.admob_native_ad_icon));
218
+ mAdView.setCallToActionView(mAdView.findViewById(R.id.admob_native_ad_call_to_action));
219
+ mAdView.setAdChoicesView(mAdView.findViewById(R.id.admob_native_ad_choices));
220
+
221
+ mViewGroup.addView(mAdView);
222
+ activitySupplier.get().getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(insetListener);
223
+ }
224
+
225
+ private void populate(NativeAd nativeAd) {
226
+ ((TextView) mAdView.getHeadlineView()).setText(nativeAd.getHeadline());
227
+
228
+ final TextView body = (TextView) mAdView.getBodyView();
229
+ body.setText(nativeAd.getBody());
230
+ body.setVisibility(nativeAd.getBody() == null ? View.GONE : View.VISIBLE);
231
+
232
+ final ImageView icon = (ImageView) mAdView.getIconView();
233
+ if (nativeAd.getIcon() == null) {
234
+ icon.setVisibility(View.GONE);
235
+ } else {
236
+ icon.setImageDrawable(nativeAd.getIcon().getDrawable());
237
+ icon.setVisibility(View.VISIBLE);
238
+ }
239
+
240
+ final Button callToAction = (Button) mAdView.getCallToActionView();
241
+ callToAction.setText(nativeAd.getCallToAction());
242
+ callToAction.setVisibility(nativeAd.getCallToAction() == null ? View.GONE : View.VISIBLE);
243
+
244
+ mAdView.setNativeAd(nativeAd);
245
+ }
246
+
247
+ private void applyStyle(JSObject style) {
248
+ final int backgroundColor = parseColor(style.getString("backgroundColor"), Color.WHITE);
249
+ final int textColor = parseColor(style.getString("textColor"), Color.BLACK);
250
+ final int secondaryTextColor = parseColor(style.getString("secondaryTextColor"), textColor);
251
+ final int buttonColor = parseColor(style.getString("buttonColor"), Color.rgb(26, 115, 232));
252
+ final int buttonTextColor = parseColor(style.getString("buttonTextColor"), Color.WHITE);
253
+ final int dividerColor = parseColor(style.getString("dividerColor"), Color.TRANSPARENT);
254
+ final float density = contextSupplier.get().getResources().getDisplayMetrics().density;
255
+
256
+ mAdView.setBackgroundColor(backgroundColor);
257
+ mAdView.findViewById(R.id.admob_native_ad_divider).setBackgroundColor(dividerColor);
258
+ ((TextView) mAdView.getHeadlineView()).setTextColor(textColor);
259
+ ((TextView) mAdView.getBodyView()).setTextColor(secondaryTextColor);
260
+
261
+ final TextView badge = mAdView.findViewById(R.id.admob_native_ad_badge);
262
+ badge.setBackground(rounded(buttonColor, 3 * density));
263
+ badge.setTextColor(buttonTextColor);
264
+
265
+ final Button callToAction = (Button) mAdView.getCallToActionView();
266
+ callToAction.setBackground(rounded(buttonColor, 18 * density));
267
+ callToAction.setTextColor(buttonTextColor);
268
+ }
269
+
270
+ // Below the status bar where the app draws under it (Android 15+), like a banner at the top.
271
+ private void applyTopInset() {
272
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return;
273
+
274
+ final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(activitySupplier.get().getWindow().getDecorView());
275
+ final int topInset = insets == null ? 0 : insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
276
+ final CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAdView.getLayoutParams();
277
+
278
+ if (params.topMargin != topInset) {
279
+ params.topMargin = topInset;
280
+ mAdView.setLayoutParams(params);
281
+ }
282
+ }
283
+
284
+ private void setVisible(boolean visible) {
285
+ mAdView.setVisibility(visible ? View.VISIBLE : View.GONE);
286
+
287
+ if (!visible) {
288
+ notifySize(0, 0);
289
+ return;
290
+ }
291
+
292
+ // The height is known once it is laid out.
293
+ mAdView.post(() -> {
294
+ if (mAdView == null || mAdView.getVisibility() != View.VISIBLE) return;
295
+
296
+ final float density = contextSupplier.get().getResources().getDisplayMetrics().density;
297
+ notifySize(Math.round(mAdView.getWidth() / density), Math.round(mAdView.getHeight() / density));
298
+ });
299
+ }
300
+
301
+ private void notifySize(int width, int height) {
302
+ final JSObject size = new JSObject();
303
+ size.put("width", width);
304
+ size.put("height", height);
305
+ notifyListeners(NativeAdPluginEvents.SizeChanged.getWebEventName(), size);
306
+ }
307
+
308
+ private void destroyNativeAd() {
309
+ if (mNativeAd == null) return;
310
+
311
+ mNativeAd.destroy();
312
+ mNativeAd = null;
313
+ mLoadedAdId = null;
314
+ }
315
+
316
+ private boolean isExpired() {
317
+ return SystemClock.elapsedRealtime() - mLoadedAt > MAX_AD_AGE_MS;
318
+ }
319
+
320
+ private static GradientDrawable rounded(int color, float radius) {
321
+ final GradientDrawable drawable = new GradientDrawable();
322
+ drawable.setColor(color);
323
+ drawable.setCornerRadius(radius);
324
+ return drawable;
325
+ }
326
+
327
+ private static int parseColor(String color, int fallback) {
328
+ if (color == null) return fallback;
329
+
330
+ try {
331
+ return Color.parseColor(color);
332
+ } catch (IllegalArgumentException ex) {
333
+ return fallback;
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,9 @@
1
+ package com.getcapacitor.community.admob.nativead
2
+
3
+ enum class NativeAdPluginEvents(val webEventName: String) {
4
+ SizeChanged("nativeAdSizeChanged"),
5
+ Loaded("nativeAdLoaded"),
6
+ FailedToLoad("nativeAdFailedToLoad"),
7
+ Clicked("nativeAdClicked"),
8
+ AdImpression("nativeAdImpression"),
9
+ }