You've already forked Commercialization.tapadn
Implement TapADN commercialization module
This commit is contained in:
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
[Ll]ibrary/
|
||||
[Tt]emp/
|
||||
[Oo]bj/
|
||||
[Bb]uild/
|
||||
[Bb]uilds/
|
||||
[Ll]ogs/
|
||||
[Uu]ser[Ss]ettings/
|
||||
Packages/CC-Framework.Commercialization/
|
||||
|
||||
.vs/
|
||||
.idea/
|
||||
.gradle/
|
||||
.vscode/
|
||||
|
||||
*.csproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
*.mdb
|
||||
*.opendb
|
||||
*.VC.db
|
||||
|
||||
*.pidb.meta
|
||||
*.pdb.meta
|
||||
*.mdb.meta
|
||||
sysinfo.txt
|
||||
*.apk
|
||||
*.aab
|
||||
*.unitypackage
|
||||
.DS_Store
|
||||
9
Assets/CHANGELOG.md
Normal file
9
Assets/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# [1.0.0]
|
||||
|
||||
### 新增
|
||||
|
||||
* 接入 Dirichlet/TapADN 聚合 Unity SDK `4.2.5.0`。
|
||||
* 新增 `TapadnAdController`、激励视频、插屏、开屏播放器,实现 `CC-Framework.Commercialization` 抽象层。
|
||||
* 新增 Android 构建后处理:Manifest 权限、TapADN FileProvider、微信 OpenSDK WXEntryActivity/queries、AndroidX/Jetifier 属性。
|
||||
* 新增 `TapadnCommercialization` 便捷入口,由实现模块创建 controller 并初始化 `ADManager`。
|
||||
* 官方 Demo Sample 从主包剔除,调试内容改为可选 `Samples~`。
|
||||
7
Assets/CHANGELOG.md.meta
Normal file
7
Assets/CHANGELOG.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae16d7859fcf496b80493823cb6fa18d
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/DirichletMediation.meta
Normal file
8
Assets/DirichletMediation.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d3b1d66c5f548b0a92be7e4a9c7d4c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/DirichletMediation/Editor.meta
Normal file
8
Assets/DirichletMediation/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d76a8c4482f84d83ab0b17d2cf0b8346
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
272
Assets/DirichletMediation/Editor/DirichletGradlePostProcessor.cs
Normal file
272
Assets/DirichletMediation/Editor/DirichletGradlePostProcessor.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
#if UNITY_ANDROID
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Dirichlet.Mediation.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Post-processes the Gradle files to inject Dirichlet SDK dependencies.
|
||||
///
|
||||
/// This approach allows coexistence with other SDKs (e.g., TapSDK) by injecting
|
||||
/// dependencies into Unity-generated Gradle files rather than shipping static templates.
|
||||
/// </summary>
|
||||
public class DirichletGradlePostProcessor : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
private const string TAG = "[DirichletMediation]";
|
||||
|
||||
// Marker comments to identify our injected content
|
||||
private const string DIRICHLET_DEPS_START = "// Dirichlet Mediation Dependencies Start";
|
||||
private const string DIRICHLET_DEPS_END = "// Dirichlet Mediation Dependencies End";
|
||||
private const string DIRICHLET_REPOS_START = "// Dirichlet Mediation Repositories Start";
|
||||
private const string DIRICHLET_REPOS_END = "// Dirichlet Mediation Repositories End";
|
||||
|
||||
public int callbackOrder => 100; // Run after EDM4U (which uses lower values)
|
||||
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
var enableCsj = EditorPrefs.GetBool("Dirichlet.Android.EnableCSJ", true);
|
||||
var enableGdt = EditorPrefs.GetBool("Dirichlet.Android.EnableGDT", true);
|
||||
var enableIqy = EditorPrefs.GetBool("Dirichlet.Android.EnableIQY", true);
|
||||
|
||||
Debug.Log($"{TAG} Processing Gradle project at: {path}");
|
||||
Debug.Log($"{TAG} CSJ enabled: {enableCsj}, GDT enabled: {enableGdt}, IQY enabled: {enableIqy}");
|
||||
|
||||
ProcessBuildGradle(path, enableCsj, enableGdt, enableIqy);
|
||||
ProcessSettingsGradle(path, enableCsj, enableGdt, enableIqy);
|
||||
}
|
||||
|
||||
private void ProcessBuildGradle(string projectPath, bool enableCsj, bool enableGdt, bool enableIqy)
|
||||
{
|
||||
// Unity 2019.3+: projectPath is unityLibrary folder, build.gradle is directly inside
|
||||
// Unity 2019.2 and below: projectPath might be the root, need to search
|
||||
var gradlePath = Path.Combine(projectPath, "build.gradle");
|
||||
|
||||
if (!File.Exists(gradlePath))
|
||||
{
|
||||
// Fallback: try to find build.gradle in subdirectories
|
||||
var searchPaths = new[]
|
||||
{
|
||||
Path.Combine(projectPath, "unityLibrary", "build.gradle"),
|
||||
Path.Combine(projectPath, "src", "main", "build.gradle")
|
||||
};
|
||||
|
||||
foreach (var path in searchPaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
gradlePath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(gradlePath))
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not find build.gradle at {projectPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"{TAG} Found build.gradle at: {gradlePath}");
|
||||
|
||||
var content = File.ReadAllText(gradlePath);
|
||||
Debug.Log($"{TAG} Original build.gradle length: {content.Length}");
|
||||
|
||||
// Remove any previously injected content (for clean re-injection)
|
||||
content = RemoveInjectedContent(content, DIRICHLET_DEPS_START, DIRICHLET_DEPS_END);
|
||||
content = RemoveInjectedContent(content, DIRICHLET_REPOS_START, DIRICHLET_REPOS_END);
|
||||
|
||||
// Inject repositories
|
||||
content = InjectRepositories(content, enableCsj, enableGdt, enableIqy);
|
||||
|
||||
// Inject dependencies
|
||||
content = InjectDependencies(content, enableCsj, enableGdt, enableIqy);
|
||||
|
||||
File.WriteAllText(gradlePath, content);
|
||||
Debug.Log($"{TAG} Updated build.gradle with Dirichlet Mediation dependencies");
|
||||
}
|
||||
|
||||
private string InjectRepositories(string content, bool enableCsj, bool enableGdt, bool enableIqy)
|
||||
{
|
||||
// Check if our repos are already injected
|
||||
if (content.Contains(DIRICHLET_REPOS_START))
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
var reposBlock = new StringBuilder();
|
||||
reposBlock.AppendLine(DIRICHLET_REPOS_START);
|
||||
reposBlock.AppendLine(" google()");
|
||||
reposBlock.AppendLine(" mavenCentral()");
|
||||
reposBlock.AppendLine(" flatDir {");
|
||||
reposBlock.AppendLine(" dirs 'libs', 'DirichletMediation/libs'");
|
||||
reposBlock.AppendLine(" }");
|
||||
|
||||
if (enableCsj)
|
||||
{
|
||||
reposBlock.AppendLine(" maven { url 'https://artifact.bytedance.com/repository/pangle' }");
|
||||
}
|
||||
if (enableGdt)
|
||||
{
|
||||
reposBlock.AppendLine(" maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }");
|
||||
}
|
||||
|
||||
reposBlock.AppendLine($" {DIRICHLET_REPOS_END}");
|
||||
|
||||
// Try to find repositories block and inject after opening brace
|
||||
var reposPattern = new Regex(@"(repositories\s*\{)");
|
||||
if (reposPattern.IsMatch(content))
|
||||
{
|
||||
content = reposPattern.Replace(content, m =>
|
||||
m.Groups[1].Value + "\n " + reposBlock.ToString(), 1);
|
||||
Debug.Log($"{TAG} Injected repositories block");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not find repositories block, adding one");
|
||||
var applyPattern = new Regex(@"(apply plugin:\s*'com\.android\.library'[^\n]*\n)");
|
||||
if (applyPattern.IsMatch(content))
|
||||
{
|
||||
content = applyPattern.Replace(content, m =>
|
||||
m.Groups[1].Value + "\nrepositories {\n " + reposBlock.ToString() + "}\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private string InjectDependencies(string content, bool enableCsj, bool enableGdt, bool enableIqy)
|
||||
{
|
||||
// Check if our deps are already injected
|
||||
if (content.Contains(DIRICHLET_DEPS_START))
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
var depsBlock = new StringBuilder();
|
||||
depsBlock.AppendLine(DIRICHLET_DEPS_START);
|
||||
|
||||
// Core Mediation AAR
|
||||
depsBlock.AppendLine(" implementation(name: 'DirichletAD_Mediation_4.2.5.0', ext: 'aar')");
|
||||
|
||||
// CSJ (穿山甲) Adapter and SDK
|
||||
if (enableCsj)
|
||||
{
|
||||
depsBlock.AppendLine(" implementation(name: 'DirichletAD_CSJ_Adapter_4.2.5.0', ext: 'aar')");
|
||||
depsBlock.AppendLine(" implementation(name: 'open_ad_sdk_7.4.2.2', ext: 'aar')");
|
||||
}
|
||||
|
||||
// GDT (广点通) Adapter and SDK
|
||||
if (enableGdt)
|
||||
{
|
||||
depsBlock.AppendLine(" implementation(name: 'DirichletAD_GDT_Adapter_4.2.5.0', ext: 'aar')");
|
||||
depsBlock.AppendLine(" implementation(name: 'GDTSDK.unionNormal.4.671.1541', ext: 'aar')");
|
||||
}
|
||||
|
||||
// IQY (爱奇艺) Adapter and SDK
|
||||
if (enableIqy)
|
||||
{
|
||||
depsBlock.AppendLine(" implementation(name: 'DirichletAD_IQY_Adapter_4.2.5.0', ext: 'aar')");
|
||||
depsBlock.AppendLine(" implementation(name: 'iadsdk-release-2.3.102.110', ext: 'aar')");
|
||||
depsBlock.AppendLine(" implementation 'com.android.support.constraint:constraint-layout:1.1.3'");
|
||||
}
|
||||
|
||||
// Maven dependencies (required for SDK functionality)
|
||||
depsBlock.AppendLine(" implementation 'com.android.support:recyclerview-v7:28.0.0'");
|
||||
depsBlock.AppendLine(" implementation 'com.github.bumptech.glide:glide:4.9.0'");
|
||||
depsBlock.AppendLine(" implementation 'com.android.support:support-v4:28.0.0'");
|
||||
depsBlock.AppendLine(" implementation 'com.android.support:support-annotations:28.0.0'");
|
||||
depsBlock.AppendLine(" implementation 'com.android.support:appcompat-v7:28.0.0'");
|
||||
depsBlock.AppendLine(" implementation 'com.squareup.okhttp3:okhttp:3.12.1'");
|
||||
|
||||
depsBlock.AppendLine($" {DIRICHLET_DEPS_END}");
|
||||
|
||||
// Find dependencies block and inject after opening brace
|
||||
var depsPattern = new Regex(@"(dependencies\s*\{)");
|
||||
if (depsPattern.IsMatch(content))
|
||||
{
|
||||
content = depsPattern.Replace(content, m =>
|
||||
m.Groups[1].Value + "\n " + depsBlock.ToString(), 1);
|
||||
Debug.Log($"{TAG} Injected dependencies block");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not find dependencies block");
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private void ProcessSettingsGradle(string projectPath, bool enableCsj, bool enableGdt, bool enableIqy)
|
||||
{
|
||||
var parentDir = Directory.GetParent(projectPath)?.FullName;
|
||||
if (string.IsNullOrEmpty(parentDir))
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not get parent directory");
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsPath = Path.Combine(parentDir, "settings.gradle");
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not find settings.gradle at {settingsPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var content = File.ReadAllText(settingsPath);
|
||||
|
||||
// Check if already injected
|
||||
if (content.Contains(DIRICHLET_REPOS_START))
|
||||
{
|
||||
Debug.Log($"{TAG} settings.gradle already has Dirichlet repos");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any previously injected content
|
||||
content = RemoveInjectedContent(content, DIRICHLET_REPOS_START, DIRICHLET_REPOS_END);
|
||||
|
||||
var reposBlock = new StringBuilder();
|
||||
reposBlock.AppendLine(DIRICHLET_REPOS_START);
|
||||
reposBlock.AppendLine(" google()");
|
||||
reposBlock.AppendLine(" mavenCentral()");
|
||||
reposBlock.AppendLine(" flatDir {");
|
||||
reposBlock.AppendLine(" dirs \"${project(':unityLibrary').projectDir}/libs\", \"${project(':unityLibrary').projectDir}/DirichletMediation/libs\"");
|
||||
reposBlock.AppendLine(" }");
|
||||
|
||||
if (enableCsj)
|
||||
{
|
||||
reposBlock.AppendLine(" maven { url 'https://artifact.bytedance.com/repository/pangle' }");
|
||||
}
|
||||
if (enableGdt)
|
||||
{
|
||||
reposBlock.AppendLine(" maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }");
|
||||
}
|
||||
|
||||
reposBlock.AppendLine($" {DIRICHLET_REPOS_END}");
|
||||
|
||||
// Find dependencyResolutionManagement repositories block
|
||||
var reposPattern = new Regex(@"(dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{)");
|
||||
if (reposPattern.IsMatch(content))
|
||||
{
|
||||
content = reposPattern.Replace(content, m =>
|
||||
m.Groups[1].Value + "\n " + reposBlock.ToString(), 1);
|
||||
File.WriteAllText(settingsPath, content);
|
||||
Debug.Log($"{TAG} Updated settings.gradle with Dirichlet Mediation repositories");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{TAG} Could not find dependencyResolutionManagement repositories block in settings.gradle");
|
||||
}
|
||||
}
|
||||
|
||||
private string RemoveInjectedContent(string content, string startMarker, string endMarker)
|
||||
{
|
||||
var pattern = new Regex($@"\s*{Regex.Escape(startMarker)}[\s\S]*?{Regex.Escape(endMarker)}\s*");
|
||||
return pattern.Replace(content, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9018fbefe0e148e0843d119260e4c8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
DirichletMediation Android/iOS Dependencies
|
||||
|
||||
This file declares Maven dependencies for EDM4U (External Dependency Manager for Unity).
|
||||
Local AAR files are handled separately by DirichletGradlePostProcessor.
|
||||
|
||||
Note: This file coexists with other SDK dependencies (e.g., TapSDK).
|
||||
EDM4U will merge all *Dependencies.xml files when resolving.
|
||||
-->
|
||||
<dependencies>
|
||||
|
||||
<!-- Android dependencies -->
|
||||
<androidPackages>
|
||||
|
||||
<!-- Support Libraries -->
|
||||
<androidPackage spec="com.android.support:recyclerview-v7:28.0.0">
|
||||
<repositories>
|
||||
<repository>https://maven.google.com</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<androidPackage spec="com.android.support:support-v4:28.0.0">
|
||||
<repositories>
|
||||
<repository>https://maven.google.com</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<androidPackage spec="com.android.support:support-annotations:28.0.0">
|
||||
<repositories>
|
||||
<repository>https://maven.google.com</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<androidPackage spec="com.android.support:appcompat-v7:28.0.0">
|
||||
<repositories>
|
||||
<repository>https://maven.google.com</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<!-- Third-party Libraries -->
|
||||
<androidPackage spec="com.github.bumptech.glide:glide:4.9.0">
|
||||
<repositories>
|
||||
<repository>https://repo.maven.apache.org/maven2</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<androidPackage spec="com.squareup.okhttp3:okhttp:3.12.1">
|
||||
<repositories>
|
||||
<repository>https://repo.maven.apache.org/maven2</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
<androidPackage spec="com.android.support.constraint:constraint-layout:1.1.3">
|
||||
<repositories>
|
||||
<repository>https://maven.google.com</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
|
||||
</androidPackages>
|
||||
|
||||
<!-- iOS dependencies (if any) -->
|
||||
<iosPods>
|
||||
<!-- Add CocoaPods dependencies here if needed -->
|
||||
</iosPods>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414bdf38f1dc04fbd9e3d14650f42ada
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,854 @@
|
||||
#if UNITY_IOS
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
using UnityEditor.iOS.Xcode.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Dirichlet.Mediation.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// iOS build post-processor for Dirichlet Mediation SDK.
|
||||
/// Generates dynamic Podfile based on adapter settings and runs CocoaPods installation.
|
||||
///
|
||||
/// 重构说明(2026-02-05):
|
||||
/// - 不再在 Podfile 内做 target 猜测,改为 Post Process 直接从 pbxproj 读取真实 target 名称
|
||||
/// - 解析失败时直接中断并输出可诊断信息,不再静默兜底
|
||||
///
|
||||
/// 修复(2026-03-12):
|
||||
/// - 所有 pods 统一放到 Framework target(UnityFramework),不再分层
|
||||
/// - 原因:SDK 通过 NSClassFromString 动态查找 Adapter 类,Adapter 必须与 SDK 在同一 target,
|
||||
/// 否则静态库符号会被 strip 导致运行时找不到适配器类
|
||||
/// </summary>
|
||||
public class DirichletMediationIOSPostProcessor
|
||||
{
|
||||
private const string SDKVersion = "4.2.0.2";
|
||||
private const string MinIOSVersion = "11.0";
|
||||
|
||||
// 环境变量 override(仅在解析失败或接入方工程极端定制时使用)
|
||||
private const string ENV_FRAMEWORK_TARGET = "DIRICHLET_UNITY_FRAMEWORK_TARGET";
|
||||
private const string ENV_APP_TARGET = "DIRICHLET_UNITY_APP_TARGET";
|
||||
|
||||
/// <summary>
|
||||
/// 解析出的 target 信息
|
||||
/// </summary>
|
||||
private class TargetInfo
|
||||
{
|
||||
public string FrameworkTargetName { get; set; }
|
||||
public string AppTargetName { get; set; }
|
||||
public string FrameworkTargetGuid { get; set; }
|
||||
public string AppTargetGuid { get; set; }
|
||||
}
|
||||
|
||||
[PostProcessBuild(100)]
|
||||
public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuiltProject)
|
||||
{
|
||||
if (buildTarget != BuildTarget.iOS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("[DirichletMediation] Starting iOS post-process...");
|
||||
|
||||
try
|
||||
{
|
||||
// 确保默认值:iOS 适配器默认勾选(避免历史版本的 EditorPrefs 默认值导致“默认不勾选”)
|
||||
EnsureDefaultAdapterPrefs();
|
||||
|
||||
// 0. 解析 target 信息(核心改动:不做猜测,直接从 pbxproj 读取)
|
||||
var targetInfo = ResolveTargetInfo(pathToBuiltProject);
|
||||
|
||||
// 1. Generate Podfile dynamically based on adapter settings and resolved targets
|
||||
GeneratePodfile(pathToBuiltProject, targetInfo);
|
||||
|
||||
// 2. Modify Xcode project settings (before pod install)
|
||||
ModifyXcodeProject(pathToBuiltProject, targetInfo);
|
||||
|
||||
// 3. Modify Info.plist for required permissions
|
||||
ModifyInfoPlist(pathToBuiltProject);
|
||||
|
||||
// 4. Run pod install
|
||||
RunPodInstall(pathToBuiltProject);
|
||||
|
||||
// 5. Embed GDT dynamic frameworks into app target (must run after pod install)
|
||||
EmbedGDTDynamicFrameworks(pathToBuiltProject, targetInfo);
|
||||
|
||||
Debug.Log("[DirichletMediation] iOS post-process completed successfully.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"[DirichletMediation] iOS post-process failed: {ex.Message}");
|
||||
Debug.LogError($"[DirichletMediation] Stack trace: {ex.StackTrace}");
|
||||
throw; // 重新抛出,让 Unity 构建失败(不静默跳过)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 pbxproj 解析出 framework target 和 app target 的真实名称
|
||||
/// 不做猜测,解析失败时直接中断
|
||||
/// </summary>
|
||||
private static TargetInfo ResolveTargetInfo(string projectPath)
|
||||
{
|
||||
Debug.Log("[DirichletMediation] Resolving target info from pbxproj...");
|
||||
|
||||
// 检查环境变量 override
|
||||
var envFrameworkTarget = System.Environment.GetEnvironmentVariable(ENV_FRAMEWORK_TARGET);
|
||||
var envAppTarget = System.Environment.GetEnvironmentVariable(ENV_APP_TARGET);
|
||||
|
||||
if (!string.IsNullOrEmpty(envFrameworkTarget) && !string.IsNullOrEmpty(envAppTarget))
|
||||
{
|
||||
Debug.Log($"[DirichletMediation] Using targets from environment variables:");
|
||||
Debug.Log($" Framework target: {envFrameworkTarget}");
|
||||
Debug.Log($" App target: {envAppTarget}");
|
||||
return new TargetInfo
|
||||
{
|
||||
FrameworkTargetName = envFrameworkTarget,
|
||||
AppTargetName = envAppTarget,
|
||||
// GUID 在需要时从 pbxproj 反查
|
||||
};
|
||||
}
|
||||
|
||||
// 查找 .xcodeproj 文件
|
||||
var xcodeProjectName = DetectXcodeProjectName(projectPath);
|
||||
var projectFilePath = Path.Combine(projectPath, $"{xcodeProjectName}.xcodeproj/project.pbxproj");
|
||||
|
||||
if (!File.Exists(projectFilePath))
|
||||
{
|
||||
throw new System.Exception($"project.pbxproj not found at: {projectFilePath}");
|
||||
}
|
||||
|
||||
var pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile(projectFilePath);
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
var frameworkGuid = pbxProject.GetUnityFrameworkTargetGuid();
|
||||
var appGuid = pbxProject.GetUnityMainTargetGuid();
|
||||
#else
|
||||
// Unity 2019.3 之前只有单一 target
|
||||
var frameworkGuid = pbxProject.TargetGuidByName("Unity-iPhone");
|
||||
var appGuid = frameworkGuid;
|
||||
#endif
|
||||
|
||||
// 直接从 pbxproj 解析 GUID -> name 映射(最稳健,避免 TargetGuidByName 行为差异)
|
||||
var nativeTargetMap = GetNativeTargetGuidToNameMap(projectFilePath);
|
||||
|
||||
if (string.IsNullOrEmpty(frameworkGuid) || string.IsNullOrEmpty(appGuid))
|
||||
{
|
||||
var allTargets = nativeTargetMap.Values.Distinct().ToArray();
|
||||
var targetList = allTargets.Any() ? string.Join(", ", allTargets) : "(none)";
|
||||
throw new System.Exception(
|
||||
$"Unity PBXProject API returned empty target GUID.\n" +
|
||||
$" .xcodeproj: {xcodeProjectName}\n" +
|
||||
$" Available targets: {targetList}\n" +
|
||||
$" Framework GUID: {frameworkGuid ?? "(null)"}\n" +
|
||||
$" App GUID: {appGuid ?? "(null)"}\n\n" +
|
||||
$"解决方案:\n" +
|
||||
$" 1. 升级 Unity 到 2019.3+(或团结引擎对应版本),确保导出包含 UnityFramework/App 双 target\n" +
|
||||
$" 2. 或使用环境变量强制指定 target:\n" +
|
||||
$" export {ENV_FRAMEWORK_TARGET}=YourFrameworkTarget\n" +
|
||||
$" export {ENV_APP_TARGET}=YourAppTarget"
|
||||
);
|
||||
}
|
||||
|
||||
// 从 GUID 反查 target name
|
||||
nativeTargetMap.TryGetValue(frameworkGuid, out var frameworkTargetName);
|
||||
nativeTargetMap.TryGetValue(appGuid, out var appTargetName);
|
||||
|
||||
// 兜底:某些 Unity 版本可能返回非 PBXNativeTarget GUID,这时再尝试旧逻辑反查
|
||||
frameworkTargetName = frameworkTargetName ?? GetTargetNameByGuid(pbxProject, frameworkGuid, projectFilePath);
|
||||
appTargetName = appTargetName ?? GetTargetNameByGuid(pbxProject, appGuid, projectFilePath);
|
||||
|
||||
// 验证解析结果
|
||||
if (string.IsNullOrEmpty(frameworkTargetName) || string.IsNullOrEmpty(appTargetName))
|
||||
{
|
||||
// 输出所有 targets 帮助诊断
|
||||
var allTargets = nativeTargetMap.Values.Distinct().ToArray();
|
||||
var targetList = allTargets.Any() ? string.Join(", ", allTargets) : "(none)";
|
||||
|
||||
throw new System.Exception(
|
||||
$"Failed to resolve target names from pbxproj.\n" +
|
||||
$" .xcodeproj: {xcodeProjectName}\n" +
|
||||
$" Available targets: {targetList}\n" +
|
||||
$" Framework GUID: {frameworkGuid ?? "(null)"}\n" +
|
||||
$" App GUID: {appGuid ?? "(null)"}\n\n" +
|
||||
$"解决方案:\n" +
|
||||
$" 1. 设置环境变量强制指定 target:\n" +
|
||||
$" export {ENV_FRAMEWORK_TARGET}=YourFrameworkTarget\n" +
|
||||
$" export {ENV_APP_TARGET}=YourAppTarget\n" +
|
||||
$" 2. 检查导出工程是否包含 UnityFramework 和 Unity-iPhone(或对应的团结引擎 target)"
|
||||
);
|
||||
}
|
||||
|
||||
Debug.Log($"[DirichletMediation] Resolved targets:");
|
||||
Debug.Log($" Framework target: {frameworkTargetName} (GUID: {frameworkGuid})");
|
||||
Debug.Log($" App target: {appTargetName} (GUID: {appGuid})");
|
||||
|
||||
return new TargetInfo
|
||||
{
|
||||
FrameworkTargetName = frameworkTargetName,
|
||||
AppTargetName = appTargetName,
|
||||
FrameworkTargetGuid = frameworkGuid,
|
||||
AppTargetGuid = appGuid
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 GUID 反查 target name
|
||||
/// Unity PBXProject API 没有直接提供此方法,需要通过 TargetGuidByName 反向验证
|
||||
/// </summary>
|
||||
private static string GetTargetNameByGuid(PBXProject pbxProject, string targetGuid, string projectFilePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetGuid))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 最可靠的方式:直接从 pbxproj 解析 GUID -> name
|
||||
var nativeTargetMap = GetNativeTargetGuidToNameMap(projectFilePath);
|
||||
if (nativeTargetMap.TryGetValue(targetGuid, out var parsedName) && !string.IsNullOrEmpty(parsedName))
|
||||
{
|
||||
return parsedName;
|
||||
}
|
||||
|
||||
// 常见的 Unity/Tuanjie target 名称
|
||||
var commonTargetNames = new[]
|
||||
{
|
||||
"UnityFramework", "Unity-iPhone",
|
||||
"TuanjieFramework", "Tuanjie-iPhone",
|
||||
"GameAssembly", // 部分定制工程
|
||||
};
|
||||
|
||||
foreach (var name in commonTargetNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var guid = pbxProject.TargetGuidByName(name);
|
||||
if (guid == targetGuid)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Target 不存在,继续尝试下一个
|
||||
}
|
||||
}
|
||||
|
||||
// 如果常见名称都不匹配,尝试从文件中解析所有 target
|
||||
var allTargets = GetAllTargetNames(projectFilePath);
|
||||
foreach (var name in allTargets)
|
||||
{
|
||||
try
|
||||
{
|
||||
var guid = pbxProject.TargetGuidByName(name);
|
||||
if (guid == targetGuid)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 继续尝试
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 project.pbxproj 解析 PBXNativeTarget 的 GUID -> name 映射
|
||||
/// </summary>
|
||||
private static System.Collections.Generic.Dictionary<string, string> GetNativeTargetGuidToNameMap(string projectFilePath)
|
||||
{
|
||||
var result = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
try
|
||||
{
|
||||
// 形如:
|
||||
// 9D25AB9C213FB47800354C27 /* UnityFramework */ = {
|
||||
var entryStartRegex = new System.Text.RegularExpressions.Regex(
|
||||
@"^\s*([0-9A-Fa-f]{24})\s*/\*\s*(.*?)\s*\*/\s*=\s*\{\s*$",
|
||||
System.Text.RegularExpressions.RegexOptions.CultureInvariant
|
||||
);
|
||||
|
||||
string currentGuid = null;
|
||||
string currentName = null;
|
||||
var braceDepth = 0;
|
||||
var isNativeTarget = false;
|
||||
|
||||
foreach (var line in File.ReadLines(projectFilePath))
|
||||
{
|
||||
if (braceDepth == 0)
|
||||
{
|
||||
var m = entryStartRegex.Match(line);
|
||||
if (!m.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
currentGuid = m.Groups[1].Value.Trim();
|
||||
currentName = m.Groups[2].Value.Trim();
|
||||
braceDepth = CountBraceDelta(line); // start line includes '{'
|
||||
isNativeTarget = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isNativeTarget && line.IndexOf("isa = PBXNativeTarget;", System.StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
isNativeTarget = true;
|
||||
}
|
||||
|
||||
braceDepth += CountBraceDelta(line);
|
||||
|
||||
if (braceDepth <= 0)
|
||||
{
|
||||
if (isNativeTarget && !string.IsNullOrEmpty(currentGuid) && !string.IsNullOrEmpty(currentName))
|
||||
{
|
||||
result[currentGuid] = currentName;
|
||||
}
|
||||
|
||||
currentGuid = null;
|
||||
currentName = null;
|
||||
braceDepth = 0;
|
||||
isNativeTarget = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int CountBraceDelta(string line)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var delta = 0;
|
||||
foreach (var c in line)
|
||||
{
|
||||
if (c == '{') delta++;
|
||||
else if (c == '}') delta--;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 pbxproj 文件解析所有 target 名称(用于诊断输出)
|
||||
/// </summary>
|
||||
private static string[] GetAllTargetNames(string projectFilePath)
|
||||
{
|
||||
var map = GetNativeTargetGuidToNameMap(projectFilePath);
|
||||
return map.Values.Distinct().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 Podfile
|
||||
/// 所有 pods 统一放到 Framework target(UnityFramework),因为:
|
||||
/// - Bridge (.mm) 和 DirichletMediationSDK 都编译/运行在 Framework target 中
|
||||
/// - SDK 通过 NSClassFromString 动态查找 Adapter 类,Adapter 必须与 SDK 在同一 target
|
||||
/// 否则静态库符号可能被 strip 或不在同一二进制中,导致运行时找不到类
|
||||
/// </summary>
|
||||
private static void GeneratePodfile(string projectPath, TargetInfo targetInfo)
|
||||
{
|
||||
// Read adapter settings from EditorPrefs
|
||||
// Note: DirichletAdSDK (DRA adapter) is always enabled as core SDK
|
||||
var enableCsj = EditorPrefs.GetBool("Dirichlet.iOS.EnableCSJ", true);
|
||||
var enableGdt = EditorPrefs.GetBool("Dirichlet.iOS.EnableGDT", true);
|
||||
|
||||
var podfileContent = new StringBuilder();
|
||||
podfileContent.AppendLine("# Generated by Dirichlet Mediation Unity Plugin");
|
||||
podfileContent.AppendLine("#");
|
||||
podfileContent.AppendLine($"# Framework target: {targetInfo.FrameworkTargetName}");
|
||||
podfileContent.AppendLine($"# App target: {targetInfo.AppTargetName}");
|
||||
podfileContent.AppendLine();
|
||||
podfileContent.AppendLine("source 'https://cdn.cocoapods.org/'");
|
||||
podfileContent.AppendLine();
|
||||
podfileContent.AppendLine($"platform :ios, '{MinIOSVersion}'");
|
||||
podfileContent.AppendLine("use_frameworks! :linkage => :static");
|
||||
podfileContent.AppendLine();
|
||||
|
||||
// 所有 pods 统一放到 Framework target
|
||||
// SDK 通过 NSClassFromString 查找 Adapter 类,必须在同一 target 中
|
||||
podfileContent.AppendLine($"target '{targetInfo.FrameworkTargetName}' do");
|
||||
podfileContent.AppendLine($" pod 'DirichletMediationSDK', '{SDKVersion}'");
|
||||
|
||||
if (enableCsj)
|
||||
{
|
||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterCSJ', '{SDKVersion}'");
|
||||
}
|
||||
|
||||
if (enableGdt)
|
||||
{
|
||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterGDT', '{SDKVersion}'");
|
||||
}
|
||||
|
||||
// DirichletAdSDK (DRA adapter) is always included as core SDK
|
||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterDRA', '{SDKVersion}'");
|
||||
|
||||
podfileContent.AppendLine("end");
|
||||
podfileContent.AppendLine();
|
||||
|
||||
// Post-install: 基础构建设置
|
||||
podfileContent.AppendLine("post_install do |installer|");
|
||||
podfileContent.AppendLine(" installer.pods_project.targets.each do |target|");
|
||||
podfileContent.AppendLine(" target.build_configurations.each do |config|");
|
||||
podfileContent.AppendLine($" config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '{MinIOSVersion}'");
|
||||
podfileContent.AppendLine(" config.build_settings['ENABLE_BITCODE'] = 'NO'");
|
||||
podfileContent.AppendLine(" end");
|
||||
podfileContent.AppendLine(" end");
|
||||
podfileContent.AppendLine("end");
|
||||
|
||||
var podfilePath = Path.Combine(projectPath, "Podfile");
|
||||
File.WriteAllText(podfilePath, podfileContent.ToString());
|
||||
|
||||
Debug.Log($"[DirichletMediation] Generated Podfile at {podfilePath}");
|
||||
Debug.Log($"[DirichletMediation] All pods allocated to {targetInfo.FrameworkTargetName} (CSJ={enableCsj}, GDT={enableGdt}, DRA=always)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测 Xcode 项目名称,兼容 Unity/Tuanjie/自定义项目
|
||||
/// 动态搜索目录下的 .xcodeproj 文件
|
||||
/// </summary>
|
||||
private static string DetectXcodeProjectName(string projectPath)
|
||||
{
|
||||
// 搜索所有 .xcodeproj 目录
|
||||
var xcodeprojDirs = Directory.GetDirectories(projectPath, "*.xcodeproj");
|
||||
|
||||
if (xcodeprojDirs.Length == 0)
|
||||
{
|
||||
throw new System.Exception(
|
||||
$"No .xcodeproj found in: {projectPath}\n" +
|
||||
"请确认 Unity 导出路径正确,且导出已完成。"
|
||||
);
|
||||
}
|
||||
|
||||
// 获取第一个 xcodeproj 的名称(不含扩展名)
|
||||
var xcodeprojPath = xcodeprojDirs[0];
|
||||
var xcodeprojName = Path.GetFileNameWithoutExtension(xcodeprojPath);
|
||||
|
||||
Debug.Log($"[DirichletMediation] Detected Xcode project: {xcodeprojName}");
|
||||
return xcodeprojName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改 Xcode 工程设置(使用已解析的 target 信息)
|
||||
/// </summary>
|
||||
private static void ModifyXcodeProject(string projectPath, TargetInfo targetInfo)
|
||||
{
|
||||
var xcodeProjectName = DetectXcodeProjectName(projectPath);
|
||||
var projectFilePath = Path.Combine(projectPath, $"{xcodeProjectName}.xcodeproj/project.pbxproj");
|
||||
var pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile(projectFilePath);
|
||||
|
||||
// 使用已解析的 target GUID,或通过名称反查
|
||||
var targetGuid = targetInfo.FrameworkTargetGuid;
|
||||
var mainTargetGuid = targetInfo.AppTargetGuid;
|
||||
|
||||
if (string.IsNullOrEmpty(targetGuid))
|
||||
{
|
||||
targetGuid = pbxProject.TargetGuidByName(targetInfo.FrameworkTargetName);
|
||||
}
|
||||
if (string.IsNullOrEmpty(mainTargetGuid))
|
||||
{
|
||||
mainTargetGuid = pbxProject.TargetGuidByName(targetInfo.AppTargetName);
|
||||
}
|
||||
|
||||
Debug.Log($"[DirichletMediation] Modifying Xcode project:");
|
||||
Debug.Log($" Framework target: {targetInfo.FrameworkTargetName} (GUID: {targetGuid})");
|
||||
Debug.Log($" App target: {targetInfo.AppTargetName} (GUID: {mainTargetGuid})");
|
||||
|
||||
// NOTE: System frameworks (AdSupport, AVFoundation, WebKit, CoreVideo, etc.)
|
||||
// are declared in SDK podspecs and will be automatically linked by CocoaPods.
|
||||
// No need to manually add them here.
|
||||
// - DirichletAdSDK.podspec: AdSupport, SystemConfiguration, Security
|
||||
// - DirichletCoreSDK.podspec: SystemConfiguration, Security
|
||||
// - DirichletMediationAdapterCSJ.podspec: CoreVideo
|
||||
// - Third-party SDKs (Ads-CN, GDTMobSDK) declare their own framework dependencies.
|
||||
|
||||
// Set build settings for framework target
|
||||
pbxProject.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
|
||||
pbxProject.SetBuildProperty(targetGuid, "CLANG_ENABLE_MODULES", "YES");
|
||||
|
||||
// Set build settings for main target
|
||||
pbxProject.SetBuildProperty(mainTargetGuid, "ENABLE_BITCODE", "NO");
|
||||
pbxProject.SetBuildProperty(mainTargetGuid, "CLANG_ENABLE_MODULES", "YES");
|
||||
pbxProject.SetBuildProperty(mainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
|
||||
|
||||
// Set LD_RUNPATH_SEARCH_PATHS to allow dynamic frameworks to be found at runtime
|
||||
pbxProject.SetBuildProperty(mainTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks");
|
||||
pbxProject.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks @loader_path/Frameworks");
|
||||
|
||||
pbxProject.WriteToFile(projectFilePath);
|
||||
Debug.Log("[DirichletMediation] Modified Xcode project settings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GDTMobSDK 提供的是预编译动态库(GDTMobSDK.framework, Tquic.framework),
|
||||
/// 必须 embed 到 app bundle 中才能在运行时加载。
|
||||
/// 由于所有 pods 都在 Framework target 上,CocoaPods 不会自动 embed 到 app target,
|
||||
/// 需要在 pod install 之后手动处理。
|
||||
/// 依赖 UnityEditor.iOS.Xcode.Extensions 中的 AddFileToEmbedFrameworks 扩展方法。
|
||||
/// </summary>
|
||||
private static void EmbedGDTDynamicFrameworks(string projectPath, TargetInfo targetInfo)
|
||||
{
|
||||
var enableGdt = EditorPrefs.GetBool("Dirichlet.iOS.EnableGDT", true);
|
||||
if (!enableGdt)
|
||||
{
|
||||
Debug.Log("[DirichletMediation] GDT adapter disabled, skipping dynamic framework embedding");
|
||||
return;
|
||||
}
|
||||
|
||||
var xcodeProjectName = DetectXcodeProjectName(projectPath);
|
||||
var projectFilePath = Path.Combine(projectPath, $"{xcodeProjectName}.xcodeproj/project.pbxproj");
|
||||
var pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile(projectFilePath);
|
||||
|
||||
var mainTargetGuid = targetInfo.AppTargetGuid;
|
||||
if (string.IsNullOrEmpty(mainTargetGuid))
|
||||
{
|
||||
mainTargetGuid = pbxProject.TargetGuidByName(targetInfo.AppTargetName);
|
||||
}
|
||||
|
||||
// GDTMobSDK 的动态框架列表
|
||||
var dynamicFrameworkNames = new[] { "GDTMobSDK.framework", "Tquic.framework" };
|
||||
var podsDir = Path.Combine(projectPath, "Pods");
|
||||
var embedded = 0;
|
||||
|
||||
foreach (var frameworkName in dynamicFrameworkNames)
|
||||
{
|
||||
var frameworkPath = FindDynamicFramework(podsDir, frameworkName);
|
||||
if (string.IsNullOrEmpty(frameworkPath))
|
||||
{
|
||||
Debug.LogWarning($"[DirichletMediation] Dynamic framework not found: {frameworkName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = frameworkPath.StartsWith(projectPath)
|
||||
? frameworkPath.Substring(projectPath.Length + 1)
|
||||
: frameworkPath;
|
||||
|
||||
var fileGuid = pbxProject.AddFile(relativePath, "Frameworks/" + frameworkName);
|
||||
PBXProjectExtensions.AddFileToEmbedFrameworks(pbxProject, mainTargetGuid, fileGuid);
|
||||
embedded++;
|
||||
Debug.Log($"[DirichletMediation] Embedded dynamic framework: {frameworkName}");
|
||||
}
|
||||
|
||||
if (embedded > 0)
|
||||
{
|
||||
pbxProject.WriteToFile(projectFilePath);
|
||||
Debug.Log($"[DirichletMediation] Embedded {embedded} GDT dynamic frameworks into {targetInfo.AppTargetName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 Pods 目录中递归搜索指定的 .framework(优先 ios-arm64 真机 slice)
|
||||
/// </summary>
|
||||
private static string FindDynamicFramework(string podsDir, string frameworkName)
|
||||
{
|
||||
if (!Directory.Exists(podsDir))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidates = Directory.GetDirectories(podsDir, frameworkName, SearchOption.AllDirectories);
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (candidate.Contains("ios-arm64") && !candidate.Contains("simulator"))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (!candidate.Contains("simulator"))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates.Length > 0 ? candidates[0] : null;
|
||||
}
|
||||
|
||||
private static void ModifyInfoPlist(string projectPath)
|
||||
{
|
||||
var plistPath = Path.Combine(projectPath, "Info.plist");
|
||||
var plist = new PlistDocument();
|
||||
plist.ReadFromFile(plistPath);
|
||||
|
||||
var rootDict = plist.root;
|
||||
|
||||
// Note: The following Info.plist keys should be configured by the developer manually
|
||||
// to avoid potential App Store review issues:
|
||||
// - NSAppTransportSecurity: Configure based on your app's network requirements
|
||||
// - NSUserTrackingUsageDescription: Required for iOS 14+ IDFA access, use your custom description
|
||||
// - NSLocationWhenInUseUsageDescription: Only add if your app uses location services
|
||||
//
|
||||
// Reference: https://ssp.dirichlet.cn/docs/dirichlet-mediation-sdk/dirichlet-mediation-sdk-guide-ios/
|
||||
|
||||
// Add SKAdNetwork identifiers for attribution tracking
|
||||
AddSKAdNetworkIds(rootDict);
|
||||
|
||||
plist.WriteToFile(plistPath);
|
||||
Debug.Log("[DirichletMediation] Modified Info.plist (SKAdNetwork IDs only)");
|
||||
}
|
||||
|
||||
private static void AddSKAdNetworkIds(PlistElementDict rootDict)
|
||||
{
|
||||
if (rootDict.values.ContainsKey("SKAdNetworkItems"))
|
||||
{
|
||||
Debug.Log("[DirichletMediation] SKAdNetworkItems already exists, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add SKAdNetwork IDs required by CSJ (Pangle/穿山甲)
|
||||
// Reference: https://www.csjplatform.com/supportcenter/5377
|
||||
var skAdNetworkArray = rootDict.CreateArray("SKAdNetworkItems");
|
||||
|
||||
var commonSkAdNetworkIds = new[]
|
||||
{
|
||||
"238da6jt44.skadnetwork", // 穿山甲 SKAdNetwork ID
|
||||
"x2jnk7ly8j.skadnetwork", // 穿山甲 SKAdNetwork ID
|
||||
"22mmun2rn5.skadnetwork" // 穿山甲 SKAdNetwork ID
|
||||
};
|
||||
|
||||
foreach (var skAdNetworkId in commonSkAdNetworkIds)
|
||||
{
|
||||
var dict = skAdNetworkArray.AddDict();
|
||||
dict.SetString("SKAdNetworkIdentifier", skAdNetworkId);
|
||||
}
|
||||
|
||||
Debug.Log($"[DirichletMediation] Added {commonSkAdNetworkIds.Length} SKAdNetwork IDs to Info.plist");
|
||||
}
|
||||
|
||||
private static void RunPodInstall(string projectPath)
|
||||
{
|
||||
var podfilePath = Path.Combine(projectPath, "Podfile");
|
||||
if (!File.Exists(podfilePath))
|
||||
{
|
||||
Debug.LogWarning("[DirichletMediation] Podfile not found, skipping pod install");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("[DirichletMediation] Running 'pod install'...");
|
||||
Debug.Log("[DirichletMediation] Note: This may take a few minutes on first build or when adapters change.");
|
||||
|
||||
try
|
||||
{
|
||||
// Try to find pod executable
|
||||
var podPath = FindPodExecutable();
|
||||
if (string.IsNullOrEmpty(podPath))
|
||||
{
|
||||
Debug.LogWarning("[DirichletMediation] CocoaPods not found. Please install CocoaPods:");
|
||||
Debug.LogWarning(" sudo gem install cocoapods");
|
||||
Debug.LogWarning("Then run 'pod install' manually in:");
|
||||
Debug.LogWarning($" {projectPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先不做 repo update(更快、更稳定);如遇到“找不到 podspec”再退化为 --repo-update
|
||||
var firstArgs = "install";
|
||||
var exitCode = RunPodCommand(podPath, firstArgs, projectPath, out var output, out var error);
|
||||
|
||||
if (exitCode != 0 && ShouldRetryWithRepoUpdate(output, error))
|
||||
{
|
||||
Debug.LogWarning("[DirichletMediation] pod install failed (missing specs suspected). Retrying with --repo-update...");
|
||||
var retryArgs = "install --repo-update";
|
||||
exitCode = RunPodCommand(podPath, retryArgs, projectPath, out output, out error);
|
||||
}
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
Debug.Log("[DirichletMediation] pod install completed successfully");
|
||||
if (!string.IsNullOrEmpty(output) && output.Length < 2000)
|
||||
{
|
||||
Debug.Log($"Output:\n{output}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogError($"[DirichletMediation] pod install failed with exit code {exitCode}");
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
Debug.LogError($"Error:\n{error}");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
Debug.LogError($"Output:\n{output}");
|
||||
}
|
||||
|
||||
if (IsCocoaPodsCdnHttp2Error(output, error))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"[DirichletMediation] Detected CocoaPods CDN HTTP/2 error (e.g. 'Error in the HTTP2 framing layer'). " +
|
||||
"This is usually a network/proxy/SSL issue. Try rerun without repo update, switch network, upgrade CocoaPods, " +
|
||||
"or run 'pod install' manually. (Repo doc: docs/FAQ/CocoaPods-SSL证书问题排查SOP.md)"
|
||||
);
|
||||
}
|
||||
|
||||
// 失败即中断:避免导出工程处于“半配置”状态,后续 Xcode build 更难排查
|
||||
throw new System.Exception($"pod install failed with exit code {exitCode}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"[DirichletMediation] Failed to run pod install: {ex.Message}");
|
||||
Debug.LogWarning("[DirichletMediation] Please run 'pod install' manually in the Xcode project directory:");
|
||||
Debug.LogWarning($" cd {projectPath}");
|
||||
Debug.LogWarning(" pod install");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static int RunPodCommand(string podPath, string arguments, string projectPath, out string output, out string error)
|
||||
{
|
||||
var processInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = podPath,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = projectPath,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
// Set UTF-8 encoding to avoid CocoaPods encoding issues
|
||||
processInfo.EnvironmentVariables["LANG"] = "en_US.UTF-8";
|
||||
processInfo.EnvironmentVariables["LC_ALL"] = "en_US.UTF-8";
|
||||
|
||||
using (var process = System.Diagnostics.Process.Start(processInfo))
|
||||
{
|
||||
output = process.StandardOutput.ReadToEnd();
|
||||
error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldRetryWithRepoUpdate(string output, string error)
|
||||
{
|
||||
var combined = (output ?? string.Empty) + "\n" + (error ?? string.Empty);
|
||||
|
||||
// 常见“本地 specs 未更新导致无法解析依赖”的报错关键字
|
||||
return combined.Contains("Unable to find a specification for")
|
||||
|| combined.Contains("None of your spec sources contain a spec satisfying")
|
||||
|| combined.Contains("spec satisfying")
|
||||
|| combined.Contains("No podspec found for")
|
||||
|| combined.Contains("could not find compatible versions for pod");
|
||||
}
|
||||
|
||||
private static bool IsCocoaPodsCdnHttp2Error(string output, string error)
|
||||
{
|
||||
var combined = (output ?? string.Empty) + "\n" + (error ?? string.Empty);
|
||||
return combined.Contains("CDN: trunk Repo update failed")
|
||||
|| combined.Contains("Error in the HTTP2 framing layer")
|
||||
|| combined.Contains("URL couldn't be downloaded: https://cdn.cocoapods.org/");
|
||||
}
|
||||
|
||||
private static string FindPodExecutable()
|
||||
{
|
||||
// 1. POD_BINARY 环境变量优先,方便 CI 或接入方手动配置
|
||||
var envValue = System.Environment.GetEnvironmentVariable("POD_BINARY");
|
||||
if (!string.IsNullOrEmpty(envValue))
|
||||
{
|
||||
var expanded = envValue.Trim();
|
||||
if (File.Exists(expanded))
|
||||
{
|
||||
Debug.Log($"[DirichletMediation] Found pod via POD_BINARY: {expanded}");
|
||||
return expanded;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[DirichletMediation] POD_BINARY points to non-existing path: {expanded}");
|
||||
}
|
||||
|
||||
var possiblePaths = new[]
|
||||
{
|
||||
"/usr/local/bin/pod",
|
||||
"/opt/homebrew/bin/pod",
|
||||
"/usr/bin/pod",
|
||||
Path.Combine(System.Environment.GetEnvironmentVariable("HOME") ?? "", ".rbenv/shims/pod"),
|
||||
Path.Combine(System.Environment.GetEnvironmentVariable("HOME") ?? "", ".rvm/wrappers/default/pod")
|
||||
};
|
||||
|
||||
foreach (var path in possiblePaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Debug.Log($"[DirichletMediation] Found pod at: {path}");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
// Try using 'which pod'
|
||||
try
|
||||
{
|
||||
var processInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "/usr/bin/env",
|
||||
Arguments = "which pod",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = System.Diagnostics.Process.Start(processInfo))
|
||||
{
|
||||
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||
var error = process.StandardError.ReadToEnd().Trim();
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output))
|
||||
{
|
||||
Debug.Log($"[DirichletMediation] Found pod via 'which': {output}");
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
Debug.LogWarning($"[DirichletMediation] 'which pod' failed: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[DirichletMediation] Failed to resolve pod via 'which': {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private const string PrefKeyDefaultsInitialized = "Dirichlet.Mediation.AdapterSettings.DefaultsInitialized.v1";
|
||||
|
||||
private static void EnsureDefaultAdapterPrefs()
|
||||
{
|
||||
if (EditorPrefs.GetBool(PrefKeyDefaultsInitialized, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认“勾上”:CSJ/GDT(iOS)
|
||||
// 注意:如接入方需要关闭,可在 Adapter Settings 窗口中手动取消勾选。
|
||||
EditorPrefs.SetBool("Dirichlet.iOS.EnableCSJ", true);
|
||||
EditorPrefs.SetBool("Dirichlet.iOS.EnableGDT", true);
|
||||
|
||||
// 同时保证 Android 默认一致(无副作用)
|
||||
EditorPrefs.SetBool("Dirichlet.Android.EnableCSJ", true);
|
||||
EditorPrefs.SetBool("Dirichlet.Android.EnableGDT", true);
|
||||
|
||||
EditorPrefs.SetBool(PrefKeyDefaultsInitialized, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bc3db08a1b434ef2bc8aba5413e588c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/DirichletMediation/Runtime.meta
Normal file
8
Assets/DirichletMediation/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33121b74910d6437b86b802fedfe5af4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Dirichlet.Mediation.Runtime",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 556788bfca0f498d945d7fa53061b066
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
|
||||
1376
Assets/DirichletMediation/Runtime/DirichletAdTypes.cs
Normal file
1376
Assets/DirichletMediation/Runtime/DirichletAdTypes.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/DirichletMediation/Runtime/DirichletAdTypes.cs.meta
Normal file
11
Assets/DirichletMediation/Runtime/DirichletAdTypes.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eec9af0b7d54e4522b6d9c84d29e65de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2036
Assets/DirichletMediation/Runtime/DirichletMediationSdk.cs
Normal file
2036
Assets/DirichletMediation/Runtime/DirichletMediationSdk.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45ede85e8de28482f92f239b9e824766
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins.meta
Normal file
8
Assets/Plugins.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30b3bfe6f7c94fd2ba05c41c8a8ae1f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android.meta
Normal file
8
Assets/Plugins/Android.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 652205868ad149ecae26404dc3f16554
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/Plugins/Android/AndroidManifest.xml
Normal file
34
Assets/Plugins/Android/AndroidManifest.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="**APPLICATIONID**">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_CONNECT"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
tools:replace="android:allowBackup">
|
||||
<activity
|
||||
android:name="com.unity3d.player.UnityPlayerActivity"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:theme="@style/UnityThemeSelector">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="unityplayer.UnityActivity"
|
||||
android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
7
Assets/Plugins/Android/AndroidManifest.xml.meta
Normal file
7
Assets/Plugins/Android/AndroidManifest.xml.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c972780d1f434d0cb5b25ce1dd8861b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/DirichletMediation.meta
Normal file
8
Assets/Plugins/Android/DirichletMediation.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 228b690bf488f4593868f1ddf81e5a45
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/DirichletMediation/libs.meta
Normal file
8
Assets/Plugins/Android/DirichletMediation/libs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3733efcb61dd489fa8152ca50efff39a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7970f095313643b682bb2d27b4ede2f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174b13ee51c7a444fb02d6f2dfdb894f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 514287dbd7374309a1b94a565a605659
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4866e4d90c0ff47018ffd97469eace11
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/DirichletMediation/src.meta
Normal file
8
Assets/Plugins/Android/DirichletMediation/src.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d971358c2bfb48d0b3f792a83924f42
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/DirichletMediation/src/main.meta
Normal file
8
Assets/Plugins/Android/DirichletMediation/src/main.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d720fa5d414114f8cb2794a88c403f42
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93eb097a3680445f6a2f78d5eae1de63
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32384e1dfd1c1422e92cc2a15056870c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef8ea0067be0345e292de883a7496086
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20ac9c2dde05f432da7fc53d70736c85
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14cb359fe1a3c478ca0969cf714aff16
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8745d7dbdbaeb7948ab964d52eafe76c
|
||||
labels:
|
||||
- gpsr
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/libs.meta
Normal file
8
Assets/Plugins/Android/libs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7e8438cf7c294b92988c3830e288191
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/libs/GDTSDK.unionNormal.4.671.1541.aar
Normal file
BIN
Assets/Plugins/Android/libs/GDTSDK.unionNormal.4.671.1541.aar
Normal file
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90b3b17e0788494398662e729c344ee2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
BIN
Assets/Plugins/Android/libs/iadsdk-release-2.3.102.110.aar
Normal file
BIN
Assets/Plugins/Android/libs/iadsdk-release-2.3.102.110.aar
Normal file
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92c7680ef2e44d279c23926510865f93
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/libs/open_ad_sdk_7.4.2.2.aar
Normal file
BIN
Assets/Plugins/Android/libs/open_ad_sdk_7.4.2.2.aar
Normal file
Binary file not shown.
33
Assets/Plugins/Android/libs/open_ad_sdk_7.4.2.2.aar.meta
Normal file
33
Assets/Plugins/Android/libs/open_ad_sdk_7.4.2.2.aar.meta
Normal file
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930136242e574a2b89110e6a25e49065
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
0
Assets/Plugins/Android/proguard-user.txt
Normal file
0
Assets/Plugins/Android/proguard-user.txt
Normal file
7
Assets/Plugins/Android/proguard-user.txt.meta
Normal file
7
Assets/Plugins/Android/proguard-user.txt.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c51429155b4914ffe99f152a28d7d6d4
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/iOS.meta
Normal file
8
Assets/Plugins/iOS.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 215b74292b87e41809a9e2c72050a616
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
Assets/Plugins/iOS/DirichletMediationUnityBridge.h
Normal file
80
Assets/Plugins/iOS/DirichletMediationUnityBridge.h
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// DirichletMediationUnityBridge.h
|
||||
// Dirichlet Mediation Unity Bridge for iOS
|
||||
//
|
||||
// Created by Dirichlet Unity SDK
|
||||
// Copyright © 2025 Dirichlet Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Initialize the Dirichlet Mediation SDK
|
||||
/// @param mediaId Media ID string
|
||||
/// @param mediaKey Media key string
|
||||
/// @param enableLog Enable debug logging
|
||||
/// @param mediaName Media name string (optional)
|
||||
/// @param gameChannel Game channel string (optional)
|
||||
/// @param shakeEnabled Enable shake interaction (for GDT adapter)
|
||||
/// @param allowIDFAAccess Allow IDFA access (for TapADN and GDT adapters)
|
||||
/// @param aTags External aTags JSON string (optional)
|
||||
/// @return YES if initialization started successfully, NO otherwise
|
||||
bool DirichletMediationUnityBridge_Initialize(
|
||||
const char* mediaId,
|
||||
const char* mediaKey,
|
||||
bool enableLog,
|
||||
const char* mediaName,
|
||||
const char* gameChannel,
|
||||
bool shakeEnabled,
|
||||
bool allowIDFAAccess,
|
||||
const char* aTags
|
||||
);
|
||||
|
||||
/// Request permissions if necessary (ATT for iOS 14+)
|
||||
void DirichletMediationUnityBridge_RequestPermissionIfNeeded(void);
|
||||
|
||||
/// Get SDK version
|
||||
/// @return SDK version string (caller should NOT free this pointer)
|
||||
const char* DirichletMediationUnityBridge_GetSdkVersion(void);
|
||||
|
||||
/// Load reward video ad
|
||||
/// @param spaceId Space/slot ID
|
||||
/// @param extras JSON string with additional parameters
|
||||
/// @return Handle ID for the ad instance (caller should copy/free)
|
||||
const char* DirichletMediationUnityBridge_LoadRewardVideoAd(long long spaceId, const char* extras);
|
||||
|
||||
/// Load interstitial ad
|
||||
/// @param spaceId Space/slot ID
|
||||
/// @param extras JSON string with additional parameters
|
||||
/// @return Handle ID for the ad instance
|
||||
const char* DirichletMediationUnityBridge_LoadInterstitialAd(long long spaceId, const char* extras);
|
||||
|
||||
/// Load banner ad
|
||||
/// @param spaceId Space/slot ID
|
||||
/// @param extras JSON string with additional parameters
|
||||
/// @return Handle ID for the ad instance
|
||||
const char* DirichletMediationUnityBridge_LoadBannerAd(long long spaceId, const char* extras);
|
||||
|
||||
/// Load splash ad
|
||||
/// @param spaceId Space/slot ID
|
||||
/// @param extras JSON string with additional parameters
|
||||
/// @return Handle ID for the ad instance
|
||||
const char* DirichletMediationUnityBridge_LoadSplashAd(long long spaceId, const char* extras);
|
||||
|
||||
/// Show ad by handle
|
||||
/// @param handleId Handle ID returned from load methods
|
||||
/// @param extras JSON string with show options (e.g., banner alignment)
|
||||
/// @return YES if show started successfully, NO otherwise
|
||||
bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extras);
|
||||
|
||||
/// Destroy ad by handle
|
||||
/// @param handleId Handle ID returned from load methods
|
||||
void DirichletMediationUnityBridge_DestroyAd(const char* handleId);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
33
Assets/Plugins/iOS/DirichletMediationUnityBridge.h.meta
Normal file
33
Assets/Plugins/iOS/DirichletMediationUnityBridge.h.meta
Normal file
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07aa84d4e2aa748719c5e30912f2cf53
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
682
Assets/Plugins/iOS/DirichletMediationUnityBridge.mm
Normal file
682
Assets/Plugins/iOS/DirichletMediationUnityBridge.mm
Normal file
@@ -0,0 +1,682 @@
|
||||
//
|
||||
// DirichletMediationUnityBridge.mm
|
||||
// Dirichlet Mediation Unity Bridge for iOS
|
||||
//
|
||||
//
|
||||
// Created by Dirichlet Unity SDK
|
||||
// Copyright © 2025 Dirichlet Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DirichletMediationUnityBridge.h"
|
||||
#import <DirichletMediationSDK/DirichletMediationSDK.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// Unity callback interface
|
||||
extern "C" void UnitySendMessage(const char* obj, const char* method, const char* msg);
|
||||
|
||||
// Constants
|
||||
static NSString * const kUnityCallbackObject = @"DirichletMediationEventReceiver";
|
||||
static NSString * const kUnityCallbackMethod = @"OnNativeEvent";
|
||||
static NSString * const kUnityLoadCallbackObject = @"DirichletMediationIOSLoadCallbackReceiver";
|
||||
static NSString * const kUnityLoadCallbackMethod = @"OnLoadCallback";
|
||||
static NSString * const kUnityInitCallbackObject = @"DirichletMediationIOSInitCallbackReceiver";
|
||||
static NSString * const kUnityInitCallbackMethod = @"OnInitCallback";
|
||||
|
||||
// Helper to convert C string to NSString
|
||||
static NSString* CreateNSString(const char* cString) {
|
||||
return cString ? [NSString stringWithUTF8String:cString] : @"";
|
||||
}
|
||||
|
||||
// Helper to convert NSString to C string (caller must free)
|
||||
static char* MakeCString(NSString* nsString) {
|
||||
if (nsString == nil) {
|
||||
return NULL;
|
||||
}
|
||||
const char* utf8String = [nsString UTF8String];
|
||||
char* cString = (char*)malloc(strlen(utf8String) + 1);
|
||||
strcpy(cString, utf8String);
|
||||
return cString;
|
||||
}
|
||||
|
||||
// Helper to send event to Unity
|
||||
static void SendEventToUnity(NSString* handleId, NSString* eventName, NSString* adType, NSDictionary* data) {
|
||||
NSMutableDictionary* payload = [NSMutableDictionary dictionary];
|
||||
payload[@"handle"] = handleId ?: @"";
|
||||
payload[@"eventName"] = eventName ?: @"";
|
||||
payload[@"adType"] = adType ?: @"";
|
||||
if (data) {
|
||||
payload[@"data"] = data;
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
|
||||
if (jsonData && !error) {
|
||||
NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
UnitySendMessage([kUnityCallbackObject UTF8String], [kUnityCallbackMethod UTF8String], [jsonString UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to send load callback to Unity (separate from ad events)
|
||||
static void SendLoadCallbackToUnity(NSString* handleId, NSString* eventName, NSString* adType, NSDictionary* data) {
|
||||
NSMutableDictionary* payload = [NSMutableDictionary dictionary];
|
||||
payload[@"handle"] = handleId ?: @"";
|
||||
payload[@"eventName"] = eventName ?: @"";
|
||||
payload[@"adType"] = adType ?: @"";
|
||||
if (data) {
|
||||
payload[@"data"] = data;
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
|
||||
if (jsonData && !error) {
|
||||
NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
UnitySendMessage([kUnityLoadCallbackObject UTF8String], [kUnityLoadCallbackMethod UTF8String], [jsonString UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to send init callback to Unity (async initialization result)
|
||||
static void SendInitCallbackToUnity(BOOL success, NSError* error, NSString* extraMessage) {
|
||||
NSMutableDictionary* payload = [NSMutableDictionary dictionary];
|
||||
payload[@"success"] = @(success);
|
||||
|
||||
NSMutableDictionary* data = [NSMutableDictionary dictionary];
|
||||
if (error) {
|
||||
data[@"code"] = @(error.code);
|
||||
data[@"message"] = error.localizedDescription ?: @"";
|
||||
if (error.domain) {
|
||||
data[@"domain"] = error.domain;
|
||||
}
|
||||
} else if (extraMessage.length > 0) {
|
||||
data[@"message"] = extraMessage;
|
||||
}
|
||||
|
||||
if (data.count > 0) {
|
||||
payload[@"data"] = data;
|
||||
}
|
||||
|
||||
NSError* jsonError = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&jsonError];
|
||||
if (jsonData && !jsonError) {
|
||||
NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
UnitySendMessage([kUnityInitCallbackObject UTF8String], [kUnityInitCallbackMethod UTF8String], [jsonString UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON extras to dictionary
|
||||
static NSDictionary* ParseExtras(const char* extrasJson) {
|
||||
if (!extrasJson || strlen(extrasJson) == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* jsonString = CreateNSString(extrasJson);
|
||||
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (!jsonData) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
|
||||
return error ? nil : dict;
|
||||
}
|
||||
|
||||
#pragma mark - Ad Instance Manager
|
||||
|
||||
@interface DirichletMediationInstanceManager : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString*, id>* adInstances;
|
||||
@property (nonatomic, strong) dispatch_queue_t syncQueue;
|
||||
|
||||
+ (instancetype)shared;
|
||||
- (void)storeAd:(id)ad forHandle:(NSString*)handleId;
|
||||
- (id)adForHandle:(NSString*)handleId;
|
||||
- (void)removeAdForHandle:(NSString*)handleId;
|
||||
- (NSString*)generateHandle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DirichletMediationInstanceManager
|
||||
|
||||
+ (instancetype)shared {
|
||||
static DirichletMediationInstanceManager* instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[DirichletMediationInstanceManager alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_adInstances = [NSMutableDictionary dictionary];
|
||||
_syncQueue = dispatch_queue_create("com.dirichlet.mediation.unity.admanager", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)storeAd:(id)ad forHandle:(NSString*)handleId {
|
||||
dispatch_sync(self.syncQueue, ^{
|
||||
self.adInstances[handleId] = ad;
|
||||
});
|
||||
}
|
||||
|
||||
- (id)adForHandle:(NSString*)handleId {
|
||||
__block id ad = nil;
|
||||
dispatch_sync(self.syncQueue, ^{
|
||||
ad = self.adInstances[handleId];
|
||||
});
|
||||
return ad;
|
||||
}
|
||||
|
||||
- (void)removeAdForHandle:(NSString*)handleId {
|
||||
dispatch_sync(self.syncQueue, ^{
|
||||
[self.adInstances removeObjectForKey:handleId];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString*)generateHandle {
|
||||
return [[NSUUID UUID] UUIDString];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Ad Delegates
|
||||
|
||||
// Reward Video Ad Delegate
|
||||
@interface DirichletMediationUnityRewardVideoAdDelegate : NSObject <DRMRewardVideoAdDelegate>
|
||||
@property (nonatomic, strong) NSString* handleId;
|
||||
@end
|
||||
|
||||
@implementation DirichletMediationUnityRewardVideoAdDelegate
|
||||
|
||||
- (void)rewardVideoAdDidShow:(DRMRewardVideoAd *)rewardVideoAd {
|
||||
SendEventToUnity(self.handleId, @"show", @"reward_video", nil);
|
||||
}
|
||||
|
||||
- (void)rewardVideoAdDidFailToShow:(DRMRewardVideoAd *)rewardVideoAd withError:(NSError *)error {
|
||||
NSDictionary* data = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendEventToUnity(self.handleId, @"show_error", @"reward_video", data);
|
||||
}
|
||||
|
||||
- (void)rewardVideoAdDidClick:(DRMRewardVideoAd *)rewardVideoAd {
|
||||
SendEventToUnity(self.handleId, @"click", @"reward_video", nil);
|
||||
}
|
||||
|
||||
- (void)rewardVideoAdDidClose:(DRMRewardVideoAd *)rewardVideoAd {
|
||||
SendEventToUnity(self.handleId, @"close", @"reward_video", nil);
|
||||
}
|
||||
|
||||
- (void)rewardVideoAdDidRewardUser:(DRMRewardVideoAd *)rewardVideoAd {
|
||||
NSDictionary* data = @{
|
||||
@"rewardVerify": @(YES),
|
||||
@"rewardAmount": @(0),
|
||||
@"rewardName": @"",
|
||||
@"code": @(0),
|
||||
@"message": @""
|
||||
};
|
||||
SendEventToUnity(self.handleId, @"reward", @"reward_video", data);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Interstitial Ad Delegate
|
||||
@interface DirichletMediationUnityInterstitialAdDelegate : NSObject <DRMInterstitialAdDelegate>
|
||||
@property (nonatomic, strong) NSString* handleId;
|
||||
@end
|
||||
|
||||
@implementation DirichletMediationUnityInterstitialAdDelegate
|
||||
|
||||
- (void)interstitialAdDidShow:(DRMInterstitialAd *)interstitialAd {
|
||||
SendEventToUnity(self.handleId, @"show", @"interstitial", nil);
|
||||
}
|
||||
|
||||
- (void)interstitialAdDidFailToShow:(DRMInterstitialAd *)interstitialAd withError:(NSError *)error {
|
||||
NSDictionary* data = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendEventToUnity(self.handleId, @"show_error", @"interstitial", data);
|
||||
}
|
||||
|
||||
- (void)interstitialAdDidClick:(DRMInterstitialAd *)interstitialAd {
|
||||
SendEventToUnity(self.handleId, @"click", @"interstitial", nil);
|
||||
}
|
||||
|
||||
- (void)interstitialAdDidClose:(DRMInterstitialAd *)interstitialAd {
|
||||
SendEventToUnity(self.handleId, @"close", @"interstitial", nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Banner Ad Delegate
|
||||
@interface DirichletMediationUnityBannerAdDelegate : NSObject <DRMBannerAdDelegate>
|
||||
@property (nonatomic, strong) NSString* handleId;
|
||||
@end
|
||||
|
||||
@implementation DirichletMediationUnityBannerAdDelegate
|
||||
|
||||
- (void)bannerAdDidShow:(DRMBannerAd *)bannerAd {
|
||||
SendEventToUnity(self.handleId, @"show", @"banner", nil);
|
||||
}
|
||||
|
||||
- (void)bannerAdDidFailToShow:(DRMBannerAd *)bannerAd withError:(NSError *)error {
|
||||
NSDictionary* data = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendEventToUnity(self.handleId, @"show_error", @"banner", data);
|
||||
}
|
||||
|
||||
- (void)bannerAdDidClick:(DRMBannerAd *)bannerAd {
|
||||
SendEventToUnity(self.handleId, @"click", @"banner", nil);
|
||||
}
|
||||
|
||||
- (void)bannerAdDidClose:(DRMBannerAd *)bannerAd {
|
||||
SendEventToUnity(self.handleId, @"close", @"banner", nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Splash Ad Delegate
|
||||
@interface DirichletMediationUnitySplashAdDelegate : NSObject <DRMSplashAdDelegate>
|
||||
@property (nonatomic, strong) NSString* handleId;
|
||||
@end
|
||||
|
||||
@implementation DirichletMediationUnitySplashAdDelegate
|
||||
|
||||
- (void)splashAdDidShow:(DRMSplashAd *)splashAd {
|
||||
SendEventToUnity(self.handleId, @"show", @"splash", nil);
|
||||
}
|
||||
|
||||
- (void)splashAdDidFailToShow:(DRMSplashAd *)splashAd withError:(NSError *)error {
|
||||
NSDictionary* data = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendEventToUnity(self.handleId, @"show_error", @"splash", data);
|
||||
}
|
||||
|
||||
- (void)splashAdDidClick:(DRMSplashAd *)splashAd {
|
||||
SendEventToUnity(self.handleId, @"click", @"splash", nil);
|
||||
}
|
||||
|
||||
- (void)splashAdDidClose:(DRMSplashAd *)splashAd {
|
||||
SendEventToUnity(self.handleId, @"close", @"splash", nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Bridge Implementation
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool DirichletMediationUnityBridge_Initialize(
|
||||
const char* mediaId,
|
||||
const char* mediaKey,
|
||||
bool enableLog,
|
||||
const char* mediaName,
|
||||
const char* gameChannel,
|
||||
bool shakeEnabled,
|
||||
bool allowIDFAAccess,
|
||||
const char* aTags
|
||||
) {
|
||||
NSString* nsMediaId = CreateNSString(mediaId);
|
||||
NSString* nsMediaKey = CreateNSString(mediaKey);
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] Initialize called with mediaId=%@, mediaKey=%@, enableLog=%d",
|
||||
nsMediaId, nsMediaKey, enableLog);
|
||||
|
||||
if (nsMediaId.length == 0 || nsMediaKey.length == 0) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] Initialize failed: mediaId and mediaKey are required");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if SDK is already initialized
|
||||
if ([DirichletMediation isInitialized]) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] SDK already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
DRMSDKConfig* config = [DRMSDKConfig configWithMediaId:nsMediaId mediaKey:nsMediaKey];
|
||||
if (!config) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] Failed to create SDK config");
|
||||
return false;
|
||||
}
|
||||
|
||||
config.isDebug = enableLog;
|
||||
config.shakeEnabled = shakeEnabled;
|
||||
config.allowIDFAAccess = allowIDFAAccess;
|
||||
|
||||
if (mediaName && strlen(mediaName) > 0) {
|
||||
config.mediaName = CreateNSString(mediaName);
|
||||
}
|
||||
|
||||
if (gameChannel && strlen(gameChannel) > 0) {
|
||||
config.gameChannel = CreateNSString(gameChannel);
|
||||
}
|
||||
|
||||
if (aTags && strlen(aTags) > 0) {
|
||||
config.aTags = CreateNSString(aTags);
|
||||
}
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] Starting SDK initialization (async callback)...");
|
||||
NSLog(@"[DirichletMediationUnityBridge] Config details - mediaId:%@, mediaKey:%@, mediaName:%@, gameChannel:%@",
|
||||
config.mediaId, config.mediaKey, config.mediaName, config.gameChannel);
|
||||
|
||||
// Use async callback pattern (aligned with Ad Unity implementation)
|
||||
void (^startBlock)(void) = ^{
|
||||
[DirichletMediation startWithConfig:config completion:^(BOOL success, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] SDK init callback - success:%d, error:%@ (code:%ld, domain:%@)",
|
||||
success, error.localizedDescription, (long)error.code, error.domain);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] SDK init callback - success:%d", success);
|
||||
}
|
||||
SendInitCallbackToUnity(success, error, success ? @"ios_mediation_bridge" : nil);
|
||||
}];
|
||||
};
|
||||
|
||||
if ([NSThread isMainThread]) {
|
||||
startBlock();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), startBlock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DirichletMediationUnityBridge_RequestPermissionIfNeeded(void) {
|
||||
// iOS 14+ ATT permission is handled internally by the SDK
|
||||
NSLog(@"[DirichletMediationUnityBridge] RequestPermissionIfNeeded called");
|
||||
}
|
||||
|
||||
const char* DirichletMediationUnityBridge_GetSdkVersion(void) {
|
||||
static char* versionCString = NULL;
|
||||
if (versionCString == NULL) {
|
||||
versionCString = MakeCString([DirichletMediation sdkVersion]);
|
||||
}
|
||||
return versionCString;
|
||||
}
|
||||
|
||||
const char* DirichletMediationUnityBridge_LoadRewardVideoAd(long long spaceId, const char* extras) {
|
||||
NSString* handleId = [[DirichletMediationInstanceManager shared] generateHandle];
|
||||
NSDictionary* extrasDict = ParseExtras(extras);
|
||||
|
||||
// Create load request
|
||||
DRMAdLoadRequest* request = [[DRMAdLoadRequest alloc] initWithSpaceId:[NSString stringWithFormat:@"%lld", spaceId]];
|
||||
|
||||
// Apply extras if provided (matching Unity C# ToBridgePayload keys)
|
||||
if (extrasDict[@"user_id"]) {
|
||||
request.rewardUserId = extrasDict[@"user_id"];
|
||||
}
|
||||
if (extrasDict[@"extra1"]) {
|
||||
request.rewardExtra = extrasDict[@"extra1"];
|
||||
}
|
||||
if (extrasDict[@"reward_name"]) {
|
||||
request.rewardName = extrasDict[@"reward_name"];
|
||||
}
|
||||
if (extrasDict[@"reward_amount"]) {
|
||||
request.rewardAmount = [extrasDict[@"reward_amount"] integerValue];
|
||||
}
|
||||
if (extrasDict[@"mina_id"]) {
|
||||
request.minaId = [NSString stringWithFormat:@"%@", extrasDict[@"mina_id"]];
|
||||
}
|
||||
|
||||
[DRMRewardVideoAd loadWithRequest:request completion:^(NSArray<DRMRewardVideoAd *> * _Nullable ads, NSError * _Nullable error) {
|
||||
if (ads && ads.count > 0) {
|
||||
DRMRewardVideoAd* ad = ads.firstObject;
|
||||
DirichletMediationUnityRewardVideoAdDelegate* delegate = [[DirichletMediationUnityRewardVideoAdDelegate alloc] init];
|
||||
delegate.handleId = handleId;
|
||||
ad.delegate = delegate;
|
||||
|
||||
[[DirichletMediationInstanceManager shared] storeAd:ad forHandle:handleId];
|
||||
[[DirichletMediationInstanceManager shared] storeAd:delegate forHandle:[handleId stringByAppendingString:@"_delegate"]];
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] RewardVideoAd loaded: %@", handleId);
|
||||
SendLoadCallbackToUnity(handleId, @"load_success", @"reward_video", nil);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] RewardVideoAd load failed: %@", error.localizedDescription);
|
||||
NSDictionary* errorData = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendLoadCallbackToUnity(handleId, @"load_error", @"reward_video", errorData);
|
||||
}
|
||||
}];
|
||||
|
||||
return MakeCString(handleId);
|
||||
}
|
||||
|
||||
const char* DirichletMediationUnityBridge_LoadInterstitialAd(long long spaceId, const char* extras) {
|
||||
NSString* handleId = [[DirichletMediationInstanceManager shared] generateHandle];
|
||||
NSDictionary* extrasDict = ParseExtras(extras);
|
||||
|
||||
// Create load request
|
||||
DRMAdLoadRequest* request = [[DRMAdLoadRequest alloc] initWithSpaceId:[NSString stringWithFormat:@"%lld", spaceId]];
|
||||
|
||||
// Apply extras if provided (matching Unity C# ToBridgePayload keys)
|
||||
if (extrasDict[@"mina_id"]) {
|
||||
request.minaId = [NSString stringWithFormat:@"%@", extrasDict[@"mina_id"]];
|
||||
}
|
||||
|
||||
[DRMInterstitialAd loadWithRequest:request completion:^(NSArray<DRMInterstitialAd *> * _Nullable ads, NSError * _Nullable error) {
|
||||
if (ads && ads.count > 0) {
|
||||
DRMInterstitialAd* ad = ads.firstObject;
|
||||
DirichletMediationUnityInterstitialAdDelegate* delegate = [[DirichletMediationUnityInterstitialAdDelegate alloc] init];
|
||||
delegate.handleId = handleId;
|
||||
ad.delegate = delegate;
|
||||
|
||||
[[DirichletMediationInstanceManager shared] storeAd:ad forHandle:handleId];
|
||||
[[DirichletMediationInstanceManager shared] storeAd:delegate forHandle:[handleId stringByAppendingString:@"_delegate"]];
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] InterstitialAd loaded: %@", handleId);
|
||||
SendLoadCallbackToUnity(handleId, @"load_success", @"interstitial", nil);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] InterstitialAd load failed: %@", error.localizedDescription);
|
||||
NSDictionary* errorData = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendLoadCallbackToUnity(handleId, @"load_error", @"interstitial", errorData);
|
||||
}
|
||||
}];
|
||||
|
||||
return MakeCString(handleId);
|
||||
}
|
||||
|
||||
const char* DirichletMediationUnityBridge_LoadBannerAd(long long spaceId, const char* extras) {
|
||||
NSString* handleId = [[DirichletMediationInstanceManager shared] generateHandle];
|
||||
NSDictionary* extrasDict = ParseExtras(extras);
|
||||
|
||||
// Create load request
|
||||
DRMAdLoadRequest* request = [[DRMAdLoadRequest alloc] initWithSpaceId:[NSString stringWithFormat:@"%lld", spaceId]];
|
||||
|
||||
// Apply extras if provided (matching Unity C# ToBridgePayload keys)
|
||||
if (extrasDict[@"mina_id"]) {
|
||||
request.minaId = [NSString stringWithFormat:@"%@", extrasDict[@"mina_id"]];
|
||||
}
|
||||
// Set ad size for Banner (CSJ/GDT adapters need this)
|
||||
NSNumber* width = extrasDict[@"express_width"];
|
||||
NSNumber* height = extrasDict[@"express_height"];
|
||||
if (width || height) {
|
||||
CGFloat w = width ? [width floatValue] : 0;
|
||||
CGFloat h = height ? [height floatValue] : 0;
|
||||
request.adSize = CGSizeMake(w, h);
|
||||
}
|
||||
|
||||
[DRMBannerAd loadWithRequest:request completion:^(NSArray<DRMBannerAd *> * _Nullable ads, NSError * _Nullable error) {
|
||||
if (ads && ads.count > 0) {
|
||||
DRMBannerAd* ad = ads.firstObject;
|
||||
DirichletMediationUnityBannerAdDelegate* delegate = [[DirichletMediationUnityBannerAdDelegate alloc] init];
|
||||
delegate.handleId = handleId;
|
||||
ad.delegate = delegate;
|
||||
|
||||
[[DirichletMediationInstanceManager shared] storeAd:ad forHandle:handleId];
|
||||
[[DirichletMediationInstanceManager shared] storeAd:delegate forHandle:[handleId stringByAppendingString:@"_delegate"]];
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] BannerAd loaded: %@", handleId);
|
||||
SendLoadCallbackToUnity(handleId, @"load_success", @"banner", nil);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] BannerAd load failed: %@", error.localizedDescription);
|
||||
NSDictionary* errorData = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendLoadCallbackToUnity(handleId, @"load_error", @"banner", errorData);
|
||||
}
|
||||
}];
|
||||
|
||||
return MakeCString(handleId);
|
||||
}
|
||||
|
||||
const char* DirichletMediationUnityBridge_LoadSplashAd(long long spaceId, const char* extras) {
|
||||
NSString* handleId = [[DirichletMediationInstanceManager shared] generateHandle];
|
||||
NSDictionary* extrasDict = ParseExtras(extras);
|
||||
|
||||
// Create load request
|
||||
DRMAdLoadRequest* request = [[DRMAdLoadRequest alloc] initWithSpaceId:[NSString stringWithFormat:@"%lld", spaceId]];
|
||||
|
||||
// Apply extras if provided (matching Unity C# ToBridgePayload keys)
|
||||
if (extrasDict[@"mina_id"]) {
|
||||
request.minaId = [NSString stringWithFormat:@"%@", extrasDict[@"mina_id"]];
|
||||
}
|
||||
// Set ad size for Splash (CSJ adapter needs this)
|
||||
NSNumber* width = extrasDict[@"express_width"];
|
||||
NSNumber* height = extrasDict[@"express_height"];
|
||||
if (width || height) {
|
||||
CGFloat w = width ? [width floatValue] : 0;
|
||||
CGFloat h = height ? [height floatValue] : 0;
|
||||
request.adSize = CGSizeMake(w, h);
|
||||
}
|
||||
|
||||
[DRMSplashAd loadWithRequest:request completion:^(NSArray<DRMSplashAd *> * _Nullable ads, NSError * _Nullable error) {
|
||||
if (ads && ads.count > 0) {
|
||||
DRMSplashAd* ad = ads.firstObject;
|
||||
DirichletMediationUnitySplashAdDelegate* delegate = [[DirichletMediationUnitySplashAdDelegate alloc] init];
|
||||
delegate.handleId = handleId;
|
||||
ad.delegate = delegate;
|
||||
|
||||
[[DirichletMediationInstanceManager shared] storeAd:ad forHandle:handleId];
|
||||
[[DirichletMediationInstanceManager shared] storeAd:delegate forHandle:[handleId stringByAppendingString:@"_delegate"]];
|
||||
|
||||
NSLog(@"[DirichletMediationUnityBridge] SplashAd loaded: %@", handleId);
|
||||
SendLoadCallbackToUnity(handleId, @"load_success", @"splash", nil);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] SplashAd load failed: %@", error.localizedDescription);
|
||||
NSDictionary* errorData = @{
|
||||
@"code": @(error.code),
|
||||
@"message": error.localizedDescription ?: @"Unknown error"
|
||||
};
|
||||
SendLoadCallbackToUnity(handleId, @"load_error", @"splash", errorData);
|
||||
}
|
||||
}];
|
||||
|
||||
return MakeCString(handleId);
|
||||
}
|
||||
|
||||
bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extras) {
|
||||
NSString* nsHandleId = CreateNSString(handleId);
|
||||
id ad = [[DirichletMediationInstanceManager shared] adForHandle:nsHandleId];
|
||||
|
||||
if (!ad) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not found for handle %@", nsHandleId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure show is always called on main thread (aligned with Ad Unity implementation)
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIViewController* rootVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
|
||||
if (!rootVC) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Root view controller not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if ([ad isKindOfClass:[DRMRewardVideoAd class]]) {
|
||||
DRMRewardVideoAd* rewardAd = (DRMRewardVideoAd*)ad;
|
||||
if ([rewardAd isReady]) {
|
||||
[rewardAd showFromViewController:rootVC];
|
||||
NSLog(@"[DirichletMediationUnityBridge] Showing reward video ad: %@", nsHandleId);
|
||||
}
|
||||
} else if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
|
||||
DRMInterstitialAd* interstitialAd = (DRMInterstitialAd*)ad;
|
||||
if ([interstitialAd isReady]) {
|
||||
[interstitialAd showFromViewController:rootVC];
|
||||
NSLog(@"[DirichletMediationUnityBridge] Showing interstitial ad: %@", nsHandleId);
|
||||
}
|
||||
} else if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
||||
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
|
||||
UIView* bannerView = bannerAd.view;
|
||||
if (bannerView) {
|
||||
// Banner 广告需要将 view 添加到视图控制器上
|
||||
// 注意:Unity 侧需要通过 Unity UI 系统来处理 Banner 视图
|
||||
// 这里我们发送一个事件通知 Unity 侧,让 Unity 侧来处理视图的展示
|
||||
// 或者直接将视图添加到根视图控制器上(临时方案)
|
||||
[rootVC.view addSubview:bannerView];
|
||||
bannerView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
// 设置约束,让 Banner 显示在底部
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[bannerView.leadingAnchor constraintEqualToAnchor:rootVC.view.leadingAnchor],
|
||||
[bannerView.trailingAnchor constraintEqualToAnchor:rootVC.view.trailingAnchor],
|
||||
[bannerView.bottomAnchor constraintEqualToAnchor:rootVC.view.safeAreaLayoutGuide.bottomAnchor],
|
||||
[bannerView.heightAnchor constraintEqualToConstant:bannerAd.size.height > 0 ? bannerAd.size.height : 50]
|
||||
]];
|
||||
NSLog(@"[DirichletMediationUnityBridge] Showing banner ad: %@", nsHandleId);
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] Banner ad view not available: %@", nsHandleId);
|
||||
}
|
||||
} else if ([ad isKindOfClass:[DRMSplashAd class]]) {
|
||||
DRMSplashAd* splashAd = (DRMSplashAd*)ad;
|
||||
if ([splashAd isReady]) {
|
||||
[splashAd showFromViewController:rootVC];
|
||||
NSLog(@"[DirichletMediationUnityBridge] Showing splash ad: %@", nsHandleId);
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not ready or unknown type");
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DirichletMediationUnityBridge_DestroyAd(const char* handleId) {
|
||||
NSString* nsHandleId = CreateNSString(handleId);
|
||||
NSLog(@"[DirichletMediationUnityBridge] Destroying ad: %@", nsHandleId);
|
||||
|
||||
// Remove ad instance
|
||||
[[DirichletMediationInstanceManager shared] removeAdForHandle:nsHandleId];
|
||||
|
||||
// Remove delegate
|
||||
NSString* delegateKey = [nsHandleId stringByAppendingString:@"_delegate"];
|
||||
[[DirichletMediationInstanceManager shared] removeAdForHandle:delegateKey];
|
||||
}
|
||||
|
||||
bool DirichletMediationUnityBridge_IsAdValid(const char* handleId) {
|
||||
NSString* nsHandleId = CreateNSString(handleId);
|
||||
id ad = [[DirichletMediationInstanceManager shared] adForHandle:nsHandleId];
|
||||
|
||||
if (!ad) {
|
||||
NSLog(@"[DirichletMediationUnityBridge] IsAdValid: Ad not found for handle %@", nsHandleId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ([ad isKindOfClass:[DRMRewardVideoAd class]]) {
|
||||
DRMRewardVideoAd* rewardAd = (DRMRewardVideoAd*)ad;
|
||||
return [rewardAd isReady];
|
||||
} else if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
|
||||
DRMInterstitialAd* interstitialAd = (DRMInterstitialAd*)ad;
|
||||
return [interstitialAd isReady];
|
||||
} else if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
||||
// Banner ads don't have isReady, assume valid if loaded
|
||||
return true;
|
||||
} else if ([ad isKindOfClass:[DRMSplashAd class]]) {
|
||||
DRMSplashAd* splashAd = (DRMSplashAd*)ad;
|
||||
return [splashAd isReady];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
33
Assets/Plugins/iOS/DirichletMediationUnityBridge.mm.meta
Normal file
33
Assets/Plugins/iOS/DirichletMediationUnityBridge.mm.meta
Normal file
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f8326f62fa7a44aebc39371a5fcddd0
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Samples~.meta
Normal file
8
Assets/Samples~.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1008dc07e5044140babef9a59886d01b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Samples~/IAAAdDebugSample.meta
Normal file
8
Assets/Samples~/IAAAdDebugSample.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5aae7ed566c84b00b0c42455e7c704b3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Samples~/IAAAdDebugSample/Configs.meta
Normal file
8
Assets/Samples~/IAAAdDebugSample/Configs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c80b68eb57e4755b9385e066e926f26
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
5
Assets/Samples~/IAAAdDebugSample/README.md
Normal file
5
Assets/Samples~/IAAAdDebugSample/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# TapADN IAA Ad Debug Sample
|
||||
|
||||
This optional sample is intentionally lightweight. Import it in a host project, create an `ADConfig`, and initialize with `TapadnCommercialization.InitADManager(...)` to validate rewarded, interstitial, and splash slots.
|
||||
|
||||
The main package does not import debug scenes or editor panels by default.
|
||||
7
Assets/Samples~/IAAAdDebugSample/README.md.meta
Normal file
7
Assets/Samples~/IAAAdDebugSample/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f23c6cf6cac240bbaa767a723ae8f313
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Samples~/IAAAdDebugSample/Runtime.meta
Normal file
8
Assets/Samples~/IAAAdDebugSample/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b2644e7dd034f42bc57d2033fd01cda
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
16
Assets/Tapadn_Adapter.asmdef
Normal file
16
Assets/Tapadn_Adapter.asmdef
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Tapadn_Adapter",
|
||||
"references": [
|
||||
"Runtime.Adaggregator",
|
||||
"Dirichlet.Mediation.Runtime"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Tapadn_Adapter.asmdef.meta
Normal file
7
Assets/Tapadn_Adapter.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5346bec840144a6f8b806ea11c6235ba
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Tapadn_Adapter.meta
Normal file
8
Assets/Tapadn_Adapter.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4af77eef1d5f4156b2b96eeb89671fb1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Tapadn_Adapter/Editor.meta
Normal file
8
Assets/Tapadn_Adapter/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95121de0d8184f92a4184be55ea40e5b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
285
Assets/Tapadn_Adapter/Editor/TapadnBuildAndroidProcess.cs
Normal file
285
Assets/Tapadn_Adapter/Editor/TapadnBuildAndroidProcess.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
#if UNITY_ANDROID
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Tapadn_Adapter.Editor
|
||||
{
|
||||
public sealed class TapadnBuildAndroidProcess : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android";
|
||||
private static readonly XNamespace ToolsNamespace = "http://schemas.android.com/tools";
|
||||
|
||||
public int callbackOrder => 180;
|
||||
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
ProcessGradleProperties(path);
|
||||
ProcessAndroidManifest(path);
|
||||
}
|
||||
|
||||
public static void ProcessAndroidManifest(string path)
|
||||
{
|
||||
var unityManifestPath = Path.Combine(path, "src/main/AndroidManifest.xml");
|
||||
var launcherManifestPath = Path.Combine(path, "../launcher/src/main/AndroidManifest.xml");
|
||||
var providerTargetManifestPath = File.Exists(launcherManifestPath) ? launcherManifestPath : unityManifestPath;
|
||||
|
||||
if (!File.Exists(unityManifestPath))
|
||||
{
|
||||
Debug.LogWarning($"[TapADN] AndroidManifest.xml is missing: {unityManifestPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var unityManifest = XDocument.Load(unityManifestPath);
|
||||
EnsureManifestPatch(unityManifest, true, false);
|
||||
unityManifest.Save(unityManifestPath);
|
||||
|
||||
if (File.Exists(providerTargetManifestPath))
|
||||
{
|
||||
var providerManifest = XDocument.Load(providerTargetManifestPath);
|
||||
EnsureManifestPatch(providerManifest, false, true);
|
||||
providerManifest.Save(providerTargetManifestPath);
|
||||
}
|
||||
|
||||
var resXmlPath = Path.Combine(path, "src/main/res/xml");
|
||||
Directory.CreateDirectory(resXmlPath);
|
||||
var sourceXmlPath = GetEditorAssetPath("tapad_ad_file_path.xml");
|
||||
if (!string.IsNullOrEmpty(sourceXmlPath) && File.Exists(sourceXmlPath))
|
||||
{
|
||||
File.Copy(sourceXmlPath, Path.Combine(resXmlPath, "tapad_ad_file_path.xml"), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureManifestPatch(XDocument manifest, bool includeWechatActivity, bool includeTapadProvider)
|
||||
{
|
||||
var manifestElement = manifest.Element("manifest");
|
||||
if (manifestElement == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureNamespace(manifestElement, "tools", ToolsNamespace.NamespaceName);
|
||||
|
||||
EnsurePermission(manifestElement, "android.permission.INTERNET");
|
||||
EnsurePermission(manifestElement, "android.permission.ACCESS_NETWORK_STATE");
|
||||
EnsurePermission(manifestElement, "android.permission.READ_PHONE_STATE");
|
||||
EnsurePermission(manifestElement, "android.permission.QUERY_ALL_PACKAGES");
|
||||
EnsurePermission(manifestElement, "android.permission.REQUEST_INSTALL_PACKAGES");
|
||||
EnsurePermission(manifestElement, "android.permission.BLUETOOTH");
|
||||
EnsurePermission(manifestElement, "android.permission.BLUETOOTH_CONNECT");
|
||||
EnsurePermission(manifestElement, "android.permission.ACCESS_FINE_LOCATION");
|
||||
EnsurePermission(manifestElement, "android.permission.ACCESS_COARSE_LOCATION");
|
||||
EnsurePermission(manifestElement, "android.permission.POST_NOTIFICATIONS");
|
||||
EnsureWechatQueries(manifestElement);
|
||||
|
||||
var applicationElement = manifestElement.Element("application");
|
||||
if (applicationElement == null)
|
||||
{
|
||||
applicationElement = new XElement("application");
|
||||
manifestElement.Add(applicationElement);
|
||||
}
|
||||
|
||||
SetUnityActivitySingleTop(applicationElement);
|
||||
|
||||
if (includeWechatActivity)
|
||||
{
|
||||
EnsureWechatEntryActivity(applicationElement);
|
||||
}
|
||||
|
||||
if (includeTapadProvider)
|
||||
{
|
||||
EnsureTapadFileProvider(applicationElement);
|
||||
SetOrAddAttribute(applicationElement, AndroidNamespace + "allowBackup", "false");
|
||||
MergeToolsReplaceAttribute(applicationElement, "android:allowBackup");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsurePermission(XElement manifestElement, string permissionName)
|
||||
{
|
||||
var exists = manifestElement.Elements("uses-permission")
|
||||
.Any(element => element.Attribute(AndroidNamespace + "name")?.Value == permissionName);
|
||||
if (exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var permission = new XElement("uses-permission");
|
||||
permission.Add(new XAttribute(AndroidNamespace + "name", permissionName));
|
||||
if (permissionName == "android.permission.BLUETOOTH_CONNECT" ||
|
||||
permissionName == "android.permission.POST_NOTIFICATIONS")
|
||||
{
|
||||
permission.Add(new XAttribute(ToolsNamespace + "targetApi", permissionName.EndsWith("CONNECT") ? "s" : "33"));
|
||||
}
|
||||
|
||||
manifestElement.Add(permission);
|
||||
}
|
||||
|
||||
private static void EnsureWechatQueries(XElement manifestElement)
|
||||
{
|
||||
var queries = manifestElement.Elements("queries").FirstOrDefault();
|
||||
if (queries == null)
|
||||
{
|
||||
queries = new XElement("queries");
|
||||
manifestElement.Add(queries);
|
||||
}
|
||||
|
||||
var exists = queries.Elements("package")
|
||||
.Any(element => element.Attribute(AndroidNamespace + "name")?.Value == "com.tencent.mm");
|
||||
if (!exists)
|
||||
{
|
||||
var packageElement = new XElement("package");
|
||||
packageElement.Add(new XAttribute(AndroidNamespace + "name", "com.tencent.mm"));
|
||||
queries.Add(packageElement);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureWechatEntryActivity(XElement applicationElement)
|
||||
{
|
||||
RemoveElementsByAndroidName(applicationElement, "activity", ".wxapi.WXEntryActivity");
|
||||
|
||||
var activity = new XElement("activity");
|
||||
activity.Add(new XAttribute(AndroidNamespace + "name", ".wxapi.WXEntryActivity"));
|
||||
activity.Add(new XAttribute(AndroidNamespace + "label", "@string/app_name"));
|
||||
activity.Add(new XAttribute(AndroidNamespace + "theme", "@android:style/Theme.Translucent.NoTitleBar"));
|
||||
activity.Add(new XAttribute(AndroidNamespace + "exported", "true"));
|
||||
activity.Add(new XAttribute(AndroidNamespace + "taskAffinity", Application.identifier));
|
||||
activity.Add(new XAttribute(AndroidNamespace + "launchMode", "singleTop"));
|
||||
applicationElement.Add(activity);
|
||||
}
|
||||
|
||||
private static void EnsureTapadFileProvider(XElement applicationElement)
|
||||
{
|
||||
RemoveElementsByAndroidName(applicationElement, "provider", "com.tapsdk.tapad.internal.TapADFileProvider");
|
||||
|
||||
var provider = new XElement("provider");
|
||||
provider.Add(new XAttribute(AndroidNamespace + "name", "com.tapsdk.tapad.internal.TapADFileProvider"));
|
||||
provider.Add(new XAttribute(AndroidNamespace + "authorities", "${applicationId}.com.tds.ad.fileprovider"));
|
||||
provider.Add(new XAttribute(AndroidNamespace + "exported", "false"));
|
||||
provider.Add(new XAttribute(AndroidNamespace + "grantUriPermissions", "true"));
|
||||
provider.Add(new XAttribute(ToolsNamespace + "replace", "android:authorities"));
|
||||
|
||||
var metadata = new XElement("meta-data");
|
||||
metadata.Add(new XAttribute(AndroidNamespace + "name", "android.support.FILE_PROVIDER_PATHS"));
|
||||
metadata.Add(new XAttribute(AndroidNamespace + "resource", "@xml/tapad_ad_file_path"));
|
||||
provider.Add(metadata);
|
||||
|
||||
applicationElement.Add(provider);
|
||||
}
|
||||
|
||||
private static void SetUnityActivitySingleTop(XElement applicationElement)
|
||||
{
|
||||
foreach (var activity in applicationElement.Elements("activity"))
|
||||
{
|
||||
var name = activity.Attribute(AndroidNamespace + "name")?.Value;
|
||||
if (name == "com.unity3d.player.UnityPlayerActivity")
|
||||
{
|
||||
SetOrAddAttribute(activity, AndroidNamespace + "launchMode", "singleTop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveElementsByAndroidName(XElement parent, string localName, string androidName)
|
||||
{
|
||||
parent.Elements(localName)
|
||||
.Where(element => element.Attribute(AndroidNamespace + "name")?.Value == androidName)
|
||||
.ToList()
|
||||
.ForEach(element => element.Remove());
|
||||
}
|
||||
|
||||
private static void ProcessGradleProperties(string path)
|
||||
{
|
||||
var root = Directory.GetParent(path)?.FullName;
|
||||
if (string.IsNullOrEmpty(root))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var gradlePropertiesPath = Path.Combine(root, "gradle.properties");
|
||||
var lines = File.Exists(gradlePropertiesPath)
|
||||
? File.ReadAllLines(gradlePropertiesPath).ToList()
|
||||
: new List<string>();
|
||||
|
||||
UpsertProperty(lines, "android.useAndroidX", "true");
|
||||
UpsertProperty(lines, "android.enableJetifier", "true");
|
||||
|
||||
File.WriteAllText(gradlePropertiesPath, string.Join("\n", lines) + "\n");
|
||||
}
|
||||
|
||||
private static void UpsertProperty(List<string> lines, string key, string value)
|
||||
{
|
||||
var prefix = key + "=";
|
||||
var index = lines.FindIndex(line => line.TrimStart().StartsWith(prefix));
|
||||
if (index >= 0)
|
||||
{
|
||||
lines[index] = prefix + value;
|
||||
return;
|
||||
}
|
||||
|
||||
lines.Add(prefix + value);
|
||||
}
|
||||
|
||||
private static void SetOrAddAttribute(XElement element, XName attributeName, string value)
|
||||
{
|
||||
var attribute = element.Attribute(attributeName);
|
||||
if (attribute == null)
|
||||
{
|
||||
element.Add(new XAttribute(attributeName, value));
|
||||
return;
|
||||
}
|
||||
|
||||
attribute.SetValue(value);
|
||||
}
|
||||
|
||||
private static void MergeToolsReplaceAttribute(XElement element, params string[] replaceValues)
|
||||
{
|
||||
var replaceAttributeName = ToolsNamespace + "replace";
|
||||
var mergedValues = new List<string>();
|
||||
var currentValue = element.Attribute(replaceAttributeName)?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(currentValue))
|
||||
{
|
||||
mergedValues.AddRange(currentValue.Split(',')
|
||||
.Select(value => value.Trim())
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||
}
|
||||
|
||||
foreach (var replaceValue in replaceValues)
|
||||
{
|
||||
if (!mergedValues.Contains(replaceValue))
|
||||
{
|
||||
mergedValues.Add(replaceValue);
|
||||
}
|
||||
}
|
||||
|
||||
element.SetAttributeValue(replaceAttributeName, string.Join(",", mergedValues));
|
||||
}
|
||||
|
||||
private static void EnsureNamespace(XElement element, string prefix, string namespaceName)
|
||||
{
|
||||
var namespaceAttributeName = XNamespace.Xmlns + prefix;
|
||||
if (element.Attribute(namespaceAttributeName) == null)
|
||||
{
|
||||
element.Add(new XAttribute(namespaceAttributeName, namespaceName));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetEditorAssetPath(string fileName)
|
||||
{
|
||||
var guids = AssetDatabase.FindAssets(Path.GetFileNameWithoutExtension(fileName));
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (Path.GetFileName(path) == fileName)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ab0342739d64cabbf5360a6f49250a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
15
Assets/Tapadn_Adapter/Editor/Tapadn_Adapter.Editor.asmdef
Normal file
15
Assets/Tapadn_Adapter/Editor/Tapadn_Adapter.Editor.asmdef
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Tapadn_Adapter.Editor",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d92d8c493204ca298403fceb36f5a71
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
11
Assets/Tapadn_Adapter/Editor/WXDependencies.xml
Normal file
11
Assets/Tapadn_Adapter/Editor/WXDependencies.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<dependencies>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.tencent.mm.opensdk:wechat-sdk-android:6.8.34">
|
||||
<repositories>
|
||||
<repository>https://maven.aliyun.com/repository/public</repository>
|
||||
<repository>https://repo.maven.apache.org/maven2</repository>
|
||||
</repositories>
|
||||
</androidPackage>
|
||||
</androidPackages>
|
||||
</dependencies>
|
||||
7
Assets/Tapadn_Adapter/Editor/WXDependencies.xml.meta
Normal file
7
Assets/Tapadn_Adapter/Editor/WXDependencies.xml.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea7cb8d4cc2e4dce8859500b44a86028
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/Tapadn_Adapter/Editor/tapad_ad_file_path.xml
Normal file
7
Assets/Tapadn_Adapter/Editor/tapad_ad_file_path.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="external" path="." />
|
||||
<external-files-path name="external_files" path="." />
|
||||
<files-path name="files" path="." />
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
7
Assets/Tapadn_Adapter/Editor/tapad_ad_file_path.xml.meta
Normal file
7
Assets/Tapadn_Adapter/Editor/tapad_ad_file_path.xml.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcb4be7a15dd43aaa4862ab35f8b309b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Tapadn_Adapter/Runtime.meta
Normal file
8
Assets/Tapadn_Adapter/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9c09f845d424a529010aa7f15b05e9c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Tapadn_Adapter/Runtime/Scripts.meta
Normal file
8
Assets/Tapadn_Adapter/Runtime/Scripts.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a07b2d839f4748b0b52bd5bcb950f111
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
75
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnAdController.cs
Normal file
75
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnAdController.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class TapadnAdController : IAdController
|
||||
{
|
||||
public static TapadnControllerOptions CurrentOptions { get; private set; }
|
||||
public static string LastSdkVersion { get; private set; }
|
||||
public static string LastInitError { get; private set; }
|
||||
|
||||
private Action<bool> _maskAction;
|
||||
private Action<string, string> _logEventAction;
|
||||
private ADConfig _adConfig;
|
||||
private TapadnControllerOptions _options;
|
||||
|
||||
public void Init(ADConfig adConfig, object[] args)
|
||||
{
|
||||
_adConfig = adConfig;
|
||||
_options = TapadnControllerOptions.Resolve(adConfig, args);
|
||||
CurrentOptions = _options;
|
||||
|
||||
var sdkConfig = _options.BuildSdkConfig();
|
||||
DirichletSdk.Init(
|
||||
sdkConfig,
|
||||
result =>
|
||||
{
|
||||
LastInitError = null;
|
||||
LastSdkVersion = DirichletSdk.GetVersion();
|
||||
EventLog("tapadn_init", "success", result?.Message);
|
||||
Debug.Log($"[TapADN] Init success. version={LastSdkVersion}, message={result?.Message}");
|
||||
if (_options.RequestPermissionOnInit)
|
||||
{
|
||||
DirichletSdk.RequestPermissionIfNecessary();
|
||||
}
|
||||
},
|
||||
error =>
|
||||
{
|
||||
LastInitError = $"{error?.Code}:{error?.Message}";
|
||||
EventLog("tapadn_init_error", error?.Code ?? "unknown", error?.Message);
|
||||
Debug.LogError($"[TapADN] Init failed. code={error?.Code}, message={error?.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public void SetListener(Action<bool> adMaskAction, Action<string, string> logEventAction)
|
||||
{
|
||||
_maskAction = adMaskAction;
|
||||
_logEventAction = logEventAction;
|
||||
}
|
||||
|
||||
public ADPlayer CreateAdPlayer(AD_Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AD_Type.AwardVideo:
|
||||
return new TapadnAwardVideoPlayer().Init(_adConfig?.BaseAwardAdKeyValue?.value);
|
||||
case AD_Type.Interaction:
|
||||
return new TapadnInteractionPlayer().Init(_adConfig?.BaseInteractionAdKeyValue?.value);
|
||||
case AD_Type.Splash:
|
||||
return new TapadnSplashPlayer().Init(_adConfig?.BaseSplashAdKeyValue?.value);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void EventLog(string eventTable, string eventValue, string eventMessage = null)
|
||||
{
|
||||
_logEventAction?.Invoke(eventTable, eventValue);
|
||||
}
|
||||
|
||||
public void SetMask(bool isOpen)
|
||||
{
|
||||
_maskAction?.Invoke(isOpen);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a08780bf18a349b58a35e2dfb79d0743
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
internal static class TapadnAdRequestFactory
|
||||
{
|
||||
public static bool TryParseSlotId(string slotId, out long parsed)
|
||||
{
|
||||
return long.TryParse(slotId, out parsed) && parsed > 0;
|
||||
}
|
||||
|
||||
public static DirichletAdRequest BuildRewarded(string slotId, TapadnControllerOptions options)
|
||||
{
|
||||
var builder = CreateBaseBuilder(slotId)
|
||||
.WithUserId(ADManager.Instance.UserId)
|
||||
.WithRewardName(options?.RewardName ?? "reward")
|
||||
.WithRewardAmount(options?.RewardAmount ?? 1);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static DirichletAdRequest BuildInterstitial(string slotId, TapadnControllerOptions options)
|
||||
{
|
||||
return ApplyExpressSize(CreateBaseBuilder(slotId), options).Build();
|
||||
}
|
||||
|
||||
public static DirichletAdRequest BuildSplash(string slotId, TapadnControllerOptions options)
|
||||
{
|
||||
var builder = CreateBaseBuilder(slotId);
|
||||
var width = options?.ExpressWidth ?? Screen.width;
|
||||
var height = options?.ExpressHeight ?? Screen.height;
|
||||
return builder.WithExpressViewSize(width, height).Build();
|
||||
}
|
||||
|
||||
private static DirichletAdRequest.Builder CreateBaseBuilder(string slotId)
|
||||
{
|
||||
if (!TryParseSlotId(slotId, out var parsed))
|
||||
{
|
||||
throw new ArgumentException($"TapADN slot id must be a positive integer. Current value: {slotId}", nameof(slotId));
|
||||
}
|
||||
|
||||
return new DirichletAdRequest.Builder().WithSpaceId(parsed);
|
||||
}
|
||||
|
||||
private static DirichletAdRequest.Builder ApplyExpressSize(DirichletAdRequest.Builder builder, TapadnControllerOptions options)
|
||||
{
|
||||
if (options?.ExpressWidth != null || options?.ExpressHeight != null)
|
||||
{
|
||||
builder.WithExpressViewSize(options?.ExpressWidth ?? Screen.width, options?.ExpressHeight ?? Screen.height);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01ac342b5e1c49cf87b649eddcd34f7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
159
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnAwardVideoPlayer.cs
Normal file
159
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnAwardVideoPlayer.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class TapadnAwardVideoPlayer : ADPlayer, IDirichletRewardVideoAutoAdListener
|
||||
{
|
||||
private DirichletAdNative _adNative;
|
||||
private DirichletRewardVideoAd _loadedAd;
|
||||
private bool _rewardVerified;
|
||||
|
||||
public override int MaxLoadAttempts => TapadnAdController.CurrentOptions?.RewardedMaxLoadAttempts ?? base.MaxLoadAttempts;
|
||||
public override float LoadRetryDelaySeconds => Math.Max(0f, (TapadnAdController.CurrentOptions?.RewardedLoadRetryDelayMs ?? 500) / 1000f);
|
||||
public override float ShowPendingTimeoutSeconds => Math.Max(1f, (TapadnAdController.CurrentOptions?.RewardedShowTimeoutMs ?? 20000) / 1000f);
|
||||
public override bool AutoPreloadOnInit => TapadnAdController.CurrentOptions?.RewardedPrewarmOnInit ?? false;
|
||||
|
||||
public override void OnInit()
|
||||
{
|
||||
_adNative = DirichletAdManager.CreateAdNative();
|
||||
}
|
||||
|
||||
public override bool IsReadly()
|
||||
{
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
return TapadnAdRequestFactory.TryParseSlotId(Key, out _);
|
||||
}
|
||||
|
||||
if (_loadedAd != null && _loadedAd.IsLoaded && _loadedAd.IsValid)
|
||||
{
|
||||
curState = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void LoadAD()
|
||||
{
|
||||
if (!TapadnAdRequestFactory.TryParseSlotId(Key, out _))
|
||||
{
|
||||
Debug.LogError($"[TapADN] Invalid rewarded slot id: {Key}");
|
||||
curState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
curState = 2;
|
||||
try
|
||||
{
|
||||
_adNative.PreLoad(TapadnAdRequestFactory.BuildRewarded(Key, TapadnAdController.CurrentOptions), 3);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogWarning($"[TapADN] Rewarded preload skipped: {exception.Message}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (curState == 1 || IsReadly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
curState = 1;
|
||||
_adNative.LoadRewardVideoAd(
|
||||
TapadnAdRequestFactory.BuildRewarded(Key, TapadnAdController.CurrentOptions),
|
||||
ad =>
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = ad;
|
||||
_loadedAd.Shown += OnManualShown;
|
||||
_loadedAd.Clicked += OnManualClicked;
|
||||
_loadedAd.RewardVerified += OnManualRewardVerify;
|
||||
_loadedAd.Closed += OnManualClosed;
|
||||
curState = 2;
|
||||
Debug.Log($"[TapADN] Rewarded loaded. slot={Key}");
|
||||
},
|
||||
error =>
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Rewarded load failed. code={error.Code}, message={error.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void ShowAD(Action onClose, Action<bool> onVideoComplete)
|
||||
{
|
||||
adListener.onClose = onClose;
|
||||
adListener.onVideoComplete = onVideoComplete;
|
||||
_rewardVerified = false;
|
||||
curState = 0;
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
_adNative.ShowRewardVideoAutoAd(TapadnAdRequestFactory.BuildRewarded(Key, TapadnAdController.CurrentOptions), this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_loadedAd == null || !_loadedAd.Show())
|
||||
{
|
||||
adListener.OnShowError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnError(DirichletError error)
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Rewarded show failed. code={error?.Code}, message={error?.Message}");
|
||||
adListener.OnShowError();
|
||||
}
|
||||
|
||||
public void OnAdShow()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
public void OnAdClose()
|
||||
{
|
||||
adListener.OnRewardVerify(_rewardVerified, TapadnAdController.CurrentOptions?.RewardAmount ?? 1, TapadnAdController.CurrentOptions?.RewardName ?? string.Empty);
|
||||
adListener.OnAdClose();
|
||||
}
|
||||
|
||||
public void OnRewardVerify(DirichletRewardVerificationEventArgs args)
|
||||
{
|
||||
_rewardVerified = args != null && args.IsVerified;
|
||||
}
|
||||
|
||||
public void OnAdClick()
|
||||
{
|
||||
}
|
||||
|
||||
private bool UseAutoLoad()
|
||||
{
|
||||
return TapadnAdController.CurrentOptions?.RewardedAutoLoad ?? true;
|
||||
}
|
||||
|
||||
private void OnManualShown()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
private void OnManualClicked()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnManualRewardVerify(DirichletRewardVerificationEventArgs args)
|
||||
{
|
||||
_rewardVerified = args != null && args.IsVerified;
|
||||
}
|
||||
|
||||
private void OnManualClosed()
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = null;
|
||||
OnAdClose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb7643101da94ca69dc8f99128d4e759
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Runtime.ADAggregator;
|
||||
|
||||
public static class TapadnCommercialization
|
||||
{
|
||||
public static TapadnAdController CreateController()
|
||||
{
|
||||
return new TapadnAdController();
|
||||
}
|
||||
|
||||
public static void InitADManager(Action onCallback, string userId, ADConfig adConfig, params object[] args)
|
||||
{
|
||||
ADManager.Instance.Init(onCallback, userId, adConfig, CreateController(), args);
|
||||
}
|
||||
|
||||
public static ADConfig CreateConfig(
|
||||
string mediaId,
|
||||
string mediaKey,
|
||||
string mediaName,
|
||||
string rewardSlotId,
|
||||
string interstitialSlotId = null,
|
||||
string splashSlotId = null)
|
||||
{
|
||||
var config = UnityEngine.ScriptableObject.CreateInstance<ADConfig>();
|
||||
config.ConfigName = "TapADN";
|
||||
config.Id = mediaId;
|
||||
config.Key = mediaKey;
|
||||
config.Key2 = mediaName;
|
||||
config.BaseAwardAdKeyValue = new AdKeyValue { key = "reward", value = rewardSlotId };
|
||||
config.BaseInteractionAdKeyValue = new AdKeyValue { key = "interaction", value = interstitialSlotId };
|
||||
config.BaseSplashAdKeyValue = new AdKeyValue { key = "splash", value = splashSlotId };
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 295d10d222a8496e872b02eff3556e86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
370
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnControllerOptions.cs
Normal file
370
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnControllerOptions.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class TapadnControllerOptions
|
||||
{
|
||||
public const string MediaNameKey = "tapadn.media_name";
|
||||
public const string MediaIdKey = "tapadn.media_id";
|
||||
public const string MediaKeyKey = "tapadn.media_key";
|
||||
public const string ChannelKey = "tapadn.channel";
|
||||
public const string SubChannelKey = "tapadn.sub_channel";
|
||||
public const string DebugKey = "tapadn.debug";
|
||||
public const string TapClientIdKey = "tapadn.tap_client_id";
|
||||
public const string ShakeEnabledKey = "tapadn.shake_enabled";
|
||||
public const string CustomConfigJsonKey = "tapadn.custom_config_json";
|
||||
public const string DataJsonKey = "tapadn.data_json";
|
||||
public const string ATagsKey = "tapadn.atags";
|
||||
public const string AllowIdfaAccessKey = "tapadn.allow_idfa_access";
|
||||
public const string RequestPermissionOnInitKey = "tapadn.request_permission_on_init";
|
||||
public const string RewardedAutoLoadKey = "tapadn.rewarded_auto_load";
|
||||
public const string RewardedPrewarmOnInitKey = "tapadn.rewarded_prewarm_on_init";
|
||||
public const string RewardedMaxLoadAttemptsKey = "tapadn.rewarded_max_load_attempts";
|
||||
public const string RewardedLoadRetryDelayMsKey = "tapadn.rewarded_load_retry_delay_ms";
|
||||
public const string RewardedShowTimeoutMsKey = "tapadn.rewarded_show_timeout_ms";
|
||||
public const string RewardNameKey = "tapadn.reward_name";
|
||||
public const string RewardAmountKey = "tapadn.reward_amount";
|
||||
public const string InterstitialAutoLoadKey = "tapadn.interstitial_auto_load";
|
||||
public const string InterstitialPrewarmOnInitKey = "tapadn.interstitial_prewarm_on_init";
|
||||
public const string InterstitialMaxLoadAttemptsKey = "tapadn.interstitial_max_load_attempts";
|
||||
public const string InterstitialLoadRetryDelayMsKey = "tapadn.interstitial_load_retry_delay_ms";
|
||||
public const string InterstitialShowTimeoutMsKey = "tapadn.interstitial_show_timeout_ms";
|
||||
public const string SplashAutoLoadKey = "tapadn.splash_auto_load";
|
||||
public const string SplashPrewarmOnInitKey = "tapadn.splash_prewarm_on_init";
|
||||
public const string SplashMaxLoadAttemptsKey = "tapadn.splash_max_load_attempts";
|
||||
public const string SplashLoadRetryDelayMsKey = "tapadn.splash_load_retry_delay_ms";
|
||||
public const string SplashShowTimeoutMsKey = "tapadn.splash_show_timeout_ms";
|
||||
public const string ExpressWidthKey = "tapadn.express_width";
|
||||
public const string ExpressHeightKey = "tapadn.express_height";
|
||||
|
||||
public long MediaId { get; set; }
|
||||
public string MediaName { get; set; }
|
||||
public string MediaKey { get; set; }
|
||||
public string Channel { get; set; } = "default";
|
||||
public string SubChannel { get; set; }
|
||||
public bool Debug { get; set; }
|
||||
public string TapClientId { get; set; }
|
||||
public bool ShakeEnabled { get; set; } = true;
|
||||
public string CustomConfigJson { get; set; }
|
||||
public string DataJson { get; set; }
|
||||
public string ATags { get; set; }
|
||||
public bool AllowIDFAAccess { get; set; } = true;
|
||||
public bool RequestPermissionOnInit { get; set; }
|
||||
public bool RewardedAutoLoad { get; set; } = true;
|
||||
public bool RewardedPrewarmOnInit { get; set; }
|
||||
public int RewardedMaxLoadAttempts { get; set; } = 1;
|
||||
public int RewardedLoadRetryDelayMs { get; set; } = 500;
|
||||
public int RewardedShowTimeoutMs { get; set; } = 20000;
|
||||
public string RewardName { get; set; } = "reward";
|
||||
public int RewardAmount { get; set; } = 1;
|
||||
public bool InterstitialAutoLoad { get; set; } = true;
|
||||
public bool InterstitialPrewarmOnInit { get; set; }
|
||||
public int InterstitialMaxLoadAttempts { get; set; } = 1;
|
||||
public int InterstitialLoadRetryDelayMs { get; set; } = 500;
|
||||
public int InterstitialShowTimeoutMs { get; set; } = 20000;
|
||||
public bool SplashAutoLoad { get; set; } = true;
|
||||
public bool SplashPrewarmOnInit { get; set; }
|
||||
public int SplashMaxLoadAttempts { get; set; } = 1;
|
||||
public int SplashLoadRetryDelayMs { get; set; } = 500;
|
||||
public int SplashShowTimeoutMs { get; set; } = 20000;
|
||||
public int? ExpressWidth { get; set; }
|
||||
public int? ExpressHeight { get; set; }
|
||||
|
||||
public static TapadnControllerOptions Resolve(ADConfig adConfig, object[] args)
|
||||
{
|
||||
var options = new TapadnControllerOptions();
|
||||
options.ApplyAdConfig(adConfig);
|
||||
options.ApplyLegacyArgs(args);
|
||||
|
||||
if (args == null)
|
||||
{
|
||||
return options;
|
||||
}
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
case TapadnControllerOptions explicitOptions:
|
||||
options.ApplyExplicitOptions(explicitOptions);
|
||||
break;
|
||||
case IDictionary dictionary:
|
||||
options.ApplyDictionary(dictionary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public DirichletAdConfig BuildSdkConfig()
|
||||
{
|
||||
return new DirichletAdConfig.Builder()
|
||||
.WithMediaId(MediaId)
|
||||
.WithMediaName(string.IsNullOrWhiteSpace(MediaName) ? Application.productName : MediaName)
|
||||
.WithMediaKey(MediaKey)
|
||||
.WithGameChannel(string.IsNullOrWhiteSpace(Channel) ? "default" : Channel)
|
||||
.WithSubChannel(SubChannel)
|
||||
.EnableDebug(Debug)
|
||||
.WithTapClientId(TapClientId)
|
||||
.ShakeEnabled(ShakeEnabled)
|
||||
.WithCustomConfigJson(CustomConfigJson)
|
||||
.WithDataJson(DataJson)
|
||||
.AllowIDFAAccess(AllowIDFAAccess)
|
||||
.WithATags(ATags)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void ApplyAdConfig(ADConfig adConfig)
|
||||
{
|
||||
if (adConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MediaId = ParseLong(adConfig.Id) ?? MediaId;
|
||||
MediaKey = string.IsNullOrWhiteSpace(adConfig.Key) ? MediaKey : adConfig.Key.Trim();
|
||||
MediaName = string.IsNullOrWhiteSpace(adConfig.Key2) ? MediaName : adConfig.Key2.Trim();
|
||||
ApplyCommonKeyValues(adConfig.CommonKeyValues);
|
||||
}
|
||||
|
||||
private void ApplyCommonKeyValues(List<AdKeyValue> keyValues)
|
||||
{
|
||||
if (keyValues == null || keyValues.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var keyValue in keyValues)
|
||||
{
|
||||
if (keyValue == null || string.IsNullOrWhiteSpace(keyValue.key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map[keyValue.key.Trim()] = keyValue.value;
|
||||
}
|
||||
|
||||
ApplyMap(map);
|
||||
}
|
||||
|
||||
private void ApplyLegacyArgs(object[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] is string channel)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
if (args.Length > 1 && TryConvertBool(args[1], out var debug))
|
||||
{
|
||||
Debug = debug;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyExplicitOptions(TapadnControllerOptions incoming)
|
||||
{
|
||||
if (incoming == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MediaId = incoming.MediaId > 0 ? incoming.MediaId : MediaId;
|
||||
MediaName = incoming.MediaName ?? MediaName;
|
||||
MediaKey = incoming.MediaKey ?? MediaKey;
|
||||
Channel = incoming.Channel ?? Channel;
|
||||
SubChannel = incoming.SubChannel ?? SubChannel;
|
||||
Debug = incoming.Debug;
|
||||
TapClientId = incoming.TapClientId ?? TapClientId;
|
||||
ShakeEnabled = incoming.ShakeEnabled;
|
||||
CustomConfigJson = incoming.CustomConfigJson ?? CustomConfigJson;
|
||||
DataJson = incoming.DataJson ?? DataJson;
|
||||
ATags = incoming.ATags ?? ATags;
|
||||
AllowIDFAAccess = incoming.AllowIDFAAccess;
|
||||
RequestPermissionOnInit = incoming.RequestPermissionOnInit;
|
||||
RewardedAutoLoad = incoming.RewardedAutoLoad;
|
||||
RewardedPrewarmOnInit = incoming.RewardedPrewarmOnInit;
|
||||
RewardedMaxLoadAttempts = incoming.RewardedMaxLoadAttempts;
|
||||
RewardedLoadRetryDelayMs = incoming.RewardedLoadRetryDelayMs;
|
||||
RewardedShowTimeoutMs = incoming.RewardedShowTimeoutMs;
|
||||
RewardName = incoming.RewardName ?? RewardName;
|
||||
RewardAmount = incoming.RewardAmount;
|
||||
InterstitialAutoLoad = incoming.InterstitialAutoLoad;
|
||||
InterstitialPrewarmOnInit = incoming.InterstitialPrewarmOnInit;
|
||||
InterstitialMaxLoadAttempts = incoming.InterstitialMaxLoadAttempts;
|
||||
InterstitialLoadRetryDelayMs = incoming.InterstitialLoadRetryDelayMs;
|
||||
InterstitialShowTimeoutMs = incoming.InterstitialShowTimeoutMs;
|
||||
SplashAutoLoad = incoming.SplashAutoLoad;
|
||||
SplashPrewarmOnInit = incoming.SplashPrewarmOnInit;
|
||||
SplashMaxLoadAttempts = incoming.SplashMaxLoadAttempts;
|
||||
SplashLoadRetryDelayMs = incoming.SplashLoadRetryDelayMs;
|
||||
SplashShowTimeoutMs = incoming.SplashShowTimeoutMs;
|
||||
ExpressWidth = incoming.ExpressWidth ?? ExpressWidth;
|
||||
ExpressHeight = incoming.ExpressHeight ?? ExpressHeight;
|
||||
}
|
||||
|
||||
private void ApplyDictionary(IDictionary dictionary)
|
||||
{
|
||||
if (dictionary == null || dictionary.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (DictionaryEntry entry in dictionary)
|
||||
{
|
||||
if (entry.Key == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map[entry.Key.ToString()] = ConvertDictionaryValue(entry.Value);
|
||||
}
|
||||
|
||||
ApplyMap(map);
|
||||
}
|
||||
|
||||
private void ApplyMap(IDictionary<string, string> map)
|
||||
{
|
||||
MediaId = GetLong(map, MediaIdKey, "media_id") ?? MediaId;
|
||||
MediaName = GetString(map, MediaNameKey, "media_name") ?? MediaName;
|
||||
MediaKey = GetString(map, MediaKeyKey, "media_key") ?? MediaKey;
|
||||
Channel = GetString(map, ChannelKey, "channel") ?? Channel;
|
||||
SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel;
|
||||
Debug = GetBool(map, DebugKey, "debug") ?? Debug;
|
||||
TapClientId = GetString(map, TapClientIdKey, "tap_client_id") ?? TapClientId;
|
||||
ShakeEnabled = GetBool(map, ShakeEnabledKey) ?? ShakeEnabled;
|
||||
CustomConfigJson = GetString(map, CustomConfigJsonKey) ?? CustomConfigJson;
|
||||
DataJson = GetString(map, DataJsonKey) ?? DataJson;
|
||||
ATags = GetString(map, ATagsKey) ?? ATags;
|
||||
AllowIDFAAccess = GetBool(map, AllowIdfaAccessKey) ?? AllowIDFAAccess;
|
||||
RequestPermissionOnInit = GetBool(map, RequestPermissionOnInitKey) ?? RequestPermissionOnInit;
|
||||
RewardedAutoLoad = GetBool(map, RewardedAutoLoadKey) ?? RewardedAutoLoad;
|
||||
RewardedPrewarmOnInit = GetBool(map, RewardedPrewarmOnInitKey) ?? RewardedPrewarmOnInit;
|
||||
RewardedMaxLoadAttempts = GetInt(map, RewardedMaxLoadAttemptsKey) ?? RewardedMaxLoadAttempts;
|
||||
RewardedLoadRetryDelayMs = GetInt(map, RewardedLoadRetryDelayMsKey) ?? RewardedLoadRetryDelayMs;
|
||||
RewardedShowTimeoutMs = GetInt(map, RewardedShowTimeoutMsKey) ?? RewardedShowTimeoutMs;
|
||||
RewardName = GetString(map, RewardNameKey) ?? RewardName;
|
||||
RewardAmount = GetInt(map, RewardAmountKey) ?? RewardAmount;
|
||||
InterstitialAutoLoad = GetBool(map, InterstitialAutoLoadKey) ?? InterstitialAutoLoad;
|
||||
InterstitialPrewarmOnInit = GetBool(map, InterstitialPrewarmOnInitKey) ?? InterstitialPrewarmOnInit;
|
||||
InterstitialMaxLoadAttempts = GetInt(map, InterstitialMaxLoadAttemptsKey) ?? InterstitialMaxLoadAttempts;
|
||||
InterstitialLoadRetryDelayMs = GetInt(map, InterstitialLoadRetryDelayMsKey) ?? InterstitialLoadRetryDelayMs;
|
||||
InterstitialShowTimeoutMs = GetInt(map, InterstitialShowTimeoutMsKey) ?? InterstitialShowTimeoutMs;
|
||||
SplashAutoLoad = GetBool(map, SplashAutoLoadKey) ?? SplashAutoLoad;
|
||||
SplashPrewarmOnInit = GetBool(map, SplashPrewarmOnInitKey) ?? SplashPrewarmOnInit;
|
||||
SplashMaxLoadAttempts = GetInt(map, SplashMaxLoadAttemptsKey) ?? SplashMaxLoadAttempts;
|
||||
SplashLoadRetryDelayMs = GetInt(map, SplashLoadRetryDelayMsKey) ?? SplashLoadRetryDelayMs;
|
||||
SplashShowTimeoutMs = GetInt(map, SplashShowTimeoutMsKey) ?? SplashShowTimeoutMs;
|
||||
ExpressWidth = GetInt(map, ExpressWidthKey) ?? ExpressWidth;
|
||||
ExpressHeight = GetInt(map, ExpressHeightKey) ?? ExpressHeight;
|
||||
}
|
||||
|
||||
private static string GetString(IDictionary<string, string> map, params string[] keys)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(key) &&
|
||||
map.TryGetValue(key, out var value) &&
|
||||
!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool? GetBool(IDictionary<string, string> map, params string[] keys)
|
||||
{
|
||||
var value = GetString(map, keys);
|
||||
return TryConvertBool(value, out var result) ? result : null;
|
||||
}
|
||||
|
||||
private static int? GetInt(IDictionary<string, string> map, params string[] keys)
|
||||
{
|
||||
var value = GetString(map, keys);
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : (int?)null;
|
||||
}
|
||||
|
||||
private static long? GetLong(IDictionary<string, string> map, params string[] keys)
|
||||
{
|
||||
return ParseLong(GetString(map, keys));
|
||||
}
|
||||
|
||||
private static long? ParseLong(string value)
|
||||
{
|
||||
return long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : (long?)null;
|
||||
}
|
||||
|
||||
private static bool TryConvertBool(object value, out bool result)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case bool boolValue:
|
||||
result = boolValue;
|
||||
return true;
|
||||
case string stringValue:
|
||||
if (bool.TryParse(stringValue, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(stringValue, "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(stringValue, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ConvertDictionaryValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is string stringValue)
|
||||
{
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
if (value is IEnumerable enumerable)
|
||||
{
|
||||
var items = new List<string>();
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
items.Add(item.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(",", items.ToArray());
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb9de9cc991b4649826dc9683c8ec78e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnInteractionPlayer.cs
Normal file
137
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnInteractionPlayer.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class TapadnInteractionPlayer : ADPlayer, IDirichletInterstitialAutoAdListener
|
||||
{
|
||||
private DirichletAdNative _adNative;
|
||||
private DirichletInterstitialAd _loadedAd;
|
||||
|
||||
public override int MaxLoadAttempts => TapadnAdController.CurrentOptions?.InterstitialMaxLoadAttempts ?? base.MaxLoadAttempts;
|
||||
public override float LoadRetryDelaySeconds => Math.Max(0f, (TapadnAdController.CurrentOptions?.InterstitialLoadRetryDelayMs ?? 500) / 1000f);
|
||||
public override float ShowPendingTimeoutSeconds => Math.Max(1f, (TapadnAdController.CurrentOptions?.InterstitialShowTimeoutMs ?? 20000) / 1000f);
|
||||
public override bool AutoPreloadOnInit => TapadnAdController.CurrentOptions?.InterstitialPrewarmOnInit ?? false;
|
||||
|
||||
public override void OnInit()
|
||||
{
|
||||
_adNative = DirichletAdManager.CreateAdNative();
|
||||
}
|
||||
|
||||
public override bool IsReadly()
|
||||
{
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
return TapadnAdRequestFactory.TryParseSlotId(Key, out _);
|
||||
}
|
||||
|
||||
if (_loadedAd != null && _loadedAd.IsLoaded && _loadedAd.IsValid)
|
||||
{
|
||||
curState = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void LoadAD()
|
||||
{
|
||||
if (!TapadnAdRequestFactory.TryParseSlotId(Key, out _))
|
||||
{
|
||||
Debug.LogError($"[TapADN] Invalid interstitial slot id: {Key}");
|
||||
curState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
curState = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
if (curState == 1 || IsReadly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
curState = 1;
|
||||
_adNative.LoadInterstitialAd(
|
||||
TapadnAdRequestFactory.BuildInterstitial(Key, TapadnAdController.CurrentOptions),
|
||||
ad =>
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = ad;
|
||||
_loadedAd.Shown += OnManualShown;
|
||||
_loadedAd.Clicked += OnManualClicked;
|
||||
_loadedAd.Closed += OnManualClosed;
|
||||
curState = 2;
|
||||
Debug.Log($"[TapADN] Interstitial loaded. slot={Key}");
|
||||
},
|
||||
error =>
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Interstitial load failed. code={error.Code}, message={error.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void ShowAD(Action onClose, Action<bool> onVideoComplete)
|
||||
{
|
||||
adListener.onClose = onClose;
|
||||
adListener.onVideoComplete = onVideoComplete;
|
||||
curState = 0;
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
_adNative.ShowInterstitialAutoAd(TapadnAdRequestFactory.BuildInterstitial(Key, TapadnAdController.CurrentOptions), this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_loadedAd == null || !_loadedAd.Show())
|
||||
{
|
||||
adListener.OnShowError();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnError(DirichletError error)
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Interstitial show failed. code={error?.Code}, message={error?.Message}");
|
||||
adListener.OnShowError();
|
||||
}
|
||||
|
||||
public void OnAdShow()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
public void OnAdClose()
|
||||
{
|
||||
adListener.OnAdClose();
|
||||
adListener.OnShowComplete();
|
||||
}
|
||||
|
||||
public void OnAdClick()
|
||||
{
|
||||
}
|
||||
|
||||
private bool UseAutoLoad()
|
||||
{
|
||||
return TapadnAdController.CurrentOptions?.InterstitialAutoLoad ?? true;
|
||||
}
|
||||
|
||||
private void OnManualShown()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
private void OnManualClicked()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnManualClosed()
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = null;
|
||||
OnAdClose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b422dcac50a0451a92815d54329fce8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnSplashPlayer.cs
Normal file
137
Assets/Tapadn_Adapter/Runtime/Scripts/TapadnSplashPlayer.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using Dirichlet.Mediation;
|
||||
using Runtime.ADAggregator;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class TapadnSplashPlayer : ADPlayer, IDirichletSplashAutoAdListener
|
||||
{
|
||||
private DirichletAdNative _adNative;
|
||||
private DirichletSplashAd _loadedAd;
|
||||
|
||||
public override int MaxLoadAttempts => TapadnAdController.CurrentOptions?.SplashMaxLoadAttempts ?? base.MaxLoadAttempts;
|
||||
public override float LoadRetryDelaySeconds => Math.Max(0f, (TapadnAdController.CurrentOptions?.SplashLoadRetryDelayMs ?? 500) / 1000f);
|
||||
public override float ShowPendingTimeoutSeconds => Math.Max(1f, (TapadnAdController.CurrentOptions?.SplashShowTimeoutMs ?? 20000) / 1000f);
|
||||
public override bool AutoPreloadOnInit => TapadnAdController.CurrentOptions?.SplashPrewarmOnInit ?? false;
|
||||
|
||||
public override void OnInit()
|
||||
{
|
||||
_adNative = DirichletAdManager.CreateAdNative();
|
||||
}
|
||||
|
||||
public override bool IsReadly()
|
||||
{
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
return TapadnAdRequestFactory.TryParseSlotId(Key, out _);
|
||||
}
|
||||
|
||||
if (_loadedAd != null && _loadedAd.IsLoaded && _loadedAd.IsValid)
|
||||
{
|
||||
curState = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void LoadAD()
|
||||
{
|
||||
if (!TapadnAdRequestFactory.TryParseSlotId(Key, out _))
|
||||
{
|
||||
Debug.LogError($"[TapADN] Invalid splash slot id: {Key}");
|
||||
curState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
curState = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
if (curState == 1 || IsReadly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
curState = 1;
|
||||
_adNative.LoadSplashAd(
|
||||
TapadnAdRequestFactory.BuildSplash(Key, TapadnAdController.CurrentOptions),
|
||||
ad =>
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = ad;
|
||||
_loadedAd.Shown += OnManualShown;
|
||||
_loadedAd.Clicked += OnManualClicked;
|
||||
_loadedAd.Closed += OnManualClosed;
|
||||
curState = 2;
|
||||
Debug.Log($"[TapADN] Splash loaded. slot={Key}");
|
||||
},
|
||||
error =>
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Splash load failed. code={error.Code}, message={error.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void ShowAD(Action onClose, Action<bool> onVideoComplete)
|
||||
{
|
||||
adListener.onClose = onClose;
|
||||
adListener.onVideoComplete = onVideoComplete;
|
||||
curState = 0;
|
||||
|
||||
if (UseAutoLoad())
|
||||
{
|
||||
_adNative.ShowSplashAutoAd(TapadnAdRequestFactory.BuildSplash(Key, TapadnAdController.CurrentOptions), this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_loadedAd == null || !_loadedAd.Show())
|
||||
{
|
||||
adListener.OnShowError();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnError(DirichletError error)
|
||||
{
|
||||
curState = 0;
|
||||
Debug.LogError($"[TapADN] Splash show failed. code={error?.Code}, message={error?.Message}");
|
||||
adListener.OnShowError();
|
||||
}
|
||||
|
||||
public void OnAdShow()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
public void OnAdClose()
|
||||
{
|
||||
adListener.OnAdClose();
|
||||
adListener.OnShowComplete();
|
||||
}
|
||||
|
||||
public void OnAdClick()
|
||||
{
|
||||
}
|
||||
|
||||
private bool UseAutoLoad()
|
||||
{
|
||||
return TapadnAdController.CurrentOptions?.SplashAutoLoad ?? true;
|
||||
}
|
||||
|
||||
private void OnManualShown()
|
||||
{
|
||||
NotifyShowStarted();
|
||||
}
|
||||
|
||||
private void OnManualClicked()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnManualClosed()
|
||||
{
|
||||
_loadedAd?.Destroy();
|
||||
_loadedAd = null;
|
||||
OnAdClose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 372f8c753ddd48d9a9e08012999a05f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/package.json
Normal file
33
Assets/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "com.commercialization.tapadn",
|
||||
"displayName": "Commercialization.tapadn",
|
||||
"description": "TapADN / Dirichlet mediation implementation for CC-Framework.Commercialization.",
|
||||
"version": "1.0.0",
|
||||
"unity": "2021.1",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://private.lightyears.ltd:18650/foldcc/Commercialization.tapadn"
|
||||
},
|
||||
"author": {
|
||||
"name": "foldcc",
|
||||
"email": "lhyuau@qq.com",
|
||||
"url": "https://gitee.com/foldcc"
|
||||
},
|
||||
"dependencies": {
|
||||
"com.foldcc.cc-framework.commercialization": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.Commercialization.git#1.0.14"
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "IAA Ad Debug Sample",
|
||||
"description": "Optional debug scene/config helper for validating TapADN rewarded, interstitial, and splash flows.",
|
||||
"path": "Samples~/IAAAdDebugSample"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"Framework",
|
||||
"Commercialization",
|
||||
"TapADN",
|
||||
"Dirichlet"
|
||||
]
|
||||
}
|
||||
7
Assets/package.json.meta
Normal file
7
Assets/package.json.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3f50282dea34d8cbc525c5becdd3b91
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
GLOBAL_DESIGN.md
Normal file
107
GLOBAL_DESIGN.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Commercialization.tapadn 全局设计
|
||||
|
||||
## 目标
|
||||
|
||||
本模块把 TapADN / Dirichlet 聚合广告 SDK 接到 `CC-Framework.Commercialization` 抽象层后面,让业务项目继续只关心 `ADManager` 和 `ADConfig`。
|
||||
|
||||
当前实现对齐 `Commercialization.topon` 的分层:
|
||||
|
||||
* `CC-Framework.Commercialization`: `ADManager`、`IAdController`、`ADPlayer` 抽象。
|
||||
* `Commercialization.tapadn`: 平台 controller、广告播放器、构建自动化。
|
||||
* `DirichletMediation`: 官方 SDK 和平台桥。
|
||||
|
||||
## 目录
|
||||
|
||||
* `Assets/DirichletMediation`: 官方聚合 Unity SDK `4.2.5.0`,已删除官方 Sample。
|
||||
* `Assets/Plugins/Android`: 官方 Android AAR、Manifest、ProGuard、本地微信 OpenSDK AAR。
|
||||
* `Assets/Plugins/iOS`: 官方 iOS bridge。
|
||||
* `Assets/Tapadn_Adapter/Runtime/Scripts`: 商业化抽象层适配。
|
||||
* `Assets/Tapadn_Adapter/Editor`: Android 构建后处理和依赖声明。
|
||||
* `Assets/Samples~`: 可选调试样例预留,不随主包自动进入业务项目。
|
||||
|
||||
## Runtime 设计
|
||||
|
||||
`TapadnAdController` 实现 `IAdController`:
|
||||
|
||||
* 从 `ADConfig` 和可选 `args` 解析 `TapadnControllerOptions`。
|
||||
* 构造 `DirichletAdConfig` 并调用 `DirichletSdk.Init`。
|
||||
* 根据 `AD_Type` 创建 `TapadnAwardVideoPlayer`、`TapadnInteractionPlayer`、`TapadnSplashPlayer`。
|
||||
|
||||
`TapadnCommercialization` 是便捷入口:
|
||||
|
||||
* `CreateController()` 隐藏 controller 创建细节。
|
||||
* `InitADManager(...)` 由模块负责创建 controller 并交给 `ADManager`。
|
||||
* `CreateConfig(...)` 用代码生成临时 `ADConfig`,适合游戏项目不想维护 TapADN SDK 细节时使用。
|
||||
|
||||
## 广告播放器
|
||||
|
||||
默认方案:使用 TapADN auto-ad 接口。
|
||||
|
||||
原因:
|
||||
|
||||
* 官方 Unity 文档推荐 Android auto-ad,接口把加载和展示合并,SDK 自己管理缓存。
|
||||
* `ADManager.AsyncAdPlayer` 已有遮罩、超时和回调收口;auto-ad 可以让业务侧首次播放少一个 preload 时序。
|
||||
|
||||
备选方案:手动 load/show。
|
||||
|
||||
* 已通过 `tapadn.rewarded_auto_load=false` 等 key 保留。
|
||||
* 手动模式保存 SDK 返回的 ad handle,并在 `Closed` 时销毁。
|
||||
|
||||
不确定点:
|
||||
|
||||
* 官方文档说明 auto-ad 目前主要是 Android 能力,iOS auto-ad 会返回 `not_supported`。本模块保留手动 fallback,但真正 iOS 出包前需要用 TapADN iOS 账号和广告位做真机验证。
|
||||
* `AD_Type` 抽象层没有 Banner 类型,所以本轮没有将 TapADN Banner 暴露到 `ADManager`。如果抽象层后续新增 Banner,可以直接复用官方 `ShowBannerAutoAd`。
|
||||
|
||||
## 配置设计
|
||||
|
||||
基础字段复用 `ADConfig`:
|
||||
|
||||
* `Id`: MediaId。
|
||||
* `Key`: MediaKey。
|
||||
* `Key2`: MediaName。
|
||||
* `BaseAwardAdKeyValue.value`: 激励视频 SpaceId。
|
||||
* `BaseInteractionAdKeyValue.value`: 插屏 SpaceId。
|
||||
* `BaseSplashAdKeyValue.value`: 开屏 SpaceId。
|
||||
|
||||
高级配置通过 `CommonKeyValues` 或字典传入,使用 `tapadn.*` 前缀,避免和 TopOn 现有 key 冲突。
|
||||
|
||||
## Android 构建设计
|
||||
|
||||
保留官方 `DirichletGradlePostProcessor`,它负责 Dirichlet AAR 和 Maven 依赖注入。
|
||||
|
||||
新增 `TapadnBuildAndroidProcess`,只负责本模块集成层额外需求:
|
||||
|
||||
* TapADN Manifest 权限补齐。
|
||||
* `com.tapsdk.tapad.internal.TapADFileProvider` 和 `@xml/tapad_ad_file_path`。
|
||||
* 微信 OpenSDK 的 `com.tencent.mm` queries 与 `.wxapi.WXEntryActivity`。
|
||||
* AndroidX / Jetifier 开关。
|
||||
|
||||
方案选择记录:
|
||||
|
||||
* 方案 A:只放 `WXDependencies.xml`,依赖宿主 EDM4U 下载微信 SDK。风险是业务项目没有 EDM4U 或未 resolve 时构建失败。
|
||||
* 方案 B:只放本地微信 AAR。风险是宿主 EDM4U 依赖图不可见。
|
||||
* 当前选择:本地 AAR + `WXDependencies.xml` 同时保留。这样最接近 TopOn 当前工程,也能覆盖无 EDM4U 的构建场景。
|
||||
|
||||
## 编辑器可见性
|
||||
|
||||
本模块不提供默认可见面板。构建自动化通过 `IPostGenerateGradleAndroidProject` 静默执行;调试能力放入 `Samples~`,由业务项目显式导入。
|
||||
|
||||
若后续需要可视化配置面板,应使用宏包裹菜单入口,例如 `COMMERCIALIZATION_TAPADN_DEBUG_MENU`,默认不在业务项目菜单里出现。
|
||||
|
||||
## UPM 与本机验证约定
|
||||
|
||||
发布包入口是 `Assets/package.json`,其中 `com.foldcc.cc-framework.commercialization` 依赖保持为远程 Git URL:
|
||||
|
||||
```json
|
||||
"com.foldcc.cc-framework.commercialization": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.Commercialization.git#1.0.14"
|
||||
```
|
||||
|
||||
当前仓库自身作为 Unity 验证工程时,可以在 `Packages/manifest.json` 使用本地 `file:` 引用:
|
||||
|
||||
```json
|
||||
"com.foldcc.cc-framework.commercialization": "file:CC-Framework.Commercialization/Assets"
|
||||
```
|
||||
|
||||
该路径以 `Packages/manifest.json` 所在目录为基准,符合 Unity 本地 UPM package 规则;`Packages/CC-Framework.Commercialization` 只是本机验证副本,已被 `.gitignore` 排除,不进入发布包。
|
||||
|
||||
如果 batchmode 出现 `Failed to resolve packages: The "path" argument must be of type string. Received undefined`,不把它直接归因为 manifest 路径错误,也不使用 `-noUpm` 绕过。处理顺序是保存 Unity Editor log 和 `%LOCALAPPDATA%\Unity\Editor\upm.log`,确认没有并发 Unity/UPM 进程,再用官方 `Unity.exe -batchmode -quit -projectPath <project> -logFile <log>` 做一次只解析 package 的验证;若仍复现,则用 Unity Hub/已打开 Editor 作为 GUI 对照入口继续看 Console 编译错误。
|
||||
45
Packages/manifest.json
Normal file
45
Packages/manifest.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.foldcc.cc-framework.commercialization": "file:CC-Framework.Commercialization/Assets",
|
||||
"com.unity.collab-proxy": "2.12.4",
|
||||
"com.unity.feature.2d": "2.0.1",
|
||||
"com.unity.ide.rider": "3.0.36",
|
||||
"com.unity.ide.visualstudio": "2.0.22",
|
||||
"com.unity.test-framework": "1.1.33",
|
||||
"com.unity.textmeshpro": "3.0.7",
|
||||
"com.unity.timeline": "1.7.7",
|
||||
"com.unity.ugui": "1.0.0",
|
||||
"com.unity.visualscripting": "1.9.4",
|
||||
"com.unity.modules.ai": "1.0.0",
|
||||
"com.unity.modules.androidjni": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.cloth": "1.0.0",
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0",
|
||||
"com.unity.modules.imgui": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.physics2d": "1.0.0",
|
||||
"com.unity.modules.screencapture": "1.0.0",
|
||||
"com.unity.modules.terrain": "1.0.0",
|
||||
"com.unity.modules.terrainphysics": "1.0.0",
|
||||
"com.unity.modules.tilemap": "1.0.0",
|
||||
"com.unity.modules.ui": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0",
|
||||
"com.unity.modules.umbra": "1.0.0",
|
||||
"com.unity.modules.unityanalytics": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestaudio": "1.0.0",
|
||||
"com.unity.modules.unitywebrequesttexture": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestwww": "1.0.0",
|
||||
"com.unity.modules.vehicles": "1.0.0",
|
||||
"com.unity.modules.video": "1.0.0",
|
||||
"com.unity.modules.vr": "1.0.0",
|
||||
"com.unity.modules.wind": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0"
|
||||
}
|
||||
}
|
||||
19
ProjectSettings/AudioManager.asset
Normal file
19
ProjectSettings/AudioManager.asset
Normal file
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!11 &1
|
||||
AudioManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Volume: 1
|
||||
Rolloff Scale: 1
|
||||
Doppler Factor: 1
|
||||
Default Speaker Mode: 2
|
||||
m_SampleRate: 0
|
||||
m_DSPBufferSize: 1024
|
||||
m_VirtualVoiceCount: 512
|
||||
m_RealVoiceCount: 32
|
||||
m_SpatializerPlugin:
|
||||
m_AmbisonicDecoderPlugin:
|
||||
m_DisableAudio: 0
|
||||
m_VirtualizeEffects: 1
|
||||
m_RequestedDSPBufferSize: 0
|
||||
6
ProjectSettings/ClusterInputManager.asset
Normal file
6
ProjectSettings/ClusterInputManager.asset
Normal file
@@ -0,0 +1,6 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!236 &1
|
||||
ClusterInputManager:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Inputs: []
|
||||
37
ProjectSettings/DynamicsManager.asset
Normal file
37
ProjectSettings/DynamicsManager.asset
Normal file
@@ -0,0 +1,37 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!55 &1
|
||||
PhysicsManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_Gravity: {x: 0, y: -9.81, z: 0}
|
||||
m_DefaultMaterial: {fileID: 0}
|
||||
m_BounceThreshold: 2
|
||||
m_DefaultMaxDepenetrationVelocity: 10
|
||||
m_SleepThreshold: 0.005
|
||||
m_DefaultContactOffset: 0.01
|
||||
m_DefaultSolverIterations: 6
|
||||
m_DefaultSolverVelocityIterations: 1
|
||||
m_QueriesHitBackfaces: 0
|
||||
m_QueriesHitTriggers: 1
|
||||
m_EnableAdaptiveForce: 0
|
||||
m_ClothInterCollisionDistance: 0.1
|
||||
m_ClothInterCollisionStiffness: 0.2
|
||||
m_ContactsGeneration: 1
|
||||
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_AutoSimulation: 1
|
||||
m_AutoSyncTransforms: 0
|
||||
m_ReuseCollisionCallbacks: 1
|
||||
m_ClothInterCollisionSettingsToggle: 0
|
||||
m_ClothGravity: {x: 0, y: -9.81, z: 0}
|
||||
m_ContactPairsMode: 0
|
||||
m_BroadphaseType: 0
|
||||
m_WorldBounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 250, y: 250, z: 250}
|
||||
m_WorldSubdivisions: 8
|
||||
m_FrictionType: 0
|
||||
m_EnableEnhancedDeterminism: 0
|
||||
m_EnableUnifiedHeightmaps: 1
|
||||
m_SolverType: 0
|
||||
m_DefaultMaxAngularSpeed: 50
|
||||
11
ProjectSettings/EditorBuildSettings.asset
Normal file
11
ProjectSettings/EditorBuildSettings.asset
Normal file
@@ -0,0 +1,11 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1045 &1
|
||||
EditorBuildSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Scenes:
|
||||
- enabled: 1
|
||||
path: Assets/Scenes/SampleScene.unity
|
||||
guid: 2cda990e2423bbf4892e6590ba056729
|
||||
m_configObjects: {}
|
||||
40
ProjectSettings/EditorSettings.asset
Normal file
40
ProjectSettings/EditorSettings.asset
Normal file
@@ -0,0 +1,40 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!159 &1
|
||||
EditorSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 11
|
||||
m_SerializationMode: 2
|
||||
m_LineEndingsForNewScripts: 0
|
||||
m_DefaultBehaviorMode: 1
|
||||
m_PrefabRegularEnvironment: {fileID: 0}
|
||||
m_PrefabUIEnvironment: {fileID: 0}
|
||||
m_SpritePackerMode: 5
|
||||
m_SpritePackerPaddingPower: 1
|
||||
m_EtcTextureCompressorBehavior: 1
|
||||
m_EtcTextureFastCompressor: 1
|
||||
m_EtcTextureNormalCompressor: 2
|
||||
m_EtcTextureBestCompressor: 4
|
||||
m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;asmref;rsp
|
||||
m_ProjectGenerationRootNamespace:
|
||||
m_EnableTextureStreamingInEditMode: 1
|
||||
m_EnableTextureStreamingInPlayMode: 1
|
||||
m_AsyncShaderCompilation: 1
|
||||
m_CachingShaderPreprocessor: 1
|
||||
m_PrefabModeAllowAutoSave: 1
|
||||
m_EnterPlayModeOptionsEnabled: 0
|
||||
m_EnterPlayModeOptions: 3
|
||||
m_GameObjectNamingDigits: 1
|
||||
m_GameObjectNamingScheme: 0
|
||||
m_AssetNamingUsesSpace: 1
|
||||
m_UseLegacyProbeSampleCount: 0
|
||||
m_SerializeInlineMappingsOnOneLine: 1
|
||||
m_DisableCookiesInLightmapper: 1
|
||||
m_AssetPipelineMode: 1
|
||||
m_CacheServerMode: 0
|
||||
m_CacheServerEndpoint:
|
||||
m_CacheServerNamespacePrefix: default
|
||||
m_CacheServerEnableDownload: 1
|
||||
m_CacheServerEnableUpload: 1
|
||||
m_CacheServerEnableAuth: 0
|
||||
m_CacheServerEnableTls: 0
|
||||
64
ProjectSettings/GraphicsSettings.asset
Normal file
64
ProjectSettings/GraphicsSettings.asset
Normal file
@@ -0,0 +1,64 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!30 &1
|
||||
GraphicsSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_Deferred:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_DeferredReflections:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_ScreenSpaceShadows:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LegacyDeferred:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_DepthNormals:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_MotionVectors:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightHalo:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LensFlare:
|
||||
m_Mode: 1
|
||||
m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_VideoShadersIncludeMode: 2
|
||||
m_AlwaysIncludedShaders:
|
||||
- {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_PreloadedShaders: []
|
||||
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_CustomRenderPipeline: {fileID: 0}
|
||||
m_TransparencySortMode: 0
|
||||
m_TransparencySortAxis: {x: 0, y: 0, z: 1}
|
||||
m_DefaultRenderingPath: 1
|
||||
m_DefaultMobileRenderingPath: 1
|
||||
m_TierSettings: []
|
||||
m_LightmapStripping: 0
|
||||
m_FogStripping: 0
|
||||
m_InstancingStripping: 0
|
||||
m_LightmapKeepPlain: 1
|
||||
m_LightmapKeepDirCombined: 1
|
||||
m_LightmapKeepDynamicPlain: 1
|
||||
m_LightmapKeepDynamicDirCombined: 1
|
||||
m_LightmapKeepShadowMask: 1
|
||||
m_LightmapKeepSubtractive: 1
|
||||
m_FogKeepLinear: 1
|
||||
m_FogKeepExp: 1
|
||||
m_FogKeepExp2: 1
|
||||
m_AlbedoSwatchInfos: []
|
||||
m_LightsUseLinearIntensity: 0
|
||||
m_LightsUseColorTemperature: 0
|
||||
m_DefaultRenderingLayerMask: 1
|
||||
m_LogWhenShaderIsCompiled: 0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user