Add iOS support for TapADN package

This commit is contained in:
2026-06-12 16:05:13 +08:00
parent 3341169f9b
commit 7e012bfd45
5 changed files with 621 additions and 72 deletions

View File

@@ -366,6 +366,10 @@ namespace Dirichlet.Mediation
private readonly IDirichletPlatformBridge bridge;
private static readonly object RewardAutoSessionLock = new object();
private static readonly Dictionary<string, AutoRewardVideoSession> RewardAutoSessions = new Dictionary<string, AutoRewardVideoSession>(StringComparer.Ordinal);
private static readonly object InterstitialAutoSessionLock = new object();
private static readonly Dictionary<string, AutoInterstitialSession> InterstitialAutoSessions = new Dictionary<string, AutoInterstitialSession>(StringComparer.Ordinal);
private static readonly object SplashAutoSessionLock = new object();
private static readonly Dictionary<string, AutoSplashSession> SplashAutoSessions = new Dictionary<string, AutoSplashSession>(StringComparer.Ordinal);
public static DirichletAdNative Create() => DirichletAdManager.CreateAdNative();
@@ -478,7 +482,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Shows an interstitial ad with automatic load-and-show logic.
/// Android only - iOS receives OnError with not_supported.
/// Android uses the native AutoAd API; iOS/editor use a load-then-show fallback without native cache.
/// </summary>
public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
{
@@ -487,13 +491,17 @@ namespace Dirichlet.Mediation
return;
}
#if UNITY_ANDROID && !UNITY_EDITOR
bridge.ShowInterstitialAutoAd(request, listener);
#else
ShowInterstitialLoadAndShowInternal(request, listener);
#endif
}
/// <summary>
/// Shows a banner ad with automatic load + rotation logic. Container is created internally and
/// anchored at the bottom of the screen by default. Use <see cref="DirichletAdRequest.Builder.WithSlideInterval"/>
/// to control rotation interval. Android only - iOS receives OnError with not_supported.
/// to control rotation interval. Native auto rotation is currently Android only.
/// </summary>
public void ShowBannerAutoAd(DirichletAdRequest request, IDirichletBannerAutoAdListener listener)
{
@@ -516,7 +524,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Shows a splash ad with automatic load logic. Container is a fullscreen overlay created internally.
/// Android only - iOS receives OnError with not_supported.
/// Android uses the native AutoAd API; iOS/editor use a load-then-show fallback without native cache.
/// </summary>
public void ShowSplashAutoAd(DirichletAdRequest request, IDirichletSplashAutoAdListener listener)
{
@@ -533,7 +541,11 @@ namespace Dirichlet.Mediation
return;
}
#if UNITY_ANDROID && !UNITY_EDITOR
bridge.ShowSplashAutoAd(request, options ?? new DirichletAdShowOptions(), listener);
#else
ShowSplashLoadAndShowInternal(request, options ?? new DirichletAdShowOptions(), listener);
#endif
}
/// <summary>
@@ -566,6 +578,7 @@ namespace Dirichlet.Mediation
var session = new AutoRewardVideoSession(sessionId, ad, listener);
RegisterRewardAutoSession(session);
ad.SetInteractionListener(session);
ad.ShowFailed += session.OnShowFailed;
// Load succeeded; show immediately.
var shown = ad.Show();
@@ -580,6 +593,66 @@ namespace Dirichlet.Mediation
});
}
private void ShowInterstitialLoadAndShowInternal(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
{
LoadInterstitialAd(
request,
ad =>
{
if (ad == null)
{
listener?.OnError(new DirichletError("invalid_ad", "Load callback returned null ad"));
return;
}
var sessionId = Guid.NewGuid().ToString("N");
var session = new AutoInterstitialSession(sessionId, ad, listener);
RegisterInterstitialAutoSession(session);
ad.SetInteractionListener(session);
ad.ShowFailed += session.OnShowFailed;
var shown = ad.Show();
if (!shown)
{
session.FailAndDispose(new DirichletError("show_failed", "ShowInterstitialAd returned false"));
}
},
error =>
{
listener?.OnError(error ?? new DirichletError("load_failed", "LoadInterstitialAd failed"));
});
}
private void ShowSplashLoadAndShowInternal(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener)
{
LoadSplashAd(
request,
ad =>
{
if (ad == null)
{
listener?.OnError(new DirichletError("invalid_ad", "Load callback returned null ad"));
return;
}
var sessionId = Guid.NewGuid().ToString("N");
var session = new AutoSplashSession(sessionId, ad, listener);
RegisterSplashAutoSession(session);
ad.SetInteractionListener(session);
ad.ShowFailed += session.OnShowFailed;
var shown = ad.Show();
if (!shown)
{
session.FailAndDispose(new DirichletError("show_failed", "ShowSplashAd returned false"));
}
},
error =>
{
listener?.OnError(error ?? new DirichletError("load_failed", "LoadSplashAd failed"));
});
}
private static bool ValidateRequest(DirichletAdRequest request, Action<DirichletError> onFailure)
{
if (request == null)
@@ -628,6 +701,58 @@ namespace Dirichlet.Mediation
}
}
private static void RegisterInterstitialAutoSession(AutoInterstitialSession session)
{
if (session == null)
{
return;
}
lock (InterstitialAutoSessionLock)
{
InterstitialAutoSessions[session.SessionId] = session;
}
}
private static void UnregisterInterstitialAutoSession(string sessionId)
{
if (string.IsNullOrEmpty(sessionId))
{
return;
}
lock (InterstitialAutoSessionLock)
{
InterstitialAutoSessions.Remove(sessionId);
}
}
private static void RegisterSplashAutoSession(AutoSplashSession session)
{
if (session == null)
{
return;
}
lock (SplashAutoSessionLock)
{
SplashAutoSessions[session.SessionId] = session;
}
}
private static void UnregisterSplashAutoSession(string sessionId)
{
if (string.IsNullOrEmpty(sessionId))
{
return;
}
lock (SplashAutoSessionLock)
{
SplashAutoSessions.Remove(sessionId);
}
}
private sealed class AutoRewardVideoSession : IDirichletRewardAdInteractionListener
{
public string SessionId { get; }
@@ -684,6 +809,11 @@ namespace Dirichlet.Mediation
listener?.OnRewardVerify(args);
}
public void OnShowFailed(DirichletError error)
{
FailAndDispose(error ?? new DirichletError("show_error", "Reward video ad failed to show"));
}
public void FailAndDispose(DirichletError error)
{
if (disposed)
@@ -707,6 +837,8 @@ namespace Dirichlet.Mediation
try
{
ad.ShowFailed -= OnShowFailed;
ad.SetInteractionListener(null);
ad?.Destroy();
}
catch (Exception ex)
@@ -715,6 +847,176 @@ namespace Dirichlet.Mediation
}
}
}
private sealed class AutoInterstitialSession : IDirichletInterstitialAdInteractionListener
{
public string SessionId { get; }
private readonly DirichletInterstitialAd ad;
private readonly IDirichletInterstitialAutoAdListener listener;
private bool disposed;
public AutoInterstitialSession(string sessionId, DirichletInterstitialAd ad, IDirichletInterstitialAutoAdListener listener)
{
SessionId = string.IsNullOrEmpty(sessionId) ? Guid.NewGuid().ToString("N") : sessionId;
this.ad = ad ?? throw new ArgumentNullException(nameof(ad));
this.listener = listener;
}
public void OnAdShow()
{
if (disposed)
{
return;
}
listener?.OnAdShow();
}
public void OnAdClick()
{
if (disposed)
{
return;
}
listener?.OnAdClick();
}
public void OnAdClose()
{
if (disposed)
{
return;
}
listener?.OnAdClose();
Dispose();
}
public void OnShowFailed(DirichletError error)
{
FailAndDispose(error ?? new DirichletError("show_error", "Interstitial ad failed to show"));
}
public void FailAndDispose(DirichletError error)
{
if (disposed)
{
return;
}
listener?.OnError(error ?? new DirichletError("unknown_error", "Unknown error"));
Dispose();
}
private void Dispose()
{
if (disposed)
{
return;
}
disposed = true;
UnregisterInterstitialAutoSession(SessionId);
try
{
ad.ShowFailed -= OnShowFailed;
ad.SetInteractionListener(null);
ad?.Destroy();
}
catch (Exception ex)
{
Debug.LogWarning($"[Dirichlet] Auto interstitial ad Destroy failed: {ex.Message}");
}
}
}
private sealed class AutoSplashSession : IDirichletSplashAdInteractionListener
{
public string SessionId { get; }
private readonly DirichletSplashAd ad;
private readonly IDirichletSplashAutoAdListener listener;
private bool disposed;
public AutoSplashSession(string sessionId, DirichletSplashAd ad, IDirichletSplashAutoAdListener listener)
{
SessionId = string.IsNullOrEmpty(sessionId) ? Guid.NewGuid().ToString("N") : sessionId;
this.ad = ad ?? throw new ArgumentNullException(nameof(ad));
this.listener = listener;
}
public void OnAdShow()
{
if (disposed)
{
return;
}
listener?.OnAdShow();
}
public void OnAdClick()
{
if (disposed)
{
return;
}
listener?.OnAdClick();
}
public void OnAdClose()
{
if (disposed)
{
return;
}
listener?.OnAdClose();
Dispose();
}
public void OnShowFailed(DirichletError error)
{
FailAndDispose(error ?? new DirichletError("show_error", "Splash ad failed to show"));
}
public void FailAndDispose(DirichletError error)
{
if (disposed)
{
return;
}
listener?.OnError(error ?? new DirichletError("unknown_error", "Unknown error"));
Dispose();
}
private void Dispose()
{
if (disposed)
{
return;
}
disposed = true;
UnregisterSplashAutoSession(SessionId);
try
{
ad.ShowFailed -= OnShowFailed;
ad.SetInteractionListener(null);
ad?.Destroy();
}
catch (Exception ex)
{
Debug.LogWarning($"[Dirichlet] Auto splash ad Destroy failed: {ex.Message}");
}
}
}
}
/// <summary>
@@ -1189,7 +1491,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Listener interface for auto interstitial ad callbacks.
/// Android only - iOS will receive OnError with not_supported error.
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
/// </summary>
public interface IDirichletInterstitialAutoAdListener
{
@@ -1201,7 +1503,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Listener interface for auto banner ad callbacks.
/// Android only - iOS will receive OnError with not_supported error.
/// Native auto rotation is Android only.
/// </summary>
public interface IDirichletBannerAutoAdListener
{
@@ -1213,7 +1515,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Listener interface for auto splash ad callbacks.
/// Android only - iOS will receive OnError with not_supported error.
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
/// </summary>
public interface IDirichletSplashAutoAdListener
{
@@ -1226,7 +1528,7 @@ namespace Dirichlet.Mediation
/// <summary>
/// Listener interface for auto reward video ad callbacks.
/// Used with ShowRewardVideoAutoAd which combines load and show into one operation.
/// Android only - iOS will receive OnError with not_supported error.
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
/// </summary>
public interface IDirichletRewardVideoAutoAdListener
{

View File

@@ -580,12 +580,12 @@ namespace Dirichlet.Mediation
/// <summary>
/// Shows a reward video ad with automatic load-and-show logic.
/// Android only - iOS will call onFailure with not_supported error.
/// Android bridge maps to native auto cache; higher layers may emulate load-then-show elsewhere.
/// </summary>
void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener);
/// <summary>
/// Shows an interstitial ad with automatic load-and-show logic. Android only.
/// Shows an interstitial ad with automatic load-and-show logic. Android bridge maps to native auto cache.
/// </summary>
void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener);
@@ -595,7 +595,7 @@ namespace Dirichlet.Mediation
void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener);
/// <summary>
/// Shows a splash ad with automatic load logic. Android only.
/// Shows a splash ad with automatic load logic. Android bridge maps to native auto cache.
/// </summary>
void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener);
@@ -1563,20 +1563,9 @@ namespace Dirichlet.Mediation
}
first = false;
jsonBuilder.Append($"\"{kv.Key}\":");
if (kv.Value is string)
{
jsonBuilder.Append($"\"{kv.Value}\"");
}
else if (kv.Value is bool)
{
jsonBuilder.Append(((bool)kv.Value) ? "true" : "false");
}
else
{
jsonBuilder.Append(kv.Value.ToString());
}
AppendJsonString(jsonBuilder, kv.Key);
jsonBuilder.Append(":");
AppendJsonValue(jsonBuilder, kv.Value);
}
jsonBuilder.Append("}");
@@ -1589,6 +1578,84 @@ namespace Dirichlet.Mediation
}
}
private static void AppendJsonValue(System.Text.StringBuilder builder, object value)
{
if (value == null)
{
builder.Append("null");
return;
}
if (value is string stringValue)
{
AppendJsonString(builder, stringValue);
return;
}
if (value is bool boolValue)
{
builder.Append(boolValue ? "true" : "false");
return;
}
if (value is IFormattable formattable)
{
builder.Append(formattable.ToString(null, CultureInfo.InvariantCulture));
return;
}
AppendJsonString(builder, value.ToString());
}
private static void AppendJsonString(System.Text.StringBuilder builder, string value)
{
builder.Append('"');
if (!string.IsNullOrEmpty(value))
{
foreach (var c in value)
{
switch (c)
{
case '\\':
builder.Append("\\\\");
break;
case '"':
builder.Append("\\\"");
break;
case '\b':
builder.Append("\\b");
break;
case '\f':
builder.Append("\\f");
break;
case '\n':
builder.Append("\\n");
break;
case '\r':
builder.Append("\\r");
break;
case '\t':
builder.Append("\\t");
break;
default:
if (char.IsControl(c))
{
builder.Append("\\u");
builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
}
else
{
builder.Append(c);
}
break;
}
}
}
builder.Append('"');
}
private void RemoveLoadCallback(string handleId)
{
if (string.IsNullOrEmpty(handleId))
@@ -2032,5 +2099,3 @@ namespace Dirichlet.Mediation
#endregion
}