update
This commit is contained in:
8
Assets/TapSDK/Core/Runtime/Internal.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d7b48bb0f4b64656a6b2bdba97e0058
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Http.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Http.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b623eff04d7c84163a2ab4387aa8206e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
177
Assets/TapSDK/Core/Runtime/Internal/Http/TapHttpClient.cs
Normal file
177
Assets/TapSDK/Core/Runtime/Internal/Http/TapHttpClient.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using TapSDK.Core.Internal.Json;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
|
||||
namespace TapSDK.Core.Internal.Http {
|
||||
public class TapHttpClient {
|
||||
private readonly string clientId;
|
||||
|
||||
private readonly string clientToken;
|
||||
|
||||
private readonly string serverUrl;
|
||||
|
||||
readonly HttpClient client;
|
||||
|
||||
private Dictionary<string, Func<Task<string>>> runtimeHeaderTasks = new Dictionary<string, Func<Task<string>>>();
|
||||
|
||||
private Dictionary<string, string> additionalHeaders = new Dictionary<string, string>();
|
||||
|
||||
public TapHttpClient(string clientID, string clientToken, string serverUrl) {
|
||||
this.clientId = clientID;
|
||||
this.clientToken = clientToken;
|
||||
this.serverUrl = serverUrl;
|
||||
|
||||
client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
client.DefaultRequestHeaders.Add("X-LC-Id", clientID);
|
||||
}
|
||||
|
||||
public void AddRuntimeHeaderTask(string key, Func<Task<string>> task) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
runtimeHeaderTasks[key] = task;
|
||||
}
|
||||
|
||||
public void AddAddtionalHeader(string key, string value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
additionalHeaders[key] = value;
|
||||
}
|
||||
|
||||
public Task<T> Get<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
Dictionary<string, object> queryParams = null,
|
||||
bool withAPIVersion = true) {
|
||||
return Request<T>(path, HttpMethod.Get, headers, null, queryParams, withAPIVersion);
|
||||
}
|
||||
|
||||
public Task<T> Post<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
object data = null,
|
||||
Dictionary<string, object> queryParams = null,
|
||||
bool withAPIVersion = true) {
|
||||
return Request<T>(path, HttpMethod.Post, headers, data, queryParams, withAPIVersion);
|
||||
}
|
||||
|
||||
public Task<T> Put<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
object data = null,
|
||||
Dictionary<string, object> queryParams = null,
|
||||
bool withAPIVersion = true) {
|
||||
return Request<T>(path, HttpMethod.Put, headers, data, queryParams, withAPIVersion);
|
||||
}
|
||||
|
||||
public Task Delete(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
object data = null,
|
||||
Dictionary<string, object> queryParams = null,
|
||||
bool withAPIVersion = true) {
|
||||
return Request<Dictionary<string, object>>(path, HttpMethod.Delete, headers, data, queryParams, withAPIVersion);
|
||||
}
|
||||
|
||||
async Task<T> Request<T>(string path,
|
||||
HttpMethod method,
|
||||
Dictionary<string, object> headers = null,
|
||||
object data = null,
|
||||
Dictionary<string, object> queryParams = null,
|
||||
bool withAPIVersion = true) {
|
||||
string url = BuildUrl(path, queryParams);
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = method,
|
||||
};
|
||||
await FillHeaders(request.Headers, headers);
|
||||
|
||||
string content = null;
|
||||
if (data != null) {
|
||||
content = JsonConvert.SerializeObject(data);
|
||||
StringContent requestContent = new StringContent(content);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
request.Content = requestContent;
|
||||
}
|
||||
TapHttpUtils.PrintRequest(client, request, content);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
TapHttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
T ret = JsonConvert.DeserializeObject<T>(resultString,
|
||||
TapJsonConverter.Default);
|
||||
return ret;
|
||||
}
|
||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||
}
|
||||
|
||||
TapException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) {
|
||||
int code = (int)statusCode;
|
||||
string message = responseContent;
|
||||
try {
|
||||
// 尝试获取 LeanCloud 返回错误信息
|
||||
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent,
|
||||
TapJsonConverter.Default);
|
||||
code = (int)error["code"];
|
||||
message = error["error"].ToString();
|
||||
} catch (Exception e) {
|
||||
TapLog.Error(e.Message ?? "");
|
||||
}
|
||||
return new TapException(code, message);
|
||||
}
|
||||
|
||||
string BuildUrl(string path, Dictionary<string, object> queryParams) {
|
||||
string apiServer = serverUrl;
|
||||
StringBuilder urlSB = new StringBuilder(apiServer.TrimEnd('/'));
|
||||
urlSB.Append($"/{path}");
|
||||
string url = urlSB.ToString();
|
||||
if (queryParams != null) {
|
||||
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
|
||||
string queries = string.Join("&", queryPairs);
|
||||
url = $"{url}?{queries}";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async Task FillHeaders(HttpRequestHeaders headers, Dictionary<string, object> reqHeaders = null) {
|
||||
// 额外 headers
|
||||
if (reqHeaders != null) {
|
||||
foreach (KeyValuePair<string, object> kv in reqHeaders) {
|
||||
headers.Add(kv.Key, kv.Value.ToString());
|
||||
}
|
||||
}
|
||||
if (additionalHeaders.Count > 0) {
|
||||
foreach (KeyValuePair<string, string> kv in additionalHeaders) {
|
||||
headers.Add(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
// 服务额外 headers
|
||||
foreach (KeyValuePair<string, Func<Task<string>>> kv in runtimeHeaderTasks) {
|
||||
if (headers.Contains(kv.Key)) {
|
||||
continue;
|
||||
}
|
||||
string value = await kv.Value.Invoke();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
headers.Add(kv.Key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7886abbfa16f547209a283a98e0b2895
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Assets/TapSDK/Core/Runtime/Internal/Http/TapHttpUtils.cs
Normal file
84
Assets/TapSDK/Core/Runtime/Internal/Http/TapHttpUtils.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net.Http;
|
||||
using UnityEngine;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
|
||||
namespace TapSDK.Core.Internal.Http {
|
||||
public class TapHttpUtils {
|
||||
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
||||
if (TapLogger.LogDelegate == null) {
|
||||
return;
|
||||
}
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("=== HTTP Request Start ===");
|
||||
sb.AppendLine($"URL: {request.RequestUri}");
|
||||
sb.AppendLine($"Method: {request.Method}");
|
||||
sb.AppendLine($"Headers: ");
|
||||
foreach (var header in client.DefaultRequestHeaders) {
|
||||
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
|
||||
}
|
||||
foreach (var header in request.Headers) {
|
||||
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
|
||||
}
|
||||
if (request.Content != null) {
|
||||
foreach (var header in request.Content.Headers) {
|
||||
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(content)) {
|
||||
sb.AppendLine($"Content: {content}");
|
||||
}
|
||||
sb.AppendLine("=== HTTP Request End ===");
|
||||
TapLog.Log(sb.ToString());
|
||||
}
|
||||
|
||||
public static void PrintResponse(HttpResponseMessage response, string content = null) {
|
||||
if (TapLogger.LogDelegate == null) {
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("=== HTTP Response Start ===");
|
||||
sb.AppendLine($"URL: {response.RequestMessage.RequestUri}");
|
||||
sb.AppendLine($"Status Code: {response.StatusCode}");
|
||||
if (!string.IsNullOrEmpty(content)) {
|
||||
sb.AppendLine($"Content: {content}");
|
||||
}
|
||||
sb.AppendLine("=== HTTP Response End ===");
|
||||
TapLog.Log(sb.ToString());
|
||||
}
|
||||
|
||||
public static NameValueCollection ParseQueryString(string queryString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(queryString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (queryString.Length > 0 && queryString[0] == '?') {
|
||||
queryString = queryString.Substring(1);
|
||||
}
|
||||
NameValueCollection queryParameters = new NameValueCollection();
|
||||
string[] querySegments = queryString.Split('&');
|
||||
foreach(string segment in querySegments)
|
||||
{
|
||||
string[] parts = segment.Split('=');
|
||||
if (parts.Length > 0)
|
||||
{
|
||||
string key = parts[0].Trim(new char[] { '?', ' ' });
|
||||
string val = parts[1].Trim();
|
||||
|
||||
queryParameters.Add(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
return queryParameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c16aefb23bd7494d83c246505380cab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Init.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Init.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d8e48cb41106433ba4f262639977f77
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/TapSDK/Core/Runtime/Internal/Init/IInitTask.cs
Normal file
18
Assets/TapSDK/Core/Runtime/Internal/Init/IInitTask.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace TapSDK.Core.Internal.Init {
|
||||
public interface IInitTask {
|
||||
/// <summary>
|
||||
/// 初始化顺序
|
||||
/// </summary>
|
||||
int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions);
|
||||
|
||||
void Init(TapTapSdkOptions coreOption);
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/Init/IInitTask.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/Init/IInitTask.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d28a8b38a6fd48249e82ff24e528080
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Json.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Json.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91e47674c73b246fdbda4fb286209bd3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/TapSDK/Core/Runtime/Internal/Json/TapJsonConverter.cs
Normal file
40
Assets/TapSDK/Core/Runtime/Internal/Json/TapJsonConverter.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TapSDK.Core.Internal.Json {
|
||||
public class TapJsonConverter : JsonConverter {
|
||||
public override bool CanConvert(Type objectType) {
|
||||
return objectType == typeof(object);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||
serializer.Serialize(writer, value);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
||||
if (reader.TokenType == JsonToken.StartObject) {
|
||||
var obj = new Dictionary<string, object>();
|
||||
serializer.Populate(reader, obj);
|
||||
return obj;
|
||||
}
|
||||
if (reader.TokenType == JsonToken.StartArray) {
|
||||
var arr = new List<object>();
|
||||
serializer.Populate(reader, arr);
|
||||
return arr;
|
||||
}
|
||||
if (reader.TokenType == JsonToken.Integer) {
|
||||
if ((long)reader.Value < int.MaxValue) {
|
||||
return Convert.ToInt32(reader.Value);
|
||||
}
|
||||
}
|
||||
if (reader.TokenType == JsonToken.Float) {
|
||||
return Convert.ToSingle(reader.Value);
|
||||
}
|
||||
|
||||
return serializer.Deserialize(reader);
|
||||
}
|
||||
|
||||
public readonly static TapJsonConverter Default = new TapJsonConverter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e7910a9ac70c45509b1520d9d614ce0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Log.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Log.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee253dc23c74a430e91bd9791fb9043d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
150
Assets/TapSDK/Core/Runtime/Internal/Log/TapLog.cs
Normal file
150
Assets/TapSDK/Core/Runtime/Internal/Log/TapLog.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.Core.Internal.Log
|
||||
{
|
||||
public class TapLog
|
||||
{
|
||||
private const string TAG = "TapSDK";
|
||||
// 颜色常量
|
||||
private const string InfoColor = "#FFFFFF"; // 白色
|
||||
private const string WarningColor = "#FFFF00"; // 黄色
|
||||
private const string ErrorColor = "#FF0000"; // 红色
|
||||
private const string MainThreadColor = "#00FF00"; // 绿色
|
||||
private const string IOThreadColor = "#FF00FF"; // 紫色
|
||||
private const string TagColor = "#00FFFF"; // 青色
|
||||
|
||||
// 开关变量,控制是否启用日志输出
|
||||
public static bool Enabled = false;
|
||||
|
||||
private string module;
|
||||
private string tag;
|
||||
|
||||
public TapLog(string module, string tag = TAG)
|
||||
{
|
||||
this.tag = tag;
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
public void Log(string message, string detail = null)
|
||||
{
|
||||
TapLog.Log(message, detail, tag, module);
|
||||
}
|
||||
|
||||
// 输出带有自定义颜色和标签的警告
|
||||
public void Warning(string message, string detail = null)
|
||||
{
|
||||
TapLog.Warning(message, detail, tag, module);
|
||||
}
|
||||
|
||||
// 输出带有自定义颜色和标签的错误
|
||||
public void Error(string message, string detail = null)
|
||||
{
|
||||
TapLog.Error(message, detail, tag, module);
|
||||
}
|
||||
|
||||
public static void Error(Exception e)
|
||||
{
|
||||
TapLog.Error(e?.Message ?? "");
|
||||
}
|
||||
|
||||
// 输出带有自定义颜色和标签的普通日志
|
||||
public static void Log(string message, string detail = null, string tag = TAG, string module = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string msg = GetFormattedMessage(message: message, detail: detail, colorHex: InfoColor, tag: tag, module: module);
|
||||
if (TapLogger.LogDelegate != null)
|
||||
{
|
||||
TapLogger.Debug(msg);
|
||||
return;
|
||||
}
|
||||
if (Enabled)
|
||||
{
|
||||
Debug.Log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出带有自定义颜色和标签的警告
|
||||
public static void Warning(string message, string detail = null, string tag = TAG, string module = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string msg = GetFormattedMessage(message: message, detail: detail, colorHex: WarningColor, tag: tag, module: module);
|
||||
if (TapLogger.LogDelegate != null)
|
||||
{
|
||||
TapLogger.Warn(msg);
|
||||
return;
|
||||
}
|
||||
if (Enabled)
|
||||
{
|
||||
Debug.LogWarning(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出带有自定义颜色和标签的错误
|
||||
public static void Error(string message, string detail = null, string tag = TAG, string module = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string msg = GetFormattedMessage(message: message, detail: detail, colorHex: ErrorColor, tag: tag, module: module);
|
||||
if (TapLogger.LogDelegate != null)
|
||||
{
|
||||
TapLogger.Error(msg);
|
||||
return;
|
||||
}
|
||||
Debug.LogError(msg);
|
||||
}
|
||||
|
||||
// 格式化带有颜色和标签的消息
|
||||
private static string GetFormattedMessage(string message, string detail, string colorHex, string tag, string module)
|
||||
{
|
||||
string threadInfo = GetThreadInfo();
|
||||
string tagColor = TagColor;
|
||||
if (module != null && module != "")
|
||||
{
|
||||
tag = $"{tag}.{module}";
|
||||
}
|
||||
if (IsMobilePlatform())
|
||||
{
|
||||
return $"[{tag}] {threadInfo} {message}\n{detail}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"<color={tagColor}>[{tag}]</color> {threadInfo} <color={colorHex}>{message}</color>\n{detail}\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 获取当前线程信息
|
||||
private static string GetThreadInfo()
|
||||
{
|
||||
bool isMainThread = System.Threading.Thread.CurrentThread.IsAlive && System.Threading.Thread.CurrentThread.ManagedThreadId == 1;
|
||||
string threadInfo = isMainThread ? "Main" : $"IO {System.Threading.Thread.CurrentThread.ManagedThreadId}";
|
||||
|
||||
if (IsMobilePlatform())
|
||||
{
|
||||
// 移动平台的线程信息不使用颜色
|
||||
return $"({threadInfo})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他平台的线程信息使用颜色
|
||||
string color = isMainThread ? MainThreadColor : IOThreadColor;
|
||||
return $"<color={color}>({threadInfo})</color>";
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是移动平台
|
||||
private static bool IsMobilePlatform()
|
||||
{
|
||||
return Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/Log/TapLog.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/Log/TapLog.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9613fc51bb6646d89b77a0fde4d4b40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Platform.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Platform.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93f336f19f5594d78bf1cd3f6ca3e876
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TapSDK.Core.Internal
|
||||
{
|
||||
public interface ITapCorePlatform
|
||||
{
|
||||
void Init(TapTapSdkOptions config);
|
||||
|
||||
void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions);
|
||||
|
||||
void UpdateLanguage(TapTapLanguageType language);
|
||||
|
||||
Task<bool> IsLaunchedFromTapTapPC();
|
||||
|
||||
void SendOpenLog(
|
||||
string project,
|
||||
string version,
|
||||
string action,
|
||||
Dictionary<string, string> properties
|
||||
);
|
||||
|
||||
#if UNITY_STANDALONE_WIN
|
||||
void RegisterTapTapPCStateChangeListener(Action<int> action);
|
||||
|
||||
void UnRegisterTapTapPCStateChangeListener(Action<int> action);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 693f034729dd341468d896eecf035ed2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace TapSDK.Core.Internal {
|
||||
public interface ITapEventPlatform {
|
||||
|
||||
void Init(TapTapEventOptions eventOptions);
|
||||
|
||||
void SetUserID(string userID);
|
||||
|
||||
void SetUserID(string userID, string properties);
|
||||
void ClearUser();
|
||||
|
||||
string GetDeviceId();
|
||||
|
||||
void LogEvent(string name, string properties);
|
||||
|
||||
void DeviceInitialize(string properties);
|
||||
|
||||
void DeviceUpdate(string properties);
|
||||
void DeviceAdd(string properties);
|
||||
|
||||
void UserInitialize(string properties);
|
||||
|
||||
void UserUpdate(string properties);
|
||||
|
||||
void UserAdd(string properties);
|
||||
|
||||
void AddCommonProperty(string key, string value);
|
||||
|
||||
void AddCommon(string properties);
|
||||
|
||||
void ClearCommonProperty(string key);
|
||||
void ClearCommonProperties(string[] keys);
|
||||
|
||||
void ClearAllCommonProperties();
|
||||
void LogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties);
|
||||
|
||||
void RegisterDynamicProperties(Func<string> callback);
|
||||
|
||||
void SetOAID(string value);
|
||||
|
||||
void LogDeviceLoginEvent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b1164922a79c4217bc3f9dc0cac0ff3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.Core.Internal {
|
||||
public static class PlatformTypeUtils {
|
||||
/// <summary>
|
||||
/// 创建平台接口实现类对象
|
||||
/// </summary>
|
||||
/// <param name="interfaceType"></param>
|
||||
/// <param name="startWith"></param>
|
||||
/// <returns></returns>
|
||||
public static object CreatePlatformImplementationObject(Type interfaceType, string startWith) {
|
||||
|
||||
// 获取所有符合条件的程序集
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(assembly => assembly.GetName().FullName.StartsWith(startWith));
|
||||
|
||||
|
||||
// 获取符合条件的类型
|
||||
Type platformSupportType = assemblies
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.SingleOrDefault(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass);
|
||||
|
||||
if (platformSupportType != null) {
|
||||
try
|
||||
{
|
||||
return Activator.CreateInstance(platformSupportType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TapLog.Error($"Failed to create instance of {platformSupportType.FullName}: {ex}");
|
||||
}
|
||||
} else {
|
||||
TapLog.Error($"No type found that implements {interfaceType} in assemblies starting with {startWith}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22c79a001ad8e4956a7af00198f39c33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/UI.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd5e6d0a0a3084dbe9172f6644b9afee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/UI/Base.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/UI/Base.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02106d3e1676c4ae283349aad59eaf9d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Const.cs
Normal file
27
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Const.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public enum EOpenMode
|
||||
{
|
||||
Sync,
|
||||
|
||||
Async,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum EAnimationMode
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Alpha = 1 << 0,
|
||||
|
||||
Scale = 1 << 1,
|
||||
|
||||
RightSlideIn = 1 << 2,
|
||||
|
||||
UpSlideIn = 1 << 3,
|
||||
ToastSlideIn = 1 << 4,
|
||||
|
||||
AlphaAndScale = Alpha | Scale,
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Const.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Const.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bd4da1dce99741fca662c311f199c2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapTap.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 修复UnityUGui中Canvas画布在Windows平台宽高比过长(横屏拉很长)导致UI的最右边一部分无法被点击的Bug,现发现与2021.3.x版本
|
||||
/// </summary>
|
||||
public class GraphicRaycasterBugFixed : GraphicRaycaster
|
||||
{
|
||||
[NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();
|
||||
private Canvas _Canvas;
|
||||
private Canvas canvasBugFixed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Canvas != null)
|
||||
return _Canvas;
|
||||
|
||||
_Canvas = GetComponent<Canvas>();
|
||||
return _Canvas;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Perform the raycast against the list of graphics associated with the Canvas.
|
||||
/// </summary>
|
||||
/// <param name="eventData">Current event data</param>
|
||||
/// <param name="resultAppendList">List of hit objects to append new results to.</param>
|
||||
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
|
||||
{
|
||||
if (canvasBugFixed == null)
|
||||
return;
|
||||
|
||||
var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvasBugFixed);
|
||||
if (canvasGraphics == null || canvasGraphics.Count == 0)
|
||||
return;
|
||||
|
||||
int displayIndex;
|
||||
var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
|
||||
|
||||
if (canvasBugFixed.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
|
||||
displayIndex = canvasBugFixed.targetDisplay;
|
||||
else
|
||||
displayIndex = currentEventCamera.targetDisplay;
|
||||
|
||||
//!! 不同之处
|
||||
var eventPosition = Display.RelativeMouseAt(eventData.position);
|
||||
//MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);
|
||||
if (eventPosition != Vector3.zero)
|
||||
{
|
||||
// We support multiple display and display identification based on event position.
|
||||
|
||||
int eventDisplayIndex = (int)eventPosition.z;
|
||||
|
||||
// Discard events that are not part of this display so the user does not interact with multiple displays at once.
|
||||
if (eventDisplayIndex != displayIndex)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The multiple display system is not supported on all platforms, when it is not supported the returned position
|
||||
// will be all zeros so when the returned index is 0 we will default to the event data to be safe.
|
||||
eventPosition = eventData.position;
|
||||
|
||||
// We dont really know in which display the event occured. We will process the event assuming it occured in our display.
|
||||
}
|
||||
|
||||
// Convert to view space
|
||||
Vector2 pos;
|
||||
if (currentEventCamera == null)
|
||||
{
|
||||
// Multiple display support only when not the main display. For display 0 the reported
|
||||
// resolution is always the desktops resolution since its part of the display API,
|
||||
// so we use the standard none multiple display method. (case 741751)
|
||||
float w = Screen.width;
|
||||
float h = Screen.height;
|
||||
if (displayIndex > 0 && displayIndex < Display.displays.Length)
|
||||
{
|
||||
w = Display.displays[displayIndex].systemWidth;
|
||||
h = Display.displays[displayIndex].systemHeight;
|
||||
}
|
||||
pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
|
||||
}
|
||||
else
|
||||
pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
|
||||
|
||||
// If it's outside the camera's viewport, do nothing
|
||||
if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
|
||||
return;
|
||||
|
||||
float hitDistance = float.MaxValue;
|
||||
|
||||
Ray ray = new Ray();
|
||||
|
||||
if (currentEventCamera != null)
|
||||
ray = currentEventCamera.ScreenPointToRay(eventPosition);
|
||||
|
||||
if (canvasBugFixed.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
|
||||
{
|
||||
float distanceToClipPlane = 100.0f;
|
||||
|
||||
if (currentEventCamera != null)
|
||||
{
|
||||
float projectionDirection = ray.direction.z;
|
||||
distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
|
||||
? Mathf.Infinity
|
||||
: Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
|
||||
}
|
||||
#if PACKAGE_PHYSICS
|
||||
if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
|
||||
{
|
||||
if (ReflectionMethodsCache.Singleton.raycast3D != null)
|
||||
{
|
||||
var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask);
|
||||
if (hits.Length > 0)
|
||||
hitDistance = hits[0].distance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if PACKAGE_PHYSICS2D
|
||||
if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
|
||||
{
|
||||
if (ReflectionMethodsCache.Singleton.raycast2D != null)
|
||||
{
|
||||
var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask);
|
||||
if (hits.Length > 0)
|
||||
hitDistance = hits[0].distance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
m_RaycastResults.Clear();
|
||||
|
||||
Raycast(canvasBugFixed, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
|
||||
|
||||
int totalCount = m_RaycastResults.Count;
|
||||
for (var index = 0; index < totalCount; index++)
|
||||
{
|
||||
var go = m_RaycastResults[index].gameObject;
|
||||
bool appendGraphic = true;
|
||||
|
||||
if (ignoreReversedGraphics)
|
||||
{
|
||||
if (currentEventCamera == null)
|
||||
{
|
||||
// If we dont have a camera we know that we should always be facing forward
|
||||
var dir = go.transform.rotation * Vector3.forward;
|
||||
appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have a camera compare the direction against the cameras forward.
|
||||
var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
|
||||
// !!不同之处
|
||||
appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (appendGraphic)
|
||||
{
|
||||
float distance = 0;
|
||||
Transform trans = go.transform;
|
||||
Vector3 transForward = trans.forward;
|
||||
|
||||
if (currentEventCamera == null || canvasBugFixed.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
distance = 0;
|
||||
else
|
||||
{
|
||||
// http://geomalgorithms.com/a06-_intersect-2.html
|
||||
distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
|
||||
|
||||
// Check to see if the go is behind the camera.
|
||||
if (distance < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (distance >= hitDistance)
|
||||
continue;
|
||||
|
||||
var castResult = new RaycastResult
|
||||
{
|
||||
gameObject = go,
|
||||
module = this,
|
||||
distance = distance,
|
||||
screenPosition = eventPosition,
|
||||
displayIndex = displayIndex,
|
||||
index = resultAppendList.Count,
|
||||
depth = m_RaycastResults[index].depth,
|
||||
sortingLayer = canvasBugFixed.sortingLayerID,
|
||||
sortingOrder = canvasBugFixed.sortingOrder,
|
||||
worldPosition = ray.origin + ray.direction * distance,
|
||||
worldNormal = -transForward
|
||||
};
|
||||
resultAppendList.Add(castResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Perform a raycast into the screen and collect all graphics underneath it.
|
||||
/// </summary>
|
||||
[NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
|
||||
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
|
||||
{
|
||||
// Necessary for the event system
|
||||
int totalCount = foundGraphics.Count;
|
||||
for (int i = 0; i < totalCount; ++i)
|
||||
{
|
||||
Graphic graphic = foundGraphics[i];
|
||||
|
||||
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
|
||||
if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
|
||||
continue;
|
||||
|
||||
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
|
||||
continue;
|
||||
|
||||
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
|
||||
continue;
|
||||
|
||||
if (graphic.Raycast(pointerPosition, eventCamera))
|
||||
{
|
||||
s_SortedGraphics.Add(graphic);
|
||||
}
|
||||
}
|
||||
|
||||
s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
|
||||
totalCount = s_SortedGraphics.Count;
|
||||
for (int i = 0; i < totalCount; ++i)
|
||||
results.Add(s_SortedGraphics[i]);
|
||||
|
||||
s_SortedGraphics.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39341cb1058fe4bdebd02bc59f5c08e1
|
||||
timeCreated: 1678941027
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public class LoadingPanelController : BasePanelController
|
||||
{
|
||||
public Transform rotater;
|
||||
|
||||
public float speed = 10;
|
||||
/// <summary>
|
||||
/// bind ugui components for every panel
|
||||
/// </summary>
|
||||
protected override void BindComponents()
|
||||
{
|
||||
rotater = transform.Find("Image");;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (rotater != null)
|
||||
{
|
||||
var localEuler = rotater.localEulerAngles;
|
||||
var z = rotater.localEulerAngles.z;
|
||||
z += Time.deltaTime * speed;
|
||||
rotater.localEulerAngles = new Vector3(localEuler.x, localEuler.y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 270dfb0d584134275afb9640c5abde2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
102
Assets/TapSDK/Core/Runtime/Internal/UI/Base/MonoSingleton.cs
Normal file
102
Assets/TapSDK/Core/Runtime/Internal/UI/Base/MonoSingleton.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public class MonoSingleton<T> : MonoBehaviour where T : Component
|
||||
{
|
||||
private static T _instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null && Application.isPlaying)
|
||||
{
|
||||
CreateInstance();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateInstance()
|
||||
{
|
||||
Type theType = typeof(T);
|
||||
|
||||
_instance = (T)FindObjectOfType(theType);
|
||||
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject(typeof(T).Name);
|
||||
_instance = go.AddComponent<T>();
|
||||
|
||||
GameObject rootObj = GameObject.Find("TapSDKSingletons");
|
||||
if (rootObj == null)
|
||||
{
|
||||
rootObj = new GameObject("TapSDKSingletons");
|
||||
DontDestroyOnLoad(rootObj);
|
||||
}
|
||||
|
||||
go.transform.SetParent(rootObj.transform);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DestroyInstance()
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
Destroy(_instance.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasInstance()
|
||||
{
|
||||
return _instance != null;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (_instance != null && _instance.gameObject != gameObject)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyImmediate(gameObject); // UNITY_EDITOR
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_instance == null)
|
||||
{
|
||||
_instance = GetComponent<T>();
|
||||
}
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Uninit();
|
||||
|
||||
if (_instance != null && _instance.gameObject == gameObject)
|
||||
{
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Uninit()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2743c376f0b344d32a58d36d253b50ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Singleton.cs
Normal file
29
Assets/TapSDK/Core/Runtime/Internal/UI/Base/Singleton.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public class Singleton<T> where T : class
|
||||
{
|
||||
private static T _instance;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = (T)Activator.CreateInstance(typeof(T), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 641507a330f89486c951299818e8d14e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public class TipPanelOpenParam : AbstractOpenPanelParameter
|
||||
{
|
||||
public string text;
|
||||
|
||||
public Color textColor;
|
||||
|
||||
public TextAnchor textAnchor;
|
||||
|
||||
public TipPanelOpenParam(string text, Color textColor, TextAnchor textAnchor)
|
||||
{
|
||||
this.text = text;
|
||||
this.textColor = textColor;
|
||||
this.textAnchor = textAnchor;
|
||||
}
|
||||
}
|
||||
public class TipPanelController : BasePanelController
|
||||
{
|
||||
public Text text;
|
||||
|
||||
protected override void BindComponents()
|
||||
{
|
||||
base.BindComponents();
|
||||
text = transform.Find("Text").GetComponent<Text>();
|
||||
}
|
||||
|
||||
protected override void OnLoadSuccess()
|
||||
{
|
||||
base.OnLoadSuccess();
|
||||
TipPanelOpenParam param = this.openParam as TipPanelOpenParam;
|
||||
if (param != null)
|
||||
{
|
||||
text.text = param.text;
|
||||
text.color = param.textColor;
|
||||
text.alignment = param.textAnchor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bf8716943aca4601b13dfac46ccf44b
|
||||
timeCreated: 1690867588
|
||||
@@ -0,0 +1,95 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapSDK.UI {
|
||||
public class ToastPanelOpenParam : AbstractOpenPanelParameter {
|
||||
public float popupTime;
|
||||
|
||||
public string text;
|
||||
|
||||
public Texture icon;
|
||||
|
||||
public ToastPanelOpenParam(string text, float popupTime, Texture icon = null) {
|
||||
this.text = text;
|
||||
this.popupTime = popupTime;
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
public class ToastBlackPanelController : BasePanelController {
|
||||
private const int MAX_CHAR_COUNT = 87;
|
||||
private const float MIN_HORIZONTAL_LENGTH = 210;
|
||||
private const float MAX_HORIZONTAL_LENGTH = 1252;
|
||||
[HideInInspector]
|
||||
public Text text;
|
||||
[HideInInspector]
|
||||
public RectTransform background;
|
||||
[HideInInspector]
|
||||
public LayoutGroup layout;
|
||||
[HideInInspector]
|
||||
public RawImage iconImage;
|
||||
|
||||
public float fixVal;
|
||||
|
||||
public float animationTime;
|
||||
|
||||
[SerializeField]
|
||||
private float sizeDeltaX = 50f;
|
||||
|
||||
protected override void BindComponents() {
|
||||
base.BindComponents();
|
||||
background = transform.Find("Root/BGM") as RectTransform;
|
||||
layout = background.transform.Find("Container").GetComponent<LayoutGroup>();
|
||||
text = layout.transform.Find("Text").GetComponent<Text>();
|
||||
iconImage = layout.transform.Find("Mask/Icon").GetComponent<RawImage>();
|
||||
fadeAnimationTime = animationTime;
|
||||
}
|
||||
|
||||
protected override void OnLoadSuccess() {
|
||||
base.OnLoadSuccess();
|
||||
ToastPanelOpenParam param = this.openParam as ToastPanelOpenParam;
|
||||
if (param != null) {
|
||||
|
||||
iconImage.gameObject.SetActive(param.icon != null);
|
||||
layout.enabled = param.icon != null;
|
||||
text.text = param.text;
|
||||
bool overflow;
|
||||
// test: 已登录账号:海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边边...
|
||||
var totalLength = CalculateLengthOfText(out overflow);
|
||||
if (overflow) {
|
||||
bool haveIcon = param.icon != null;
|
||||
int length = Mathf.Min(MAX_CHAR_COUNT - (haveIcon ? 4 : 0), param.text.Length - 1);
|
||||
var transformedText = param.text.Substring(0, length) + "...";
|
||||
text.text = transformedText;
|
||||
}
|
||||
var x = totalLength;
|
||||
var y = background.sizeDelta.y;
|
||||
background.sizeDelta = new Vector2(x, y);
|
||||
var textRect = text.transform as RectTransform;
|
||||
|
||||
if (param.icon != null) {
|
||||
iconImage.texture = param.icon;
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
textRect.sizeDelta = new Vector2(x - sizeDeltaX, 24);
|
||||
}
|
||||
else {
|
||||
text.alignment = TextAnchor.MiddleCenter;
|
||||
textRect.anchoredPosition = Vector2.zero;
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.sizeDelta = Vector2.zero;
|
||||
}
|
||||
this.Invoke("Close", param.popupTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected float CalculateLengthOfText( out bool horizontalOverflow) {
|
||||
var width = text.preferredWidth + fixVal + 12;
|
||||
width = Mathf.Max(MIN_HORIZONTAL_LENGTH, width);
|
||||
var maxWidth = MAX_HORIZONTAL_LENGTH;
|
||||
horizontalOverflow = width > maxWidth;
|
||||
width = Mathf.Min(maxWidth, width);
|
||||
return width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfadb0c2f9cbd4e26821f6acbffb6dac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapSDK.UI {
|
||||
public class ToastWhitePanelController : BasePanelController {
|
||||
private const int MAX_CHAR_COUNT = 69;
|
||||
private const float MIN_HORIZONTAL_LENGTH = 120;
|
||||
private const float MAX_HORIZONTAL_LENGTH = 1252;
|
||||
private const float BACKGROUND_WIDTH_HELPER = 60;
|
||||
[HideInInspector]
|
||||
public Text text;
|
||||
[HideInInspector]
|
||||
public RectTransform background;
|
||||
[HideInInspector]
|
||||
public RawImage iconImage;
|
||||
|
||||
public float fixVal;
|
||||
|
||||
public float animationTime;
|
||||
|
||||
public ToastPanelOpenParam toastParam;
|
||||
|
||||
protected override void BindComponents() {
|
||||
base.BindComponents();
|
||||
background = transform.Find("Root/BGM") as RectTransform;
|
||||
text = background.transform.Find("Container/Text").GetComponent<Text>();
|
||||
iconImage = background.transform.Find("Container/Image").GetComponent<RawImage>();
|
||||
fadeAnimationTime = animationTime;
|
||||
}
|
||||
|
||||
protected override void OnLoadSuccess() {
|
||||
base.OnLoadSuccess();
|
||||
ToastPanelOpenParam param = this.openParam as ToastPanelOpenParam;
|
||||
toastParam = param;
|
||||
if (param != null) {
|
||||
iconImage.gameObject.SetActive(param.icon != null);
|
||||
text.text = param.text;
|
||||
bool overflow = text.text.Length > MAX_CHAR_COUNT;
|
||||
// test: 已登录账号:海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边边...
|
||||
var totalLength = CalculateLengthOfText();
|
||||
if (overflow) {
|
||||
bool haveIcon = param.icon != null;
|
||||
int length = Mathf.Min(MAX_CHAR_COUNT - (haveIcon ? 4 : 0), param.text.Length - 1);
|
||||
var transformedText = param.text.Substring(0, length) + "...";
|
||||
text.text = transformedText;
|
||||
}
|
||||
var x = totalLength;
|
||||
var y = background.sizeDelta.y;
|
||||
background.sizeDelta = new Vector2(x + BACKGROUND_WIDTH_HELPER, y);
|
||||
var textRect = text.transform as RectTransform;
|
||||
|
||||
if (param.icon != null) {
|
||||
iconImage.texture = param.icon;
|
||||
textRect.sizeDelta = new Vector2(x, y);
|
||||
}
|
||||
else {
|
||||
textRect.anchoredPosition = Vector2.zero;
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.sizeDelta = Vector2.zero;
|
||||
}
|
||||
this.Invoke("Close", param.popupTime);
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateLengthOfText() {
|
||||
var width = text.preferredWidth + fixVal;
|
||||
width = Mathf.Max(MIN_HORIZONTAL_LENGTH, width);
|
||||
var maxWidth = MAX_HORIZONTAL_LENGTH;
|
||||
width = Mathf.Min(maxWidth, width);
|
||||
return width;
|
||||
}
|
||||
|
||||
public void Refresh(float refreshTime) {
|
||||
this.CancelInvoke("Close");
|
||||
this.Invoke("Close", refreshTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e2bbf05d8861431bb645d167b1ab76c
|
||||
timeCreated: 1695367197
|
||||
725
Assets/TapSDK/Core/Runtime/Internal/UI/Base/UIManager.cs
Normal file
725
Assets/TapSDK/Core/Runtime/Internal/UI/Base/UIManager.cs
Normal file
@@ -0,0 +1,725 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public class UIManager : MonoSingleton<UIManager>
|
||||
{
|
||||
public enum GeneralToastLevel {
|
||||
None = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Success =4,
|
||||
}
|
||||
|
||||
private class ToastConfig {
|
||||
public bool white;
|
||||
public string text;
|
||||
public float popupTime;
|
||||
public Texture icon;
|
||||
}
|
||||
|
||||
private static Texture whiteToastSuccessIcon;
|
||||
private static Texture whiteToastErrorIcon;
|
||||
private static Texture whiteToastInfoIcon;
|
||||
private static Texture whiteToastWarningIcon;
|
||||
private static Texture taptapToastIcon;
|
||||
|
||||
public static Texture WhiteToastSuccessIcon {
|
||||
get {
|
||||
if (whiteToastSuccessIcon == null)
|
||||
whiteToastSuccessIcon = UnityEngine.Resources.Load<Texture>("taptap-toast-success");
|
||||
return whiteToastSuccessIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture WhiteToastErrorIcon {
|
||||
get {
|
||||
if (whiteToastErrorIcon == null)
|
||||
whiteToastErrorIcon = UnityEngine.Resources.Load<Texture>("taptap-toast-error");
|
||||
return whiteToastErrorIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture WhiteToastWarningIcon {
|
||||
get {
|
||||
if (whiteToastWarningIcon == null)
|
||||
whiteToastWarningIcon = UnityEngine.Resources.Load<Texture>("taptap-toast-warning");
|
||||
return whiteToastWarningIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture WhiteToastInfoIcon {
|
||||
get {
|
||||
if (whiteToastInfoIcon == null)
|
||||
whiteToastInfoIcon = UnityEngine.Resources.Load<Texture>("taptap-toast-info");
|
||||
return whiteToastInfoIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture TapTapToastIcon {
|
||||
get {
|
||||
if (taptapToastIcon == null)
|
||||
taptapToastIcon = Resources.Load<Texture>("TapSDKCommonTapIcon-v2");
|
||||
return taptapToastIcon;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ToastConfig> _toastCacheData = new List<ToastConfig>(12);
|
||||
public const int TOPPEST_SORTING_ORDER = 32767;
|
||||
|
||||
private const int LOADING_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER;
|
||||
|
||||
private const int TOAST_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER - 10;
|
||||
private const int TIP_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER - 20;
|
||||
|
||||
private const int REFERENCE_WIDTH = 1920;
|
||||
private const int REFERENCE_HEIGHT = 1080;
|
||||
|
||||
private readonly Dictionary<Type, BasePanelController> _registerPanels = new Dictionary<Type, BasePanelController>();
|
||||
|
||||
// uicamera
|
||||
private Camera _uiCamera;
|
||||
|
||||
private GameObject _uiRoot;
|
||||
private GameObject _uiConstantRoot;
|
||||
private Canvas _uiRootCanvas;
|
||||
|
||||
public Camera UiCamera => _uiCamera;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (_uiRoot == null && Application.isPlaying)
|
||||
{
|
||||
CreateUIRoot();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateUIRoot()
|
||||
{
|
||||
_uiRoot = Instantiate(Resources.Load<GameObject>("TapSDKUIRoot"));
|
||||
DontDestroyOnLoad(_uiRoot);
|
||||
var canvas = _uiRoot.GetComponent<Canvas>();
|
||||
_uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? Camera.main : canvas.worldCamera;
|
||||
_uiRoot.transform.SetParent(UIManager.Instance.transform);
|
||||
_uiRootCanvas = _uiRoot.transform.GetComponent<Canvas>();
|
||||
|
||||
CanvasScaler scaler = _uiRoot.GetComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.referenceResolution = new Vector2(REFERENCE_WIDTH, REFERENCE_HEIGHT);
|
||||
float referenceRatio = REFERENCE_WIDTH * 1.0f / REFERENCE_HEIGHT;
|
||||
float screenRatio = Screen.width * 1.0f / Screen.height;
|
||||
scaler.matchWidthOrHeight = screenRatio > referenceRatio ? 1 : 0;
|
||||
|
||||
_uiConstantRoot = Instantiate(Resources.Load<GameObject>("TapSDKConstantUIRoot"));
|
||||
DontDestroyOnLoad(_uiConstantRoot);
|
||||
canvas = _uiConstantRoot.GetComponent<Canvas>();
|
||||
_uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? Camera.main : canvas.worldCamera;
|
||||
_uiConstantRoot.transform.SetParent(UIManager.Instance.transform);
|
||||
_uiRootCanvas = _uiConstantRoot.transform.GetComponent<Canvas>();
|
||||
}
|
||||
|
||||
public override void Uninit()
|
||||
{
|
||||
_uiCamera = null;
|
||||
_uiRoot = null;
|
||||
_uiConstantRoot = null;
|
||||
_uiRootCanvas = null;
|
||||
base.Uninit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Or Create UI asynchronously
|
||||
/// </summary>
|
||||
/// <param name="path">the prefab path</param>
|
||||
/// <param name="param">opening param</param>
|
||||
/// <param name="onAsyncGet">if u want to get ui do something,here is for u, which is invoked after BasePanelController.OnLoadSuccess</param>
|
||||
/// <typeparam name="TPanelController"></typeparam>
|
||||
/// <returns>get panel instance if sync mode load</returns>
|
||||
public async Task<TPanelController> OpenUIAsync<TPanelController>(string path, AbstractOpenPanelParameter param = null, Action<BasePanelController> onAsyncGet = null) where TPanelController : BasePanelController
|
||||
{
|
||||
var basePanelController = GetUI<TPanelController>();
|
||||
|
||||
// 如果已有界面(之前缓存过的),则不执行任何操作
|
||||
if (basePanelController != null)
|
||||
{
|
||||
if (!basePanelController.canvas.enabled)
|
||||
{
|
||||
basePanelController.canvas.enabled = true;
|
||||
}
|
||||
|
||||
onAsyncGet?.Invoke(basePanelController);
|
||||
|
||||
return basePanelController;
|
||||
}
|
||||
|
||||
ResourceRequest request = Resources.LoadAsync<GameObject>(path);
|
||||
while (request.isDone == false)
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
GameObject go = request.asset as GameObject;
|
||||
var basePanel = Instantiate(go).GetComponent<TPanelController>();
|
||||
if (basePanel != null)
|
||||
{
|
||||
var prefabRectTransform = basePanel.transform as RectTransform;
|
||||
var anchorMin = prefabRectTransform.anchorMin;
|
||||
var anchorMax = prefabRectTransform.anchorMax;
|
||||
var anchoredPosition = prefabRectTransform.anchoredPosition;
|
||||
var sizeDelta = prefabRectTransform.sizeDelta;
|
||||
|
||||
InternalOnPanelLoaded(typeof(TPanelController), basePanel, param);
|
||||
if (IsNeedCorrectRectTransform(basePanel))
|
||||
{
|
||||
var panelRectTransform = basePanel.transform as RectTransform;
|
||||
panelRectTransform.anchorMin = anchorMin;
|
||||
panelRectTransform.anchorMax = anchorMax;
|
||||
panelRectTransform.anchoredPosition = anchoredPosition;
|
||||
panelRectTransform.sizeDelta = sizeDelta;
|
||||
}
|
||||
onAsyncGet?.Invoke(basePanel);
|
||||
|
||||
EnsureSpecialPanel();
|
||||
return basePanel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Or Create UI asynchronously
|
||||
/// </summary>
|
||||
/// <param name="panelType">the panel Type</param>
|
||||
/// <param name="path">the prefab path</param>
|
||||
/// <param name="param">opening param</param>
|
||||
/// <param name="onAsyncGet">if u want to get ui do something,here is for u, which is invoked after BasePanelController.OnLoadSuccess</param>
|
||||
/// <returns>get panel instance if sync mode load</returns>
|
||||
public async Task<BasePanelController> OpenUIAsync(Type panelType, string path, AbstractOpenPanelParameter param = null, Action<BasePanelController> onAsyncGet = null)
|
||||
{
|
||||
if (!typeof(BasePanelController).IsAssignableFrom(panelType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var basePanelController = GetUI(panelType);
|
||||
|
||||
// 如果已有界面(之前缓存过的),则不执行任何操作
|
||||
if (basePanelController != null)
|
||||
{
|
||||
if (!basePanelController.canvas.enabled)
|
||||
{
|
||||
basePanelController.canvas.enabled = true;
|
||||
}
|
||||
|
||||
onAsyncGet?.Invoke(basePanelController);
|
||||
|
||||
return basePanelController;
|
||||
}
|
||||
|
||||
ResourceRequest request = Resources.LoadAsync<GameObject>(path);
|
||||
while (request.isDone == false)
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
GameObject go = request.asset as GameObject;
|
||||
var basePanel = Instantiate(go).GetComponent<BasePanelController>();
|
||||
if (basePanel != null)
|
||||
{
|
||||
var prefabRectTransform = basePanel.transform as RectTransform;
|
||||
var anchorMin = prefabRectTransform.anchorMin;
|
||||
var anchorMax = prefabRectTransform.anchorMax;
|
||||
var anchoredPosition = prefabRectTransform.anchoredPosition;
|
||||
var sizeDelta = prefabRectTransform.sizeDelta;
|
||||
|
||||
InternalOnPanelLoaded(panelType, basePanel, param);
|
||||
if (IsNeedCorrectRectTransform(basePanel))
|
||||
{
|
||||
var panelRectTransform = basePanel.transform as RectTransform;
|
||||
panelRectTransform.anchorMin = anchorMin;
|
||||
panelRectTransform.anchorMax = anchorMax;
|
||||
panelRectTransform.anchoredPosition = anchoredPosition;
|
||||
panelRectTransform.sizeDelta = sizeDelta;
|
||||
}
|
||||
onAsyncGet?.Invoke(basePanel);
|
||||
|
||||
EnsureSpecialPanel();
|
||||
return basePanel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Or Create UI
|
||||
/// </summary>
|
||||
/// <param name="path">the prefab path</param>
|
||||
/// <param name="param">opening param</param>
|
||||
/// <typeparam name="TPanelController"></typeparam>
|
||||
/// <returns>get panel instance if sync mode load</returns>
|
||||
public TPanelController OpenUI<TPanelController>(string path, AbstractOpenPanelParameter param = null) where TPanelController : BasePanelController
|
||||
{
|
||||
TPanelController basePanelController = GetUI<TPanelController>();
|
||||
|
||||
// 如果已有界面(之前缓存过的),则不执行任何操作
|
||||
if (basePanelController != null)
|
||||
{
|
||||
if (!basePanelController.canvas.enabled)
|
||||
{
|
||||
basePanelController.canvas.enabled = true;
|
||||
}
|
||||
|
||||
return basePanelController;
|
||||
}
|
||||
|
||||
var go = Resources.Load(path) as GameObject;
|
||||
if (go != null)
|
||||
{
|
||||
var prefabRectTransform = go.transform as RectTransform;
|
||||
var anchorMin = prefabRectTransform.anchorMin;
|
||||
var anchorMax = prefabRectTransform.anchorMax;
|
||||
var anchoredPosition = prefabRectTransform.anchoredPosition;
|
||||
var sizeDelta = prefabRectTransform.sizeDelta;
|
||||
|
||||
GameObject panelGo = GameObject.Instantiate(go);
|
||||
|
||||
var basePanel = panelGo.GetComponent<TPanelController>();
|
||||
if (basePanel == null) {
|
||||
basePanel = panelGo.AddComponent<TPanelController>();
|
||||
}
|
||||
_registerPanels.Add(typeof(TPanelController), basePanel);
|
||||
|
||||
basePanel.transform.SetAsLastSibling();
|
||||
if (IsNeedCorrectRectTransform(basePanel))
|
||||
{
|
||||
var panelRectTransform = basePanel.transform as RectTransform;
|
||||
panelRectTransform.anchorMin = anchorMin;
|
||||
panelRectTransform.anchorMax = anchorMax;
|
||||
panelRectTransform.anchoredPosition = anchoredPosition;
|
||||
panelRectTransform.sizeDelta = sizeDelta;
|
||||
}
|
||||
|
||||
basePanel.OnLoaded(param);
|
||||
|
||||
EnsureSpecialPanel();
|
||||
return basePanel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Or Create UI
|
||||
/// </summary>
|
||||
/// <param name="panelType">panel type MUST based on BasePanelController</param>
|
||||
/// <param name="path">the prefab path</param>
|
||||
/// <param name="param">opening param</param>
|
||||
/// <returns>get panel instance if sync mode load</returns>
|
||||
public BasePanelController OpenUI(Type panelType, string path, AbstractOpenPanelParameter param = null)
|
||||
{
|
||||
if (!typeof(BasePanelController).IsAssignableFrom(panelType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var basePanelController = GetUI(panelType);
|
||||
|
||||
// 如果已有界面(之前缓存过的),则不执行任何操作
|
||||
if (basePanelController != null)
|
||||
{
|
||||
if (basePanelController != null && !basePanelController.canvas.enabled)
|
||||
{
|
||||
basePanelController.canvas.enabled = true;
|
||||
}
|
||||
|
||||
return basePanelController;
|
||||
}
|
||||
|
||||
var panelGo = Resources.Load(path) as GameObject;
|
||||
if (panelGo != null)
|
||||
{
|
||||
var prefabRectTransform = panelGo.transform as RectTransform;
|
||||
var anchorMin = prefabRectTransform.anchorMin;
|
||||
var anchorMax = prefabRectTransform.anchorMax;
|
||||
var anchoredPosition = prefabRectTransform.anchoredPosition;
|
||||
var sizeDelta = prefabRectTransform.sizeDelta;
|
||||
|
||||
var basePanel = GameObject.Instantiate(panelGo).GetComponent<BasePanelController>();
|
||||
|
||||
_registerPanels.Add(panelType, basePanel);
|
||||
|
||||
// basePanel.SetOpenOrder(uiOpenOrder);
|
||||
basePanel.transform.SetAsLastSibling();
|
||||
|
||||
if (IsNeedCorrectRectTransform(basePanel))
|
||||
{
|
||||
var panelRectTransform = basePanel.transform as RectTransform;
|
||||
panelRectTransform.anchorMin = anchorMin;
|
||||
panelRectTransform.anchorMax = anchorMax;
|
||||
panelRectTransform.anchoredPosition = anchoredPosition;
|
||||
panelRectTransform.sizeDelta = sizeDelta;
|
||||
}
|
||||
basePanel.OnLoaded(param);
|
||||
|
||||
EnsureSpecialPanel();
|
||||
return basePanel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public BasePanelController GetUI(Type panelType)
|
||||
{
|
||||
if (!typeof(BasePanelController).IsAssignableFrom(panelType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_registerPanels.TryGetValue(panelType, out BasePanelController basePanelController))
|
||||
{
|
||||
return basePanelController;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TPanelController GetUI<TPanelController>() where TPanelController : BasePanelController
|
||||
{
|
||||
Type panelType = typeof(TPanelController);
|
||||
|
||||
if (_registerPanels.TryGetValue(panelType, out BasePanelController panel))
|
||||
{
|
||||
return (TPanelController)panel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool CloseUI(Type panelType)
|
||||
{
|
||||
if (!typeof(BasePanelController).IsAssignableFrom(panelType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BasePanelController baseController = GetUI(panelType);
|
||||
if (baseController != null)
|
||||
{
|
||||
if (panelType == typeof(BasePanelController)) // 标尺界面是测试界面 不用关闭
|
||||
return false;
|
||||
else
|
||||
baseController.Close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CloseUI<TPanelController>() where TPanelController : BasePanelController
|
||||
{
|
||||
TPanelController panel = GetUI<TPanelController>();
|
||||
if (panel != null)
|
||||
{
|
||||
panel.Close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// add ui would invoked after create ui automatically, don't need mannually call it in most case
|
||||
/// </summary>
|
||||
public void AddUI(BasePanelController panel)
|
||||
{
|
||||
if (panel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var rt = panel.transform as RectTransform;
|
||||
Vector2 cacheAP = Vector2.zero;
|
||||
Vector2 cacheOffsetMax = Vector2.zero;
|
||||
Vector2 cacheOffsetMin = Vector2.zero;
|
||||
Vector2 cacheSizeDelta = Vector2.zero;
|
||||
if (rt != null)
|
||||
{
|
||||
cacheAP = rt.anchoredPosition;
|
||||
cacheOffsetMax = rt.offsetMax;
|
||||
cacheOffsetMin = rt.offsetMin;
|
||||
cacheSizeDelta = rt.sizeDelta;
|
||||
}
|
||||
panel.transform.SetParent(panel.AttachedParent);
|
||||
if (rt != null)
|
||||
{
|
||||
rt.anchoredPosition = cacheAP;
|
||||
rt.offsetMax = cacheOffsetMax;
|
||||
rt.offsetMin = cacheOffsetMin;
|
||||
rt.sizeDelta = cacheSizeDelta;
|
||||
}
|
||||
panel.transform.localScale = Vector3.one;
|
||||
panel.transform.localRotation = Quaternion.identity;
|
||||
panel.gameObject.name = panel.gameObject.name.Replace("(Clone)", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// remove ui would invoked automatically, don't need mannually call it in most case
|
||||
/// </summary>
|
||||
public void RemoveUI(BasePanelController panel)
|
||||
{
|
||||
if (panel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Type panelType = panel.GetType();
|
||||
if (_registerPanels.ContainsKey(panelType))
|
||||
{
|
||||
_registerPanels.Remove(panelType);
|
||||
}
|
||||
|
||||
if (panelType == typeof(ToastBlackPanelController) || panelType == typeof(ToastWhitePanelController)) {
|
||||
CheckToastCache();
|
||||
}
|
||||
|
||||
panel.Dispose();
|
||||
}
|
||||
|
||||
private void CheckToastCache() {
|
||||
if (_toastCacheData.Count > 0) {
|
||||
var config = _toastCacheData[0];
|
||||
InternalOpenToast(config.white, config.text, config.popupTime, config.icon);
|
||||
_toastCacheData.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take some ui to the most top layer
|
||||
/// </summary>
|
||||
/// <param name="panelType"></param>
|
||||
public void ToppedUI(Type panelType)
|
||||
{
|
||||
if (!typeof(BasePanelController).IsAssignableFrom(panelType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ToppedUI(GetUI(panelType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take some ui to the most top layer
|
||||
/// </summary>
|
||||
/// <param name="panel"></param>
|
||||
/// <param name="toppedSepcialUI">特殊 UI(Loading,Toast之类) 是否需要重新制定</param>
|
||||
public void ToppedUI(BasePanelController panel, bool toppedSepcialUI = true)
|
||||
{
|
||||
if (panel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// panel.SetOpenOrder(uiOpenOrder);
|
||||
panel.transform.SetAsLastSibling();
|
||||
if (toppedSepcialUI) EnsureSpecialPanel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// open toast panel for tip info
|
||||
/// </summary>
|
||||
public void OpenToast(bool white, string text, float popupTime = 3.0f, Texture icon = null) {
|
||||
// print this function parameters
|
||||
if (!string.IsNullOrEmpty(text) && popupTime > 0) {
|
||||
bool isShowing = GetUI<ToastWhitePanelController>() != null || GetUI<ToastBlackPanelController>() != null;
|
||||
if (ToastTryMerge(white, text, popupTime, icon)) {
|
||||
return;
|
||||
}
|
||||
if (isShowing) {
|
||||
var config = new ToastConfig() {
|
||||
white = white,
|
||||
text = text,
|
||||
popupTime = popupTime,
|
||||
icon = icon,
|
||||
};
|
||||
_toastCacheData.Add(config);
|
||||
}
|
||||
else {
|
||||
InternalOpenToast(white, text, popupTime, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ToastTryMerge(bool white, string text, float popupTime, Texture icon) {
|
||||
var currentToastPanel = GetUI<ToastWhitePanelController>();
|
||||
if (currentToastPanel?.toastParam != null) {
|
||||
var param = currentToastPanel?.toastParam;
|
||||
// 目前只有白色 Toast 进行 Merge
|
||||
bool sameToast = param.icon == icon && param.text == text && white;
|
||||
if (sameToast) {
|
||||
currentToastPanel.Refresh(popupTime);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开通用 Toast(白色底)
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="toastLevel">设置 Toast 提示级别,会展示提示图片,如果不想要提示图片,请使用 GeneralToastLevel.None</param>
|
||||
/// <param name="popupTime"></param>
|
||||
public void OpenToast(string text, GeneralToastLevel toastLevel, float popupTime = 3.0f) {
|
||||
OpenToast(true, text, popupTime, GetToastIcon(toastLevel));
|
||||
}
|
||||
|
||||
private Texture GetToastIcon(GeneralToastLevel toastLevel) {
|
||||
if (toastLevel == GeneralToastLevel.None)
|
||||
return null;
|
||||
switch (toastLevel) {
|
||||
case GeneralToastLevel.Info:
|
||||
return UIManager.WhiteToastInfoIcon;
|
||||
case GeneralToastLevel.Warning:
|
||||
return UIManager.WhiteToastWarningIcon;
|
||||
case GeneralToastLevel.Error:
|
||||
return UIManager.WhiteToastErrorIcon;
|
||||
case GeneralToastLevel.Success:
|
||||
return UIManager.WhiteToastSuccessIcon;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void InternalOpenToast(bool white, string text, float popupTime, Texture icon) {
|
||||
if (string.IsNullOrEmpty(text) || popupTime <= 0) return;
|
||||
if (white) {
|
||||
var toast = OpenUI<ToastWhitePanelController>("TapCommonToastWhite", new ToastPanelOpenParam(text, popupTime, icon));
|
||||
if (toast != null) {
|
||||
toast.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
else {
|
||||
var toast = OpenUI<ToastBlackPanelController>("TapCommonToastBlack", new ToastPanelOpenParam(text, popupTime, icon));
|
||||
if (toast != null) {
|
||||
toast.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// open toast panel for tip info
|
||||
/// </summary>
|
||||
public void OpenTip(string text, Color textColor, TextAnchor textAnchor = TextAnchor.MiddleCenter)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
TapLog.Log("[UIManager] Call OpenTip text: " + text);
|
||||
var tip = OpenUI<TipPanelController>("TapCommonTip", new TipPanelOpenParam(text, textColor, textAnchor));
|
||||
if (tip != null)
|
||||
{
|
||||
tip.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// open loading panel that would at the toppest layer and block interaction
|
||||
/// </summary>
|
||||
public void OpenLoading()
|
||||
{
|
||||
var loadingPanel = OpenUI<LoadingPanelController>("Loading");
|
||||
if (loadingPanel != null)
|
||||
{
|
||||
// https://www.reddit.com/r/Unity3D/comments/2b1g1l/order_in_layer_maximum_value/
|
||||
// loadingPanel.SetOpenOrder(LOADING_PANEL_SORTING_ORDER);
|
||||
loadingPanel.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// open loading panel that would at the toppest layer and block interaction
|
||||
/// </summary>
|
||||
public async Task OpenLoadingAsync()
|
||||
{
|
||||
var loadingPanel = await OpenUIAsync<LoadingPanelController>("Loading");
|
||||
if (loadingPanel != null)
|
||||
{
|
||||
// https://www.reddit.com/r/Unity3D/comments/2b1g1l/order_in_layer_maximum_value/
|
||||
// loadingPanel.SetOpenOrder(LOADING_PANEL_SORTING_ORDER);
|
||||
loadingPanel.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseLoading()
|
||||
{
|
||||
var loadingPanel = GetUI<LoadingPanelController>();
|
||||
if (loadingPanel != null)
|
||||
{
|
||||
loadingPanel.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseTip()
|
||||
{
|
||||
var panel = GetUI<TipPanelController>();
|
||||
if (panel != null)
|
||||
{
|
||||
panel.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNeedCorrectRectTransform(BasePanelController controller)
|
||||
{
|
||||
if (controller == null) return false;
|
||||
return (controller.panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.None;
|
||||
}
|
||||
|
||||
private void InternalOnPanelLoaded(Type tPanelController, BasePanelController basePanel, AbstractOpenPanelParameter param = null)
|
||||
{
|
||||
_registerPanels.Add(tPanelController, basePanel);
|
||||
|
||||
basePanel.OnLoaded(param);
|
||||
|
||||
// basePanel.SetOpenOrder(uiOpenOrder);
|
||||
|
||||
basePanel.transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
private void EnsureSpecialPanel()
|
||||
{
|
||||
var temp = _registerPanels.Where(panel =>
|
||||
panel.Value != null && panel.Value.gameObject.activeInHierarchy && panel.Value.toppedOrder != 0);
|
||||
var toppedPanels = new List<KeyValuePair<Type,BasePanelController>>(temp);
|
||||
if (toppedPanels.Count == 0) return;
|
||||
toppedPanels.Sort((x,y)=>x.Value.toppedOrder.CompareTo(y.Value.toppedOrder));
|
||||
foreach (var toppedPanel in toppedPanels)
|
||||
{
|
||||
toppedPanel.Value.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
|
||||
public Transform GetUIRootTransform(BasePanelController panel)
|
||||
{
|
||||
if (panel?.openParam != null && panel.openParam.NeedConstantResolution)
|
||||
return _uiConstantRoot.transform;
|
||||
else
|
||||
return _uiRoot.transform;
|
||||
}
|
||||
|
||||
|
||||
#region external api
|
||||
public void SetSortOrder(int order)
|
||||
{
|
||||
_uiRootCanvas.sortingOrder = order;
|
||||
}
|
||||
|
||||
public Camera GetUICamera()
|
||||
{
|
||||
return _uiCamera;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 753f42bc9ae8a4035ae94f4733957527
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/UI/BasePanel.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/UI/BasePanel.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930467091d41f45c09b2d67e0a3ced30
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// base panel of TapSDK UI module
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CanvasGroup))]
|
||||
[RequireComponent(typeof(GraphicRaycaster))]
|
||||
public abstract class BasePanelController : MonoBehaviour
|
||||
{
|
||||
protected virtual float GetToastSlideInOffset() => 300;
|
||||
/// <summary>
|
||||
/// the canvas related to this panel
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public Canvas canvas;
|
||||
|
||||
/// <summary>
|
||||
/// the canvas group related to this panel
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public CanvasGroup canvasGroup;
|
||||
|
||||
/// <summary>
|
||||
/// fade in/out time
|
||||
/// </summary>
|
||||
protected float fadeAnimationTime = 0.15f;
|
||||
|
||||
/// <summary>
|
||||
/// animation elapse time
|
||||
/// </summary>
|
||||
private float _animationElapse;
|
||||
|
||||
private Vector2 _screenSize;
|
||||
private Vector2 _cachedAnchorPos;
|
||||
|
||||
private RectTransform _rectTransform;
|
||||
|
||||
private Coroutine _animationCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// open parameter
|
||||
/// </summary>
|
||||
protected internal AbstractOpenPanelParameter openParam;
|
||||
|
||||
/// <summary>
|
||||
/// settings about this panel
|
||||
/// </summary>
|
||||
public BasePanelConfig panelConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 特殊面板需要一直保持置顶的,需要填写 toppedOrder, toppedOrder 越大越置顶
|
||||
/// </summary>
|
||||
public int toppedOrder;
|
||||
|
||||
/// <summary>
|
||||
/// the transform parent when created it would be attached to
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public virtual Transform AttachedParent => UIManager.Instance.GetUIRootTransform(this);
|
||||
|
||||
#region Load
|
||||
protected virtual void Awake()
|
||||
{
|
||||
canvas = GetComponent<Canvas>();
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
_rectTransform = transform as RectTransform;
|
||||
|
||||
_screenSize = new Vector2(Screen.width, Screen.height);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (canvas == null)
|
||||
{
|
||||
TapLog.Log("[TapSDK UI] BasePanel Must Be Related To Canvas Component!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bind ugui components for every panel
|
||||
/// </summary>
|
||||
protected virtual void BindComponents() {}
|
||||
|
||||
/// <summary>
|
||||
/// create the prefab instance
|
||||
/// </summary>
|
||||
/// <param name="param"></param>
|
||||
public void OnLoaded(AbstractOpenPanelParameter param = null)
|
||||
{
|
||||
openParam = param;
|
||||
// 寻找组件
|
||||
BindComponents();
|
||||
|
||||
// 添加到控制层
|
||||
UIManager.Instance.AddUI(this);
|
||||
|
||||
// 更新层级信息
|
||||
InitCanvasSetting();
|
||||
// 开始动画效果
|
||||
OnShowEffectStart();
|
||||
|
||||
// 调用加载成功方法
|
||||
OnLoadSuccess();
|
||||
}
|
||||
|
||||
private void InitCanvasSetting()
|
||||
{
|
||||
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
var camera = UIManager.Instance.GetUICamera();
|
||||
if (camera != null)
|
||||
{
|
||||
canvas.worldCamera = camera;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.pixelPerfect = true;
|
||||
// canvas.overrideSorting = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// init panel logic here
|
||||
/// </summary>
|
||||
protected virtual void OnLoadSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Animation
|
||||
|
||||
protected virtual void OnShowEffectStart()
|
||||
{
|
||||
if (panelConfig.animationType == EAnimationMode.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = 0;
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = Vector3.zero;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
_cachedAnchorPos = _rectTransform.anchoredPosition;
|
||||
_rectTransform.anchoredPosition += new Vector2(_screenSize.x, 0);
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) {
|
||||
_cachedAnchorPos = _rectTransform.anchoredPosition;
|
||||
_rectTransform.anchoredPosition += new Vector2(0, _screenSize.y);
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) {
|
||||
_cachedAnchorPos = _rectTransform.anchoredPosition;
|
||||
_rectTransform.anchoredPosition += new Vector2(0, GetToastSlideInOffset());
|
||||
}
|
||||
OnEffectStart();
|
||||
_animationCoroutine = StartCoroutine(FadeInCoroutine(fadeAnimationTime));
|
||||
}
|
||||
|
||||
protected virtual void OnShowEffectEnd()
|
||||
{
|
||||
OnEffectEnd();
|
||||
}
|
||||
|
||||
protected virtual void OnCloseEffectStart()
|
||||
{
|
||||
OnEffectStart();
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = 1;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = Vector3.one;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
_animationCoroutine = StartCoroutine(FadeOutCoroutine(fadeAnimationTime));
|
||||
}
|
||||
|
||||
protected virtual void OnCloseEffectEnd()
|
||||
{
|
||||
OnEffectEnd();
|
||||
GameObject.Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void OnEffectStart()
|
||||
{
|
||||
_animationElapse = 0;
|
||||
if (_animationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_animationCoroutine);
|
||||
_animationCoroutine = null;
|
||||
}
|
||||
canvasGroup.interactable = false;
|
||||
}
|
||||
|
||||
private void OnEffectEnd()
|
||||
{
|
||||
canvasGroup.interactable = true;
|
||||
_animationElapse = 0;
|
||||
_animationCoroutine = null;
|
||||
}
|
||||
|
||||
private IEnumerator FadeInCoroutine(float time)
|
||||
{
|
||||
while (_animationElapse < time)
|
||||
{
|
||||
yield return null;
|
||||
_animationElapse += Time.deltaTime;
|
||||
float value = Mathf.Clamp01(_animationElapse / time);
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = value;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = new Vector3(value, value, value);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * _screenSize.x;
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + temp, _cachedAnchorPos.y);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * _screenSize.y;
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + + temp);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * GetToastSlideInOffset();
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + + temp);
|
||||
}
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = 1;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = Vector3.one;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = _cachedAnchorPos;
|
||||
}
|
||||
|
||||
OnShowEffectEnd();
|
||||
}
|
||||
|
||||
private IEnumerator FadeOutCoroutine(float time)
|
||||
{
|
||||
while (_animationElapse < time)
|
||||
{
|
||||
yield return null;
|
||||
_animationElapse += Time.deltaTime;
|
||||
float value = 1 - Mathf.Clamp01(_animationElapse / time);
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = value;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = new Vector3(value, value, value);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * _screenSize.x;
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + temp, _cachedAnchorPos.y);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * _screenSize.y;
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + temp);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn)
|
||||
{
|
||||
var temp = (1 - value) * GetToastSlideInOffset();
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + temp);
|
||||
}
|
||||
}
|
||||
|
||||
if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha)
|
||||
{
|
||||
canvasGroup.alpha = 0;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale)
|
||||
{
|
||||
transform.localScale = Vector3.zero;
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + _screenSize.x, _cachedAnchorPos.y);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + _screenSize.y);
|
||||
}
|
||||
if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn)
|
||||
{
|
||||
_rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + GetToastSlideInOffset());
|
||||
}
|
||||
|
||||
OnCloseEffectEnd();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// on receive resolution change event
|
||||
/// </summary>
|
||||
/// <param name="res"></param>
|
||||
protected virtual void UIAdapt(Vector2Int res) {}
|
||||
|
||||
/// <summary>
|
||||
/// common close api
|
||||
/// </summary>
|
||||
public virtual void Close()
|
||||
{
|
||||
UIManager.Instance.RemoveUI(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// set canvas sorting order
|
||||
/// </summary>
|
||||
/// <param name="openOrder"></param>
|
||||
public void SetOpenOrder(int openOrder)
|
||||
{
|
||||
if (canvas != null)
|
||||
{
|
||||
canvas.sortingOrder = openOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// also would destroy panel gameObject
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (panelConfig.animationType == EAnimationMode.None)
|
||||
{
|
||||
GameObject.Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
OnCloseEffectStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 607d7f7e1e2a44319bfc7d85788fd805
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/UI/Params.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/UI/Params.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00f81d135c9e44ec4b4a064781b1bcd0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct BasePanelConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// animation effect related to opening and closing
|
||||
/// </summary>
|
||||
public EAnimationMode animationType;
|
||||
|
||||
public BasePanelConfig(EAnimationMode animationMode = EAnimationMode.None)
|
||||
{
|
||||
animationType = animationMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35a0b66e8719e4104bf30ae06108d41a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TapSDK.UI
|
||||
{
|
||||
public abstract class AbstractOpenPanelParameter
|
||||
{
|
||||
public virtual bool NeedConstantResolution => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5b19f6b895fa4059a0e3b7aaf9581c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/UI/ScrollViewEx.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/UI/ScrollViewEx.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5124d8bb5646548db8970683b0a33abd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 765cb640e73334f7a9ccc676064c70be
|
||||
folderAsset: yes
|
||||
timeCreated: 1533284978
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="SimpleObjPool.cs" company="AillieoTech">
|
||||
// Copyright (c) AillieoTech. All rights reserved.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace TapSDK.UI.AillieoTech
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class SimpleObjPool<T>
|
||||
{
|
||||
private readonly Stack<T> stack;
|
||||
private readonly Func<T> ctor;
|
||||
private readonly Action<T> onRecycle;
|
||||
private int size;
|
||||
private int usedCount;
|
||||
|
||||
public SimpleObjPool(int max = 7, Action<T> onRecycle = null, Func<T> ctor = null)
|
||||
{
|
||||
this.stack = new Stack<T>(max);
|
||||
this.size = max;
|
||||
this.onRecycle = onRecycle;
|
||||
this.ctor = ctor;
|
||||
}
|
||||
|
||||
public T Get()
|
||||
{
|
||||
T item;
|
||||
if (this.stack.Count == 0)
|
||||
{
|
||||
if (this.ctor != null)
|
||||
{
|
||||
item = this.ctor();
|
||||
}
|
||||
else
|
||||
{
|
||||
item = Activator.CreateInstance<T>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item = this.stack.Pop();
|
||||
}
|
||||
|
||||
this.usedCount++;
|
||||
return item;
|
||||
}
|
||||
|
||||
public void Recycle(T item)
|
||||
{
|
||||
if (this.onRecycle != null)
|
||||
{
|
||||
this.onRecycle.Invoke(item);
|
||||
}
|
||||
|
||||
if (this.stack.Count < this.size)
|
||||
{
|
||||
this.stack.Push(item);
|
||||
}
|
||||
|
||||
this.usedCount--;
|
||||
}
|
||||
|
||||
public void Purge()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SimpleObjPool: item=[{typeof(T)}], inUse=[{this.usedCount}], restInPool=[{this.stack.Count}/{this.size}] ";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88625144948bd48d296916f8b0e9b41a
|
||||
timeCreated: 1533284979
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab23454192f22412c8ae6f7699645396
|
||||
folderAsset: yes
|
||||
timeCreated: 1533284978
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,828 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ScrollView.cs" company="AillieoTech">
|
||||
// Copyright (c) AillieoTech. All rights reserved.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace TapSDK.UI.AillieoTech
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
[DisallowMultipleComponent]
|
||||
public class ScrollView : ScrollRect
|
||||
{
|
||||
[Tooltip("默认item尺寸")]
|
||||
public Vector2 defaultItemSize;
|
||||
|
||||
[Tooltip("item的模板")]
|
||||
public RectTransform itemTemplate;
|
||||
|
||||
// 0001
|
||||
protected const int flagScrollDirection = 1;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("m_layoutType")]
|
||||
protected ItemLayoutType layoutType = ItemLayoutType.Vertical;
|
||||
|
||||
// 只保存4个临界index
|
||||
protected int[] criticalItemIndex = new int[4];
|
||||
|
||||
// callbacks for items
|
||||
protected Action<int, RectTransform> updateFunc;
|
||||
protected Func<int, Vector2> itemSizeFunc;
|
||||
protected Func<int> itemCountFunc;
|
||||
protected Func<int, RectTransform> itemGetFunc;
|
||||
protected Action<RectTransform> itemRecycleFunc;
|
||||
|
||||
private readonly List<ScrollItemWithRect> managedItems = new List<ScrollItemWithRect>();
|
||||
|
||||
private Rect refRect;
|
||||
|
||||
// resource management
|
||||
private SimpleObjPool<RectTransform> itemPool = null;
|
||||
|
||||
private int dataCount = 0;
|
||||
|
||||
[Tooltip("初始化时池内item数量")]
|
||||
[SerializeField]
|
||||
private int poolSize;
|
||||
|
||||
// status
|
||||
private bool initialized = false;
|
||||
private int willUpdateData = 0;
|
||||
|
||||
private Vector3[] viewWorldConers = new Vector3[4];
|
||||
private Vector3[] rectCorners = new Vector3[2];
|
||||
|
||||
// for hide and show
|
||||
public enum ItemLayoutType
|
||||
{
|
||||
// 最后一位表示滚动方向
|
||||
Vertical = 0b0001, // 0001
|
||||
Horizontal = 0b0010, // 0010
|
||||
VerticalThenHorizontal = 0b0100, // 0100
|
||||
HorizontalThenVertical = 0b0101, // 0101
|
||||
}
|
||||
|
||||
public virtual void SetUpdateFunc(Action<int, RectTransform> func)
|
||||
{
|
||||
this.updateFunc = func;
|
||||
}
|
||||
|
||||
public virtual void SetItemSizeFunc(Func<int, Vector2> func)
|
||||
{
|
||||
this.itemSizeFunc = func;
|
||||
}
|
||||
|
||||
public virtual void SetItemCountFunc(Func<int> func)
|
||||
{
|
||||
this.itemCountFunc = func;
|
||||
}
|
||||
|
||||
public void SetItemGetAndRecycleFunc(Func<int, RectTransform> getFunc, Action<RectTransform> recycleFunc)
|
||||
{
|
||||
if (getFunc != null && recycleFunc != null)
|
||||
{
|
||||
this.itemGetFunc = getFunc;
|
||||
this.itemRecycleFunc = recycleFunc;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.itemGetFunc = null;
|
||||
this.itemRecycleFunc = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetAllDelegates()
|
||||
{
|
||||
this.SetUpdateFunc(null);
|
||||
this.SetItemSizeFunc(null);
|
||||
this.SetItemCountFunc(null);
|
||||
this.SetItemGetAndRecycleFunc(null, null);
|
||||
}
|
||||
|
||||
public void UpdateData(bool immediately = true)
|
||||
{
|
||||
if (immediately)
|
||||
{
|
||||
this.willUpdateData |= 3; // 0011
|
||||
this.InternalUpdateData();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.willUpdateData == 0 && this.IsActive())
|
||||
{
|
||||
this.StartCoroutine(this.DelayUpdateData());
|
||||
}
|
||||
|
||||
this.willUpdateData |= 3;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDataIncrementally(bool immediately = true)
|
||||
{
|
||||
if (immediately)
|
||||
{
|
||||
this.willUpdateData |= 1; // 0001
|
||||
this.InternalUpdateData();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.willUpdateData == 0)
|
||||
{
|
||||
this.StartCoroutine(this.DelayUpdateData());
|
||||
}
|
||||
|
||||
this.willUpdateData |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void ScrollTo(int index)
|
||||
{
|
||||
this.InternalScrollTo(index);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (this.willUpdateData != 0)
|
||||
{
|
||||
this.StartCoroutine(this.DelayUpdateData());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
this.initialized = false;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected virtual void InternalScrollTo(int index)
|
||||
{
|
||||
index = Mathf.Clamp(index, 0, this.dataCount - 1);
|
||||
this.EnsureItemRect(index);
|
||||
Rect r = this.managedItems[index].rect;
|
||||
|
||||
var dir = (int)this.layoutType & flagScrollDirection;
|
||||
if (dir == 1)
|
||||
{
|
||||
// vertical
|
||||
var value = 1 - (-r.yMax / (this.content.sizeDelta.y - this.refRect.height));
|
||||
this.SetNormalizedPosition(value, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal
|
||||
var value = r.xMin / (this.content.sizeDelta.x - this.refRect.width);
|
||||
this.SetNormalizedPosition(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetContentAnchoredPosition(Vector2 position)
|
||||
{
|
||||
base.SetContentAnchoredPosition(position);
|
||||
this.UpdateCriticalItems();
|
||||
}
|
||||
|
||||
protected override void SetNormalizedPosition(float value, int axis)
|
||||
{
|
||||
base.SetNormalizedPosition(value, axis);
|
||||
this.ResetCriticalItems();
|
||||
}
|
||||
|
||||
protected void EnsureItemRect(int index)
|
||||
{
|
||||
if (!this.managedItems[index].rectDirty)
|
||||
{
|
||||
// 已经是干净的了
|
||||
return;
|
||||
}
|
||||
|
||||
ScrollItemWithRect firstItem = this.managedItems[0];
|
||||
if (firstItem.rectDirty)
|
||||
{
|
||||
Vector2 firstSize = this.GetItemSize(0);
|
||||
firstItem.rect = CreateWithLeftTopAndSize(Vector2.zero, firstSize);
|
||||
firstItem.rectDirty = false;
|
||||
}
|
||||
|
||||
// 当前item之前的最近的已更新的rect
|
||||
var nearestClean = 0;
|
||||
for (var i = index; i >= 0; --i)
|
||||
{
|
||||
if (!this.managedItems[i].rectDirty)
|
||||
{
|
||||
nearestClean = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 需要更新 从 nearestClean 到 index 的尺寸
|
||||
Rect nearestCleanRect = this.managedItems[nearestClean].rect;
|
||||
Vector2 curPos = GetLeftTop(nearestCleanRect);
|
||||
Vector2 size = nearestCleanRect.size;
|
||||
this.MovePos(ref curPos, size);
|
||||
|
||||
for (var i = nearestClean + 1; i <= index; i++)
|
||||
{
|
||||
size = this.GetItemSize(i);
|
||||
this.managedItems[i].rect = CreateWithLeftTopAndSize(curPos, size);
|
||||
this.managedItems[i].rectDirty = false;
|
||||
this.MovePos(ref curPos, size);
|
||||
}
|
||||
|
||||
var range = new Vector2(Mathf.Abs(curPos.x), Mathf.Abs(curPos.y));
|
||||
switch (this.layoutType)
|
||||
{
|
||||
case ItemLayoutType.VerticalThenHorizontal:
|
||||
range.x += size.x;
|
||||
range.y = this.refRect.height;
|
||||
break;
|
||||
case ItemLayoutType.HorizontalThenVertical:
|
||||
range.x = this.refRect.width;
|
||||
if (curPos.x != 0)
|
||||
{
|
||||
range.y += size.y;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.content.sizeDelta = range;
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
if (this.itemPool != null)
|
||||
{
|
||||
this.itemPool.Purge();
|
||||
}
|
||||
}
|
||||
|
||||
protected Rect GetItemLocalRect(int index)
|
||||
{
|
||||
if (index >= 0 && index < this.dataCount)
|
||||
{
|
||||
this.EnsureItemRect(index);
|
||||
return this.managedItems[index].rect;
|
||||
}
|
||||
|
||||
return (Rect)default;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
var dir = (int)this.layoutType & flagScrollDirection;
|
||||
if (dir == 1)
|
||||
{
|
||||
// vertical
|
||||
if (this.horizontalScrollbar != null)
|
||||
{
|
||||
this.horizontalScrollbar.gameObject.SetActive(false);
|
||||
this.horizontalScrollbar = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal
|
||||
if (this.verticalScrollbar != null)
|
||||
{
|
||||
this.verticalScrollbar.gameObject.SetActive(false);
|
||||
this.verticalScrollbar = null;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnValidate();
|
||||
}
|
||||
#endif
|
||||
|
||||
private static Vector2 GetLeftTop(Rect rect)
|
||||
{
|
||||
Vector2 ret = rect.position;
|
||||
ret.y += rect.size.y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Rect CreateWithLeftTopAndSize(Vector2 leftTop, Vector2 size)
|
||||
{
|
||||
Vector2 leftBottom = leftTop - new Vector2(0, size.y);
|
||||
return new Rect(leftBottom, size);
|
||||
}
|
||||
|
||||
private IEnumerator DelayUpdateData()
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
this.InternalUpdateData();
|
||||
}
|
||||
|
||||
private void InternalUpdateData()
|
||||
{
|
||||
if (!this.IsActive())
|
||||
{
|
||||
this.willUpdateData |= 3;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.initialized)
|
||||
{
|
||||
this.InitScrollView();
|
||||
}
|
||||
|
||||
var newDataCount = 0;
|
||||
var keepOldItems = (this.willUpdateData & 2) == 0;
|
||||
|
||||
if (this.itemCountFunc != null)
|
||||
{
|
||||
newDataCount = this.itemCountFunc();
|
||||
}
|
||||
|
||||
if (newDataCount != this.managedItems.Count)
|
||||
{
|
||||
if (this.managedItems.Count < newDataCount)
|
||||
{
|
||||
// 增加
|
||||
if (!keepOldItems)
|
||||
{
|
||||
foreach (var itemWithRect in this.managedItems)
|
||||
{
|
||||
// 重置所有rect
|
||||
itemWithRect.rectDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (this.managedItems.Count < newDataCount)
|
||||
{
|
||||
this.managedItems.Add(new ScrollItemWithRect());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 减少 保留空位 避免GC
|
||||
for (int i = 0, count = this.managedItems.Count; i < count; ++i)
|
||||
{
|
||||
if (i < newDataCount)
|
||||
{
|
||||
// 重置所有rect
|
||||
if (!keepOldItems)
|
||||
{
|
||||
this.managedItems[i].rectDirty = true;
|
||||
}
|
||||
|
||||
if (i == newDataCount - 1)
|
||||
{
|
||||
this.managedItems[i].rectDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 超出部分 清理回收item
|
||||
if (i >= newDataCount)
|
||||
{
|
||||
this.managedItems[i].rectDirty = true;
|
||||
if (this.managedItems[i].item != null)
|
||||
{
|
||||
this.RecycleOldItem(this.managedItems[i].item);
|
||||
this.managedItems[i].item = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!keepOldItems)
|
||||
{
|
||||
for (int i = 0, count = this.managedItems.Count; i < count; ++i)
|
||||
{
|
||||
// 重置所有rect
|
||||
this.managedItems[i].rectDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.dataCount = newDataCount;
|
||||
|
||||
this.ResetCriticalItems();
|
||||
|
||||
this.willUpdateData = 0;
|
||||
}
|
||||
|
||||
private void ResetCriticalItems()
|
||||
{
|
||||
bool hasItem, shouldShow;
|
||||
int firstIndex = -1, lastIndex = -1;
|
||||
|
||||
for (var i = 0; i < this.dataCount; i++)
|
||||
{
|
||||
hasItem = this.managedItems[i].item != null;
|
||||
shouldShow = this.ShouldItemSeenAtIndex(i);
|
||||
|
||||
if (shouldShow)
|
||||
{
|
||||
if (firstIndex == -1)
|
||||
{
|
||||
firstIndex = i;
|
||||
}
|
||||
|
||||
lastIndex = i;
|
||||
}
|
||||
|
||||
if (hasItem && shouldShow)
|
||||
{
|
||||
// 应显示且已显示
|
||||
this.SetDataForItemAtIndex(this.managedItems[i].item, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasItem == shouldShow)
|
||||
{
|
||||
// 不应显示且未显示
|
||||
// if (firstIndex != -1)
|
||||
// {
|
||||
// // 已经遍历完所有要显示的了 后边的先跳过
|
||||
// break;
|
||||
// }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasItem && !shouldShow)
|
||||
{
|
||||
// 不该显示 但是有
|
||||
this.RecycleOldItem(this.managedItems[i].item);
|
||||
this.managedItems[i].item = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldShow && !hasItem)
|
||||
{
|
||||
// 需要显示 但是没有
|
||||
RectTransform item = this.GetNewItem(i);
|
||||
this.OnGetItemForDataIndex(item, i);
|
||||
this.managedItems[i].item = item;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// content.localPosition = Vector2.zero;
|
||||
this.criticalItemIndex[CriticalItemType.UpToHide] = firstIndex;
|
||||
this.criticalItemIndex[CriticalItemType.DownToHide] = lastIndex;
|
||||
this.criticalItemIndex[CriticalItemType.UpToShow] = Mathf.Max(firstIndex - 1, 0);
|
||||
this.criticalItemIndex[CriticalItemType.DownToShow] = Mathf.Min(lastIndex + 1, this.dataCount - 1);
|
||||
}
|
||||
|
||||
private RectTransform GetCriticalItem(int type)
|
||||
{
|
||||
var index = this.criticalItemIndex[type];
|
||||
if (index >= 0 && index < this.dataCount)
|
||||
{
|
||||
return this.managedItems[index].item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UpdateCriticalItems()
|
||||
{
|
||||
var dirty = true;
|
||||
|
||||
while (dirty)
|
||||
{
|
||||
dirty = false;
|
||||
|
||||
for (int i = CriticalItemType.UpToHide; i <= CriticalItemType.DownToShow; i++)
|
||||
{
|
||||
if (i <= CriticalItemType.DownToHide)
|
||||
{
|
||||
// 隐藏离开可见区域的item
|
||||
dirty = dirty || this.CheckAndHideItem(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 显示进入可见区域的item
|
||||
dirty = dirty || this.CheckAndShowItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckAndHideItem(int criticalItemType)
|
||||
{
|
||||
RectTransform item = this.GetCriticalItem(criticalItemType);
|
||||
var criticalIndex = this.criticalItemIndex[criticalItemType];
|
||||
if (item != null && !this.ShouldItemSeenAtIndex(criticalIndex))
|
||||
{
|
||||
this.RecycleOldItem(item);
|
||||
this.managedItems[criticalIndex].item = null;
|
||||
|
||||
if (criticalItemType == CriticalItemType.UpToHide)
|
||||
{
|
||||
// 最上隐藏了一个
|
||||
this.criticalItemIndex[criticalItemType + 2] = Mathf.Max(criticalIndex, this.criticalItemIndex[criticalItemType + 2]);
|
||||
this.criticalItemIndex[criticalItemType]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 最下隐藏了一个
|
||||
this.criticalItemIndex[criticalItemType + 2] = Mathf.Min(criticalIndex, this.criticalItemIndex[criticalItemType + 2]);
|
||||
this.criticalItemIndex[criticalItemType]--;
|
||||
}
|
||||
|
||||
this.criticalItemIndex[criticalItemType] = Mathf.Clamp(this.criticalItemIndex[criticalItemType], 0, this.dataCount - 1);
|
||||
|
||||
if (this.criticalItemIndex[CriticalItemType.UpToHide] > this.criticalItemIndex[CriticalItemType.DownToHide])
|
||||
{
|
||||
// 偶然的情况 拖拽超出一屏
|
||||
this.ResetCriticalItems();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckAndShowItem(int criticalItemType)
|
||||
{
|
||||
RectTransform item = this.GetCriticalItem(criticalItemType);
|
||||
var criticalIndex = this.criticalItemIndex[criticalItemType];
|
||||
|
||||
if (item == null && this.ShouldItemSeenAtIndex(criticalIndex))
|
||||
{
|
||||
RectTransform newItem = this.GetNewItem(criticalIndex);
|
||||
this.OnGetItemForDataIndex(newItem, criticalIndex);
|
||||
this.managedItems[criticalIndex].item = newItem;
|
||||
|
||||
if (criticalItemType == CriticalItemType.UpToShow)
|
||||
{
|
||||
// 最上显示了一个
|
||||
this.criticalItemIndex[criticalItemType - 2] = Mathf.Min(criticalIndex, this.criticalItemIndex[criticalItemType - 2]);
|
||||
this.criticalItemIndex[criticalItemType]--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 最下显示了一个
|
||||
this.criticalItemIndex[criticalItemType - 2] = Mathf.Max(criticalIndex, this.criticalItemIndex[criticalItemType - 2]);
|
||||
this.criticalItemIndex[criticalItemType]++;
|
||||
}
|
||||
|
||||
this.criticalItemIndex[criticalItemType] = Mathf.Clamp(this.criticalItemIndex[criticalItemType], 0, this.dataCount - 1);
|
||||
|
||||
if (this.criticalItemIndex[CriticalItemType.UpToShow] >= this.criticalItemIndex[CriticalItemType.DownToShow])
|
||||
{
|
||||
// 偶然的情况 拖拽超出一屏
|
||||
this.ResetCriticalItems();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ShouldItemSeenAtIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.dataCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.EnsureItemRect(index);
|
||||
return new Rect(this.refRect.position - this.content.anchoredPosition, this.refRect.size).Overlaps(this.managedItems[index].rect);
|
||||
}
|
||||
|
||||
private bool ShouldItemFullySeenAtIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.dataCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.EnsureItemRect(index);
|
||||
return this.IsRectContains(new Rect(this.refRect.position - this.content.anchoredPosition, this.refRect.size), this.managedItems[index].rect);
|
||||
}
|
||||
|
||||
private bool IsRectContains(Rect outRect, Rect inRect, bool bothDimensions = false)
|
||||
{
|
||||
if (bothDimensions)
|
||||
{
|
||||
var xContains = (outRect.xMax >= inRect.xMax) && (outRect.xMin <= inRect.xMin);
|
||||
var yContains = (outRect.yMax >= inRect.yMax) && (outRect.yMin <= inRect.yMin);
|
||||
return xContains && yContains;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dir = (int)this.layoutType & flagScrollDirection;
|
||||
if (dir == 1)
|
||||
{
|
||||
// 垂直滚动 只计算y向
|
||||
return (outRect.yMax >= inRect.yMax) && (outRect.yMin <= inRect.yMin);
|
||||
}
|
||||
else
|
||||
{
|
||||
// = 0
|
||||
// 水平滚动 只计算x向
|
||||
return (outRect.xMax >= inRect.xMax) && (outRect.xMin <= inRect.xMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitPool()
|
||||
{
|
||||
var poolNode = new GameObject("POOL");
|
||||
poolNode.SetActive(false);
|
||||
poolNode.transform.SetParent(this.transform, false);
|
||||
this.itemPool = new SimpleObjPool<RectTransform>(
|
||||
this.poolSize,
|
||||
(RectTransform item) =>
|
||||
{
|
||||
item.transform.SetParent(poolNode.transform, false);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
GameObject itemObj = Instantiate(this.itemTemplate.gameObject);
|
||||
RectTransform item = itemObj.GetComponent<RectTransform>();
|
||||
itemObj.transform.SetParent(poolNode.transform, false);
|
||||
|
||||
item.anchorMin = Vector2.up;
|
||||
item.anchorMax = Vector2.up;
|
||||
item.pivot = Vector2.zero;
|
||||
|
||||
itemObj.SetActive(true);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGetItemForDataIndex(RectTransform item, int index)
|
||||
{
|
||||
this.SetDataForItemAtIndex(item, index);
|
||||
item.transform.SetParent(this.content, false);
|
||||
}
|
||||
|
||||
private void SetDataForItemAtIndex(RectTransform item, int index)
|
||||
{
|
||||
if (this.updateFunc != null)
|
||||
{
|
||||
this.updateFunc(index, item);
|
||||
}
|
||||
|
||||
this.SetPosForItemAtIndex(item, index);
|
||||
}
|
||||
|
||||
private void SetPosForItemAtIndex(RectTransform item, int index)
|
||||
{
|
||||
this.EnsureItemRect(index);
|
||||
Rect r = this.managedItems[index].rect;
|
||||
item.localPosition = r.position;
|
||||
item.sizeDelta = r.size;
|
||||
}
|
||||
|
||||
private Vector2 GetItemSize(int index)
|
||||
{
|
||||
if (index >= 0 && index <= this.dataCount)
|
||||
{
|
||||
if (this.itemSizeFunc != null)
|
||||
{
|
||||
return this.itemSizeFunc(index);
|
||||
}
|
||||
}
|
||||
|
||||
return this.defaultItemSize;
|
||||
}
|
||||
|
||||
private RectTransform GetNewItem(int index)
|
||||
{
|
||||
RectTransform item;
|
||||
if (this.itemGetFunc != null)
|
||||
{
|
||||
item = this.itemGetFunc(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = this.itemPool.Get();
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private void RecycleOldItem(RectTransform item)
|
||||
{
|
||||
if (this.itemRecycleFunc != null)
|
||||
{
|
||||
this.itemRecycleFunc(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.itemPool.Recycle(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitScrollView()
|
||||
{
|
||||
this.initialized = true;
|
||||
|
||||
// 根据设置来控制原ScrollRect的滚动方向
|
||||
var dir = (int)this.layoutType & flagScrollDirection;
|
||||
this.vertical = dir == 1;
|
||||
this.horizontal = dir == 0;
|
||||
|
||||
this.content.pivot = Vector2.up;
|
||||
this.content.anchorMin = Vector2.up;
|
||||
this.content.anchorMax = Vector2.up;
|
||||
this.content.anchoredPosition = Vector2.zero;
|
||||
|
||||
this.InitPool();
|
||||
this.UpdateRefRect();
|
||||
}
|
||||
|
||||
// refRect是在Content节点下的 viewport的 rect
|
||||
private void UpdateRefRect()
|
||||
{
|
||||
/*
|
||||
* WorldCorners
|
||||
*
|
||||
* 1 ------- 2
|
||||
* | |
|
||||
* | |
|
||||
* 0 ------- 3
|
||||
*
|
||||
*/
|
||||
|
||||
if (!CanvasUpdateRegistry.IsRebuildingLayout())
|
||||
{
|
||||
Canvas.ForceUpdateCanvases();
|
||||
}
|
||||
|
||||
this.viewRect.GetWorldCorners(this.viewWorldConers);
|
||||
this.rectCorners[0] = this.content.transform.InverseTransformPoint(this.viewWorldConers[0]);
|
||||
this.rectCorners[1] = this.content.transform.InverseTransformPoint(this.viewWorldConers[2]);
|
||||
this.refRect = new Rect((Vector2)this.rectCorners[0] - this.content.anchoredPosition, this.rectCorners[1] - this.rectCorners[0]);
|
||||
}
|
||||
|
||||
private void MovePos(ref Vector2 pos, Vector2 size)
|
||||
{
|
||||
// 注意 所有的rect都是左下角为基准
|
||||
switch (this.layoutType)
|
||||
{
|
||||
case ItemLayoutType.Vertical:
|
||||
// 垂直方向 向下移动
|
||||
pos.y -= size.y;
|
||||
break;
|
||||
case ItemLayoutType.Horizontal:
|
||||
// 水平方向 向右移动
|
||||
pos.x += size.x;
|
||||
break;
|
||||
case ItemLayoutType.VerticalThenHorizontal:
|
||||
pos.y -= size.y;
|
||||
if (pos.y <= -this.refRect.height)
|
||||
{
|
||||
pos.y = 0;
|
||||
pos.x += size.x;
|
||||
}
|
||||
|
||||
break;
|
||||
case ItemLayoutType.HorizontalThenVertical:
|
||||
pos.x += size.x;
|
||||
if (pos.x >= this.refRect.width)
|
||||
{
|
||||
pos.x = 0;
|
||||
pos.y -= size.y;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// const int 代替 enum 减少 (int)和(CriticalItemType)转换
|
||||
protected static class CriticalItemType
|
||||
{
|
||||
public static byte UpToHide = 0;
|
||||
public static byte DownToHide = 1;
|
||||
public static byte UpToShow = 2;
|
||||
public static byte DownToShow = 3;
|
||||
}
|
||||
|
||||
private class ScrollItemWithRect
|
||||
{
|
||||
// scroll item 身上的 RectTransform组件
|
||||
public RectTransform item;
|
||||
|
||||
// scroll item 在scrollview中的位置
|
||||
public Rect rect;
|
||||
|
||||
// rect 是否需要更新
|
||||
public bool rectDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bac7eb1be8f6f40459d388c929d1946f
|
||||
timeCreated: 1533042733
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,245 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ScrollViewEx.cs" company="AillieoTech">
|
||||
// Copyright (c) AillieoTech. All rights reserved.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace TapSDK.UI.AillieoTech
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
[DisallowMultipleComponent]
|
||||
public class ScrollViewEx : ScrollView
|
||||
{
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("m_pageSize")]
|
||||
private int pageSize = 50;
|
||||
|
||||
private int startOffset = 0;
|
||||
|
||||
private Func<int> realItemCountFunc;
|
||||
|
||||
private Vector2 lastPosition;
|
||||
|
||||
private bool reloadFlag = false;
|
||||
|
||||
public override void SetUpdateFunc(Action<int, RectTransform> func)
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
var f = func;
|
||||
func = (index, rect) =>
|
||||
{
|
||||
f(index + this.startOffset, rect);
|
||||
};
|
||||
}
|
||||
|
||||
base.SetUpdateFunc(func);
|
||||
}
|
||||
|
||||
public override void SetItemSizeFunc(Func<int, Vector2> func)
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
var f = func;
|
||||
func = (index) =>
|
||||
{
|
||||
return f(index + this.startOffset);
|
||||
};
|
||||
}
|
||||
|
||||
base.SetItemSizeFunc(func);
|
||||
}
|
||||
|
||||
public override void SetItemCountFunc(Func<int> func)
|
||||
{
|
||||
this.realItemCountFunc = func;
|
||||
if (func != null)
|
||||
{
|
||||
var f = func;
|
||||
func = () => Mathf.Min(f(), this.pageSize);
|
||||
}
|
||||
|
||||
base.SetItemCountFunc(func);
|
||||
}
|
||||
|
||||
public override void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (this.reloadFlag)
|
||||
{
|
||||
this.reloadFlag = false;
|
||||
this.OnEndDrag(eventData);
|
||||
this.OnBeginDrag(eventData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnDrag(eventData);
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
this.lastPosition = Vector2.up;
|
||||
this.onValueChanged.AddListener(this.OnValueChanged);
|
||||
}
|
||||
|
||||
protected override void InternalScrollTo(int index)
|
||||
{
|
||||
var count = 0;
|
||||
if (this.realItemCountFunc != null)
|
||||
{
|
||||
count = this.realItemCountFunc();
|
||||
}
|
||||
|
||||
index = Mathf.Clamp(index, 0, count - 1);
|
||||
this.startOffset = Mathf.Clamp(index - (this.pageSize / 2), 0, count - this.itemCountFunc());
|
||||
this.UpdateData(true);
|
||||
base.InternalScrollTo(index - this.startOffset);
|
||||
}
|
||||
|
||||
private void OnValueChanged(Vector2 position)
|
||||
{
|
||||
int toShow;
|
||||
int critical;
|
||||
bool downward;
|
||||
int pin;
|
||||
|
||||
Vector2 delta = position - this.lastPosition;
|
||||
this.lastPosition = position;
|
||||
|
||||
this.reloadFlag = false;
|
||||
|
||||
if (((int)this.layoutType & flagScrollDirection) == 1)
|
||||
{
|
||||
// 垂直滚动 只计算y向
|
||||
if (delta.y < 0)
|
||||
{
|
||||
// 向上
|
||||
toShow = this.criticalItemIndex[CriticalItemType.DownToShow];
|
||||
critical = this.pageSize - 1;
|
||||
if (toShow < critical)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pin = critical - 1;
|
||||
downward = false;
|
||||
}
|
||||
else if (delta.y > 0)
|
||||
{
|
||||
// 向下
|
||||
toShow = this.criticalItemIndex[CriticalItemType.UpToShow];
|
||||
critical = 0;
|
||||
if (toShow > critical)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pin = critical + 1;
|
||||
downward = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// = 0
|
||||
// 水平滚动 只计算x向
|
||||
if (delta.x > 0)
|
||||
{
|
||||
// 向右
|
||||
toShow = this.criticalItemIndex[CriticalItemType.UpToShow];
|
||||
critical = 0;
|
||||
if (toShow > critical)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pin = critical + 1;
|
||||
downward = true;
|
||||
}
|
||||
else if (delta.x < 0)
|
||||
{
|
||||
// 向左
|
||||
toShow = this.criticalItemIndex[CriticalItemType.DownToShow];
|
||||
critical = this.pageSize - 1;
|
||||
if (toShow < critical)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pin = critical - 1;
|
||||
downward = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 该翻页了 翻半页吧
|
||||
var old = this.startOffset;
|
||||
if (downward)
|
||||
{
|
||||
this.startOffset -= this.pageSize / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.startOffset += this.pageSize / 2;
|
||||
}
|
||||
|
||||
var realDataCount = 0;
|
||||
if (this.realItemCountFunc != null)
|
||||
{
|
||||
realDataCount = this.realItemCountFunc();
|
||||
}
|
||||
|
||||
this.startOffset = Mathf.Clamp(this.startOffset, 0, Mathf.Max(realDataCount - this.pageSize, 0));
|
||||
|
||||
if (old != this.startOffset)
|
||||
{
|
||||
this.reloadFlag = true;
|
||||
|
||||
// 记录 原先的速度
|
||||
Vector2 oldVelocity = this.velocity;
|
||||
|
||||
// 计算 pin元素的世界坐标
|
||||
Rect rect = this.GetItemLocalRect(pin);
|
||||
|
||||
Vector2 oldWorld = this.content.TransformPoint(rect.position);
|
||||
var dataCount = 0;
|
||||
if (this.itemCountFunc != null)
|
||||
{
|
||||
dataCount = this.itemCountFunc();
|
||||
}
|
||||
|
||||
if (dataCount > 0)
|
||||
{
|
||||
this.EnsureItemRect(0);
|
||||
if (dataCount > 1)
|
||||
{
|
||||
this.EnsureItemRect(dataCount - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 pin元素的世界坐标 计算出content的position
|
||||
var pin2 = pin + old - this.startOffset;
|
||||
Rect rect2 = this.GetItemLocalRect(pin2);
|
||||
Vector2 newWorld = this.content.TransformPoint(rect2.position);
|
||||
Vector2 deltaWorld = newWorld - oldWorld;
|
||||
Vector2 deltaLocal = this.content.InverseTransformVector(deltaWorld);
|
||||
this.SetContentAnchoredPosition(this.content.anchoredPosition - deltaLocal);
|
||||
this.UpdateData(true);
|
||||
this.velocity = oldVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17ff80d59e3504ef385ee40177f0b4a4
|
||||
timeCreated: 1533042733
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Internal/Utils.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Internal/Utils.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49cacbc5313da4067862ab3b83239ae5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
86
Assets/TapSDK/Core/Runtime/Internal/Utils/BridgeUtils.cs
Normal file
86
Assets/TapSDK/Core/Runtime/Internal/Utils/BridgeUtils.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils {
|
||||
public static class BridgeUtils {
|
||||
public static bool IsSupportMobilePlatform => Application.platform == RuntimePlatform.Android ||
|
||||
Application.platform == RuntimePlatform.IPhonePlayer;
|
||||
|
||||
public static bool IsSupportStandalonePlatform => Application.platform == RuntimePlatform.OSXPlayer ||
|
||||
Application.platform == RuntimePlatform.WindowsPlayer ||
|
||||
Application.platform == RuntimePlatform.LinuxPlayer;
|
||||
|
||||
public static object CreateBridgeImplementation(Type interfaceType, string startWith) {
|
||||
// 跳过初始化直接使用 TapLoom会在子线程被TapSDK.Core.BridgeCallback.Invoke 初始化
|
||||
TapLoom.Initialize();
|
||||
// 获取所有程序集
|
||||
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
// 查找以 startWith 开头的程序集
|
||||
var matchingAssemblies = allAssemblies
|
||||
.Where(assembly => assembly.GetName().FullName.StartsWith(startWith))
|
||||
.ToList();
|
||||
// 从匹配的程序集中查找实现指定接口的类
|
||||
List<Type> allCandidateTypes = new List<Type>();
|
||||
foreach (var assembly in matchingAssemblies) {
|
||||
try {
|
||||
var types = assembly.GetTypes()
|
||||
.Where(type => type.IsClass && interfaceType.IsAssignableFrom(type))
|
||||
.ToList();
|
||||
foreach (var type in types) {
|
||||
allCandidateTypes.Add(type);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
TapLog.Error($"[TapTap] 获取程序集 {assembly.GetName().Name} 中的类型时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
// 使用原始逻辑查找实现类
|
||||
Type bridgeImplementationType = null;
|
||||
try {
|
||||
bridgeImplementationType = matchingAssemblies
|
||||
.SelectMany(assembly => {
|
||||
try {
|
||||
return assembly.GetTypes();
|
||||
} catch {
|
||||
return Type.EmptyTypes;
|
||||
}
|
||||
})
|
||||
.SingleOrDefault(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass);
|
||||
// 如果使用 SingleOrDefault 没找到,尝试使用 FirstOrDefault
|
||||
if (bridgeImplementationType == null && allCandidateTypes.Count > 0) {
|
||||
bridgeImplementationType = allCandidateTypes.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
TapLog.Error($"[TapTap] 在查找实现类时发生异常: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
if (bridgeImplementationType == null) {
|
||||
// 尝试在所有程序集中查找实现(不限制命名空间前缀)
|
||||
if (matchingAssemblies.Count == 0) {
|
||||
List<Type> implementationsInAllAssemblies = new List<Type>();
|
||||
foreach (var assembly in allAssemblies) {
|
||||
try {
|
||||
var types = assembly.GetTypes()
|
||||
.Where(type => type.IsClass && !type.IsAbstract && interfaceType.IsAssignableFrom(type))
|
||||
.ToList();
|
||||
if (types.Count > 0) {
|
||||
implementationsInAllAssemblies.AddRange(types);
|
||||
}
|
||||
}
|
||||
catch { /* 忽略错误 */ }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Activator.CreateInstance(bridgeImplementationType);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56e7026e809cb4a4ead9bf6479d212a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
47
Assets/TapSDK/Core/Runtime/Internal/Utils/EventManager.cs
Normal file
47
Assets/TapSDK/Core/Runtime/Internal/Utils/EventManager.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TapSDK.UI;
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils
|
||||
{
|
||||
public sealed class EventManager : Singleton<EventManager>
|
||||
{
|
||||
public const string OnApplicationPause = "OnApplicationPause";
|
||||
public const string OnApplicationQuit = "OnApplicationQuit";
|
||||
|
||||
public const string OnComplianceUserChanged = "OnComplianceUserChanged";
|
||||
public const string OnTapUserChanged = "OnTapUserChanged";
|
||||
public const string IsLaunchedFromTapTapPCFinished = "IsLaunchedFromTapTapPCFinished";
|
||||
private Dictionary<string, Action<object>> eventRegistries = new Dictionary<string, Action<object>>();
|
||||
|
||||
public static void AddListener(string eventName, Action<object> listener) {
|
||||
if (listener == null) return;
|
||||
if (string.IsNullOrEmpty(eventName)) return;
|
||||
Action<object> thisEvent;
|
||||
if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) {
|
||||
thisEvent += listener;
|
||||
Instance.eventRegistries[eventName] = thisEvent;
|
||||
} else {
|
||||
thisEvent += listener;
|
||||
Instance.eventRegistries.Add(eventName, thisEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveListener(string eventName, Action<object> listener) {
|
||||
if (listener == null) return;
|
||||
if (string.IsNullOrEmpty(eventName)) return;
|
||||
Action<object> thisEvent;
|
||||
if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) {
|
||||
thisEvent -= listener;
|
||||
Instance.eventRegistries[eventName] = thisEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void TriggerEvent(string eventName, object message) {
|
||||
Action<object> thisEvent = null;
|
||||
if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) {
|
||||
thisEvent.Invoke(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3283bdf1ebdf64e859e7bcff4043ac04
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
Assets/TapSDK/Core/Runtime/Internal/Utils/ImageUtils.cs
Normal file
158
Assets/TapSDK/Core/Runtime/Internal/Utils/ImageUtils.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils {
|
||||
public class ImageUtils {
|
||||
private readonly static string OldCacheDirName = "tap-cache";
|
||||
private readonly static MD5 md5 = MD5.Create();
|
||||
|
||||
private readonly static Dictionary<string, WeakReference<Texture>> cachedTextures = new Dictionary<string, WeakReference<Texture>>();
|
||||
|
||||
public static async Task<Texture> LoadImage(string url, int timeout = 30, bool useMemoryCache = true) {
|
||||
if (string.IsNullOrEmpty(url)) {
|
||||
TapLog.Warning(string.Format($"ImageUtils Fetch image is null! url is null or empty!"));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cachedTextures.TryGetValue(url, out WeakReference<Texture> refTex) &&
|
||||
refTex.TryGetTarget(out Texture tex)) {
|
||||
// 从内存加载
|
||||
return tex;
|
||||
} else {
|
||||
try {
|
||||
// 从本地缓存加载
|
||||
Texture cachedImage = await LoadCachedImaged(url, timeout);
|
||||
|
||||
if (useMemoryCache) {
|
||||
cachedTextures[url] = new WeakReference<Texture>(cachedImage);
|
||||
}
|
||||
|
||||
return cachedImage;
|
||||
} catch (Exception e) {
|
||||
TapLog.Warning(e.Message);
|
||||
try {
|
||||
// 从网络加载
|
||||
Texture2D newTex = await FetchImage(url, timeout);
|
||||
|
||||
if (useMemoryCache) {
|
||||
cachedTextures[url] = new WeakReference<Texture>(newTex);
|
||||
}
|
||||
|
||||
// 缓存到本地
|
||||
_ = CacheImage(url, newTex);
|
||||
|
||||
return newTex;
|
||||
} catch (Exception ex) {
|
||||
TapLog.Warning(ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Texture2D> FetchImage(string url, int timeout = 30) {
|
||||
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url)) {
|
||||
request.timeout = timeout;
|
||||
UnityWebRequestAsyncOperation operation = request.SendWebRequest();
|
||||
while (!operation.isDone) {
|
||||
await Task.Delay(30);
|
||||
}
|
||||
|
||||
if (request.isNetworkError || request.isHttpError) {
|
||||
throw new Exception("Fetch image error.");
|
||||
} else {
|
||||
Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler)?.texture;
|
||||
if (texture == null) {
|
||||
TapLog.Warning($"ImageUtils Fetch image is null! url: {url}");
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<Texture> LoadCachedImaged(string url, int timeout = 30) {
|
||||
string cachedImagePath = GetCachedPath(url);
|
||||
if (!File.Exists(cachedImagePath)) {
|
||||
throw new Exception("No cached image.");
|
||||
}
|
||||
string cachedImageUrl = $"file://{cachedImagePath}";
|
||||
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(cachedImageUrl)) {
|
||||
request.timeout = timeout;
|
||||
UnityWebRequestAsyncOperation operation = request.SendWebRequest();
|
||||
while (!operation.isDone) {
|
||||
await Task.Delay(30);
|
||||
}
|
||||
|
||||
if (request.isNetworkError || request.isHttpError) {
|
||||
RemoveCachedImage(cachedImagePath);
|
||||
throw new Exception("Load cache image error.");
|
||||
} else {
|
||||
var texture = ((DownloadHandlerTexture)request.downloadHandler)?.texture;
|
||||
if (texture == null) {
|
||||
RemoveCachedImage(cachedImagePath);
|
||||
throw new Exception("Cached image is invalid.");
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task CacheImage(string url, Texture2D tex) {
|
||||
string cacheImagePath = GetCachedPath(url);
|
||||
// 写入缓存
|
||||
byte[] imageData = tex.EncodeToPNG();
|
||||
using (FileStream fileStream = new FileStream(cacheImagePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) {
|
||||
await fileStream.WriteAsync(imageData, 0, imageData.Length);
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveCachedImage(string cachedImagePath) {
|
||||
try {
|
||||
File.Delete(cachedImagePath);
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static string ToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < bytes.Length; i++) {
|
||||
sb.Append(bytes[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static string GetCachedPath(string url) {
|
||||
string cachedHashName = ToHex(md5.ComputeHash(Encoding.UTF8.GetBytes(url)));
|
||||
return Path.Combine(CacheDirPath, cachedHashName);
|
||||
}
|
||||
|
||||
static string CacheDirPath {
|
||||
get {
|
||||
|
||||
string newCacheDirPath = Path.Combine(Application.persistentDataPath, OldCacheDirName);
|
||||
if (TapTapSDK.taptapSdkOptions != null && !string.IsNullOrEmpty(TapTapSDK.taptapSdkOptions.clientId) ){
|
||||
newCacheDirPath = Path.Combine(Application.persistentDataPath, OldCacheDirName + "_" + TapTapSDK.taptapSdkOptions.clientId);
|
||||
}
|
||||
if(!Directory.Exists(newCacheDirPath)) {
|
||||
string oldPath = Path.Combine(Application.persistentDataPath, OldCacheDirName);
|
||||
if (Directory.Exists(oldPath)) {
|
||||
Directory.Move(oldPath, newCacheDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(newCacheDirPath)) {
|
||||
Directory.CreateDirectory(newCacheDirPath);
|
||||
}
|
||||
return newCacheDirPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/Utils/ImageUtils.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/Utils/ImageUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82045c1fa54ce4e6ab3e286669044f15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
241
Assets/TapSDK/Core/Runtime/Internal/Utils/TapLoom.cs
Normal file
241
Assets/TapSDK/Core/Runtime/Internal/Utils/TapLoom.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils
|
||||
{
|
||||
public class TapLoom : MonoBehaviour
|
||||
{
|
||||
public static int maxThreads = 8;
|
||||
static int numThreads;
|
||||
|
||||
private static TapLoom _current;
|
||||
private int _count;
|
||||
|
||||
private bool isPause = false;
|
||||
|
||||
// 记录主线程 ID
|
||||
private static int _mainThreadId = -1;
|
||||
|
||||
public static TapLoom Current
|
||||
{
|
||||
get
|
||||
{
|
||||
Initialize();
|
||||
return _current;
|
||||
}
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_current = this;
|
||||
initialized = true;
|
||||
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
static bool initialized;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
return;
|
||||
initialized = true;
|
||||
var g = new GameObject("TapLoom");
|
||||
DontDestroyOnLoad(g);
|
||||
_current = g.AddComponent<TapLoom>();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Action> _actions = new List<Action>();
|
||||
|
||||
public struct DelayedQueueItem
|
||||
{
|
||||
public float time;
|
||||
public Action action;
|
||||
}
|
||||
|
||||
private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
|
||||
|
||||
List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
|
||||
|
||||
public static void QueueOnMainThread(Action action)
|
||||
{
|
||||
QueueOnMainThread(action, 0f);
|
||||
}
|
||||
|
||||
public static void QueueOnMainThread(Action action, float time)
|
||||
{
|
||||
if (time != 0)
|
||||
{
|
||||
lock (Current._delayed)
|
||||
{
|
||||
Current._delayed.Add(
|
||||
new DelayedQueueItem { time = Time.time, action = action }
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Current != null && Current._actions != null)
|
||||
{
|
||||
lock (Current._actions)
|
||||
{
|
||||
Current._actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在线程池中执行任务,非主线程
|
||||
/// </summary>
|
||||
/// <param name="a"> 任务 </param>
|
||||
/// <returns></returns>
|
||||
public static Thread RunAsync(Action a)
|
||||
{
|
||||
Initialize();
|
||||
while (numThreads >= maxThreads)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
Interlocked.Increment(ref numThreads);
|
||||
ThreadPool.QueueUserWorkItem(RunAction, a);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阻塞式在主线程执行任务并返回值,当发生异常或超时时,返回默认值
|
||||
/// </summary>
|
||||
/// <param name="func"> 任务 </param>
|
||||
/// <param name="defaultValue"> 默认值 </param>
|
||||
/// <param name="timeout"> 超时时间,默认 100 毫秒</param>
|
||||
/// <returns> 任务返回值或默认值 </returns>
|
||||
public static object RunOnMainThreadSync(
|
||||
Func<object> func,
|
||||
object defaultValue,
|
||||
int timeout = 100
|
||||
)
|
||||
{
|
||||
// 主线程未就绪,直接返回默认值
|
||||
if (_mainThreadId < 0)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
// 已经在主线程,直接执行
|
||||
if (Thread.CurrentThread.ManagedThreadId == _mainThreadId)
|
||||
{
|
||||
return func();
|
||||
}
|
||||
object result = defaultValue;
|
||||
var evt = new ManualResetEvent(false);
|
||||
try
|
||||
{
|
||||
QueueOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
result = func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TapLogger.Error("RunOnMainThreadSync failed " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
evt.Set();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// evt 已被释放,直接忽略
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
evt.WaitOne(timeout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
evt.Dispose(); // WaitOne 返回后再 Dispose
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void RunAction(object action)
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)action)();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref numThreads);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (_current == this)
|
||||
{
|
||||
_current = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use this for initialization
|
||||
void Start() { }
|
||||
|
||||
List<Action> _currentActions = new List<Action>();
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
lock (_actions)
|
||||
{
|
||||
_currentActions.Clear();
|
||||
_currentActions.AddRange(_actions);
|
||||
_actions.Clear();
|
||||
}
|
||||
foreach (var a in _currentActions)
|
||||
{
|
||||
a();
|
||||
}
|
||||
lock (_delayed)
|
||||
{
|
||||
_currentDelayed.Clear();
|
||||
_currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
|
||||
foreach (var item in _currentDelayed)
|
||||
_delayed.Remove(item);
|
||||
}
|
||||
foreach (var delayed in _currentDelayed)
|
||||
{
|
||||
delayed.action();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (pauseStatus && isPause == false)
|
||||
{
|
||||
isPause = true;
|
||||
EventManager.TriggerEvent(EventManager.OnApplicationPause, true);
|
||||
}
|
||||
else if (!pauseStatus && isPause)
|
||||
{
|
||||
isPause = false;
|
||||
EventManager.TriggerEvent(EventManager.OnApplicationPause, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
EventManager.TriggerEvent(EventManager.OnApplicationQuit, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/Utils/TapLoom.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/Utils/TapLoom.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b2d946237d1b489c8e6f14fd553878e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
69
Assets/TapSDK/Core/Runtime/Internal/Utils/TapMessage.cs
Normal file
69
Assets/TapSDK/Core/Runtime/Internal/Utils/TapMessage.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
public class TapMessage : MonoBehaviour {
|
||||
|
||||
public enum Time
|
||||
{
|
||||
threeSecond,
|
||||
twoSecond,
|
||||
oneSecond
|
||||
};
|
||||
public enum Position
|
||||
{
|
||||
top,
|
||||
bottom
|
||||
};
|
||||
public static void ShowMessage ( string msg, TapMessage.Position position, TapMessage.Time time )
|
||||
{
|
||||
|
||||
//Load message prefab from resources folder
|
||||
GameObject messagePrefab = Resources.Load ( "TapMessage" ) as GameObject;
|
||||
//Get container object of message
|
||||
GameObject containerObject = messagePrefab.gameObject.transform.GetChild ( 0 ).gameObject;
|
||||
//Get text object
|
||||
GameObject textObject = containerObject.gameObject.transform.GetChild ( 0 ).GetChild ( 0 ).gameObject;
|
||||
//Get text property
|
||||
Text msg_text = textObject.GetComponent<Text> ( );
|
||||
//Set message to text ui
|
||||
msg_text.text = msg;
|
||||
//Set position of container object of message
|
||||
SetPosition ( containerObject.GetComponent<RectTransform> ( ), position );
|
||||
//Spawn message object with all changes
|
||||
GameObject clone = Instantiate ( messagePrefab );
|
||||
// Destroy clone of message object according to the time
|
||||
RemoveClone ( clone, time );
|
||||
}
|
||||
|
||||
private static void SetPosition ( RectTransform rectTransform, Position position )
|
||||
{
|
||||
if (position == Position.top)
|
||||
{
|
||||
rectTransform.anchorMin = new Vector2 ( 0.5f, 1f );
|
||||
rectTransform.anchorMax = new Vector2 ( 0.5f, 1f );
|
||||
rectTransform.anchoredPosition = new Vector3 ( 0.5f, -100f, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
rectTransform.anchorMin = new Vector2 ( 0.5f, 0 );
|
||||
rectTransform.anchorMax = new Vector2 ( 0.5f, 0 );
|
||||
rectTransform.anchoredPosition = new Vector3 ( 0.5f, 100f, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveClone ( GameObject clone, Time time )
|
||||
{
|
||||
if (time == Time.oneSecond)
|
||||
{
|
||||
Destroy ( clone.gameObject, 1f );
|
||||
}
|
||||
else if (time == Time.twoSecond)
|
||||
{
|
||||
Destroy ( clone.gameObject, 2f );
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy ( clone.gameObject, 3f );
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Assets/TapSDK/Core/Runtime/Internal/Utils/TapMessage.cs.meta
Normal file
13
Assets/TapSDK/Core/Runtime/Internal/Utils/TapMessage.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87a6eaa9efa41844890325132c22595a
|
||||
timeCreated: 1532842822
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using TapSDK.Core.Internal.Log;
|
||||
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils
|
||||
{
|
||||
public class TapVerifyInitStateUtils
|
||||
{
|
||||
|
||||
public static void ShowVerifyErrorMsg(string error, string errorMsg)
|
||||
{
|
||||
if (error != null || error.Length > 0)
|
||||
{
|
||||
TapMessage.ShowMessage(error, TapMessage.Position.bottom, TapMessage.Time.twoSecond);
|
||||
}
|
||||
if (errorMsg != null && errorMsg.Length > 0)
|
||||
{
|
||||
TapLog.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f1bb7b9efe304ef2a058482236a3e9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/TapSDK/Core/Runtime/Internal/Utils/UrlUtils.cs
Normal file
34
Assets/TapSDK/Core/Runtime/Internal/Utils/UrlUtils.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace TapSDK.Core.Internal.Utils {
|
||||
public class UrlUtils {
|
||||
public static NameValueCollection ParseQueryString(string query) {
|
||||
NameValueCollection nvc = new NameValueCollection();
|
||||
|
||||
if (query.StartsWith("?")) {
|
||||
query = query.Substring(1);
|
||||
}
|
||||
|
||||
foreach (var param in query.Split('&')) {
|
||||
string[] pair = param.Split('=');
|
||||
if (pair.Length == 2) {
|
||||
string key = Uri.UnescapeDataString(pair[0]);
|
||||
string value = Uri.UnescapeDataString(pair[1]);
|
||||
nvc[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return nvc;
|
||||
}
|
||||
|
||||
public static string ToQueryString(NameValueCollection nvc) {
|
||||
var array = (from key in nvc.AllKeys
|
||||
from value in nvc.GetValues(key)
|
||||
select $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}"
|
||||
).ToArray();
|
||||
return string.Join("&", array);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Internal/Utils/UrlUtils.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Internal/Utils/UrlUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 022260b0620b74f97971e1314da78c69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Public.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Public.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b758418efc1d49e58b2aad6c3a456d4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
181
Assets/TapSDK/Core/Runtime/Public/DataStorage.cs
Normal file
181
Assets/TapSDK/Core/Runtime/Public/DataStorage.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using TapSDK.Core.Internal.Log;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public static class DataStorage
|
||||
{
|
||||
private static Dictionary<string, string> dataCache;
|
||||
private static byte[] Keys = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
|
||||
|
||||
// 缓存 MacAddress,避免多次获取
|
||||
private static string cachedMacAddress;
|
||||
public static void SaveString(string key, string value)
|
||||
{
|
||||
string storageKey = GenerateStorageKey(key);
|
||||
SaveStringToCache(storageKey, value);
|
||||
string encodeValue = EncodeString(value);
|
||||
PlayerPrefs.SetString(storageKey, encodeValue);
|
||||
}
|
||||
|
||||
public static string LoadString(string key)
|
||||
{
|
||||
string storageKey = GenerateStorageKey(key);
|
||||
string value = LoadStringFromCache(storageKey);
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
value = PlayerPrefs.HasKey(storageKey) ? DecodeString(PlayerPrefs.GetString(storageKey)) : null;
|
||||
// 尝试从本地获取旧版本数据
|
||||
if (value == null)
|
||||
{
|
||||
value = PlayerPrefs.HasKey(key) ? DecodeString(PlayerPrefs.GetString(key)) : null;
|
||||
// 旧版本存在有效数据时,转储为新的 storageKey
|
||||
if (value != null)
|
||||
{
|
||||
PlayerPrefs.SetString(storageKey, EncodeString(value));
|
||||
PlayerPrefs.DeleteKey(key);
|
||||
}
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
SaveStringToCache(storageKey, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void RemoveCacheKey(string key)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
PlayerPrefs.DeleteKey(key);
|
||||
PlayerPrefs.DeleteKey(GenerateStorageKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveStringToCache(string key, string value)
|
||||
{
|
||||
if (dataCache == null)
|
||||
{
|
||||
dataCache = new Dictionary<string, string>();
|
||||
}
|
||||
if (dataCache.ContainsKey(key))
|
||||
{
|
||||
dataCache[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataCache.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static string LoadStringFromCache(string key)
|
||||
{
|
||||
if (dataCache == null)
|
||||
{
|
||||
dataCache = new Dictionary<string, string>();
|
||||
}
|
||||
return dataCache.ContainsKey(key) ? dataCache[key] : null;
|
||||
}
|
||||
|
||||
private static string EncodeString(string encryptString)
|
||||
{
|
||||
if (Platform.IsAndroid() || Platform.IsIOS())
|
||||
{
|
||||
return encryptString;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(GetMacAddress().Substring(0, 8));
|
||||
byte[] rgbIV = Keys;
|
||||
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
|
||||
DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider();
|
||||
MemoryStream mStream = new MemoryStream();
|
||||
CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
cStream.Close();
|
||||
return Convert.ToBase64String(mStream.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return encryptString;
|
||||
}
|
||||
}
|
||||
|
||||
private static string DecodeString(string decryptString)
|
||||
{
|
||||
if (Platform.IsAndroid() || Platform.IsIOS())
|
||||
{
|
||||
return decryptString;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(GetMacAddress().Substring(0, 8));
|
||||
byte[] rgbIV = Keys;
|
||||
byte[] inputByteArray = Convert.FromBase64String(decryptString);
|
||||
DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider();
|
||||
MemoryStream mStream = new MemoryStream();
|
||||
CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
cStream.Close();
|
||||
return Encoding.UTF8.GetString(mStream.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TapLog.Log(e.Message);
|
||||
return decryptString;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetMacAddress()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cachedMacAddress))
|
||||
{
|
||||
return cachedMacAddress;
|
||||
}
|
||||
string physicalAddress = "FFFFFFFFFFFF";
|
||||
if (Platform.IsAndroid() || Platform.IsIOS())
|
||||
{
|
||||
return physicalAddress;
|
||||
}
|
||||
NetworkInterface[] nice = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (NetworkInterface adaper in nice)
|
||||
{
|
||||
if (adaper.Description == "en0")
|
||||
{
|
||||
physicalAddress = adaper.GetPhysicalAddress().ToString();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
physicalAddress = adaper.GetPhysicalAddress().ToString();
|
||||
|
||||
if (physicalAddress != "")
|
||||
{
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
cachedMacAddress = physicalAddress;
|
||||
return physicalAddress;
|
||||
}
|
||||
|
||||
// 生成存储在本地文件中 key
|
||||
private static string GenerateStorageKey(string originKey){
|
||||
if(TapTapSDK.taptapSdkOptions != null && !string.IsNullOrEmpty(TapTapSDK.taptapSdkOptions.clientId)){
|
||||
return originKey + "_" + TapTapSDK.taptapSdkOptions.clientId;
|
||||
}
|
||||
return originKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/DataStorage.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/DataStorage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32c169d8c12eb4e9598b83c11ad84f48
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
6
Assets/TapSDK/Core/Runtime/Public/ITapPropertiesProxy.cs
Normal file
6
Assets/TapSDK/Core/Runtime/Public/ITapPropertiesProxy.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
namespace TapSDK.Core {
|
||||
public interface ITapPropertiesProxy {
|
||||
string GetProperties();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c03335ad3e42d4092aa308d6503ce66a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
636
Assets/TapSDK/Core/Runtime/Public/Json.cs
Normal file
636
Assets/TapSDK/Core/Runtime/Public/Json.cs
Normal file
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Calvin Rien
|
||||
*
|
||||
* Based on the JSON parser by Patrick van Bergen
|
||||
* http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
|
||||
*
|
||||
* Simplified it so that it doesn't throw exceptions
|
||||
* and can be used in Unity iPhone with maximum code stripping.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
|
||||
//Serializer,Serialize
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
// Example usage:
|
||||
//
|
||||
// using UnityEngine;
|
||||
// using System.Collections;
|
||||
// using System.Collections.Generic;
|
||||
// using MiniJSON;
|
||||
//
|
||||
// public class MiniJSONTest : MonoBehaviour {
|
||||
// void Start () {
|
||||
// var jsonString = "{ \"array\": [1.44,2,3], " +
|
||||
// "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
|
||||
// "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
|
||||
// "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
|
||||
// "\"int\": 65536, " +
|
||||
// "\"float\": 3.1415926, " +
|
||||
// "\"bool\": true, " +
|
||||
// "\"null\": null }";
|
||||
//
|
||||
// var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
|
||||
//
|
||||
// Debug.Log("deserialized: " + dict.GetType());
|
||||
// Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
|
||||
// Debug.Log("dict['string']: " + (string) dict["string"]);
|
||||
// Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles
|
||||
// Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
|
||||
// Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
|
||||
//
|
||||
// var str = Json.Serialize(dict);
|
||||
//
|
||||
// Debug.Log("serialized: " + str);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// This class encodes and decodes JSON strings.
|
||||
/// Spec. details, see http://www.json.org/
|
||||
///
|
||||
/// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
|
||||
/// All numbers are parsed to doubles.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Parses the string json into a value
|
||||
/// </summary>
|
||||
/// <param name="json">A JSON string.</param>
|
||||
/// <returns>An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false</returns>
|
||||
public static object Deserialize(string json)
|
||||
{
|
||||
// save the string for debug information
|
||||
if (json == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Parser.Parse(json);
|
||||
}
|
||||
|
||||
sealed class Parser : IDisposable
|
||||
{
|
||||
const string WORD_BREAK = "{}[],:\"";
|
||||
|
||||
public static bool IsWordBreak(char c)
|
||||
{
|
||||
return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1;
|
||||
}
|
||||
|
||||
enum TOKEN
|
||||
{
|
||||
NONE,
|
||||
CURLY_OPEN,
|
||||
CURLY_CLOSE,
|
||||
SQUARED_OPEN,
|
||||
SQUARED_CLOSE,
|
||||
COLON,
|
||||
COMMA,
|
||||
STRING,
|
||||
NUMBER,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NULL
|
||||
};
|
||||
|
||||
StringReader json;
|
||||
|
||||
Parser(string jsonString)
|
||||
{
|
||||
json = new StringReader(jsonString);
|
||||
}
|
||||
|
||||
public static object Parse(string jsonString)
|
||||
{
|
||||
using (var instance = new Parser(jsonString))
|
||||
{
|
||||
return instance.ParseValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
json.Dispose();
|
||||
json = null;
|
||||
}
|
||||
|
||||
Dictionary<string, object> ParseObject()
|
||||
{
|
||||
Dictionary<string, object> table = new Dictionary<string, object>();
|
||||
|
||||
// ditch opening brace
|
||||
json.Read();
|
||||
|
||||
// {
|
||||
while (true)
|
||||
{
|
||||
switch (NextToken)
|
||||
{
|
||||
case TOKEN.NONE:
|
||||
return null;
|
||||
case TOKEN.COMMA:
|
||||
continue;
|
||||
case TOKEN.CURLY_CLOSE:
|
||||
return table;
|
||||
default:
|
||||
// name
|
||||
string name = ParseString();
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// :
|
||||
if (NextToken != TOKEN.COLON)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// ditch the colon
|
||||
json.Read();
|
||||
|
||||
// value
|
||||
table[name] = ParseValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<object> ParseArray()
|
||||
{
|
||||
List<object> array = new List<object>();
|
||||
|
||||
// ditch opening bracket
|
||||
json.Read();
|
||||
|
||||
// [
|
||||
var parsing = true;
|
||||
while (parsing)
|
||||
{
|
||||
TOKEN nextToken = NextToken;
|
||||
|
||||
switch (nextToken)
|
||||
{
|
||||
case TOKEN.NONE:
|
||||
return null;
|
||||
case TOKEN.COMMA:
|
||||
continue;
|
||||
case TOKEN.SQUARED_CLOSE:
|
||||
parsing = false;
|
||||
break;
|
||||
default:
|
||||
object value = ParseByToken(nextToken);
|
||||
|
||||
array.Add(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
object ParseValue()
|
||||
{
|
||||
TOKEN nextToken = NextToken;
|
||||
return ParseByToken(nextToken);
|
||||
}
|
||||
|
||||
object ParseByToken(TOKEN token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case TOKEN.STRING:
|
||||
return ParseString();
|
||||
case TOKEN.NUMBER:
|
||||
return ParseNumber();
|
||||
case TOKEN.CURLY_OPEN:
|
||||
return ParseObject();
|
||||
case TOKEN.SQUARED_OPEN:
|
||||
return ParseArray();
|
||||
case TOKEN.TRUE:
|
||||
return true;
|
||||
case TOKEN.FALSE:
|
||||
return false;
|
||||
case TOKEN.NULL:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string ParseString()
|
||||
{
|
||||
StringBuilder s = new StringBuilder();
|
||||
char c;
|
||||
|
||||
// ditch opening quote
|
||||
json.Read();
|
||||
|
||||
bool parsing = true;
|
||||
while (parsing)
|
||||
{
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
parsing = false;
|
||||
break;
|
||||
}
|
||||
|
||||
c = NextChar;
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
parsing = false;
|
||||
break;
|
||||
case '\\':
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
parsing = false;
|
||||
break;
|
||||
}
|
||||
|
||||
c = NextChar;
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
s.Append(c);
|
||||
break;
|
||||
case 'b':
|
||||
s.Append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
s.Append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
s.Append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
s.Append('\r');
|
||||
break;
|
||||
case 't':
|
||||
s.Append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
var hex = new char[4];
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
hex[i] = NextChar;
|
||||
}
|
||||
|
||||
s.Append((char)Convert.ToInt32(new string(hex), 16));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
object ParseNumber()
|
||||
{
|
||||
string number = NextWord;
|
||||
|
||||
if (number.IndexOf('.') == -1)
|
||||
{
|
||||
long parsedInt;
|
||||
Int64.TryParse(number, NumberStyles.Number, CultureInfo.InvariantCulture, out parsedInt);
|
||||
return parsedInt;
|
||||
}
|
||||
|
||||
double parsedDouble;
|
||||
Double.TryParse(number, NumberStyles.Number, CultureInfo.InvariantCulture, out parsedDouble);
|
||||
return parsedDouble;
|
||||
}
|
||||
|
||||
void EatWhitespace()
|
||||
{
|
||||
while (Char.IsWhiteSpace(PeekChar))
|
||||
{
|
||||
json.Read();
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char PeekChar
|
||||
{
|
||||
get
|
||||
{
|
||||
return Convert.ToChar(json.Peek());
|
||||
}
|
||||
}
|
||||
|
||||
char NextChar
|
||||
{
|
||||
get
|
||||
{
|
||||
return Convert.ToChar(json.Read());
|
||||
}
|
||||
}
|
||||
|
||||
string NextWord
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder word = new StringBuilder();
|
||||
|
||||
while (!IsWordBreak(PeekChar))
|
||||
{
|
||||
word.Append(NextChar);
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return word.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
TOKEN NextToken
|
||||
{
|
||||
get
|
||||
{
|
||||
EatWhitespace();
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
return TOKEN.NONE;
|
||||
}
|
||||
|
||||
switch (PeekChar)
|
||||
{
|
||||
case '{':
|
||||
return TOKEN.CURLY_OPEN;
|
||||
case '}':
|
||||
json.Read();
|
||||
return TOKEN.CURLY_CLOSE;
|
||||
case '[':
|
||||
return TOKEN.SQUARED_OPEN;
|
||||
case ']':
|
||||
json.Read();
|
||||
return TOKEN.SQUARED_CLOSE;
|
||||
case ',':
|
||||
json.Read();
|
||||
return TOKEN.COMMA;
|
||||
case '"':
|
||||
return TOKEN.STRING;
|
||||
case ':':
|
||||
return TOKEN.COLON;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
return TOKEN.NUMBER;
|
||||
}
|
||||
|
||||
switch (NextWord)
|
||||
{
|
||||
case "false":
|
||||
return TOKEN.FALSE;
|
||||
case "true":
|
||||
return TOKEN.TRUE;
|
||||
case "null":
|
||||
return TOKEN.NULL;
|
||||
}
|
||||
|
||||
return TOKEN.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
|
||||
/// </summary>
|
||||
/// <param name="json">A Dictionary<string, object> / List<object></param>
|
||||
/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
|
||||
public static string Serialize(object obj)
|
||||
{
|
||||
return Serializer.Serialize(obj);
|
||||
}
|
||||
|
||||
sealed class Serializer
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
Serializer()
|
||||
{
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
|
||||
public static string Serialize(object obj)
|
||||
{
|
||||
var instance = new Serializer();
|
||||
|
||||
instance.SerializeValue(obj);
|
||||
|
||||
return instance.builder.ToString();
|
||||
}
|
||||
|
||||
void SerializeValue(object value)
|
||||
{
|
||||
IList asList;
|
||||
IDictionary asDict;
|
||||
string asStr;
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
builder.Append("null");
|
||||
}
|
||||
else if ((asStr = value as string) != null)
|
||||
{
|
||||
SerializeString(asStr);
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
builder.Append((bool)value ? "true" : "false");
|
||||
}
|
||||
else if ((asList = value as IList) != null)
|
||||
{
|
||||
SerializeArray(asList);
|
||||
}
|
||||
else if ((asDict = value as IDictionary) != null)
|
||||
{
|
||||
SerializeObject(asDict);
|
||||
}
|
||||
else if (value is char)
|
||||
{
|
||||
SerializeString(new string((char)value, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeOther(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeObject(IDictionary obj)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
builder.Append('{');
|
||||
|
||||
foreach (object e in obj.Keys)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
}
|
||||
|
||||
SerializeString(e.ToString());
|
||||
builder.Append(':');
|
||||
|
||||
SerializeValue(obj[e]);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
builder.Append('}');
|
||||
}
|
||||
|
||||
void SerializeArray(IList anArray)
|
||||
{
|
||||
builder.Append('[');
|
||||
|
||||
bool first = true;
|
||||
|
||||
foreach (object obj in anArray)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
}
|
||||
|
||||
SerializeValue(obj);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
void SerializeString(string str)
|
||||
{
|
||||
builder.Append('\"');
|
||||
|
||||
char[] charArray = str.ToCharArray();
|
||||
foreach (var c in charArray)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
builder.Append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
builder.Append("\\\\");
|
||||
break;
|
||||
case '\b':
|
||||
builder.Append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
builder.Append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
builder.Append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
builder.Append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
builder.Append("\\t");
|
||||
break;
|
||||
default:
|
||||
int codepoint = Convert.ToInt32(c);
|
||||
if ((codepoint >= 32) && (codepoint <= 126))
|
||||
{
|
||||
builder.Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("\\u");
|
||||
builder.Append(codepoint.ToString("x4"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append('\"');
|
||||
}
|
||||
|
||||
void SerializeOther(object value)
|
||||
{
|
||||
// NOTE: decimals lose precision during serialization.
|
||||
// They always have, I'm just letting you know.
|
||||
// Previously floats and doubles lost precision too.
|
||||
if (value is float)
|
||||
{
|
||||
builder.Append(((float)value).ToString("R", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (value is int
|
||||
|| value is uint
|
||||
|| value is long
|
||||
|| value is sbyte
|
||||
|| value is byte
|
||||
|| value is short
|
||||
|| value is ushort
|
||||
|| value is ulong)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
else if (value is double
|
||||
|| value is decimal)
|
||||
{
|
||||
builder.Append(Convert.ToDouble(value).ToString("R", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeString(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/Json.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/Json.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03626f52014bc4f14b549e8bb318ca74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Public/Log.meta
Normal file
8
Assets/TapSDK/Core/Runtime/Public/Log.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50f5141d913874984bd6a97cace57cbb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/TapSDK/Core/Runtime/Public/Log/TapLogLevel.cs
Normal file
7
Assets/TapSDK/Core/Runtime/Public/Log/TapLogLevel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace TapSDK.Core {
|
||||
public enum TapLogLevel {
|
||||
Debug,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/Log/TapLogLevel.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/Log/TapLogLevel.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1456af90a56064fe9afe2ccd9692f261
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/TapSDK/Core/Runtime/Public/Log/TapLogger.cs
Normal file
48
Assets/TapSDK/Core/Runtime/Public/Log/TapLogger.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace TapSDK.Core {
|
||||
public class TapLogger {
|
||||
/// <summary>
|
||||
/// Configures the logger.
|
||||
/// </summary>
|
||||
/// <value>The log delegate.</value>
|
||||
public static Action<TapLogLevel, string> LogDelegate {
|
||||
internal get; set;
|
||||
}
|
||||
|
||||
public static void Debug(string log) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Debug, log);
|
||||
}
|
||||
|
||||
public static void Debug(string format, params object[] args) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Debug, string.Format(format, args));
|
||||
}
|
||||
|
||||
public static void Warn(string log) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Warn, log);
|
||||
}
|
||||
|
||||
public static void Warn(string format, params object[] args) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Warn, string.Format(format, args));
|
||||
}
|
||||
|
||||
public static void Error(string log) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Error, log);
|
||||
}
|
||||
|
||||
public static void Error(string format, params object[] args) {
|
||||
LogDelegate?.Invoke(TapLogLevel.Error, string.Format(format, args));
|
||||
}
|
||||
|
||||
public static void Error(Exception e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(e.GetType());
|
||||
sb.Append("\n");
|
||||
sb.Append(e.Message);
|
||||
sb.Append("\n");
|
||||
sb.Append(e.StackTrace);
|
||||
Error(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/Log/TapLogger.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/Log/TapLogger.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 256e6b07da183466a9621d24df1e3ae7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/TapSDK/Core/Runtime/Public/Platform.cs
Normal file
27
Assets/TapSDK/Core/Runtime/Public/Platform.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public class Platform
|
||||
{
|
||||
public static bool IsAndroid()
|
||||
{
|
||||
return Application.platform == RuntimePlatform.Android;
|
||||
}
|
||||
|
||||
public static bool IsIOS()
|
||||
{
|
||||
return Application.platform == RuntimePlatform.IPhonePlayer;
|
||||
}
|
||||
|
||||
public static bool IsWin32()
|
||||
{
|
||||
return Application.platform == RuntimePlatform.WindowsPlayer;
|
||||
}
|
||||
|
||||
public static bool IsMacOS()
|
||||
{
|
||||
return Application.platform == RuntimePlatform.OSXPlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/Platform.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/Platform.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef69730c212ed4cc9b4f39d224e60de5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TapSDK/Core/Runtime/Public/RegionType.cs
Normal file
8
Assets/TapSDK/Core/Runtime/Public/RegionType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public enum RegionType : int
|
||||
{
|
||||
CN = 0,
|
||||
IO = 1
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/RegionType.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/RegionType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77cbd92bb921740e7b83329a483a1221
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/TapSDK/Core/Runtime/Public/SafeDictionary.cs
Normal file
27
Assets/TapSDK/Core/Runtime/Public/SafeDictionary.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public static class SafeDictionary
|
||||
{
|
||||
public static T GetValue<T> (Dictionary<string, object> dic, string key, T defaultVal = default(T))
|
||||
{
|
||||
if (dic == null || dic.Keys.Count == 0) return default(T);
|
||||
if (!dic.TryGetValue(key, out var outputValue))
|
||||
return defaultVal;
|
||||
if(typeof(T) == typeof(int)){
|
||||
return (T)(object)int.Parse(outputValue.ToString());
|
||||
}
|
||||
if(typeof(T) == typeof(double)){
|
||||
return (T)(object)double.Parse(outputValue.ToString());
|
||||
}
|
||||
if(typeof(T) == typeof(long)){
|
||||
return (T)(object)long.Parse(outputValue.ToString());
|
||||
}
|
||||
if(typeof(T) == typeof(bool)){
|
||||
return (T)outputValue;
|
||||
}
|
||||
return (T) outputValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/SafeDictionary.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/SafeDictionary.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c5d3a73d3e1d4bf890c3a0b14b560fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/TapSDK/Core/Runtime/Public/TapEngineBridgeResult.cs
Normal file
14
Assets/TapSDK/Core/Runtime/Public/TapEngineBridgeResult.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public class TapEngineBridgeResult
|
||||
{
|
||||
public const int RESULT_SUCCESS = 0;
|
||||
public const int RESULT_ERROR = -1;
|
||||
|
||||
[JsonProperty("code")] public int code { get; set; }
|
||||
[JsonProperty("message")] public string message { get; set; }
|
||||
[JsonProperty("content")] public string content { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ec1ea022bd44fcba1ad7c0fa5af9401
|
||||
timeCreated: 1752474613
|
||||
62
Assets/TapSDK/Core/Runtime/Public/TapError.cs
Normal file
62
Assets/TapSDK/Core/Runtime/Public/TapError.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public class TapError
|
||||
{
|
||||
public int code;
|
||||
|
||||
public string errorDescription;
|
||||
|
||||
public TapError(string json)
|
||||
{
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dic = Json.Deserialize(json) as Dictionary<string, object>;
|
||||
code = SafeDictionary.GetValue<int>(dic, "code");
|
||||
errorDescription = SafeDictionary.GetValue<string>(dic, "error_description");
|
||||
}
|
||||
|
||||
public TapError()
|
||||
{
|
||||
}
|
||||
|
||||
public static TapError SafeConstructorTapError(string json)
|
||||
{
|
||||
return string.IsNullOrEmpty(json) ? null : new TapError(json);
|
||||
}
|
||||
|
||||
public static TapError UndefinedError()
|
||||
{
|
||||
return new TapError(TapErrorCode.ERROR_CODE_UNDEFINED, "UnKnown Error");
|
||||
}
|
||||
|
||||
public static TapError LoginCancelError()
|
||||
{
|
||||
return new TapError(TapErrorCode.ERROR_CODE_LOGIN_CANCEL, "Login Cancel");
|
||||
}
|
||||
|
||||
public TapError(int code, string errorDescription)
|
||||
{
|
||||
this.code = code;
|
||||
this.errorDescription = errorDescription;
|
||||
}
|
||||
|
||||
private static TapErrorCode ParseCode(int parseCode)
|
||||
{
|
||||
return Enum.IsDefined(typeof(TapErrorCode), parseCode)
|
||||
? (TapErrorCode) Enum.ToObject(typeof(TapErrorCode), parseCode)
|
||||
: TapErrorCode.ERROR_CODE_UNDEFINED;
|
||||
}
|
||||
|
||||
public TapError(TapErrorCode code, string errorDescription)
|
||||
{
|
||||
this.code = (int) code;
|
||||
this.errorDescription = errorDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/TapError.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/TapError.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c93fd38e60af46b78131cf90dddff5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
44
Assets/TapSDK/Core/Runtime/Public/TapErrorCode.cs
Normal file
44
Assets/TapSDK/Core/Runtime/Public/TapErrorCode.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public enum TapErrorCode
|
||||
{
|
||||
/*
|
||||
* 未知错误
|
||||
*/
|
||||
ERROR_CODE_UNDEFINED = 80000,
|
||||
|
||||
/**
|
||||
* SDK 未初始化
|
||||
*/
|
||||
ERROR_CODE_UNINITIALIZED = 80001,
|
||||
|
||||
/**
|
||||
* 绑定取消
|
||||
*/
|
||||
ERROR_CODE_BIND_CANCEL = 80002,
|
||||
/**
|
||||
* 绑定错误
|
||||
*/
|
||||
ERROR_CODE_BIND_ERROR = 80003,
|
||||
|
||||
/**
|
||||
* 登陆错误
|
||||
*/
|
||||
ERROR_CODE_LOGOUT_INVALID_LOGIN_STATE = 80004,
|
||||
|
||||
/**
|
||||
* 登陆被踢出
|
||||
*/
|
||||
ERROR_CODE_LOGOUT_KICKED = 80007,
|
||||
|
||||
/**
|
||||
* 桥接回调错误
|
||||
*/
|
||||
ERROR_CODE_BRIDGE_EXECUTE = 80080,
|
||||
|
||||
/**
|
||||
* 登录取消
|
||||
*/
|
||||
ERROR_CODE_LOGIN_CANCEL = 80081
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/TapErrorCode.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/TapErrorCode.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28bd88a8759b74f55a9e42d04f7aacf3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/TapSDK/Core/Runtime/Public/TapException.cs
Normal file
24
Assets/TapSDK/Core/Runtime/Public/TapException.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace TapSDK.Core
|
||||
{
|
||||
public class TapException : Exception
|
||||
{
|
||||
public int code;
|
||||
|
||||
public int Code {
|
||||
get => code;
|
||||
set {
|
||||
code = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string message;
|
||||
|
||||
public TapException(int code, string message) : base(message)
|
||||
{
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TapSDK/Core/Runtime/Public/TapException.cs.meta
Normal file
11
Assets/TapSDK/Core/Runtime/Public/TapException.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8258d80e38ee46738be23d42027f05c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user