You've already forked Commercialization.tapadn
1392 lines
43 KiB
C#
1392 lines
43 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using UnityEngine;
|
||
|
||
namespace Dirichlet.Mediation
|
||
{
|
||
/// <summary>
|
||
/// Platform handle for ad instances. This is a simple wrapper around the handle string
|
||
/// returned from native bridge, matching the native SDK pattern where ad objects are
|
||
/// returned directly from loadXXXAd() methods.
|
||
/// </summary>
|
||
public sealed class DirichletPlatformAdHandle
|
||
{
|
||
public string DebugId { get; }
|
||
|
||
internal DirichletPlatformAdHandle(string debugId)
|
||
{
|
||
DebugId = debugId ?? throw new ArgumentNullException(nameof(debugId));
|
||
}
|
||
|
||
internal static DirichletPlatformAdHandle FromNative(string handleId)
|
||
{
|
||
if (string.IsNullOrEmpty(handleId))
|
||
{
|
||
handleId = Guid.NewGuid().ToString("N");
|
||
}
|
||
return new DirichletPlatformAdHandle(handleId);
|
||
}
|
||
|
||
internal static DirichletPlatformAdHandle CreateStub()
|
||
{
|
||
return new DirichletPlatformAdHandle(Guid.NewGuid().ToString("N"));
|
||
}
|
||
|
||
public override string ToString() => $"DirichletPlatformAdHandle(DebugId={DebugId})";
|
||
}
|
||
|
||
public enum DirichletAdType
|
||
{
|
||
RewardVideo = 0,
|
||
Interstitial = 1,
|
||
Banner = 2,
|
||
Splash = 3,
|
||
ExpressFeed = 4,
|
||
NativeFeed = 5
|
||
}
|
||
|
||
/// <summary>
|
||
/// Mirrors the Android-side DirichletAdManager to keep configuration state and create ad natives.
|
||
/// </summary>
|
||
public static class DirichletAdManager
|
||
{
|
||
private static readonly object StateLock = new object();
|
||
private static DirichletAdConfig currentConfig;
|
||
|
||
/// <summary>
|
||
/// Returns the last configuration applied to the native SDK.
|
||
/// </summary>
|
||
public static DirichletAdConfig CurrentConfig
|
||
{
|
||
get
|
||
{
|
||
lock (StateLock)
|
||
{
|
||
return currentConfig;
|
||
}
|
||
}
|
||
}
|
||
|
||
internal static void ApplyConfig(DirichletAdConfig config)
|
||
{
|
||
lock (StateLock)
|
||
{
|
||
currentConfig = config;
|
||
}
|
||
}
|
||
|
||
internal static void Clear()
|
||
{
|
||
lock (StateLock)
|
||
{
|
||
currentConfig = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a Unity-side DirichletAdNative wrapper that mirrors the Android aggregator API.
|
||
/// </summary>
|
||
public static DirichletAdNative CreateAdNative()
|
||
{
|
||
return new DirichletAdNative(DirichletSdk.GetBridge());
|
||
}
|
||
|
||
internal static DirichletAdNative CreateAdNative(IDirichletPlatformBridge bridge)
|
||
{
|
||
return new DirichletAdNative(bridge);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Thin wrapper around native ad handles provided by the platform bridge.
|
||
/// </summary>
|
||
|
||
public sealed class DirichletAdRequest
|
||
{
|
||
public long SpaceId { get; }
|
||
public string Extra1 { get; }
|
||
public string UserId { get; }
|
||
public string RewardName { get; }
|
||
public int? RewardAmount { get; }
|
||
public string Query { get; }
|
||
public int? ExpressViewWidth { get; }
|
||
public int? ExpressViewHeight { get; }
|
||
public int? ExpressImageWidth { get; }
|
||
public int? ExpressImageHeight { get; }
|
||
public long? MinaId { get; }
|
||
|
||
/// <summary>
|
||
/// Banner auto-ad slide rotation interval, in seconds. Native default is 30.
|
||
/// </summary>
|
||
public int? SlideInterval { get; }
|
||
|
||
private DirichletAdRequest(Builder builder)
|
||
{
|
||
SpaceId = builder.spaceId;
|
||
Extra1 = builder.extra1;
|
||
UserId = builder.userId;
|
||
RewardName = builder.rewardName;
|
||
RewardAmount = builder.rewardAmount;
|
||
Query = builder.query;
|
||
ExpressViewWidth = builder.expressViewWidth;
|
||
ExpressViewHeight = builder.expressViewHeight;
|
||
ExpressImageWidth = builder.expressImageWidth;
|
||
ExpressImageHeight = builder.expressImageHeight;
|
||
MinaId = builder.minaId;
|
||
SlideInterval = builder.slideInterval;
|
||
}
|
||
|
||
public Builder ToBuilder()
|
||
{
|
||
var builder = new Builder()
|
||
.WithSpaceId(SpaceId)
|
||
.WithExtra1(Extra1)
|
||
.WithUserId(UserId)
|
||
.WithRewardName(RewardName)
|
||
.WithQuery(Query);
|
||
|
||
if (RewardAmount.HasValue)
|
||
{
|
||
builder.WithRewardAmount(RewardAmount.Value);
|
||
}
|
||
|
||
if (ExpressViewWidth.HasValue || ExpressViewHeight.HasValue)
|
||
{
|
||
builder.WithExpressViewSize(ExpressViewWidth ?? -1, ExpressViewHeight ?? -1);
|
||
}
|
||
|
||
if (ExpressImageWidth.HasValue || ExpressImageHeight.HasValue)
|
||
{
|
||
builder.WithExpressImageSize(ExpressImageWidth ?? -1, ExpressImageHeight ?? -1);
|
||
}
|
||
|
||
if (MinaId.HasValue)
|
||
{
|
||
builder.WithMinaId(MinaId.Value);
|
||
}
|
||
|
||
if (SlideInterval.HasValue)
|
||
{
|
||
builder.WithSlideInterval(SlideInterval.Value);
|
||
}
|
||
|
||
return builder;
|
||
}
|
||
|
||
internal Dictionary<string, object> ToBridgePayload()
|
||
{
|
||
var payload = new Dictionary<string, object>(StringComparer.Ordinal)
|
||
{
|
||
["space_id"] = SpaceId
|
||
};
|
||
|
||
if (!string.IsNullOrEmpty(Extra1))
|
||
{
|
||
payload["extra1"] = Extra1;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(UserId))
|
||
{
|
||
payload["user_id"] = UserId;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(RewardName))
|
||
{
|
||
payload["reward_name"] = RewardName;
|
||
}
|
||
|
||
if (RewardAmount.HasValue)
|
||
{
|
||
payload["reward_amount"] = RewardAmount.Value;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(Query))
|
||
{
|
||
payload["query"] = Query;
|
||
}
|
||
|
||
if (ExpressViewWidth.HasValue)
|
||
{
|
||
payload["express_width"] = ExpressViewWidth.Value;
|
||
}
|
||
|
||
if (ExpressViewHeight.HasValue)
|
||
{
|
||
payload["express_height"] = ExpressViewHeight.Value;
|
||
}
|
||
|
||
if (ExpressImageWidth.HasValue)
|
||
{
|
||
payload["express_image_width"] = ExpressImageWidth.Value;
|
||
}
|
||
|
||
if (ExpressImageHeight.HasValue)
|
||
{
|
||
payload["express_image_height"] = ExpressImageHeight.Value;
|
||
}
|
||
|
||
if (MinaId.HasValue)
|
||
{
|
||
payload["mina_id"] = MinaId.Value;
|
||
}
|
||
|
||
if (SlideInterval.HasValue)
|
||
{
|
||
// Native Java API is `withSlideInternal` (spelled "Internal" by upstream);
|
||
// keep the wire key matching the native method name.
|
||
payload["slide_internal"] = SlideInterval.Value;
|
||
}
|
||
|
||
return payload;
|
||
}
|
||
|
||
public sealed class Builder
|
||
{
|
||
internal long spaceId;
|
||
internal string extra1;
|
||
internal string userId;
|
||
internal string rewardName;
|
||
internal int? rewardAmount;
|
||
internal string query;
|
||
internal int? expressViewWidth;
|
||
internal int? expressViewHeight;
|
||
internal int? expressImageWidth;
|
||
internal int? expressImageHeight;
|
||
internal long? minaId;
|
||
internal int? slideInterval;
|
||
|
||
public Builder WithSpaceId(long value)
|
||
{
|
||
if (value <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(value), "SpaceId must be greater than zero.");
|
||
}
|
||
|
||
spaceId = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithExtra1(string value)
|
||
{
|
||
extra1 = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithUserId(string value)
|
||
{
|
||
userId = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithRewardName(string value)
|
||
{
|
||
rewardName = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithRewardAmount(int value)
|
||
{
|
||
rewardAmount = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithQuery(string value)
|
||
{
|
||
query = value;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithExpressViewSize(int width, int height)
|
||
{
|
||
expressViewWidth = width;
|
||
expressViewHeight = height;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithExpressImageSize(int width, int height)
|
||
{
|
||
expressImageWidth = width;
|
||
expressImageHeight = height;
|
||
return this;
|
||
}
|
||
|
||
public Builder WithMinaId(long value)
|
||
{
|
||
minaId = value;
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets banner auto-ad slide rotation interval, in seconds (recommended 30–120, native default 30).
|
||
/// </summary>
|
||
public Builder WithSlideInterval(int seconds)
|
||
{
|
||
slideInterval = seconds;
|
||
return this;
|
||
}
|
||
|
||
public DirichletAdRequest Build()
|
||
{
|
||
if (spaceId <= 0)
|
||
{
|
||
throw new InvalidOperationException("SpaceId must be set to a value greater than zero before building the request.");
|
||
}
|
||
|
||
return new DirichletAdRequest(this);
|
||
}
|
||
}
|
||
}
|
||
|
||
public enum DirichletBannerAlignment
|
||
{
|
||
Top = 0,
|
||
Bottom = 1
|
||
}
|
||
|
||
public sealed class DirichletAdShowOptions
|
||
{
|
||
public DirichletBannerAlignment BannerAlignment { get; set; } = DirichletBannerAlignment.Bottom;
|
||
public int BannerOffset { get; set; }
|
||
|
||
internal Dictionary<string, object> ToBridgePayload()
|
||
{
|
||
var payload = new Dictionary<string, object>(StringComparer.Ordinal);
|
||
|
||
// Banner options are always included if set, Bridge will handle based on ad type
|
||
payload["banner_baseline"] = (int)BannerAlignment;
|
||
payload["banner_offset"] = BannerOffset;
|
||
|
||
return payload;
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletAdNative
|
||
{
|
||
private readonly IDirichletPlatformBridge bridge;
|
||
private static readonly object RewardAutoSessionLock = new object();
|
||
private static readonly Dictionary<string, AutoRewardVideoSession> RewardAutoSessions = new Dictionary<string, AutoRewardVideoSession>(StringComparer.Ordinal);
|
||
|
||
public static DirichletAdNative Create() => DirichletAdManager.CreateAdNative();
|
||
|
||
internal DirichletAdNative(IDirichletPlatformBridge bridge)
|
||
{
|
||
this.bridge = bridge ?? throw new ArgumentNullException(nameof(bridge));
|
||
}
|
||
|
||
public void LoadRewardVideoAd(DirichletAdRequest request, Action<DirichletRewardVideoAd> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
if (!ValidateRequest(request, onFailure))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.LoadRewardVideoAd(request,
|
||
handle =>
|
||
{
|
||
var ad = DirichletRewardVideoAd.Create(bridge, handle, request.SpaceId);
|
||
ad.MarkLoaded();
|
||
onLoaded?.Invoke(ad);
|
||
},
|
||
onFailure);
|
||
}
|
||
|
||
public void LoadInterstitialAd(DirichletAdRequest request, Action<DirichletInterstitialAd> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
if (!ValidateRequest(request, onFailure))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.LoadInterstitialAd(request,
|
||
handle =>
|
||
{
|
||
var ad = DirichletInterstitialAd.Create(bridge, handle, request.SpaceId);
|
||
ad.MarkLoaded();
|
||
onLoaded?.Invoke(ad);
|
||
},
|
||
onFailure);
|
||
}
|
||
|
||
public void LoadBannerAd(DirichletAdRequest request, Action<DirichletBannerAd> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
if (!ValidateRequest(request, onFailure))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.LoadBannerAd(request,
|
||
handle =>
|
||
{
|
||
var ad = DirichletBannerAd.Create(bridge, handle, request.SpaceId);
|
||
ad.MarkLoaded();
|
||
onLoaded?.Invoke(ad);
|
||
},
|
||
onFailure);
|
||
}
|
||
|
||
public void LoadSplashAd(DirichletAdRequest request, Action<DirichletSplashAd> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
if (!ValidateRequest(request, onFailure))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.LoadSplashAd(request,
|
||
handle =>
|
||
{
|
||
var ad = DirichletSplashAd.Create(bridge, handle, request.SpaceId);
|
||
ad.MarkLoaded();
|
||
onLoaded?.Invoke(ad);
|
||
},
|
||
onFailure);
|
||
}
|
||
|
||
public void LoadExpressFeedAd(DirichletAdRequest request, Action<IReadOnlyList<DirichletAd>> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
NotifyNotSupported("express_feed", onFailure);
|
||
}
|
||
|
||
public void LoadNativeFeedAd(DirichletAdRequest request, Action<IReadOnlyList<DirichletAd>> onLoaded, Action<DirichletError> onFailure)
|
||
{
|
||
NotifyNotSupported("native_feed", onFailure);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Shows a reward video ad with automatic load-and-show logic.
|
||
/// This method combines loading and showing into a single operation.
|
||
///
|
||
/// - Android: 使用 native 侧的 AutoAd(可能包含缓存策略,保持原有行为)
|
||
/// - iOS: 使用 Unity C# 侧的“load 成功后立刻 show”的简化方案(不做缓存)
|
||
/// </summary>
|
||
/// <param name="request">Ad request parameters</param>
|
||
/// <param name="listener">Listener for all ad events (show/close/reward/click/error)</param>
|
||
public void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener)
|
||
{
|
||
if (!ValidateRequest(request, error => listener?.OnError(error)))
|
||
{
|
||
return;
|
||
}
|
||
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
bridge.ShowRewardVideoAutoAd(request, listener);
|
||
#else
|
||
// iOS/Editor/other platforms: use simplified load+show implementation (no cache).
|
||
ShowRewardVideoLoadAndShowInternal(request, listener);
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// Shows an interstitial ad with automatic load-and-show logic.
|
||
/// Android only - iOS receives OnError with not_supported.
|
||
/// </summary>
|
||
public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
|
||
{
|
||
if (!ValidateRequest(request, error => listener?.OnError(error)))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.ShowInterstitialAutoAd(request, listener);
|
||
}
|
||
|
||
/// <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.
|
||
/// </summary>
|
||
public void ShowBannerAutoAd(DirichletAdRequest request, IDirichletBannerAutoAdListener listener)
|
||
{
|
||
ShowBannerAutoAd(request, null, listener);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Banner auto-ad with explicit alignment/offset overrides. Advanced overload —
|
||
/// the standard form is <see cref="ShowBannerAutoAd(DirichletAdRequest, IDirichletBannerAutoAdListener)"/>.
|
||
/// </summary>
|
||
public void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener)
|
||
{
|
||
if (!ValidateRequest(request, error => listener?.OnError(error)))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.ShowBannerAutoAd(request, options ?? new DirichletAdShowOptions(), listener);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Shows a splash ad with automatic load logic. Container is a fullscreen overlay created internally.
|
||
/// Android only - iOS receives OnError with not_supported.
|
||
/// </summary>
|
||
public void ShowSplashAutoAd(DirichletAdRequest request, IDirichletSplashAutoAdListener listener)
|
||
{
|
||
ShowSplashAutoAd(request, null, listener);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Splash auto-ad advanced overload (currently no effective options for splash).
|
||
/// </summary>
|
||
public void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener)
|
||
{
|
||
if (!ValidateRequest(request, error => listener?.OnError(error)))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.ShowSplashAutoAd(request, options ?? new DirichletAdShowOptions(), listener);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Preloads an ad for later auto-show. Currently only type=3 (reward video) is handled by the native SDK.
|
||
/// Android only - iOS is a no-op.
|
||
/// </summary>
|
||
public void PreLoad(DirichletAdRequest request, int type)
|
||
{
|
||
if (!ValidateRequest(request, null))
|
||
{
|
||
return;
|
||
}
|
||
|
||
bridge.PreLoad(request, type);
|
||
}
|
||
|
||
private void ShowRewardVideoLoadAndShowInternal(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener)
|
||
{
|
||
LoadRewardVideoAd(
|
||
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 AutoRewardVideoSession(sessionId, ad, listener);
|
||
RegisterRewardAutoSession(session);
|
||
ad.SetInteractionListener(session);
|
||
|
||
// Load succeeded; show immediately.
|
||
var shown = ad.Show();
|
||
if (!shown)
|
||
{
|
||
session.FailAndDispose(new DirichletError("show_failed", "ShowRewardVideoAd returned false"));
|
||
}
|
||
},
|
||
error =>
|
||
{
|
||
listener?.OnError(error ?? new DirichletError("load_failed", "LoadRewardVideoAd failed"));
|
||
});
|
||
}
|
||
|
||
private static bool ValidateRequest(DirichletAdRequest request, Action<DirichletError> onFailure)
|
||
{
|
||
if (request == null)
|
||
{
|
||
onFailure?.Invoke(new DirichletError("invalid_request", "DirichletAdRequest cannot be null"));
|
||
return false;
|
||
}
|
||
|
||
if (request.SpaceId <= 0)
|
||
{
|
||
onFailure?.Invoke(new DirichletError("invalid_space_id", "DirichletAdRequest.SpaceId must be greater than zero"));
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private void NotifyNotSupported(string feature, Action<DirichletError> onFailure)
|
||
{
|
||
onFailure?.Invoke(new DirichletError("not_supported", $"Dirichlet Unity bridge does not yet support {feature} ads."));
|
||
}
|
||
|
||
private static void RegisterRewardAutoSession(AutoRewardVideoSession session)
|
||
{
|
||
if (session == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (RewardAutoSessionLock)
|
||
{
|
||
RewardAutoSessions[session.SessionId] = session;
|
||
}
|
||
}
|
||
|
||
private static void UnregisterRewardAutoSession(string sessionId)
|
||
{
|
||
if (string.IsNullOrEmpty(sessionId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (RewardAutoSessionLock)
|
||
{
|
||
RewardAutoSessions.Remove(sessionId);
|
||
}
|
||
}
|
||
|
||
private sealed class AutoRewardVideoSession : IDirichletRewardAdInteractionListener
|
||
{
|
||
public string SessionId { get; }
|
||
|
||
private readonly DirichletRewardVideoAd ad;
|
||
private readonly IDirichletRewardVideoAutoAdListener listener;
|
||
private bool disposed;
|
||
|
||
public AutoRewardVideoSession(string sessionId, DirichletRewardVideoAd ad, IDirichletRewardVideoAutoAdListener 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 OnRewardVerify(DirichletRewardVerificationEventArgs args)
|
||
{
|
||
if (disposed)
|
||
{
|
||
return;
|
||
}
|
||
|
||
listener?.OnRewardVerify(args);
|
||
}
|
||
|
||
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;
|
||
UnregisterRewardAutoSession(SessionId);
|
||
|
||
try
|
||
{
|
||
ad?.Destroy();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[Dirichlet] Auto reward ad Destroy failed: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Base Unity-side ad handle. Responsible for keeping track of lifecycle state.
|
||
/// Matches the native SDK pattern where ad objects are returned from loadXXXAd() methods.
|
||
/// </summary>
|
||
public abstract class DirichletAd
|
||
{
|
||
protected readonly DirichletPlatformAdHandle PlatformHandle;
|
||
private readonly IDirichletPlatformBridge bridge;
|
||
private readonly long spaceId;
|
||
|
||
public string SlotId => spaceId > 0 ? spaceId.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||
public bool IsLoaded { get; protected set; }
|
||
|
||
/// <summary>
|
||
/// Returns whether the ad is still valid and can be shown.
|
||
/// This checks the native ad object's validity, which may expire after some time.
|
||
/// Always call this before Show() to ensure the ad hasn't expired.
|
||
/// </summary>
|
||
public bool IsValid => bridge?.IsAdValid(PlatformHandle) ?? false;
|
||
|
||
/// <summary>
|
||
/// Raised when the native layer confirms the ad was shown to the user.
|
||
/// </summary>
|
||
public event Action Shown;
|
||
|
||
/// <summary>
|
||
/// Raised when the user clicks the ad.
|
||
/// </summary>
|
||
public event Action Clicked;
|
||
|
||
/// <summary>
|
||
/// Raised when the ad is closed (either by user action or channel logic).
|
||
/// </summary>
|
||
public event Action Closed;
|
||
|
||
/// <summary>
|
||
/// Raised when the native layer reports that a loaded ad failed to show.
|
||
/// </summary>
|
||
public event Action<DirichletError> ShowFailed;
|
||
|
||
internal DirichletAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle platformHandle, long spaceId)
|
||
{
|
||
this.bridge = bridge ?? throw new ArgumentNullException(nameof(bridge));
|
||
PlatformHandle = platformHandle ?? throw new ArgumentNullException(nameof(platformHandle));
|
||
this.spaceId = spaceId;
|
||
if (!string.IsNullOrEmpty(PlatformHandle.DebugId))
|
||
{
|
||
DirichletAdEventRouter.Register(PlatformHandle.DebugId, this);
|
||
}
|
||
}
|
||
|
||
internal IDirichletPlatformBridge Bridge => bridge;
|
||
|
||
public virtual bool Show()
|
||
{
|
||
if (!IsLoaded)
|
||
{
|
||
Debug.LogWarning($"[Dirichlet] Attempted to show ad before load success. Slot={SlotId}");
|
||
}
|
||
|
||
var shown = ShowInternal(null);
|
||
if (shown)
|
||
{
|
||
IsLoaded = false;
|
||
}
|
||
return shown;
|
||
}
|
||
|
||
public void Destroy()
|
||
{
|
||
if (!string.IsNullOrEmpty(PlatformHandle?.DebugId))
|
||
{
|
||
DirichletAdEventRouter.Unregister(PlatformHandle.DebugId);
|
||
}
|
||
DestroyInternal();
|
||
IsLoaded = false;
|
||
}
|
||
|
||
internal void MarkLoaded()
|
||
{
|
||
IsLoaded = true;
|
||
}
|
||
|
||
protected abstract bool ShowInternal(DirichletAdShowOptions options);
|
||
|
||
protected abstract void DestroyInternal();
|
||
|
||
internal virtual void HandleNativeEvent(DirichletNativeEvent nativeEvent)
|
||
{
|
||
switch (nativeEvent.EventName)
|
||
{
|
||
case DirichletNativeEventNames.Show:
|
||
OnShown();
|
||
break;
|
||
case DirichletNativeEventNames.Click:
|
||
OnClicked();
|
||
break;
|
||
case DirichletNativeEventNames.Close:
|
||
OnClosed();
|
||
break;
|
||
case DirichletNativeEventNames.ShowError:
|
||
OnShowFailed(nativeEvent.Data);
|
||
break;
|
||
}
|
||
}
|
||
|
||
protected virtual void OnShown()
|
||
{
|
||
Shown?.Invoke();
|
||
}
|
||
|
||
protected virtual void OnClicked()
|
||
{
|
||
Clicked?.Invoke();
|
||
}
|
||
|
||
protected virtual void OnClosed()
|
||
{
|
||
Closed?.Invoke();
|
||
}
|
||
|
||
protected virtual void OnShowFailed(DirichletNativeEventData data)
|
||
{
|
||
var code = data != null && data.Code != 0 ? data.Code.ToString(CultureInfo.InvariantCulture) : "show_error";
|
||
var message = data?.Message;
|
||
ShowFailed?.Invoke(new DirichletError(code, string.IsNullOrEmpty(message) ? "Ad failed to show" : message));
|
||
}
|
||
}
|
||
|
||
public abstract class DirichletRewardAdBase : DirichletAd
|
||
{
|
||
public event Action<DirichletRewardVerificationEventArgs> RewardVerified;
|
||
private IDirichletRewardAdInteractionListener interactionListener;
|
||
|
||
internal DirichletRewardAdBase(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
: base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
|
||
public void SetInteractionListener(IDirichletRewardAdInteractionListener listener)
|
||
{
|
||
interactionListener = listener;
|
||
}
|
||
|
||
public void SetRewardAdInteractionListener(IDirichletRewardAdInteractionListener listener)
|
||
{
|
||
SetInteractionListener(listener);
|
||
}
|
||
|
||
protected override void OnShown()
|
||
{
|
||
base.OnShown();
|
||
interactionListener?.OnAdShow();
|
||
}
|
||
|
||
protected override void OnClicked()
|
||
{
|
||
base.OnClicked();
|
||
interactionListener?.OnAdClick();
|
||
}
|
||
|
||
protected override void OnClosed()
|
||
{
|
||
base.OnClosed();
|
||
interactionListener?.OnAdClose();
|
||
}
|
||
|
||
internal override void HandleNativeEvent(DirichletNativeEvent nativeEvent)
|
||
{
|
||
base.HandleNativeEvent(nativeEvent);
|
||
|
||
if (nativeEvent.EventName == DirichletNativeEventNames.Reward && nativeEvent.Data != null)
|
||
{
|
||
var data = nativeEvent.Data;
|
||
var args = new DirichletRewardVerificationEventArgs(
|
||
data.RewardVerify,
|
||
data.RewardAmount,
|
||
data.RewardName,
|
||
data.Code,
|
||
data.Message);
|
||
RewardVerified?.Invoke(args);
|
||
interactionListener?.OnRewardVerify(args);
|
||
}
|
||
}
|
||
|
||
protected override bool ShowInternal(DirichletAdShowOptions options)
|
||
{
|
||
return Bridge.ShowRewardVideoAd(PlatformHandle);
|
||
}
|
||
|
||
protected override void DestroyInternal()
|
||
{
|
||
Bridge.DestroyAd(PlatformHandle);
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletRewardVideoAd : DirichletRewardAdBase
|
||
{
|
||
private DirichletRewardVideoAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
: base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
|
||
internal static DirichletRewardVideoAd Create(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
{
|
||
return new DirichletRewardVideoAd(bridge, handle, spaceId);
|
||
}
|
||
}
|
||
|
||
[Obsolete("Use DirichletRewardVideoAd instead.")]
|
||
public sealed class DirichletRewardAd : DirichletRewardAdBase
|
||
{
|
||
internal DirichletRewardAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId) : base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletInterstitialAd : DirichletAd
|
||
{
|
||
private IDirichletInterstitialAdInteractionListener interactionListener;
|
||
|
||
private DirichletInterstitialAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
: base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
|
||
public void SetInteractionListener(IDirichletInterstitialAdInteractionListener listener)
|
||
{
|
||
interactionListener = listener;
|
||
}
|
||
|
||
protected override void OnShown()
|
||
{
|
||
base.OnShown();
|
||
interactionListener?.OnAdShow();
|
||
}
|
||
|
||
protected override void OnClicked()
|
||
{
|
||
base.OnClicked();
|
||
interactionListener?.OnAdClick();
|
||
}
|
||
|
||
protected override void OnClosed()
|
||
{
|
||
base.OnClosed();
|
||
interactionListener?.OnAdClose();
|
||
}
|
||
|
||
protected override bool ShowInternal(DirichletAdShowOptions options)
|
||
{
|
||
return Bridge.ShowInterstitialAd(PlatformHandle);
|
||
}
|
||
|
||
protected override void DestroyInternal()
|
||
{
|
||
Bridge.DestroyAd(PlatformHandle);
|
||
}
|
||
|
||
internal static DirichletInterstitialAd Create(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
{
|
||
return new DirichletInterstitialAd(bridge, handle, spaceId);
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletBannerAd : DirichletAd
|
||
{
|
||
private IDirichletBannerAdInteractionListener interactionListener;
|
||
|
||
private DirichletBannerAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
: base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
|
||
public void SetInteractionListener(IDirichletBannerAdInteractionListener listener)
|
||
{
|
||
interactionListener = listener;
|
||
}
|
||
|
||
public bool Show(DirichletAdShowOptions options)
|
||
{
|
||
var showOptions = options ?? new DirichletAdShowOptions();
|
||
var shown = ShowInternal(showOptions);
|
||
if (shown)
|
||
{
|
||
IsLoaded = false;
|
||
}
|
||
return shown;
|
||
}
|
||
|
||
public override bool Show()
|
||
{
|
||
return Show(null);
|
||
}
|
||
|
||
protected override void OnShown()
|
||
{
|
||
base.OnShown();
|
||
interactionListener?.OnAdShow();
|
||
}
|
||
|
||
protected override void OnClicked()
|
||
{
|
||
base.OnClicked();
|
||
interactionListener?.OnAdClick();
|
||
}
|
||
|
||
protected override void OnClosed()
|
||
{
|
||
base.OnClosed();
|
||
interactionListener?.OnAdClose();
|
||
}
|
||
|
||
protected override bool ShowInternal(DirichletAdShowOptions options)
|
||
{
|
||
return Bridge.ShowBannerAd(PlatformHandle, options ?? new DirichletAdShowOptions());
|
||
}
|
||
|
||
protected override void DestroyInternal()
|
||
{
|
||
Bridge.DestroyAd(PlatformHandle);
|
||
}
|
||
|
||
internal static DirichletBannerAd Create(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
{
|
||
return new DirichletBannerAd(bridge, handle, spaceId);
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletSplashAd : DirichletAd
|
||
{
|
||
private IDirichletSplashAdInteractionListener interactionListener;
|
||
|
||
private DirichletSplashAd(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
: base(bridge, handle, spaceId)
|
||
{
|
||
}
|
||
|
||
public void SetInteractionListener(IDirichletSplashAdInteractionListener listener)
|
||
{
|
||
interactionListener = listener;
|
||
}
|
||
|
||
protected override void OnShown()
|
||
{
|
||
base.OnShown();
|
||
interactionListener?.OnAdShow();
|
||
}
|
||
|
||
protected override void OnClicked()
|
||
{
|
||
base.OnClicked();
|
||
interactionListener?.OnAdClick();
|
||
}
|
||
|
||
protected override void OnClosed()
|
||
{
|
||
base.OnClosed();
|
||
interactionListener?.OnAdClose();
|
||
}
|
||
|
||
protected override bool ShowInternal(DirichletAdShowOptions options)
|
||
{
|
||
return Bridge.ShowSplashAd(PlatformHandle, options ?? new DirichletAdShowOptions());
|
||
}
|
||
|
||
protected override void DestroyInternal()
|
||
{
|
||
Bridge.DestroyAd(PlatformHandle);
|
||
}
|
||
|
||
internal static DirichletSplashAd Create(IDirichletPlatformBridge bridge, DirichletPlatformAdHandle handle, long spaceId)
|
||
{
|
||
return new DirichletSplashAd(bridge, handle, spaceId);
|
||
}
|
||
}
|
||
|
||
internal static class DirichletNativeEventNames
|
||
{
|
||
internal const string Show = "show";
|
||
internal const string Click = "click";
|
||
internal const string Close = "close";
|
||
internal const string Reward = "reward";
|
||
internal const string ShowError = "show_error";
|
||
}
|
||
|
||
internal readonly struct DirichletNativeEvent
|
||
{
|
||
public string EventName { get; }
|
||
public string AdType { get; }
|
||
public DirichletNativeEventData Data { get; }
|
||
|
||
public DirichletNativeEvent(string eventName, string adType, DirichletNativeEventData data)
|
||
{
|
||
EventName = string.IsNullOrEmpty(eventName) ? string.Empty : eventName;
|
||
AdType = string.IsNullOrEmpty(adType) ? string.Empty : adType;
|
||
Data = data;
|
||
}
|
||
}
|
||
|
||
internal sealed class DirichletNativeEventData
|
||
{
|
||
public bool RewardVerify { get; }
|
||
public int RewardAmount { get; }
|
||
public string RewardName { get; }
|
||
public int Code { get; }
|
||
public string Message { get; }
|
||
|
||
public DirichletNativeEventData(bool rewardVerify, int rewardAmount, string rewardName, int code, string message)
|
||
{
|
||
RewardVerify = rewardVerify;
|
||
RewardAmount = rewardAmount;
|
||
RewardName = string.IsNullOrEmpty(rewardName) ? string.Empty : rewardName;
|
||
Code = code;
|
||
Message = message ?? string.Empty;
|
||
}
|
||
}
|
||
|
||
public sealed class DirichletRewardVerificationEventArgs : EventArgs
|
||
{
|
||
public bool IsVerified { get; }
|
||
public int RewardAmount { get; }
|
||
public string RewardName { get; }
|
||
public int Code { get; }
|
||
public string Message { get; }
|
||
|
||
internal DirichletRewardVerificationEventArgs(bool rewardVerified, int rewardAmount, string rewardName, int code, string message)
|
||
{
|
||
IsVerified = rewardVerified;
|
||
RewardAmount = rewardAmount;
|
||
RewardName = string.IsNullOrEmpty(rewardName) ? string.Empty : rewardName;
|
||
Code = code;
|
||
Message = message ?? string.Empty;
|
||
}
|
||
}
|
||
|
||
public interface IDirichletRewardAdInteractionListener
|
||
{
|
||
void OnAdShow();
|
||
void OnAdClick();
|
||
void OnAdClose();
|
||
void OnRewardVerify(DirichletRewardVerificationEventArgs args);
|
||
}
|
||
|
||
public interface IDirichletInterstitialAdInteractionListener
|
||
{
|
||
void OnAdShow();
|
||
void OnAdClick();
|
||
void OnAdClose();
|
||
}
|
||
|
||
public interface IDirichletBannerAdInteractionListener
|
||
{
|
||
void OnAdShow();
|
||
void OnAdClick();
|
||
void OnAdClose();
|
||
}
|
||
|
||
public interface IDirichletSplashAdInteractionListener
|
||
{
|
||
void OnAdShow();
|
||
void OnAdClick();
|
||
void OnAdClose();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Listener interface for auto interstitial ad callbacks.
|
||
/// Android only - iOS will receive OnError with not_supported error.
|
||
/// </summary>
|
||
public interface IDirichletInterstitialAutoAdListener
|
||
{
|
||
void OnError(DirichletError error);
|
||
void OnAdShow();
|
||
void OnAdClose();
|
||
void OnAdClick();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Listener interface for auto banner ad callbacks.
|
||
/// Android only - iOS will receive OnError with not_supported error.
|
||
/// </summary>
|
||
public interface IDirichletBannerAutoAdListener
|
||
{
|
||
void OnError(DirichletError error);
|
||
void OnAdShow();
|
||
void OnAdClose();
|
||
void OnAdClick();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Listener interface for auto splash ad callbacks.
|
||
/// Android only - iOS will receive OnError with not_supported error.
|
||
/// </summary>
|
||
public interface IDirichletSplashAutoAdListener
|
||
{
|
||
void OnError(DirichletError error);
|
||
void OnAdShow();
|
||
void OnAdClose();
|
||
void OnAdClick();
|
||
}
|
||
|
||
/// <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.
|
||
/// </summary>
|
||
public interface IDirichletRewardVideoAutoAdListener
|
||
{
|
||
/// <summary>
|
||
/// Called when ad fails to load or show.
|
||
/// </summary>
|
||
void OnError(DirichletError error);
|
||
|
||
/// <summary>
|
||
/// Called when ad is shown to the user.
|
||
/// </summary>
|
||
void OnAdShow();
|
||
|
||
/// <summary>
|
||
/// Called when ad is closed.
|
||
/// </summary>
|
||
void OnAdClose();
|
||
|
||
/// <summary>
|
||
/// Called when reward verification is completed.
|
||
/// </summary>
|
||
void OnRewardVerify(DirichletRewardVerificationEventArgs args);
|
||
|
||
/// <summary>
|
||
/// Called when ad is clicked.
|
||
/// </summary>
|
||
void OnAdClick();
|
||
}
|
||
|
||
internal static class DirichletAdEventRouter
|
||
{
|
||
private const string CallbackObjectName = "DirichletMediationEventReceiver";
|
||
private static readonly Dictionary<string, WeakReference> Ads = new Dictionary<string, WeakReference>(StringComparer.Ordinal);
|
||
private static bool receiverInitialized;
|
||
|
||
internal static void Register(string handleId, DirichletAd ad)
|
||
{
|
||
if (string.IsNullOrEmpty(handleId) || ad == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
EnsureReceiver();
|
||
Ads[handleId] = new WeakReference(ad);
|
||
}
|
||
|
||
internal static void Unregister(string handleId)
|
||
{
|
||
if (string.IsNullOrEmpty(handleId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
Ads.Remove(handleId);
|
||
}
|
||
|
||
private static void EnsureReceiver()
|
||
{
|
||
if (receiverInitialized)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!DirichletSdk.IsUnityThread)
|
||
{
|
||
DirichletSdk.DispatchToUnityThread(EnsureReceiver);
|
||
return;
|
||
}
|
||
|
||
var existing = GameObject.Find(CallbackObjectName);
|
||
if (existing == null)
|
||
{
|
||
var host = new GameObject(CallbackObjectName)
|
||
{
|
||
hideFlags = HideFlags.HideAndDontSave
|
||
};
|
||
UnityEngine.Object.DontDestroyOnLoad(host);
|
||
host.AddComponent<DirichletAdEventReceiver>();
|
||
}
|
||
|
||
receiverInitialized = true;
|
||
}
|
||
|
||
internal static void HandleNativeEvent(string payload)
|
||
{
|
||
if (string.IsNullOrEmpty(payload))
|
||
{
|
||
return;
|
||
}
|
||
|
||
NativeEventPayload message;
|
||
try
|
||
{
|
||
message = JsonUtility.FromJson<NativeEventPayload>(payload);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[Dirichlet] Failed to parse native ad event: {ex.Message}\n{payload}");
|
||
return;
|
||
}
|
||
|
||
if (message == null || string.IsNullOrEmpty(message.handle) || string.IsNullOrEmpty(message.eventName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!Ads.TryGetValue(message.handle, out var weakReference))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!(weakReference.Target is DirichletAd ad) || ad == null)
|
||
{
|
||
Ads.Remove(message.handle);
|
||
return;
|
||
}
|
||
|
||
var data = message.data != null
|
||
? new DirichletNativeEventData(
|
||
message.data.rewardVerify,
|
||
message.data.rewardAmount,
|
||
message.data.rewardName,
|
||
message.data.code,
|
||
message.data.message)
|
||
: null;
|
||
|
||
var nativeEvent = new DirichletNativeEvent(message.eventName, message.adType, data);
|
||
|
||
if (DirichletSdk.IsUnityThread)
|
||
{
|
||
ad.HandleNativeEvent(nativeEvent);
|
||
}
|
||
else
|
||
{
|
||
DirichletSdk.DispatchToUnityThread(() => ad.HandleNativeEvent(nativeEvent));
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
private class NativeEventPayload
|
||
{
|
||
public string handle;
|
||
public string eventName;
|
||
public string adType;
|
||
public NativeEventPayloadData data;
|
||
}
|
||
|
||
[Serializable]
|
||
private class NativeEventPayloadData
|
||
{
|
||
public bool rewardVerify;
|
||
public int rewardAmount;
|
||
public string rewardName;
|
||
public int code;
|
||
public string message;
|
||
}
|
||
|
||
private sealed class DirichletAdEventReceiver : MonoBehaviour
|
||
{
|
||
public void OnNativeEvent(string payload)
|
||
{
|
||
HandleNativeEvent(payload);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|