using System; using System.Collections.Generic; using System.Globalization; using UnityEngine; namespace Dirichlet.Mediation { /// /// 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. /// 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 } /// /// Mirrors the Android-side DirichletAdManager to keep configuration state and create ad natives. /// public static class DirichletAdManager { private static readonly object StateLock = new object(); private static DirichletAdConfig currentConfig; /// /// Returns the last configuration applied to the native SDK. /// 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; } } /// /// Creates a Unity-side DirichletAdNative wrapper that mirrors the Android aggregator API. /// public static DirichletAdNative CreateAdNative() { return new DirichletAdNative(DirichletSdk.GetBridge()); } internal static DirichletAdNative CreateAdNative(IDirichletPlatformBridge bridge) { return new DirichletAdNative(bridge); } } /// /// Thin wrapper around native ad handles provided by the platform bridge. /// 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; } /// /// Banner auto-ad slide rotation interval, in seconds. Native default is 30. /// 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 ToBridgePayload() { var payload = new Dictionary(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; } /// /// Sets banner auto-ad slide rotation interval, in seconds (recommended 30–120, native default 30). /// 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 ToBridgePayload() { var payload = new Dictionary(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 RewardAutoSessions = new Dictionary(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 onLoaded, Action 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 onLoaded, Action 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 onLoaded, Action 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 onLoaded, Action 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> onLoaded, Action onFailure) { NotifyNotSupported("express_feed", onFailure); } public void LoadNativeFeedAd(DirichletAdRequest request, Action> onLoaded, Action onFailure) { NotifyNotSupported("native_feed", onFailure); } /// /// 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”的简化方案(不做缓存) /// /// Ad request parameters /// Listener for all ad events (show/close/reward/click/error) 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 } /// /// Shows an interstitial ad with automatic load-and-show logic. /// Android only - iOS receives OnError with not_supported. /// public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener) { if (!ValidateRequest(request, error => listener?.OnError(error))) { return; } bridge.ShowInterstitialAutoAd(request, listener); } /// /// Shows a banner ad with automatic load + rotation logic. Container is created internally and /// anchored at the bottom of the screen by default. Use /// to control rotation interval. Android only - iOS receives OnError with not_supported. /// public void ShowBannerAutoAd(DirichletAdRequest request, IDirichletBannerAutoAdListener listener) { ShowBannerAutoAd(request, null, listener); } /// /// Banner auto-ad with explicit alignment/offset overrides. Advanced overload — /// the standard form is . /// public void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener) { if (!ValidateRequest(request, error => listener?.OnError(error))) { return; } bridge.ShowBannerAutoAd(request, options ?? new DirichletAdShowOptions(), listener); } /// /// Shows a splash ad with automatic load logic. Container is a fullscreen overlay created internally. /// Android only - iOS receives OnError with not_supported. /// public void ShowSplashAutoAd(DirichletAdRequest request, IDirichletSplashAutoAdListener listener) { ShowSplashAutoAd(request, null, listener); } /// /// Splash auto-ad advanced overload (currently no effective options for splash). /// public void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener) { if (!ValidateRequest(request, error => listener?.OnError(error))) { return; } bridge.ShowSplashAutoAd(request, options ?? new DirichletAdShowOptions(), listener); } /// /// 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. /// 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 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 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}"); } } } } /// /// 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. /// 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; } /// /// 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. /// public bool IsValid => bridge?.IsAdValid(PlatformHandle) ?? false; /// /// Raised when the native layer confirms the ad was shown to the user. /// public event Action Shown; /// /// Raised when the user clicks the ad. /// public event Action Clicked; /// /// Raised when the ad is closed (either by user action or channel logic). /// public event Action Closed; 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; } } protected virtual void OnShown() { Shown?.Invoke(); } protected virtual void OnClicked() { Clicked?.Invoke(); } protected virtual void OnClosed() { Closed?.Invoke(); } } public abstract class DirichletRewardAdBase : DirichletAd { public event Action 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 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(); } /// /// Listener interface for auto interstitial ad callbacks. /// Android only - iOS will receive OnError with not_supported error. /// public interface IDirichletInterstitialAutoAdListener { void OnError(DirichletError error); void OnAdShow(); void OnAdClose(); void OnAdClick(); } /// /// Listener interface for auto banner ad callbacks. /// Android only - iOS will receive OnError with not_supported error. /// public interface IDirichletBannerAutoAdListener { void OnError(DirichletError error); void OnAdShow(); void OnAdClose(); void OnAdClick(); } /// /// Listener interface for auto splash ad callbacks. /// Android only - iOS will receive OnError with not_supported error. /// public interface IDirichletSplashAutoAdListener { void OnError(DirichletError error); void OnAdShow(); void OnAdClose(); void OnAdClick(); } /// /// 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. /// public interface IDirichletRewardVideoAutoAdListener { /// /// Called when ad fails to load or show. /// void OnError(DirichletError error); /// /// Called when ad is shown to the user. /// void OnAdShow(); /// /// Called when ad is closed. /// void OnAdClose(); /// /// Called when reward verification is completed. /// void OnRewardVerify(DirichletRewardVerificationEventArgs args); /// /// Called when ad is clicked. /// void OnAdClick(); } internal static class DirichletAdEventRouter { private const string CallbackObjectName = "DirichletMediationEventReceiver"; private static readonly Dictionary Ads = new Dictionary(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(); } receiverInitialized = true; } internal static void HandleNativeEvent(string payload) { if (string.IsNullOrEmpty(payload)) { return; } NativeEventPayload message; try { message = JsonUtility.FromJson(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); } } } }