diff --git a/README.md b/README.md index dea1fb0e..94876434 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

@capacitor-community/admob
AppOpenAdOptions |
+
+--------------------
+
+
+### showAppOpen()
+
+```typescript
+showAppOpen() => PromisePromise<{ value: boolean; }>
+
+--------------------
+
+
+### addListener(AppOpenAdPluginEvents, ...)
+
+```typescript
+addListener(eventName: AppOpenAdPluginEvents, listenerFunc: (...args: any[]) => void) => PromiseAppOpenAdPluginEvents |
+| **`listenerFunc`** | (...args: any[]) => void |
+
+**Returns:** Promise<PluginListenerHandle>
+
+--------------------
+
+
### Interfaces
@@ -1136,7 +1237,7 @@ When notice listener of OnAdLoaded, you can get banner size.
#### AdMobError
-For more information
+For more information
https://developers.google.com/android/reference/com/google/android/gms/ads/AdError
| Prop | Type | Description |
@@ -1196,7 +1297,7 @@ https://developers.google.com/android/reference/com/google/android/gms/ads/AdErr
#### AdMobRewardItem
-For more information
+For more information
https://developers.google.com/admob/android/rewarded-video-adapters?hl=en
| Prop | Type | Description |
@@ -1228,6 +1329,15 @@ https://developers.google.com/admob/android/rewarded-video-adapters?hl=en
| **`amount`** | number | Rewarded amount user got |
+#### AppOpenAdOptions
+
+| Prop | Type |
+| ---------------------- | -------------------- |
+| **`adUnitId`** | string |
+| **`showOnColdStart`** | boolean |
+| **`showOnForeground`** | boolean |
+
+
### Type Aliases
@@ -1240,7 +1350,7 @@ https://developers.google.com/admob/android/rewarded-video-adapters?hl=en
From T, pick a set of properties whose keys are in the union K
-{
[P in K]: T[P];
}
+{ [P in K]: T[P] }
### Enums
@@ -1354,6 +1464,17 @@ From T, pick a set of properties whose keys are in the union K
| **`Dismissed`** | 'onRewardedInterstitialAdDismissed' | Emits when the AdReward video is not visible to the user anymore. **Important**: This has nothing to do with the reward it self. This event will emits in this two cases: 1. The user starts the video ad but close it before the reward emit. 2. The user start the video and see it until end, then gets the reward and after that the ad is closed. |
| **`Rewarded`** | 'onRewardedInterstitialAdReward' | Emits when user get rewarded from AdReward |
+
+#### AppOpenAdPluginEvents
+
+| Members | Value |
+| ------------------ | ------------------------------------ |
+| **`Loaded`** | 'appOpenAdLoaded' |
+| **`FailedToLoad`** | 'appOpenAdFailedToLoad' |
+| **`Opened`** | 'appOpenAdOpened' |
+| **`Closed`** | 'appOpenAdClosed' |
+| **`FailedToShow`** | 'appOpenAdFailedToShow' |
+
## TROUBLE SHOOTING
diff --git a/android/src/main/java/com/getcapacitor/community/admob/AdMob.java b/android/src/main/java/com/getcapacitor/community/admob/AdMob.java
index b8b36c79..1ade459e 100644
--- a/android/src/main/java/com/getcapacitor/community/admob/AdMob.java
+++ b/android/src/main/java/com/getcapacitor/community/admob/AdMob.java
@@ -8,6 +8,7 @@
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
+
import com.getcapacitor.community.admob.banner.BannerExecutor;
import com.getcapacitor.community.admob.consent.AdConsentExecutor;
import com.getcapacitor.community.admob.helpers.AuthorizationStatusEnum;
@@ -15,14 +16,19 @@
import com.getcapacitor.community.admob.interstitial.InterstitialAdCallbackAndListeners;
import com.getcapacitor.community.admob.rewarded.AdRewardExecutor;
import com.getcapacitor.community.admob.rewardedinterstitial.AdRewardInterstitialExecutor;
+import com.getcapacitor.community.admob.appopen.AppOpenAdPlugin;
+
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.RequestConfiguration;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
+
import org.json.JSONException;
@CapacitorPlugin(
- permissions = { @Permission(alias = "network", strings = { Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.INTERNET }) }
+ permissions = {
+ @Permission(alias = "network", strings = { Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.INTERNET })
+ }
)
public class AdMob extends Plugin {
@@ -34,18 +40,21 @@ public class AdMob extends Plugin {
this::notifyListeners,
getLogTag()
);
+
private final AdRewardExecutor adRewardExecutor = new AdRewardExecutor(
this::getContext,
this::getActivity,
this::notifyListeners,
getLogTag()
);
+
private final AdRewardInterstitialExecutor adRewardInterstitialExecutor = new AdRewardInterstitialExecutor(
this::getContext,
this::getActivity,
this::notifyListeners,
getLogTag()
);
+
private final AdInterstitialExecutor adInterstitialExecutor = new AdInterstitialExecutor(
this::getContext,
this::getActivity,
@@ -61,7 +70,27 @@ public class AdMob extends Plugin {
getLogTag()
);
- // Initialize AdMob with appId
+ private final AppOpenAdPlugin appOpenAdPlugin = new AppOpenAdPlugin();
+
+ @PluginMethod
+ public void loadAppOpen(final PluginCall call) {
+ appOpenAdPlugin.loadAppOpen(call);
+ }
+
+ @PluginMethod
+ public void showAppOpen(final PluginCall call) {
+ appOpenAdPlugin.showAppOpen(call);
+ }
+
+ @PluginMethod
+ public void isAppOpenLoaded(final PluginCall call) {
+ appOpenAdPlugin.isAppOpenLoaded(call);
+ }
+
+ // ---------------------------------------------------------
+ // MAIN METHODS
+ // ---------------------------------------------------------
+
@PluginMethod
public void initialize(final PluginCall call) {
this.setRequestConfiguration(call);
@@ -93,7 +122,10 @@ public void trackingAuthorizationStatus(final PluginCall call) {
call.resolve(response);
}
- // User Consent
+ // ---------------------------------------------------------
+ // USER CONSENT
+ // ---------------------------------------------------------
+
@PluginMethod
public void requestConsentInfo(final PluginCall call) {
adConsentExecutor.requestConsentInfo(call, this::notifyListeners);
@@ -114,6 +146,10 @@ public void resetConsentInfo(final PluginCall call) {
adConsentExecutor.resetConsentInfo(call, this::notifyListeners);
}
+ // ---------------------------------------------------------
+ // APP SETTINGS
+ // ---------------------------------------------------------
+
@PluginMethod
public void setApplicationMuted(final PluginCall call) {
Boolean muted = call.getBoolean("muted");
@@ -136,41 +172,48 @@ public void setApplicationVolume(final PluginCall call) {
call.resolve();
}
- // Show a banner Ad
+ // ---------------------------------------------------------
+ // BANNER ADS
+ // ---------------------------------------------------------
+
@PluginMethod
public void showBanner(final PluginCall call) {
bannerExecutor.showBanner(call);
}
- // Hide the banner, remove it from screen, but can show it later
@PluginMethod
public void hideBanner(final PluginCall call) {
bannerExecutor.hideBanner(call);
}
- // Resume the banner, show it after hide
@PluginMethod
public void resumeBanner(final PluginCall call) {
bannerExecutor.resumeBanner(call);
}
- // Destroy the banner, remove it from screen.
@PluginMethod
public void removeBanner(final PluginCall call) {
bannerExecutor.removeBanner(call);
}
+ // ---------------------------------------------------------
+ // INTERSTITIAL ADS
+ // ---------------------------------------------------------
+
@PluginMethod
public void prepareInterstitial(final PluginCall call) {
adInterstitialExecutor.prepareInterstitial(call, this::notifyListeners);
}
- // Show interstitial Ad
@PluginMethod
public void showInterstitial(final PluginCall call) {
adInterstitialExecutor.showInterstitial(call, this::notifyListeners);
}
+ // ---------------------------------------------------------
+ // REWARDED ADS
+ // ---------------------------------------------------------
+
@PluginMethod
public void prepareRewardVideoAd(final PluginCall call) {
adRewardExecutor.prepareRewardVideoAd(call, this::notifyListeners);
@@ -191,10 +234,10 @@ public void showRewardInterstitialAd(final PluginCall call) {
adRewardInterstitialExecutor.showRewardInterstitialAd(call, this::notifyListeners);
}
- /**
- * @see Test Devices
- * @see Target Settings
- */
+ // ---------------------------------------------------------
+ // REQUEST CONFIGURATION
+ // ---------------------------------------------------------
+
private void setRequestConfiguration(final PluginCall call) {
// Testing Devices
final boolean initializeForTesting = call.getBoolean("initializeForTesting", false);
diff --git a/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdManager.java b/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdManager.java
new file mode 100644
index 00000000..d897292d
--- /dev/null
+++ b/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdManager.java
@@ -0,0 +1,94 @@
+package com.getcapacitor.community.admob.appopen;
+
+import android.app.Activity;
+import android.content.Context;
+import androidx.annotation.NonNull;
+import com.google.android.gms.ads.appopen.AppOpenAd;
+import com.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.LoadAdError;
+import com.google.android.gms.ads.FullScreenContentCallback;
+
+public class AppOpenAdManager {
+ private AppOpenAd appOpenAd = null;
+ private boolean isLoadingAd = false;
+ private boolean isShowingAd = false;
+ private String adUnitId;
+
+ public AppOpenAdManager(String adUnitId) {
+ this.adUnitId = adUnitId;
+ }
+
+ public void loadAd(Context context, final Runnable onLoaded, final Runnable onFailed) {
+ if (isLoadingAd || appOpenAd != null) {
+ return;
+ }
+
+ isLoadingAd = true;
+ AdRequest request = new AdRequest.Builder().build();
+
+ AppOpenAd.load(
+ context,
+ adUnitId,
+ request,
+ AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT,
+ new AppOpenAd.AppOpenAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull AppOpenAd ad) {
+ appOpenAd = ad;
+ isLoadingAd = false;
+
+ if (onLoaded != null) {
+ onLoaded.run();
+ }
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ isLoadingAd = false;
+
+ if (onFailed != null) {
+ onFailed.run();
+ }
+ }
+ }
+ );
+ }
+
+ public void showAdIfAvailable(Activity activity, final Runnable onClosed, final Runnable onFailedToShow) {
+ if (appOpenAd == null || isShowingAd) {
+ if (onFailedToShow != null) {
+ onFailedToShow.run();
+ }
+ return;
+ }
+
+ isShowingAd = true;
+ appOpenAd.setFullScreenContentCallback(new FullScreenContentCallback() {
+ @Override
+ public void onAdDismissedFullScreenContent() {
+ appOpenAd = null;
+ isShowingAd = false;
+
+ if (onClosed != null) {
+ onClosed.run();
+ }
+ }
+
+ @Override
+ public void onAdFailedToShowFullScreenContent(com.google.android.gms.ads.AdError adError) {
+ appOpenAd = null;
+ isShowingAd = false;
+
+ if (onFailedToShow != null) {
+ onFailedToShow.run();
+ }
+ }
+ });
+
+ appOpenAd.show(activity);
+ }
+
+ public boolean isAdLoaded() {
+ return appOpenAd != null;
+ }
+}
diff --git a/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdPlugin.java b/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdPlugin.java
new file mode 100644
index 00000000..551aaf38
--- /dev/null
+++ b/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdPlugin.java
@@ -0,0 +1,59 @@
+package com.getcapacitor.community.admob.appopen;
+
+import android.app.Activity;
+import android.content.Context;
+import com.getcapacitor.PluginCall;
+import com.getcapacitor.PluginMethod;
+import com.getcapacitor.annotation.CapacitorPlugin;
+import com.getcapacitor.Plugin;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+@CapacitorPlugin(name = "AppOpenAd")
+public class AppOpenAdPlugin extends Plugin {
+ private AppOpenAdManager appOpenAdManager;
+
+ @PluginMethod
+ public void loadAppOpen(PluginCall call) {
+ String adUnitId = call.getString("adUnitId");
+ if (adUnitId == null) {
+ call.reject("adUnitId is required");
+ return;
+ }
+ if (appOpenAdManager == null) {
+ appOpenAdManager = new AppOpenAdManager(adUnitId);
+ }
+ Context context = getContext();
+ appOpenAdManager.loadAd(context, () -> {
+ notifyListeners("appOpenAdLoaded", new JSONObject());
+ call.resolve();
+ }, () -> {
+ notifyListeners("appOpenAdFailedToLoad", new JSONObject());
+ call.reject("Failed to load App Open Ad");
+ });
+ }
+
+ @PluginMethod
+ public void showAppOpen(PluginCall call) {
+ Activity activity = getActivity();
+ appOpenAdManager.showAdIfAvailable(activity, () -> {
+ notifyListeners("appOpenAdClosed", new JSONObject());
+ call.resolve();
+ }, () -> {
+ notifyListeners("appOpenAdFailedToShow", new JSONObject());
+ call.reject("Failed to show App Open Ad");
+ });
+ }
+
+ @PluginMethod
+ public void isAppOpenLoaded(PluginCall call) {
+ boolean loaded = appOpenAdManager != null && appOpenAdManager.isAdLoaded();
+ try {
+ JSONObject result = new JSONObject();
+ result.put("value", loaded);
+ call.resolve(result);
+ } catch (JSONException e) {
+ call.reject("JSON error");
+ }
+ }
+}
diff --git a/android/src/test/java/com/getcapacitor/community/admob/models/AdOptionsTest.java b/android/src/test/java/com/getcapacitor/community/admob/models/AdOptionsTest.java
index 197ba426..e6858647 100644
--- a/android/src/test/java/com/getcapacitor/community/admob/models/AdOptionsTest.java
+++ b/android/src/test/java/com/getcapacitor/community/admob/models/AdOptionsTest.java
@@ -114,6 +114,84 @@ public void ssv() {
final AdOptions adOptions = AdOptions.getFactory().createGenericOptions(pluginCallMock, "");
+ verify(pluginCallMock, atLeastOnce()).getObject(wantedProperty);
+ assertEquals(userId, adOptions.ssvInfo.getUserId());
+ assertEquals(customData, adOptions.ssvInfo.getCustomData());
+ }
+ @Test
+ public void appOpen_ad_Id() {
+ final String expected = "Some Given AppOpen Test Id";
+ when(pluginCallMock.getString(eq("adId"), anyString())).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
+ assertEquals(expected, adOptions.adId);
+ }
+
+ @Test
+ public void appOpen_position() {
+ final String wantedProperty = "position";
+ final String expected = "TOP_CENTER";
+ final String defaultValue = "BOTTOM_CENTER";
+ when(pluginCallMock.getString(eq(wantedProperty), anyString())).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
+ verify(pluginCallMock).getString(wantedProperty, defaultValue);
+ assertEquals(expected, adOptions.position);
+ }
+
+ @Test
+ public void appOpen_margin() {
+ final String wantedProperty = "margin";
+ final int expected = 10;
+ final int defaultValue = 0;
+ when(pluginCallMock.getInt(eq(wantedProperty), anyInt())).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
+ verify(pluginCallMock).getInt(wantedProperty, defaultValue);
+ assertEquals(expected, adOptions.margin);
+ }
+
+ @Test
+ public void appOpen_isTesting() {
+ final String wantedProperty = "isTesting";
+ final boolean expected = true;
+ final boolean defaultValue = false;
+ when(pluginCallMock.getBoolean(eq(wantedProperty), anyBoolean())).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
+ verify(pluginCallMock).getBoolean(wantedProperty, defaultValue);
+ assertEquals(expected, adOptions.isTesting);
+ }
+
+ @Test
+ public void appOpen_npa() {
+ final String wantedProperty = "npa";
+ final boolean expected = true;
+ final boolean defaultValue = false;
+ lenient().when(pluginCallMock.getBoolean(eq(wantedProperty), anyBoolean())).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
+ verify(pluginCallMock).getBoolean(wantedProperty, defaultValue);
+ assertEquals(expected, adOptions.npa);
+ }
+
+ @Test
+ public void appOpen_ssv() {
+ final String customData = "customData";
+ final String userId = "userId";
+ final String wantedProperty = "ssv";
+ final JSObject expected = new JSObject();
+ expected.put(customData, customData);
+ expected.put(userId, userId);
+ lenient().when(pluginCallMock.getObject(eq(wantedProperty))).thenReturn(expected);
+
+ final AdOptions adOptions = AdOptions.getFactory().createAppOpenOptions(pluginCallMock);
+
verify(pluginCallMock, atLeastOnce()).getObject(wantedProperty);
assertEquals(userId, adOptions.ssvInfo.getUserId());
assertEquals(customData, adOptions.ssvInfo.getCustomData());
diff --git a/demo/angular/src/app/app.component.ts b/demo/angular/src/app/app.component.ts
index 2980fb71..df84bf0a 100644
--- a/demo/angular/src/app/app.component.ts
+++ b/demo/angular/src/app/app.component.ts
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { IonApp, IonRouterOutlet, Platform } from '@ionic/angular/standalone';
-import { AdMob } from '@capacitor-community/admob';
+import { AdMob, AppOpenAdPluginEvents, AppOpenAdOptions } from '@capacitor-community/admob';
@Component({
selector: 'app-root',
@@ -32,6 +32,39 @@ export class AppComponent {
AdMob.setApplicationVolume({
volume: 0.5,
});
+
+ // example of App Open Ad
+ this.showAppOpenAd();
+ });
+ }
+
+ async showAppOpenAd() {
+ // Listen to events
+ AdMob.addListener(AppOpenAdPluginEvents.Loaded, () => {
+ console.log('App Open Ad loaded');
+ });
+ AdMob.addListener(AppOpenAdPluginEvents.FailedToLoad, () => {
+ console.log('Failed to load App Open Ad');
});
+ AdMob.addListener(AppOpenAdPluginEvents.Opened, () => {
+ console.log('App Open Ad open');
+ });
+ AdMob.addListener(AppOpenAdPluginEvents.Closed, () => {
+ console.log('App Open Ad close');
+ });
+ AdMob.addListener(AppOpenAdPluginEvents.FailedToShow, () => {
+ console.log('Failed to load App Open Ad');
+ });
+
+ const options: AppOpenAdOptions = {
+ adUnitId: 'TU_AD_UNIT_ID', // Replace with your real ID
+ showOnColdStart: true,
+ showOnForeground: true,
+ };
+ await AdMob.loadAppOpen(options);
+ const { value } = await AdMob.isAppOpenLoaded();
+ if (value) {
+ await AdMob.showAppOpen();
+ }
}
}
diff --git a/ios/Sources/AdMobPlugin/AdMobPlugin.swift b/ios/Sources/AdMobPlugin/AdMobPlugin.swift
index 529fbaac..a71a362d 100644
--- a/ios/Sources/AdMobPlugin/AdMobPlugin.swift
+++ b/ios/Sources/AdMobPlugin/AdMobPlugin.swift
@@ -28,8 +28,23 @@ public class AdMobPlugin: CAPPlugin, CAPBridgedPlugin {
CAPPluginMethod(name: "prepareRewardVideoAd", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "showRewardVideoAd", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "prepareRewardInterstitialAd", returnType: CAPPluginReturnPromise),
- CAPPluginMethod(name: "showRewardInterstitialAd", returnType: CAPPluginReturnPromise)
+ CAPPluginMethod(name: "showRewardInterstitialAd", returnType: CAPPluginReturnPromise),
+ CAPPluginMethod(name: "loadAppOpen", returnType: CAPPluginReturnPromise),
+ CAPPluginMethod(name: "showAppOpen", returnType: CAPPluginReturnPromise),
+ CAPPluginMethod(name: "isAppOpenLoaded", returnType: CAPPluginReturnPromise)
]
+ private let appOpenAdPlugin = AppOpenAdPlugin()
+ @objc func loadAppOpen(_ call: CAPPluginCall) {
+ appOpenAdPlugin.loadAppOpen(call)
+ }
+
+ @objc func showAppOpen(_ call: CAPPluginCall) {
+ appOpenAdPlugin.showAppOpen(call)
+ }
+
+ @objc func isAppOpenLoaded(_ call: CAPPluginCall) {
+ appOpenAdPlugin.isAppOpenLoaded(call)
+ }
var testingDevices: [String] = []
diff --git a/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdManager.swift b/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdManager.swift
new file mode 100644
index 00000000..9c2302e7
--- /dev/null
+++ b/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdManager.swift
@@ -0,0 +1,66 @@
+import Foundation
+import GoogleMobileAds
+import UIKit
+
+@objc public class AppOpenAdManager: NSObject {
+ private var appOpenAd: GADAppOpenAd?
+ private var isLoadingAd = false
+ private var isShowingAd = false
+ private var adUnitId: String
+
+ public init(adUnitId: String) {
+ self.adUnitId = adUnitId
+ }
+
+ public func loadAd(rootViewController: UIViewController, onLoaded: @escaping () -> Void, onFailed: @escaping () -> Void) {
+ if isLoadingAd || appOpenAd != nil {
+ return
+ }
+
+ isLoadingAd = true
+ GADAppOpenAd.load(withAdUnitID: adUnitId, request: GADRequest(), orientation: .portrait) { [weak self] ad, error in
+ self?.isLoadingAd = false
+
+ if let ad = ad {
+ self?.appOpenAd = ad
+ onLoaded()
+ } else {
+ onFailed()
+ }
+ }
+ }
+
+ public func showAdIfAvailable(rootViewController: UIViewController, onClosed: @escaping () -> Void, onFailedToShow: @escaping () -> Void) {
+ guard let ad = appOpenAd, !isShowingAd else {
+ onFailedToShow()
+ return
+ }
+
+ isShowingAd = true
+ ad.fullScreenContentDelegate = self
+ ad.present(fromRootViewController: rootViewController)
+ self.onClosed = onClosed
+ self.onFailedToShow = onFailedToShow
+ }
+
+ public func isAdLoaded() -> Bool {
+ return appOpenAd != nil
+ }
+
+ private var onClosed: (() -> Void)?
+ private var onFailedToShow: (() -> Void)?
+}
+
+extension AppOpenAdManager: GADFullScreenContentDelegate {
+ public func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
+ appOpenAd = nil
+ isShowingAd = false
+ onClosed?()
+ }
+
+ public func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
+ appOpenAd = nil
+ isShowingAd = false
+ onFailedToShow?()
+ }
+}
diff --git a/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPlugin.swift b/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPlugin.swift
new file mode 100644
index 00000000..05bb2fd4
--- /dev/null
+++ b/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPlugin.swift
@@ -0,0 +1,52 @@
+import Foundation
+import Capacitor
+import UIKit
+
+@objc(AppOpenAdPlugin)
+public class AppOpenAdPlugin: CAPPlugin {
+ private var appOpenAdManager: AppOpenAdManager?
+
+ @objc func loadAppOpen(_ call: CAPPluginCall) {
+ guard let adUnitId = call.getString("adUnitId") else {
+ call.reject("adUnitId is required")
+ return
+ }
+ if appOpenAdManager == nil {
+ appOpenAdManager = AppOpenAdManager(adUnitId: adUnitId)
+ }
+ DispatchQueue.main.async {
+ if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
+ self.appOpenAdManager?.loadAd(rootViewController: rootVC, onLoaded: {
+ self.notifyListeners("appOpenAdLoaded", data: [:])
+ call.resolve()
+ }, onFailed: {
+ self.notifyListeners("appOpenAdFailedToLoad", data: [:])
+ call.reject("Failed to load App Open Ad")
+ })
+ } else {
+ call.reject("No rootViewController")
+ }
+ }
+ }
+
+ @objc func showAppOpen(_ call: CAPPluginCall) {
+ DispatchQueue.main.async {
+ if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
+ self.appOpenAdManager?.showAdIfAvailable(rootViewController: rootVC, onClosed: {
+ self.notifyListeners("appOpenAdClosed", data: [:])
+ call.resolve()
+ }, onFailedToShow: {
+ self.notifyListeners("appOpenAdFailedToShow", data: [:])
+ call.reject("Failed to show App Open Ad")
+ })
+ } else {
+ call.reject("No rootViewController")
+ }
+ }
+ }
+
+ @objc func isAppOpenLoaded(_ call: CAPPluginCall) {
+ let loaded = appOpenAdManager?.isAdLoaded() ?? false
+ call.resolve(["value": loaded])
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index f11084e8..4ca9a3ac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8277,8 +8277,9 @@
},
"node_modules/rimraf": {
"version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
+ "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dev": true,
- "license": "ISC",
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
diff --git a/src/app-open/app-open-ad-options.interface.ts b/src/app-open/app-open-ad-options.interface.ts
new file mode 100644
index 00000000..01cd3835
--- /dev/null
+++ b/src/app-open/app-open-ad-options.interface.ts
@@ -0,0 +1,5 @@
+export interface AppOpenAdOptions {
+ adUnitId: string;
+ showOnColdStart?: boolean;
+ showOnForeground?: boolean;
+}
diff --git a/src/app-open/app-open-ad-plugin-events.enum.ts b/src/app-open/app-open-ad-plugin-events.enum.ts
new file mode 100644
index 00000000..327b1e7c
--- /dev/null
+++ b/src/app-open/app-open-ad-plugin-events.enum.ts
@@ -0,0 +1,7 @@
+export enum AppOpenAdPluginEvents {
+ Loaded = 'appOpenAdLoaded',
+ FailedToLoad = 'appOpenAdFailedToLoad',
+ Opened = 'appOpenAdOpened',
+ Closed = 'appOpenAdClosed',
+ FailedToShow = 'appOpenAdFailedToShow',
+}
diff --git a/src/app-open/app-open-definitions.interface.ts b/src/app-open/app-open-definitions.interface.ts
new file mode 100644
index 00000000..acb1a663
--- /dev/null
+++ b/src/app-open/app-open-definitions.interface.ts
@@ -0,0 +1,34 @@
+import type { PluginListenerHandle } from '@capacitor/core';
+import type { ValidateAllEventsEnumAreImplemented } from '../private/validate-all-events-implemented.type';
+import type { AppOpenAdPluginEvents } from './app-open-ad-plugin-events.enum';
+import type { AppOpenAdOptions } from './app-open-ad-options.interface';
+
+export type AppOpenDefinitionsHasAllEvents = ValidateAllEventsEnumAreImplemented<
+ AppOpenAdPluginEvents,
+ AppOpenAdPlugin
+>;
+
+export interface AppOpenAdPlugin {
+ /**
+ * Load an ad App Open
+ */
+ loadAppOpen(options: AppOpenAdOptions): Promise