Compare commits
	
		
			209 Commits 
		
	
	
		
			feature/it
			...
			main
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 58cc5a3b46 | |
|  | 1ef9e3ccec | |
|  | 88efffa89f | |
|  | 018fb2c35b | |
|  | 0870e71cf9 | |
|  | 15785b92a2 | |
|  | a67ba09408 | |
|  | 6dbbd27eb7 | |
|  | 643bb23939 | |
|  | b0670ccafc | |
|  | 7214c447a6 | |
|  | 49774c3281 | |
|  | c001fe09ac | |
|  | a063c4866d | |
|  | 3062467e84 | |
|  | d4f5342e7f | |
|  | f099a99c95 | |
|  | 6b95c2ab2f | |
|  | 06792d098f | |
|  | af1ec1e04f | |
|  | 2494bd2748 | |
|  | 5c3be01eb3 | |
|  | 149bc438ac | |
|  | e814324e98 | |
|  | 46cd1717fb | |
|  | aec21f3789 | |
|  | cc65e9173c | |
|  | 2bde58e27e | |
|  | b3c541cb84 | |
|  | ceeeeaf15f | |
|  | 257f9796a5 | |
|  | fd01939a5f | |
|  | 48c5235335 | |
|  | 42c7302ea3 | |
|  | 4da06c8f96 | |
|  | ecf615598c | |
|  | 0a2fca05ab | |
|  | 555638e31e | |
|  | 3555b70ea7 | |
|  | 314aaaa88b | |
|  | 4246251295 | |
|  | 1e59e5efa6 | |
|  | 4721e027b2 | |
|  | b9291e7885 | |
|  | 3050b67a31 | |
|  | 9f0ba03957 | |
|  | 3de35b69e5 | |
|  | 25f4d7e588 | |
|  | 1841edcbf3 | |
|  | 957f60e797 | |
|  | 2fc6faf831 | |
|  | 4d86931605 | |
|  | 804618b12b | |
|  | f85eb8d2bb | |
|  | 31991a780b | |
|  | 331a845f02 | |
|  | d8967001bf | |
|  | f33fc2c764 | |
|  | 9d168cdd46 | |
|  | 6e5ede3958 | |
|  | 2724683377 | |
|  | d89de67e44 | |
|  | 3be05511e4 | |
|  | 6fc0c24823 | |
|  | 52b6af9fd0 | |
|  | d574ec5757 | |
|  | 855cc61936 | |
|  | bcaed876f7 | |
|  | 42966759b3 | |
|  | 4a39747d3d | |
|  | d51759bbd1 | |
|  | 68e90a4ed9 | |
|  | 3d8bc1c642 | |
|  | 338c886ef6 | |
|  | fa3ec5adef | |
|  | cd7074f490 | |
|  | 9d907463ee | |
|  | 86742abe46 | |
|  | 5306922cca | |
|  | 67000cbc63 | |
|  | 8acd6faf94 | |
|  | 1b97f8ec03 | |
|  | 529e2ddeda | |
|  | 68161b82c3 | |
|  | 0d06131677 | |
|  | 051baed476 | |
|  | c3403a335f | |
|  | 2d857fc6a6 | |
|  | 31cb08b6df | |
|  | d70f9c3566 | |
|  | 8b5f42706e | |
|  | 0c4d6b31e9 | |
|  | bc62aec38e | |
|  | 2ea2db79c8 | |
|  | d6ff9736d9 | |
|  | 8932d6893f | |
|  | f2949347df | |
|  | c2be368962 | |
|  | 8d1afddcd5 | |
|  | 5b1097464d | |
|  | 3fdd02c7b6 | |
|  | 9d7fd12cf3 | |
|  | 093e309e52 | |
|  | aad0fd6d76 | |
|  | b5a64eac4e | |
|  | c6d6a1f5d8 | |
|  | fe36a57a81 | |
|  | 6779c6ef73 | |
|  | 049de5acd3 | |
|  | bc002d1501 | |
|  | 5494e6c3f9 | |
|  | 7fe982f5bc | |
|  | 852853629e | |
|  | 23a2616b95 | |
|  | 777ac87b18 | |
|  | e4f51978d1 | |
|  | 4db35e10fc | |
|  | a6f0d8d884 | |
|  | 5d7e32a9f1 | |
|  | 8e005c9237 | |
|  | 20938c5678 | |
|  | 7883d1f0e2 | |
|  | 41fcd7746e | |
|  | 0e4a0a68cb | |
|  | 4cd2e5ff19 | |
|  | cd3c53d674 | |
|  | 2cc9d66aa1 | |
|  | ad47a9696b | |
|  | e11b61fa88 | |
|  | 50b99d00a2 | |
|  | f69245a8c4 | |
|  | 3097635271 | |
|  | 14340e4f8b | |
|  | 92a05cf9ee | |
|  | 5089d82d66 | |
|  | 85ef81ff5e | |
|  | 4743fa298f | |
|  | 54ef66a97d | |
|  | cdf379be10 | |
|  | af54ea3864 | |
|  | eba0171628 | |
|  | 6ae05aad01 | |
|  | 75abc82500 | |
|  | 860d353b9a | |
|  | baae2a6eb4 | |
|  | ef3a11c382 | |
|  | 78f9e3b330 | |
|  | 7b6e8c7cbe | |
|  | 75909ccc1b | |
|  | faa8692667 | |
|  | 93935f87c4 | |
|  | 311b53ea5f | |
|  | fec486e8a6 | |
|  | b2fa553fb7 | |
|  | 2217ab8e46 | |
|  | d3cf9a1b63 | |
|  | ac3f88422a | |
|  | 17ea49120a | |
|  | 7bbbf34b60 | |
|  | 0ecf957902 | |
|  | 9d08938d77 | |
|  | 794673c272 | |
|  | 6d17f595b2 | |
|  | 87ff0521d3 | |
|  | b5b7c46a46 | |
|  | 1cc30c8a1f | |
|  | fb492af633 | |
|  | e647f0ce6b | |
|  | 407e242b8c | |
|  | a692c4f592 | |
|  | d70c6eff8e | |
|  | abf252360c | |
|  | 7f9263045b | |
|  | 01008ca8c7 | |
|  | a962f7bb8c | |
|  | 16a99087d9 | |
|  | 4fadb2b469 | |
|  | 04d1c6df21 | |
|  | 75745ceb11 | |
|  | 7f805c56de | |
|  | d2232e303b | |
|  | b7051d3267 | |
|  | 60e6ae0716 | |
|  | 1f71522fa6 | |
|  | 19be8b5d48 | |
|  | e451155063 | |
|  | 53f543887e | |
|  | b1bf57cbed | |
|  | bb4073aa2f | |
|  | 3871841988 | |
|  | d17a6e2eae | |
|  | 6275afac9b | |
|  | 69edbeb6d9 | |
|  | 064edbd180 | |
|  | ffff79d012 | |
|  | 219ab130d1 | |
|  | 748962528f | |
|  | c7e255c95d | |
|  | c50c5d170e | |
|  | 98c025a285 | |
|  | 7044f87d3b | |
|  | 8e88cb88ec | |
|  | 3995bba61a | |
|  | ab0a27d835 | |
|  | e5ce48dc7f | |
|  | 068f39a85d | |
|  | 3673e3c594 | |
|  | bc67eb6301 | |
|  | 017d7177f4 | 
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: f9c48f29655a4b8bb90520de5a7c2fa4 | ||||
| timeCreated: 1719552474 | ||||
|  | @ -0,0 +1,863 @@ | |||
| #if GURU_SDK_DEV | ||||
| 
 | ||||
| namespace Guru.Editor | ||||
| { | ||||
|     using System; | ||||
|     using System.IO; | ||||
|     using UnityEditor; | ||||
|     using UnityEngine; | ||||
|     using UnityEngine.Networking; | ||||
|     using System.Collections.Generic; | ||||
|     using System.Linq; | ||||
|      | ||||
|     public class GuruServiceJsonBuilder: EditorWindow | ||||
|     { | ||||
|         const string GuruProjectSettingsName = "guru-project-settings.cfg"; | ||||
|          | ||||
|          | ||||
|         // Tool Version | ||||
|         public const string Version = "0.2.0"; | ||||
|          | ||||
|         private const string K_APP_SETTINGS = "app_settings"; | ||||
|         private const string K_ADJUST_SETTINGS = "adjust_settings"; | ||||
|         private const string K_FB_SETTINGS = "fb_settings"; | ||||
|         private const string K_AD_SETTINGS = "ad_settings"; | ||||
|         private const string K_IAP_SETTINGS = "iap_settings"; | ||||
|         private const char K_SPLITTER_TAB = '\t'; | ||||
|         private const char K_SPLITTER_COMMA = ','; | ||||
|          | ||||
|         private const string NoSelectionName = "------"; | ||||
|         private const string STATE_IDLE = "s_idle"; | ||||
|         private const string STATE_LOADING = "s_loading"; | ||||
|         private const string STATE_SUCCESS = "s_success"; | ||||
| 
 | ||||
|         private static GuruServiceJsonBuilder _instance; | ||||
|         private List<string> _projectNames; | ||||
|         private int _selectedProjectIndex = 0; | ||||
|         private static Dictionary<string, string> _publishLinks; | ||||
|         public static Dictionary<string, string> PublishLinks | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (_publishLinks == null) _publishLinks = LoadProjectSettingsCfg(); | ||||
|                 return _publishLinks; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         #region Link & Keys | ||||
| 
 | ||||
|         private const string TSVLink = "https://docs.google.com/spreadsheets/d/e/{0}/pub?gid=0&single=true&output=tsv"; | ||||
|          | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region Export JSON | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 从 TSV 文件进行转化 | ||||
|         /// </summary> | ||||
|         /// <param name="tsv"></param> | ||||
|         /// <param name="savePath"></param> | ||||
|         public static void ConvertFromTSV(string tsv, string savePath = "") | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(tsv)) | ||||
|             { | ||||
|                 EditorUtility.DisplayDialog("空文件!", $"文件格式错误!\n{tsv}", "OK"); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             var guru_service = EditorGuruServiceIO.CreateEmpty(); | ||||
| 
 | ||||
|             var lines = tsv.Split('\n'); | ||||
|             string line = ""; | ||||
|             for (int index = 0; index < lines.Length; index++) | ||||
|             { | ||||
|                 line = lines[index]; | ||||
|                 if (!IsInvalidLine(line)) | ||||
|                 { | ||||
|                     //---------------- app_settings ---------------- | ||||
|                     if (line.StartsWith(K_APP_SETTINGS)) | ||||
|                     { | ||||
|                         index++; | ||||
|                         while (!line.StartsWith(K_ADJUST_SETTINGS)) | ||||
|                         { | ||||
|                             line = lines[index]; | ||||
|                             FillAppSettings(guru_service, line); | ||||
|                             index++; | ||||
|                             line = lines[index]; | ||||
|                         } | ||||
|                     } | ||||
|                     //---------------- adjust_settings ---------------- | ||||
|                     if (line.StartsWith(K_ADJUST_SETTINGS)) | ||||
|                     { | ||||
|                         index++; | ||||
|                         FillAdjustSettings(guru_service, lines, ref index); | ||||
|                     } | ||||
|                     //---------------- fb_settings ---------------- | ||||
|                     if (line.StartsWith(K_FB_SETTINGS)) | ||||
|                     { | ||||
|                         index++; | ||||
|                         FillFacebookSettings(guru_service, lines, ref index); | ||||
|                     } | ||||
|                     //---------------- ad_settings ---------------- | ||||
|                     if (line.StartsWith(K_AD_SETTINGS)) | ||||
|                     { | ||||
|                         index++; | ||||
|                         FillAdSettings(guru_service, lines, ref index); | ||||
|                     } | ||||
|                     //---------------- iap_settings ---------------- | ||||
|                     if (line.StartsWith(K_IAP_SETTINGS)) | ||||
|                     { | ||||
|                         index++; | ||||
|                         FillProducts(guru_service, lines, ref index); | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
| 
 | ||||
|             guru_service.version = GetFileVersionByDate(); | ||||
|              | ||||
|             if (string.IsNullOrEmpty(savePath)) | ||||
|             { | ||||
|                 var dir = Path.GetFullPath($"{Application.dataPath}/../output"); | ||||
|                 if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); | ||||
|                 savePath = $"{dir}/guru-services-{guru_service.app_settings.app_id.ToLower()}.json"; | ||||
|             } | ||||
| 
 | ||||
|             var arr = savePath.Split('/'); | ||||
|             var fileName = arr[arr.Length - 1]; | ||||
| 
 | ||||
|             EditorGuruServiceIO.SourceServiceFilePath = savePath; | ||||
|             EditorGuruServiceIO.SaveConfig(guru_service, savePath); | ||||
| 
 | ||||
|             if (EditorUtility.DisplayDialog("CONVERT SUCCESS!", $"Export Json File\n{fileName}\nto:\n{savePath}", "OK")) | ||||
|             { | ||||
|                 GuruEditorHelper.OpenPath(Directory.GetParent(savePath)?.FullName ?? Application.dataPath); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public static void ConvertFromTsvFile(string tsvPath, string savePath = "") | ||||
|         { | ||||
|             if (!File.Exists(tsvPath)) | ||||
|             { | ||||
|                 EditorUtility.DisplayDialog("FILE NOT FOUND!", $"File not exist:\n{tsvPath}", "OK"); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             var tsvString = File.ReadAllText(tsvPath); | ||||
|             if (!string.IsNullOrEmpty(tsvString)) | ||||
|             { | ||||
|                 ConvertFromTSV(tsvString, savePath); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// AppSettings 填充 | ||||
|         /// </summary> | ||||
|         /// <param name="settings"></param> | ||||
|         /// <param name="line"></param> | ||||
|         private static void FillAppSettings(GuruServicesConfig settings, string line) | ||||
|         { | ||||
|             // 对于空行和空值直接跳过 | ||||
|             if (IsInvalidLine(line)) return; | ||||
| 
 | ||||
|             string value = ""; | ||||
|             if (settings.app_settings == null) settings.app_settings = new GuruAppSettings(); | ||||
|             if (settings.parameters == null) settings.parameters = new GuruParameters(); | ||||
| 
 | ||||
|             //------------------- AppSettings ------------------------------- | ||||
|             // 拾取值和注入 | ||||
|             if (GetValue(line, "app_id", out value)) | ||||
|             { | ||||
|                 settings.app_settings.app_id = value; | ||||
|             } | ||||
|             else if (GetValue(line, "product_name", out value)) | ||||
|             { | ||||
|                 settings.app_settings.product_name = value; | ||||
|             } | ||||
|             else if (GetValue(line, "bundle_id", out value)) | ||||
|             { | ||||
|                 settings.app_settings.bundle_id = value; | ||||
|             } | ||||
|             else if (GetValue(line, "support_email", out value)) | ||||
|             { | ||||
|                 settings.app_settings.support_email = value; | ||||
|             } | ||||
|             else if (GetValue(line, "privacy_url", out value)) | ||||
|             { | ||||
|                 settings.app_settings.privacy_url = value; | ||||
|             } | ||||
|             else if (GetValue(line, "terms_url", out value)) | ||||
|             { | ||||
|                 settings.app_settings.terms_url = value; | ||||
|             } | ||||
|             else if (GetValue(line, "android_store", out value)) | ||||
|             { | ||||
|                 settings.app_settings.android_store = value; | ||||
|             } | ||||
|             else if (GetValue(line, "ios_store", out value)) | ||||
|             { | ||||
|                 settings.app_settings.ios_store = value; | ||||
|             } | ||||
|             else if (GetValue(line, "enable_firebase", out value)) | ||||
|             { | ||||
|                 settings.app_settings.enable_firebase = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "enable_facebook", out value)) | ||||
|             { | ||||
|                 settings.app_settings.enable_facebook = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "enable_adjust", out value)) | ||||
|             { | ||||
|                 settings.app_settings.enable_adjust = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "enable_iap", out value)) | ||||
|             { | ||||
|                 settings.app_settings.enable_iap = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "custom_keystore", out value)) | ||||
|             { | ||||
|                 settings.app_settings.custom_keystore = GetBool(value); | ||||
|             } | ||||
|             //------------------- Parameters ------------------------------- | ||||
|             else if (GetValue(line, "tch_020", out value)) | ||||
|             { | ||||
|                 settings.parameters.tch_020 = GetDouble(value); | ||||
|             } | ||||
|             else if (GetValue(line, "using_uuid", out value)) | ||||
|             { | ||||
|                 settings.parameters.using_uuid = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "cdn_host", out value)) | ||||
|             { | ||||
|                 settings.parameters.cdn_host = value; | ||||
|             } | ||||
|             else if (GetValue(line, "enable_errorlog", out value)) | ||||
|             { | ||||
|                 settings.parameters.enable_errorlog = GetBool(value); | ||||
|             } | ||||
|             else if (GetValue(line, "level_end_success_num", out value)) | ||||
|             { | ||||
|                 settings.parameters.level_end_success_num = GetInt(value); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// AdjustSettings 填充 | ||||
|         /// </summary> | ||||
|         /// <param name="settings"></param> | ||||
|         /// <param name="line"></param> | ||||
|         private static void FillAdjustSettings(GuruServicesConfig settings, string[] lines, ref int index) | ||||
|         { | ||||
|             if(settings.adjust_settings == null) settings.adjust_settings = new GuruAdjustSettings(); | ||||
|             string[] list = null; | ||||
|             string line = lines[index]; | ||||
|             bool pass = false; | ||||
|             List<string> events = new List<string>(40); | ||||
|              | ||||
|             while (!lines[index].StartsWith(K_FB_SETTINGS)) | ||||
|             { | ||||
|                 line = lines[index]; | ||||
|                 if (!IsInvalidLine(line)) | ||||
|                 { | ||||
|                     if (line.StartsWith("app_token")) | ||||
|                     { | ||||
|                         list = GetStringArray(line, 1, 2); | ||||
|                         settings.adjust_settings.app_token = list; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         list = GetStringArray(line, 0, 3); | ||||
|                         pass = list != null && !string.IsNullOrEmpty(list[0])  | ||||
|                                             && (!string.IsNullOrEmpty(list[1]) || !string.IsNullOrEmpty(list[2])); | ||||
|                         if( pass) events.Add(string.Join(",", list)); | ||||
|                     } | ||||
|                 } | ||||
|                 index++; | ||||
|             } | ||||
| 
 | ||||
|             settings.adjust_settings.events = events.ToArray(); | ||||
|             index--; | ||||
|         } | ||||
| 
 | ||||
|         private static long GetFileVersionByDate() | ||||
|         { | ||||
|             var startDt = new DateTime(1970,1,1,0,0,0); | ||||
|             return (long) (DateTime.UtcNow.Ticks - startDt.Ticks) / 10000; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static void FillFacebookSettings(GuruServicesConfig settings, string[] lines, ref int index) | ||||
|         { | ||||
|             string value = ""; | ||||
|             if(settings.fb_settings == null) settings.fb_settings = new GuruFbSettings(); | ||||
|             var line = ""; | ||||
| 
 | ||||
|             while (!lines[index].StartsWith(K_AD_SETTINGS)) | ||||
|             { | ||||
|                 line = lines[index]; | ||||
|                 if (!IsInvalidLine(line)) | ||||
|                 { | ||||
|                     // 拾取值和注入 | ||||
|                     if (GetValue(line, "fb_app_id", out value)) | ||||
|                     { | ||||
|                         settings.fb_settings.fb_app_id = value; | ||||
|                     } | ||||
|                     else if (GetValue(line, "fb_client_token", out value)) | ||||
|                     { | ||||
|                         settings.fb_settings.fb_client_token = value; | ||||
|                     } | ||||
|                 } | ||||
|                 index++; | ||||
|             } | ||||
|              | ||||
|             index--; | ||||
|         } | ||||
|          | ||||
|         private static void FillAdSettings(GuruServicesConfig settings, string[] lines, ref int index) | ||||
|         { | ||||
|             string value = ""; | ||||
|             if(settings.ad_settings == null) settings.ad_settings = new GuruAdSettings(); | ||||
|              | ||||
|              | ||||
|             var line = lines[index]; | ||||
|             // SDK Key | ||||
|             | ||||
| 
 | ||||
|             string[] max_ids_android = new string[3]; | ||||
|             string[] max_ids_ios = new string[3]; | ||||
|             string[] amazon_ids_android = new string[4]; | ||||
|             string[] amazon_ids_ios = new string[4]; | ||||
|             string[] pubmatic_ids_android = new string[3]; | ||||
|             string[] pubmatic_ids_ios = new string[3]; | ||||
|             string[] moloco_ids_android = new string[3]; | ||||
|             string[] moloco_ids_ios = new string[3]; | ||||
|             string[] tradplus_ids_android = new string[3]; | ||||
|             string[] tradplus_ids_ios = new string[3]; | ||||
|              | ||||
|              | ||||
|             //------- 开始记录广告配置; | ||||
|             string[] arr; | ||||
|             while (!lines[index].StartsWith(K_IAP_SETTINGS)) | ||||
|             { | ||||
|                 line = lines[index]; | ||||
|                  | ||||
|                 if (GetValue(line, "sdk_key", out value)) | ||||
|                 { | ||||
|                     settings.ad_settings.sdk_key = value; | ||||
|                 } | ||||
|                 else if (line.StartsWith("admob_app_id")) | ||||
|                 { | ||||
|                     settings.ad_settings.admob_app_id = GetStringArray(line, 1, 2); | ||||
|                 } | ||||
|                 else if (line.StartsWith("max_bads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     max_ids_android[0] = arr[0]; | ||||
|                     max_ids_ios[0] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("max_iads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     max_ids_android[1] = arr[0]; | ||||
|                     max_ids_ios[1] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("max_rads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     max_ids_android[2] = arr[0]; | ||||
|                     max_ids_ios[2] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("amazon_app_id")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     amazon_ids_android[0] = arr[0]; | ||||
|                     amazon_ids_ios[0] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("amazon_bads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     amazon_ids_android[1] = arr[0]; | ||||
|                     amazon_ids_ios[1] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("amazon_iads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     amazon_ids_android[2] = arr[0]; | ||||
|                     amazon_ids_ios[2] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("amazon_rads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     amazon_ids_android[3] = arr[0]; | ||||
|                     amazon_ids_ios[3] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("pubmatic_bads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     pubmatic_ids_android[0] = arr[0]; | ||||
|                     pubmatic_ids_ios[0] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("pubmatic_iads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     pubmatic_ids_android[1] = arr[0]; | ||||
|                     pubmatic_ids_ios[1] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("pubmatic_rads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     pubmatic_ids_android[2] = arr[0]; | ||||
|                     pubmatic_ids_ios[2] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("moloco_bads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     moloco_ids_android[0] = arr[0]; | ||||
|                     moloco_ids_ios[0] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("moloco_iads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     moloco_ids_android[1] = arr[0]; | ||||
|                     moloco_ids_ios[1] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("moloco_rads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     moloco_ids_android[2] = arr[0]; | ||||
|                     moloco_ids_ios[2] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("tradplus_bads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     tradplus_ids_android[0] = arr[0]; | ||||
|                     tradplus_ids_ios[0] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("tradplus_iads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     tradplus_ids_android[1] = arr[0]; | ||||
|                     tradplus_ids_ios[1] = arr[1]; | ||||
|                 } | ||||
|                 else if (line.StartsWith("tradplus_rads")) | ||||
|                 { | ||||
|                     arr = GetStringArray(line, 1, 2); | ||||
|                     tradplus_ids_android[2] = arr[0]; | ||||
|                     tradplus_ids_ios[2] = arr[1]; | ||||
|                 } | ||||
|                 index++; | ||||
|             } | ||||
|              | ||||
|             //-------- Fill all data ----------- | ||||
|             settings.ad_settings.max_ids_android = max_ids_android; | ||||
|             settings.ad_settings.max_ids_ios = max_ids_ios; | ||||
|             settings.ad_settings.amazon_ids_android = amazon_ids_android; | ||||
|             settings.ad_settings.amazon_ids_ios = amazon_ids_ios; | ||||
|             settings.ad_settings.pubmatic_ids_android = pubmatic_ids_android; | ||||
|             settings.ad_settings.pubmatic_ids_ios = pubmatic_ids_ios; | ||||
|             settings.ad_settings.moloco_ids_android = moloco_ids_android; | ||||
|             settings.ad_settings.moloco_ids_ios = moloco_ids_ios; | ||||
|             settings.ad_settings.tradplus_ids_android = tradplus_ids_android; | ||||
|             settings.ad_settings.tradplus_ids_ios = tradplus_ids_ios; | ||||
|              | ||||
|             index--; | ||||
|         } | ||||
|          | ||||
|         private static void FillProducts(GuruServicesConfig settings, string[] lines, ref int index) | ||||
|         { | ||||
|             string line = ""; | ||||
|             List<string> iaps = new List<string>(30); | ||||
|              | ||||
|             string[] arr; | ||||
|             while (index < lines.Length) | ||||
|             { | ||||
|                 line = lines[index]; | ||||
|                 if(IsInvalidLine(line)) continue; | ||||
|                 arr = GetStringArray(line, 0, 7); | ||||
|                 if(string.IsNullOrEmpty(arr[5])) arr[5] = "Store"; | ||||
|                 if(string.IsNullOrEmpty(arr[6])) arr[6] = "0"; | ||||
|                 iaps.Add(string.Join(",", arr).Replace("\r", "")); | ||||
|                 index++; | ||||
|             } | ||||
|             settings.products = iaps.ToArray(); | ||||
|             index--; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Utils | ||||
|          | ||||
|         private static bool GetBool(string value) | ||||
|         { | ||||
|             return value.ToLower() == "true" || value == "1"; | ||||
|         } | ||||
|          | ||||
|         private static int GetInt(string value) | ||||
|         { | ||||
|             int val = 0; | ||||
|             int.TryParse(value, out val); | ||||
|             return val; | ||||
|         } | ||||
|          | ||||
|         private static double GetDouble(string value) | ||||
|         { | ||||
|             double val = 0; | ||||
|             double.TryParse(value, out val); | ||||
|             return val; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static bool IsInvalidLine(string line) | ||||
|         { | ||||
|             return string.IsNullOrEmpty(line) || line.StartsWith(K_SPLITTER_TAB.ToString()); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static bool GetValue(string line, string key, out string value) | ||||
|         { | ||||
|             value = ""; // default   | ||||
|             if (line.StartsWith(key)) | ||||
|             { | ||||
|                 value = line.Split(K_SPLITTER_TAB)[1]; | ||||
|                 if (string.IsNullOrEmpty(value)) value = "empty"; | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         private static string[] GetStringArray(string line, int startIndex = 0, int length = 0, char spliter = K_SPLITTER_TAB) | ||||
|         { | ||||
|             if (IsInvalidLine(line)) return null; | ||||
|              | ||||
|             var raw = line.Split(spliter); | ||||
|             if (length == 0) length = raw.Length; | ||||
|              | ||||
|             var a = new List<string>(length); | ||||
|             for (int i = startIndex; i < length + startIndex; i++) | ||||
|             { | ||||
|                 if (i < raw.Length) | ||||
|                 { | ||||
|                     a.Add(raw[i]); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     a.Add(""); | ||||
|                 } | ||||
|             } | ||||
|             return a.ToArray(); | ||||
|         } | ||||
|         #endregion | ||||
|          | ||||
|         #region Menu Items | ||||
| 
 | ||||
| #if GURU_SDK_DEV | ||||
|         [MenuItem("Tools/Export Guru Service", false, 0 )] | ||||
| #endif | ||||
|         private static void ExportJsonFile() | ||||
|         { | ||||
|             string saveDir = Path.GetFullPath($"{Application.dataPath}/../output"); | ||||
|             string saveFile = Path.Combine(saveDir,$"guru-services____{DateTime.Now:yyyy-M-d-HH-mm}.json"); | ||||
|              | ||||
|             if(!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir); | ||||
|              | ||||
|             string searchPath = "~/Downloads/"; | ||||
|             string tsvPath = EditorUtility.OpenFilePanel("Load Guru Service TSV", searchPath, ".tsv"); | ||||
|             if (!string.IsNullOrEmpty(tsvPath)) | ||||
|             { | ||||
|                 ConvertFromTsvFile(tsvPath, saveFile); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         [MenuItem("Guru/Guru-Service Json Builder...", false, 0)] | ||||
|         private static void OpenWindow() | ||||
|         { | ||||
|             if(_instance != null ) _instance.Close(); | ||||
|             _instance = GetWindow<GuruServiceJsonBuilder>(); | ||||
|             _instance.Show(); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Settings | ||||
| 
 | ||||
|         private static string GetRelativeDir() | ||||
|         { | ||||
|             var guids = AssetDatabase.FindAssets(nameof(GuruServiceJsonBuilder)); | ||||
|             if (guids.Length > 0) | ||||
|             { | ||||
|                 var path = AssetDatabase.GUIDToAssetPath(guids[0]); | ||||
|                 var rpath = Directory.GetParent(path).FullName; | ||||
|                 return rpath; | ||||
|             } | ||||
|             return Path.GetFullPath($"Assets/../Packages/Editor/GuruJsonBuilder"); | ||||
|         } | ||||
| 
 | ||||
|         private static Dictionary<string, string> LoadProjectSettingsCfg() | ||||
|         { | ||||
|             var cfgPath = $"{GetRelativeDir()}/{GuruProjectSettingsName}"; | ||||
|             if (File.Exists(cfgPath)) | ||||
|             { | ||||
|                 var lines = File.ReadAllLines(cfgPath); | ||||
|                 int len = lines?.Length ?? -1; | ||||
|                 if (len > 0) | ||||
|                 { | ||||
|                     Dictionary<string, string> dict = new Dictionary<string, string>(lines.Length); | ||||
|                     int i = 0; | ||||
|                     string[] raw; | ||||
|                     string line, key, value; | ||||
|                     while (i < len) | ||||
|                     { | ||||
|                         line = lines[i]; | ||||
|                         if(string.IsNullOrEmpty(line)) continue; | ||||
|                         raw = lines[i].Split(','); | ||||
|                         value = ""; | ||||
|                         key = raw[0]; | ||||
|                         if(string.IsNullOrEmpty(key)) continue;  | ||||
|                         if(raw.Length > 1) value = raw[1]; | ||||
|                         dict[key] = value; | ||||
|                         i++; | ||||
|                     } | ||||
|                     return dict; | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region Window | ||||
| 
 | ||||
|         private void Awake() | ||||
|         { | ||||
|             Debug.Log($"------- Awake -------"); | ||||
|             this.titleContent = new GUIContent("Json Builder"); | ||||
| 
 | ||||
|             _projectNames = new List<string>(20); | ||||
|             string[] names = PublishLinks.Keys.ToArray(); | ||||
|             string name = ""; | ||||
|             for(int i = 0; i < names.Length; i++) | ||||
|             { | ||||
|                 name = names[i]; | ||||
|                 if (name == "Default") | ||||
|                 { | ||||
|                     _projectNames.Insert(0, NoSelectionName); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _projectNames.Add(name); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             _selectedProjectIndex = 0; | ||||
|             _state = STATE_IDLE; | ||||
| 
 | ||||
| 
 | ||||
|             var json = LoadProjectSettingsCfg(); | ||||
|             Debug.Log(json); | ||||
| 
 | ||||
|         } | ||||
|          | ||||
|          | ||||
|          | ||||
|          | ||||
| 
 | ||||
|         private void OnEnable() | ||||
|         { | ||||
|             // Debug.Log($"------- OnEnable -------"); | ||||
|         } | ||||
|          | ||||
|         private void OnGUI() | ||||
|         { | ||||
|             GUI_Title(); | ||||
|             switch (_state) | ||||
|             { | ||||
|                 case STATE_IDLE: | ||||
|                     GUI_Projects(); | ||||
|                     break; | ||||
|                 case STATE_LOADING: | ||||
|                     GUI_Loading(); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region GUI | ||||
| 
 | ||||
| 
 | ||||
|         private string _state = ""; | ||||
|          | ||||
|         private void GUI_Title() | ||||
|         { | ||||
|             var s = new GUIStyle("box") | ||||
|             { | ||||
|                 fontSize = 20, | ||||
|                 fontStyle = FontStyle.Bold, | ||||
|                 stretchWidth = true, | ||||
|                 alignment = TextAnchor.MiddleCenter, | ||||
|                 padding = new RectOffset(4, 4, 4, 4), | ||||
|             }; | ||||
|             GUILayout.Label("Guru-Service Json Builder", s, GUILayout.Height(60)); | ||||
|             s.fontSize = 14; | ||||
|             GUILayout.Label($"Version: {Version}", s); | ||||
|             GUILayout.Space(10); | ||||
|         } | ||||
|          | ||||
|         private void GUI_Loading() | ||||
|         { | ||||
|             var s = new GUIStyle("box") | ||||
|             { | ||||
|                 fontSize = 16, | ||||
|                 alignment = TextAnchor.MiddleCenter, | ||||
|                 stretchWidth = true, | ||||
|                 padding = new RectOffset(4, 4, 4, 4), | ||||
|             }; | ||||
| 
 | ||||
|             GUILayout.Label("Loading...", s); | ||||
|             GUILayout.Space(10); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private void GUI_Projects() | ||||
|         { | ||||
|             _selectedProjectIndex = EditorGUILayout.Popup("选择生成项目", _selectedProjectIndex, _projectNames.ToArray()); | ||||
|             GUILayout.Space(5); | ||||
|             if (GUILayout.Button("生成 guru-services.json", GUILayout.Height(40))) | ||||
|             { | ||||
|                 var id = _projectNames[_selectedProjectIndex]; | ||||
|                 if (id == NoSelectionName) | ||||
|                 { | ||||
|                     ShowDialog("选择错误", "请选择一个存在的项目"); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     DownloadTsvAndBuild(_selectedProjectIndex, (success, txt) => | ||||
|                     { | ||||
|                         if (success) | ||||
|                         { | ||||
|                             ConvertFromTSV(txt); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             ShowDialog("网络错误", txt); | ||||
|                         } | ||||
|                         _state = STATE_IDLE; | ||||
|                     }); | ||||
| 
 | ||||
|                     _state = STATE_LOADING; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private void ShowDialog(string title, string content, string okName = "OK", Action onOKCallback = null, | ||||
|             string cancelName = "", Action onCancelCallback = null) | ||||
|         { | ||||
|             if (EditorUtility.DisplayDialog(title, content, okName, cancelName)) | ||||
|             { | ||||
|                 onOKCallback?.Invoke(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 onCancelCallback?.Invoke(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region Networking | ||||
| 
 | ||||
|          | ||||
|         private static string GetProjectTSVUrl(string pid) | ||||
|         { | ||||
|             if (PublishLinks.TryGetValue(pid, out var id)) | ||||
|             { | ||||
|                 string url = string.Format(TSVLink, id); | ||||
|                 return url; | ||||
|             } | ||||
| 
 | ||||
|             return ""; | ||||
|         } | ||||
|          | ||||
|         private void DownloadTsvAndBuild(int projIndex, Action<bool, string> loadCompleted = null) | ||||
|         { | ||||
|             var pid = _projectNames[projIndex]; | ||||
|             var id = GetProjectTSVUrl(pid); | ||||
|             string title, msg; | ||||
|             if(string.IsNullOrEmpty(id)) | ||||
|             { | ||||
|                 title = "参数错误"; | ||||
|                 msg = $"项目 {pid} 不正确, 请重新选择..."; | ||||
|                 ShowDialog(title, msg); | ||||
|                 Debug.LogError($"{title}\n{msg}"); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             var www = UnityEngine.Networking.UnityWebRequest.Get(id); | ||||
|             www.SendWebRequest().completed += ap => | ||||
|             { | ||||
|                 if (www.result == UnityWebRequest.Result.Success) | ||||
|                 { | ||||
|                     Debug.Log($"<color=#088ff00>--- Load Success ---</color>"); | ||||
|                     // Debug.Log(www.downloadHandler.text); | ||||
|                     loadCompleted?.Invoke(true, www.downloadHandler.text); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     msg = $"Result {www.result}: {www.responseCode}\n\r{www.error}"; | ||||
|                     Debug.LogError(msg); | ||||
|                     loadCompleted?.Invoke(false, msg); | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region TEST | ||||
| 
 | ||||
| #if GURU_SDK_DEV | ||||
|         // [MenuItem("Tools/Test/Fetch Config File", false, 1)] | ||||
| #endif | ||||
|         private static void Test_FetchConfigFile() | ||||
|         { | ||||
| 
 | ||||
|             var pid = "FindOut"; | ||||
|             var url = GetProjectTSVUrl(pid); | ||||
|              | ||||
| 
 | ||||
|             if(string.IsNullOrEmpty(url)) | ||||
|             { | ||||
|                 Debug.LogError($"Wrong ProjectId: {pid}"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var www = UnityEngine.Networking.UnityWebRequest.Get(url); | ||||
|             www.SendWebRequest().completed += ap => | ||||
|             { | ||||
|                 if (www.result == UnityWebRequest.Result.Success) | ||||
|                 { | ||||
|                     Debug.Log($"<color=#088ff00>--- Load Success ---</color>"); | ||||
|                     Debug.Log(www.downloadHandler.text); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     Debug.LogError($"Loading Failed: {www.error} : {www.result} : {www.responseCode}"); | ||||
|                 } | ||||
|             }; | ||||
|              | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 8c7dc826ea0848f0a3bf05009fab6377 | ||||
| timeCreated: 1706856053 | ||||
|  | @ -0,0 +1,22 @@ | |||
| Default | ||||
| BallSort,2PACX-1vSPus7415l66-zScY1B1JOgLfSkm0yRPnRDG-BW0JgtnVZJziGDzvtBZr8D9oEZL2x478SdCS2yh0S_ | ||||
| BallSort2,2PACX-1vSZIZYbFuURK_ZMeMHV9ruL0SWBGMPA13er_J_DLRPVw5HBnU8_6c4mvek5UBFo1Ebbk63TMr-rsY6y | ||||
| WaterSort,2PACX-1vTPxWbpP7KnT-e2xg9Uz-nukbLmqYc8SjwNL9MgycIhInNgmAjOmRnmyH6PWm3-hsEugKTJHKDcoKdI | ||||
| WaterSort2,2PACX-1vQnZNtE7ZpT6eYagQDt686Be9Jr0tg22sRFg5cGiqFIsCVhWLu6jxDUg7qmyfIrX3iL5awat8FFnN7B | ||||
| BallSortPlus,2PACX-1vTa324nQIwNmXNtHC5sZZXYBdpUryKXdb8rFbTAwBa4KVlQ5ZXnErx2IvifkRxD8qKcNupfO1Kv5pJw | ||||
| TileBurst,2PACX-1vS5wvSZ5eSlK8TmYwVvgScJstaqN8cAB7Uxlq_nsbQwQ4VeD1BkhQuDbRdcO9ncuOCtoKjzSnviHVmc | ||||
| Parking3D,2PACX-1vQY18hIrYjYNOqQNE12BcPtF4gtFMhBbfDrakBcIVQKoStmfPBY7C4O_Efj1Y8sNbytbKZr-0lPmBml | ||||
| TileConnect,2PACX-1vRfSd9UbrLilele8Fw1BafFjlvHG5EMnODfbppPiUaacr7wZ62IvgM65SYSjGEXJmU9g9AczollaFCf | ||||
| DOF,2PACX-1vSDpYZEUrCioCBRSkoZLra5nWk53Ks1f180TD1g2dnKcm-MZtAYvFSXDieAF4xromZCmxIoZuIfGyCJ | ||||
| D2,2PACX-1vS1V35WTTPx2Jdeu6sVjPbaqFpjVwFu1Rn9tZkvxm8aEHbuRWibxt2pxPkLdDzwmCmrGBtU-PilABJm | ||||
| FindOut,2PACX-1vQA3R66yWRHmUn6sneeIUU1qXEiaXv6h9QdzYzVRTEOg-yZf7WCJ6tuvXMWzwcOgGihiSnr9shMX__d | ||||
| FindIt,2PACX-1vQs75JLsmTv1PESzOP6ANRqVk4zA3Y7PD5OV0yQbA5MM3wmp-hu4qE9gztZy7ETCP2nHJgDziwVpcFE | ||||
| AP,2PACX-1vSvXTqbTcNCuHZYbKGA-fPKQj8XuixYUB9UXWqTQXz5QLXNurCtmBlziMbUxte_eqsGO0tB2GXe-sEC | ||||
| Arrow,2PACX-1vS5w8rNcJycK-VYgum7gkrLAx2Ln_7wykXLBX4EulI8XNGnlPsVlENQ8LupDdtiIu-JlngJaTw5dIzR | ||||
| ApFindDifference,2PACX-1vSmodKOvKjxmiNoYbrXFI6n1XC0aqvlnbYkEGyabLKqS6-C3Yi3nstmC89Hc31cdIzCk1FAl54beW2P | ||||
| GoodsSort,2PACX-1vQrg6Ss2bNYAI7A250t3_zp10UJtbbQXb8I4LtDJ2Q3uopUG_PUadoJy7T4w0jlf4xcTqot2NWsfp1m | ||||
| CookingBlitz,2PACX-1vShqtSSpwYQ8CvK-BVr-wQ5ygGeKKLdpaaWysSN_QumoDZYATemayAQIOdnFRzMP69nEwFYSx51oey8 | ||||
| WoodNuts,2PACX-1vQhtRg354eThBpWGCEk5f_2cLVbz1clQXv0n6w4Cyip0Knl6EQ4XwWMlcCec-legZdHU3E0-_cqKipc | ||||
| MahjongSolitaire,2PACX-1vQpcaj8CpO__K1KGl-mg_940WOOIXBVzi0lmcjYTt1sqBI2PtK37s29McLTGU2I6N3fWM0ZepChedq7 | ||||
| NutsSort,2PACX-1vShdEGGEeMp51fMhg0UnMRHA51xfP8OkCdaD3gu-GTZCcJ6NvveP9Q0ahWDwSi1HS4Okyabn3gnLoLU | ||||
| FindMaster,2PACX-1vRXB42qVcqE0nhWxPScs_qtaUkkVz_BXmdMiEZm580VC4a3I6Av8MrhOEhixVzg2NcPhQh_8HIHt7wk | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 88694fb6aaa64ea192e0bffb4cb5b744 | ||||
| timeCreated: 1719552889 | ||||
|  | @ -10,12 +10,12 @@ namespace Guru.Editor | |||
|      | ||||
|     public class EditorGuruServiceIO | ||||
|     { | ||||
|         private static readonly string SourceConfigFileName = "guru-service"; | ||||
|         private const string LocalServicesConfigPath = "Guru/Resources"; | ||||
|         private const string SourceConfigExtension = ".json"; | ||||
|         private const string LocalConfigExtension = ".txt"; | ||||
|         internal static readonly string SourceConfigFileName = "guru-service"; | ||||
|         internal const string LocalServicesConfigPath = "Guru/Resources"; | ||||
|         internal const string SourceConfigExtension = ".json"; | ||||
|         internal const string LocalConfigExtension = ".txt"; | ||||
| 
 | ||||
|         private static string DefaultFilePath = | ||||
|         internal static string DefaultFilePath = | ||||
|             Path.GetFullPath(Path.Combine(Application.dataPath, $"{SourceConfigFileName}{SourceConfigExtension}")); | ||||
| 
 | ||||
|         internal static string SourceServiceFilePath = ""; | ||||
|  | @ -29,7 +29,7 @@ namespace Guru.Editor | |||
|             var a = AssetDatabase.FindAssets($"*{SourceConfigFileName}* t:TextAsset", new []{"Assets"}); | ||||
|             if (a == null || a.Length == 0) | ||||
|             { | ||||
|                 Debug.Log($"<color=orange>--- Can't find guru-services file</color>"); | ||||
|                 UnityEngine.Debug.Log($"<color=orange>--- Can't find guru-services file</color>"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|  | @ -37,7 +37,7 @@ namespace Guru.Editor | |||
|                 var fp = Path.GetFullPath(p); | ||||
|                 if (File.Exists(fp)) SourceServiceFilePath = fp; | ||||
|                 var t = AssetDatabase.LoadAssetAtPath<TextAsset>(p); | ||||
|                 // Debug.Log($"<color=#88ff00>--- find services file:{p} \n{t.text}</color>"); | ||||
|                 // UnityEngine.Debug.Log($"<color=#88ff00>--- find services file:{p} \n{t.text}</color>"); | ||||
|                 return JsonMapper.ToObject<GuruServicesConfig>(t.text); | ||||
|             } | ||||
|             return null; | ||||
|  | @ -47,7 +47,7 @@ namespace Guru.Editor | |||
|         /// 保存配置 | ||||
|         /// </summary> | ||||
|         /// <param name="config"></param> | ||||
|         internal static void SaveConfig(GuruServicesConfig config = null) | ||||
|         internal static void SaveConfig(GuruServicesConfig config = null, string savePath = "") | ||||
|         { | ||||
|             if (config == null) | ||||
|             { | ||||
|  | @ -59,12 +59,13 @@ namespace Guru.Editor | |||
|                 PrettyPrint = true, | ||||
|             }; | ||||
|             JsonMapper.ToJson(config, jw); | ||||
| 
 | ||||
|             var json = jw.ToString(); | ||||
| 
 | ||||
|             if (string.IsNullOrEmpty(SourceServiceFilePath)) SourceServiceFilePath = DefaultFilePath; | ||||
|             File.WriteAllText(SourceServiceFilePath, json); | ||||
|             Debug.Log($"Save config to {SourceServiceFilePath}"); | ||||
|             if (string.IsNullOrEmpty(savePath)) savePath = SourceServiceFilePath; | ||||
|             if (string.IsNullOrEmpty(savePath)) savePath = DefaultFilePath; | ||||
|              | ||||
|             File.WriteAllText(savePath, json); | ||||
|             UnityEngine.Debug.Log($"Save config to {savePath}"); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|  | @ -79,6 +80,7 @@ namespace Guru.Editor | |||
|             cfg.ad_settings = new GuruAdSettings(); | ||||
|             cfg.adjust_settings = new GuruAdjustSettings(); | ||||
|             cfg.fb_settings = new GuruFbSettings(); | ||||
|             cfg.parameters = new GuruParameters(); | ||||
|             return cfg; | ||||
|         } | ||||
| 
 | ||||
|  | @ -107,7 +109,7 @@ namespace Guru.Editor | |||
|             if (null != config) | ||||
|             { | ||||
|                 if (File.Exists(path)) File.Delete(path); | ||||
|                 Debug.Log($"<color=#88ff00> --- setup {GuruSDK.ServicesConfigKey} to local resources.</color>"); | ||||
|                 UnityEngine.Debug.Log($"<color=#88ff00> --- setup {GuruSDK.ServicesConfigKey} to local resources.</color>"); | ||||
|                 File.Copy(from, path); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -0,0 +1,31 @@ | |||
| allprojects { | ||||
|     buildscript { | ||||
|         repositories {**ARTIFACTORYREPOSITORY** | ||||
|             google() | ||||
|             jcenter() | ||||
|             mavenCentral() | ||||
|         } | ||||
|          | ||||
|         dependencies { | ||||
|             // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity | ||||
|             // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html | ||||
|             // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle | ||||
|             // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version | ||||
| //             classpath 'com.android.tools.build:gradle:4.0.1' | ||||
|             classpath 'com.android.tools.build:gradle:7.4.2' | ||||
|             **BUILD_SCRIPT_DEPS** | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     repositories {**ARTIFACTORYREPOSITORY** | ||||
|         google() | ||||
|         jcenter() | ||||
|         flatDir { | ||||
|             dirs "${project(':unityLibrary').projectDir}/libs" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| task clean(type: Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 210888b84fe449e88319129ae097514d | ||||
| timeCreated: 1722245010 | ||||
|  | @ -2,7 +2,6 @@ org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M | |||
| org.gradle.parallel=true | ||||
| org.gradle.daemon=true | ||||
| org.gradle.caching=true | ||||
| android.enableR8=**MINIFY_WITH_R_EIGHT** | ||||
| unityStreamingAssets=**STREAMING_ASSETS** | ||||
| # Android Resolver Properties Start | ||||
| android.useAndroidX=true | ||||
|  |  | |||
|  | @ -5,7 +5,8 @@ dependencies { | |||
|     } | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion **APIVERSION** | ||||
|     namespace "**NAMESPACE**" | ||||
|     compileSdkVersion **TARGETSDKVERSION** | ||||
|     buildToolsVersion '**BUILDTOOLS**' | ||||
| 
 | ||||
|     compileOptions { | ||||
|  | @ -22,6 +23,7 @@ android { | |||
|         } | ||||
|         versionCode **VERSIONCODE** | ||||
|         versionName '**VERSIONNAME**' | ||||
|         multiDexEnabled true | ||||
|     } | ||||
| 
 | ||||
|     aaptOptions { | ||||
|  |  | |||
|  | @ -0,0 +1,41 @@ | |||
| apply plugin: 'com.android.library' | ||||
| **APPLY_PLUGINS** | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||
| **DEPS**} | ||||
| 
 | ||||
| android { | ||||
|     namespace "com.unity3d.player" | ||||
|     ndkPath "**NDKPATH**" | ||||
|     compileSdkVersion **APIVERSION** | ||||
|     buildToolsVersion '**BUILDTOOLS**' | ||||
| 
 | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_11 | ||||
|         targetCompatibility JavaVersion.VERSION_11 | ||||
|     } | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion **MINSDKVERSION** | ||||
|         targetSdkVersion **TARGETSDKVERSION** | ||||
|         ndk { | ||||
|             abiFilters **ABIFILTERS** | ||||
|         } | ||||
|         versionCode **VERSIONCODE** | ||||
|         versionName '**VERSIONNAME**' | ||||
|         consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD** | ||||
|     } | ||||
| 
 | ||||
|     lintOptions { | ||||
|         abortOnError false | ||||
|     } | ||||
| 
 | ||||
|     aaptOptions { | ||||
|         noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ') | ||||
|         ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" | ||||
|     }**PACKAGING_OPTIONS** | ||||
| } | ||||
| **IL_CPP_BUILD_SETUP** | ||||
| **SOURCE_BUILD_SETUP** | ||||
| **EXTERNAL_SOURCES** | ||||
|  | @ -0,0 +1,134 @@ | |||
| -keep class com.unity3d.plugin.* { *; } | ||||
| 
 | ||||
| #proguard-adjust.pro | ||||
| -keep public class com.adjust.sdk.** { *; } | ||||
| -keep class com.amazon.device.ads.** { *; }  | ||||
| -keep class com.amazon.aps.** { *; }  | ||||
| -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { | ||||
|     com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context); | ||||
| } | ||||
| -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info { | ||||
|     java.lang.String getId(); | ||||
|     boolean isLimitAdTrackingEnabled(); | ||||
| } | ||||
| -keep public class com.android.installreferrer.** { *; } | ||||
| -keepclassmembers class com.ironsource.sdk.controller.IronSourceWebView$JSInterface { | ||||
|     public *; | ||||
| } | ||||
| -keepclassmembers class * implements android.os.Parcelable { | ||||
|     public static final android.os.Parcelable$Creator *; | ||||
| } | ||||
| -keep public class com.google.android.gms.ads.** { | ||||
|     public *; | ||||
| } | ||||
| -keep class com.ironsource.adapters.** { *; } | ||||
| -dontwarn com.ironsource.mediationsdk.** | ||||
| -dontwarn com.ironsource.adapters.** | ||||
| -keepattributes JavascriptInterface | ||||
| -keepclassmembers class * { | ||||
|     @android.webkit.JavascriptInterface <methods>; | ||||
| } | ||||
| 
 | ||||
| -keep class dalvik.system.VMRuntime { | ||||
|     java.lang.String getRuntime(); | ||||
| } | ||||
| -keep class android.os.Build { | ||||
|     java.lang.String[] SUPPORTED_ABIS; | ||||
|     java.lang.String CPU_ABI; | ||||
| } | ||||
| -keep class android.content.res.Configuration { | ||||
|     android.os.LocaleList getLocales(); | ||||
|     java.util.Locale locale; | ||||
| } | ||||
| -keep class android.os.LocaledList { | ||||
|     java.util.Locale get(int); | ||||
| } | ||||
| 
 | ||||
| #proguard-facebook.pro | ||||
| -keep class com.facebook.** { *; } | ||||
| -keepattributes Signature | ||||
| -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip | ||||
| -keep @com.facebook.common.internal.DoNotStrip class * | ||||
| -keep class com.facebook.stetho.** { *; } | ||||
| -keepclassmembers class * { | ||||
|     @com.facebook.common.internal.DoNotStrip *; | ||||
| } | ||||
| -keepclassmembers class * { | ||||
|     native <methods>; | ||||
| } | ||||
| -dontwarn okio.** | ||||
| -dontwarn javax.annotation.** | ||||
| -dontwarn com.android.volley.toolbox.** | ||||
| -keep class com.google.firebase.** { *; } | ||||
| -dontwarn com.google.firebase.** | ||||
| -keep class com.bytedance.sdk.** { *; } | ||||
| -keep class com.pgl.sys.ces.* { *; } | ||||
| -keep class com.facebook.** { *; } | ||||
| -keep class com.google.android.play.core.** { *; } | ||||
| -keep class com.google.games.bridge.** { *; } | ||||
| -keep class com.google.android.gms.** { *; } | ||||
| -keep class com.google.android.gms.games.leaderboard.** { *; } | ||||
| -keep class com.google.android.gms.games.snapshot.** { *; } | ||||
| -keep class com.google.android.gms.games.achievement.** { *; } | ||||
| -keep class com.google.android.gms.games.event.** { *; } | ||||
| -keep class com.google.android.gms.games.stats.** { *; } | ||||
| -keep class com.google.android.gms.games.video.** { *; } | ||||
| -keep class com.google.android.gms.games.* { *; } | ||||
| -keep class com.google.android.gms.common.api.ResultCallback { *; } | ||||
| -keep class com.google.android.gms.signin.** { *; } | ||||
| -keep class com.google.android.gms.dynamic.** { *; } | ||||
| -keep class com.google.android.gms.dynamite.** { *; } | ||||
| -keep class com.google.android.gms.tasks.** { *; } | ||||
| -keep class com.google.android.gms.security.** { *; } | ||||
| -keep class com.google.android.gms.base.** { *; } | ||||
| -keep class com.google.android.gms.actions.** { *; } | ||||
| -keep class com.google.android.gms.common.ConnectionResult { *; } | ||||
| -keep class com.google.android.gms.common.GooglePlayServicesUtil { *; } | ||||
| -keep class com.google.android.gms.common.api.** { *; } | ||||
| -keep class com.google.android.gms.common.data.DataBufferUtils { *; } | ||||
| -keep class com.google.android.gms.games.quest.** { *; } | ||||
| -keep class com.google.android.gms.nearby.** { *; } | ||||
| -keep class com.google.android.gms.ads.** { *; } | ||||
| 
 | ||||
| -keep class com.pubmatic.sdk.** { *; } | ||||
| -keep class com.applovin.** { *; } | ||||
| -keep class com.chartboost.** { *; } | ||||
| 
 | ||||
| -keep class com.guru.** { *; } | ||||
| -keep class guru.core.** { *; } | ||||
| 
 | ||||
| -keep class com.onevcat.uniwebview.* { *; } | ||||
| -keep class com.iab.omid.* { *; } | ||||
| 
 | ||||
| -keep public class com.tradplus.** { *; } | ||||
| -keep class com.tradplus.ads.** { *; } | ||||
| -keep class com.applovin.mediation.adapters.** { *; } | ||||
| 
 | ||||
| -keep public class com.applovin.sdk.AppLovinSdk{ *; } | ||||
| -keep public class com.applovin.sdk.AppLovin* { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.nativeAds.AppLovin* { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.adview.* { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.mediation.* { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.mediation.ads.* { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.impl.*.AppLovin { | ||||
|     public protected *; | ||||
| } | ||||
| -keep public class com.applovin.impl.**.*Impl { | ||||
|     public protected *; | ||||
| } | ||||
| -keepclassmembers class com.applovin.sdk.AppLovinSdkSettings { | ||||
|     private java.util.Map localSettings; | ||||
| } | ||||
| -keep class com.applovin.mediation.adapters.** { *; } | ||||
| -keep class com.applovin.mediation.adapter.** { *; } | ||||
| -keep class com.applovin.mediation.unity.** { *; } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: a08608d11bf7483fa4c49df80ebab657 | ||||
| timeCreated: 1722245439 | ||||
|  | @ -1,8 +0,0 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 11c5c61708cec45b3886f7c93951d083 | ||||
| folderAsset: yes | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -1,6 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|           package="com.guru.unity.sdk.android.res.network.security" | ||||
|           android:versionCode="1" | ||||
|           android:versionName="1.0"> | ||||
| </manifest> | ||||
|  | @ -1,2 +0,0 @@ | |||
| target=android-9 | ||||
| android.library=true | ||||
|  | @ -1,8 +0,0 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: c6c2e979b6e21424a8c8807754828741 | ||||
| folderAsset: yes | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -1,3 +0,0 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 29db8462008d4d8d9220c83bf5dcd6e0 | ||||
| timeCreated: 1706057866 | ||||
|  | @ -0,0 +1,60 @@ | |||
| pluginManagement { | ||||
|     repositories { | ||||
|         **ARTIFACTORYREPOSITORY** | ||||
|         gradlePluginPortal() | ||||
|         google() | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| include ':launcher', ':unityLibrary' | ||||
| **INCLUDES** | ||||
| 
 | ||||
| dependencyResolutionManagement { | ||||
|     repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) | ||||
|     repositories { | ||||
|         **ARTIFACTORYREPOSITORY** | ||||
|         google() | ||||
|         mavenCentral() | ||||
| // Android Resolver Repos Start | ||||
|         def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") | ||||
|         maven { | ||||
|             url "https://verve.jfrog.io/artifactory/verve-gradle-release" // Packages/com.guru.unity.max/Mediation/Verve/Editor/Dependencies.xml:7 | ||||
|         } | ||||
|         maven { | ||||
|             url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Packages/com.google.firebase.firestore/Firebase/Editor/FirestoreDependencies.xml:20, Packages/com.google.firebase.app/Firebase/Editor/AppDependencies.xml:22, Packages/com.google.firebase.auth/Firebase/Editor/AuthDependencies.xml:20, Packages/com.google.firebase.messaging/Firebase/Editor/MessagingDependencies.xml:24, Packages/com.google.firebase.crashlytics/Firebase/Editor/CrashlyticsDependencies.xml:20, Packages/com.google.firebase.dynamic-links/Firebase/Editor/DynamicLinksDependencies.xml:20, Packages/com.google.firebase.installations/Firebase/Editor/InstallationsDependencies.xml:20, Packages/com.google.firebase.remote-config/Firebase/Editor/RemoteConfigDependencies.xml:20, Packages/com.google.firebase.analytics/Firebase/Editor/AnalyticsDependencies.xml:18 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://aws.oss.sonatype.org/content/repositories/releases/" // Packages/com.guru.unity.max/Amazon/Scripts/Editor/AmazonDependencies.xml:10 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://artifactory.bidmachine.io/bidmachine" // Packages/com.guru.unity.max/Mediation/BidMachine/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://artifact.bytedance.com/repository/pangle" // Packages/com.guru.unity.max/Mediation/ByteDance/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://cboost.jfrog.io/artifactory/chartboost-ads/" // Packages/com.guru.unity.max/Mediation/Chartboost/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://android-sdk.is.com/" // Packages/com.guru.unity.max/Mediation/IronSource/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" // Packages/com.guru.unity.max/Mediation/Mintegral/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://maven.ogury.co" // Packages/com.guru.unity.max/Mediation/OguryPresage/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://s3.amazonaws.com/smaato-sdk-releases/" // Packages/com.guru.unity.max/Mediation/Smaato/Editor/Dependencies.xml:8 | ||||
|         } | ||||
|         maven { | ||||
|             url "https://repo.pubmatic.com/artifactory/public-repos" // Packages/com.guru.unity.max/OpenWrapSDK/Editor/OpenWrapSDKDependencies.xml:18, Packages/com.guru.unity.max/OpenWrapSDK/Mediation/AppLovinMAX/Editor/ALOpenWrapMediationDependencies.xml:7 | ||||
|         } | ||||
|         mavenLocal() | ||||
| // Android Resolver Repos End | ||||
|         flatDir { | ||||
|             dirs "${project(':unityLibrary').projectDir}/libs" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 4f7213bbbc8741bd89b3bd17a7bea43e | ||||
| timeCreated: 1722240718 | ||||
|  | @ -0,0 +1,57 @@ | |||
| namespace Guru.Editor | ||||
| { | ||||
|     using System; | ||||
|     using System.IO; | ||||
|     using UnityEngine; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Create androidlib assets | ||||
|     /// </summary> | ||||
|     public class AndroidLibHelper | ||||
|     { | ||||
|          | ||||
|         private static readonly string PluginsRoot = "Plugins/Android"; | ||||
|         private static readonly string Extends = "androidlib"; | ||||
|         private static readonly string ProjectPropertiesName = "project.properties"; | ||||
|         private static readonly string ProjectPropertiesContent= "target=android-9\nandroid.library=true"; | ||||
|         private static readonly string AndroidManifestName = "AndroidManifest.xml"; | ||||
|         private static readonly string AndroidManifestContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"{0}\"\n          android:versionCode=\"1\"\n          android:versionName=\"1.0\">\n</manifest>"; | ||||
| 
 | ||||
|          | ||||
|         public static bool IsEmbeddedAndroidLibExists(string fileName) | ||||
|         { | ||||
|             string dir = Path.GetFullPath($"{Application.dataPath}/{PluginsRoot}/{fileName}.{Extends}"); | ||||
|             return Directory.Exists(dir); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public static string CreateLibRoot(string packageName, string fileName = "") | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(packageName)) return ""; | ||||
|              | ||||
|             if(string.IsNullOrEmpty(fileName)) fileName = packageName; | ||||
|              | ||||
|             string dir = Path.GetFullPath($"{Application.dataPath}/{PluginsRoot}/{fileName}.{Extends}"); | ||||
|             if (Directory.Exists(dir)) | ||||
|             { | ||||
|                 return dir; | ||||
|             } | ||||
|             Directory.CreateDirectory(dir); | ||||
| 
 | ||||
|             string path = ""; | ||||
|             string content = ""; | ||||
|              | ||||
|             //------ Create project.properties ------ | ||||
|             content = ProjectPropertiesContent; | ||||
|             path = $"{dir}/{ProjectPropertiesName}"; | ||||
|             File.WriteAllText(path, content); | ||||
|             // ------ Create AndroidManifest.xml ------ | ||||
|             content = AndroidManifestContent.Replace("{0}", packageName); | ||||
|             path = $"{dir}/{AndroidManifestName}"; | ||||
|             File.WriteAllText(path, content); | ||||
|              | ||||
|             return dir; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: ee3d47ab5d544373ae6a43fdd36d96fb | ||||
| timeCreated: 1711761343 | ||||
|  | @ -0,0 +1,389 @@ | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Guru.Editor | ||||
| { | ||||
|     using System.Xml; | ||||
|     using System.IO; | ||||
|     using UnityEngine; | ||||
|     using System.Collections.Generic; | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Android 配置修改器 | ||||
|     /// </summary> | ||||
|     public class AndroidManifestDoc | ||||
|     { | ||||
|         private const string TargetPath = "Plugins/Android/AndroidManifest.xml"; | ||||
|         private const string XmlnsAndroid = "xmlns:android"; | ||||
|         private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android"; | ||||
|         private const string XmlnsTools= "xmlns:tools"; | ||||
|         private const string NamespaceTools = "http://schemas.android.com/tools"; | ||||
|          | ||||
|         private const string UserPermission = "uses-permission"; | ||||
|         private const string MetaData = "meta-data"; | ||||
|         private const string KName = "name"; | ||||
| 
 | ||||
|         private XmlDocument _doc; | ||||
|         public XmlDocument Doc => _doc; | ||||
| 
 | ||||
|         private string _docPath; | ||||
|         private bool _isReady = false; | ||||
|          | ||||
|         private XmlElement _manifestNode; | ||||
|         private XmlElement _applicationNode; | ||||
|         private XmlElement _queriesNode; | ||||
| 
 | ||||
|          | ||||
|         #region Initiallize | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 加载文件 | ||||
|         /// </summary> | ||||
|         /// <param name="docPath"></param> | ||||
|         /// <returns></returns> | ||||
|         public static AndroidManifestDoc Load(string docPath = "") | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(docPath)) | ||||
|             { | ||||
|                 docPath = Path.GetFullPath(Path.Combine(Application.dataPath, TargetPath)); | ||||
|             } | ||||
| 
 | ||||
|             if (!File.Exists(docPath)) | ||||
|             { | ||||
|                 Debug.LogError($"--- File not found: {docPath}"); | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             var mod = new AndroidManifestDoc(); | ||||
|             mod.ReadFromPath(docPath); | ||||
|             return mod; | ||||
|         } | ||||
| 
 | ||||
|         public static AndroidManifestDoc Read(string xmlStr, string docPath = "") | ||||
|         { | ||||
|             var mod = new AndroidManifestDoc(); | ||||
|             mod.ReadFromXml(xmlStr, docPath); | ||||
|             return mod; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 从文件路径读取 | ||||
|         /// </summary> | ||||
|         /// <param name="docPath"></param> | ||||
|         public void ReadFromPath(string docPath) | ||||
|         { | ||||
|             _isReady = false; | ||||
|             if (File.Exists(docPath)) | ||||
|             { | ||||
|                 var xmlStr = File.ReadAllText(docPath); | ||||
|                 ReadFromXml(xmlStr, docPath); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Debug.LogError($"--- File not found: {docPath}"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public void ReadFromXml(string xmlStr, string docPath = "") | ||||
|         { | ||||
|             _doc = new XmlDocument(); | ||||
|             _doc.LoadXml(xmlStr); | ||||
|             if(!string.IsNullOrEmpty(docPath)) _docPath = docPath; | ||||
|             Init(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes the Doc | ||||
|         /// </summary> | ||||
|         private void Init() | ||||
|         { | ||||
|             // --- Root Nodes --- | ||||
|             _manifestNode = _doc.SelectSingleNode("manifest") as XmlElement; | ||||
|             _applicationNode = _doc.SelectSingleNode("manifest/application") as XmlElement; | ||||
|             _queriesNode = _doc.SelectSingleNode("manifest/queries") as XmlElement; | ||||
|              | ||||
|              | ||||
|             AddXmlnsAndroid(); | ||||
|             AddXmlnsTools(); | ||||
|             _isReady = true; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Save Doc | ||||
|         /// </summary> | ||||
|         public void Save(string docPath = "") | ||||
|         { | ||||
|             if (_isReady) | ||||
|             { | ||||
|                 if (!string.IsNullOrEmpty(docPath)) _docPath = docPath; | ||||
|                 if (!string.IsNullOrEmpty(_docPath)) | ||||
|                 { | ||||
|                     var dir = Directory.GetParent(_docPath); | ||||
|                     if(!dir.Exists) dir.Create(); | ||||
|                     _doc.Save(_docPath); | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Node Opreation | ||||
| 
 | ||||
|          | ||||
|         public static bool AddAttribute(XmlElement node, string key, string value) | ||||
|         { | ||||
|             if (node != null) | ||||
|             { | ||||
|                 if (node.HasAttribute(key)) | ||||
|                 { | ||||
|                     node.Attributes[key].Value = value; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     node.SetAttribute(key, value); | ||||
|                 } | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|          | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region API | ||||
| 
 | ||||
|         public bool AddXmlnsAndroid() | ||||
|         { | ||||
|             return AddAttribute(_manifestNode, XmlnsAndroid, NamespaceAndroid); | ||||
|         } | ||||
|          | ||||
|         public bool AddXmlnsTools() | ||||
|         { | ||||
|             return AddAttribute(_manifestNode, XmlnsTools, NamespaceTools); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// Add Replace Item | ||||
|         /// </summary> | ||||
|         /// <param name="item"></param> | ||||
|         public void AddApplicationReplaceItem(string item) | ||||
|         { | ||||
|             if (_applicationNode != null) | ||||
|             { | ||||
|                 List<string> items = new List<string>(5); | ||||
|                 if (_applicationNode.HasAttribute("replace", NamespaceTools)) | ||||
|                 { | ||||
|                     var arr = _applicationNode.GetAttribute("replace",NamespaceTools).Split(','); | ||||
|                     if(arr != null && arr.Length > 0) | ||||
|                     { | ||||
|                         items.AddRange(arr); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (!items.Contains(item)) items.Add(item); | ||||
|                  | ||||
|                 _applicationNode.SetAttribute("replace",  NamespaceTools, string.Join(",", items)); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public void SetApplicationAttribute(string key, string value) | ||||
|         { | ||||
|             if (_applicationNode != null) | ||||
|             { | ||||
|                 _applicationNode.SetAttribute(key, NamespaceAndroid, value); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// Set metadata | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <param name="value"></param> | ||||
|         /// <param name="valueName"></param> | ||||
|         /// <param name="keyName"></param> | ||||
|         public void SetMetadata(string key, string value, string valueName = "value", string keyName = KName) | ||||
|         { | ||||
|             if (_doc == null || !_isReady) return; | ||||
|              | ||||
|             XmlElement node = null; | ||||
|             if (!TryGetMetadata(key, out node, keyName)) | ||||
|             { | ||||
|                 node = _doc.CreateElement(MetaData); | ||||
|                 _applicationNode?.AppendChild(node); | ||||
|             } | ||||
| 
 | ||||
|             node.SetAttribute(keyName, NamespaceAndroid, key); | ||||
|             node.SetAttribute(valueName, NamespaceAndroid, value); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 添加权限 | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <param name="keyName"></param> | ||||
|         public void AddPermission(string key, string keyName = KName) | ||||
|         { | ||||
|             if (_doc == null || !_isReady) return; | ||||
| 
 | ||||
|             XmlElement node = null; | ||||
|             if(!TryGetPermission(key, out node, keyName)) | ||||
|             { | ||||
|                 node = _doc.CreateElement(UserPermission); | ||||
|                 _manifestNode?.AppendChild(node); | ||||
|             } | ||||
|             node.SetAttribute(keyName, NamespaceAndroid, key); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 删除 Permission | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <param name="value"></param> | ||||
|         /// <param name="keyName"></param> | ||||
|         /// <param name="valueName"></param> | ||||
|         public void RemovePermission(string key, string value = "remove", string keyName = "name",  string valueName = "node") | ||||
|         { | ||||
|             if (_doc == null || !_isReady) return; | ||||
|              | ||||
|             XmlElement node = null; | ||||
|             if(!TryGetPermission(key, out node, keyName)) | ||||
|             { | ||||
|                 node = _doc.CreateElement(UserPermission); | ||||
|                 _manifestNode?.AppendChild(node); | ||||
|             } | ||||
|             node.SetAttribute(keyName, NamespaceAndroid, key); | ||||
|             node.SetAttribute(valueName, NamespaceTools, value); | ||||
|         } | ||||
|          | ||||
|         public bool SetPackageName(string packageName) | ||||
|         { | ||||
|             if (_manifestNode != null) | ||||
|             { | ||||
|                 _manifestNode.Attributes["package"].Value = packageName; | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 添加 Queries Intent | ||||
|         /// </summary> | ||||
|         /// <param name="value"></param> | ||||
|         /// <param name="keyName"></param> | ||||
|         /// <returns></returns> | ||||
|         public void AddQueriesIntent(string value, string keyName = "name") | ||||
|         { | ||||
|             if (_queriesNode == null) | ||||
|             { | ||||
|                 _queriesNode = _doc.CreateElement("queries"); | ||||
|                 _manifestNode?.AppendChild(_queriesNode); | ||||
|             } | ||||
| 
 | ||||
|             var intentList = _queriesNode.SelectNodes("intent"); | ||||
|             if (intentList != null) | ||||
|             { | ||||
|                 foreach (XmlElement intent in intentList) | ||||
|                 { | ||||
|                     var action = intent?.SelectSingleNode("action") as XmlElement; | ||||
|                      | ||||
|                     if (action != null  | ||||
|                         && action.GetAttribute(keyName, NamespaceAndroid) == value) | ||||
|                     { | ||||
|                         return; // Has injected,skip ... | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Inject new intent node | ||||
|             XmlElement intentNode = _doc.CreateElement("intent"); | ||||
|             _queriesNode.AppendChild(intentNode); | ||||
|             XmlElement actionNode = _doc.CreateElement("action"); | ||||
|             intentNode.AppendChild(actionNode); | ||||
|             actionNode.SetAttribute(keyName, NamespaceAndroid, value); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Data Opration | ||||
| 
 | ||||
|         public bool TryGetMetadata(string name, out XmlElement node, string keyName = KName) | ||||
|         { | ||||
|             node = null; | ||||
| 
 | ||||
|             if(_applicationNode != null) | ||||
|             { | ||||
|                 var list = _applicationNode.SelectNodes(MetaData); | ||||
|                 if (list != null) | ||||
|                 { | ||||
|                     foreach (XmlElement e in list) | ||||
|                     { | ||||
|                         if (e.GetAttribute(keyName, NamespaceAndroid) == name) | ||||
|                         { | ||||
|                             node = e; | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|          | ||||
|         public bool TryGetPermission(string name, out XmlElement node, string keyName = KName) | ||||
|         { | ||||
|             node = null; | ||||
| 
 | ||||
|             if(_manifestNode != null) | ||||
|             { | ||||
|                 var list = _manifestNode.SelectNodes(UserPermission); | ||||
|                 if (list != null) | ||||
|                 { | ||||
|                     foreach (XmlElement e in list) | ||||
|                     { | ||||
|                         if (e.GetAttribute(keyName, NamespaceAndroid) == name) | ||||
|                         { | ||||
|                             node = e; | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         public bool TryRootNode(string nodeName, out XmlElement node) | ||||
|         { | ||||
|             node = null; | ||||
|             if (_doc != null) | ||||
|             { | ||||
|                 node = _doc.SelectSingleNode(nodeName) as XmlElement; | ||||
|                 return node != null; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
|          | ||||
|         #region Output | ||||
| 
 | ||||
| 
 | ||||
|         public override string ToString() | ||||
|         { | ||||
|             if (_doc != null) return _doc.InnerXml; | ||||
|             return this.ToString(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 89a1c7f77fcf4982adbbaf6dc61bd62d | ||||
| timeCreated: 1711505949 | ||||
|  | @ -1,5 +1,3 @@ | |||
| using System.Collections; | ||||
| using Unity.EditorCoroutines.Editor; | ||||
| 
 | ||||
| namespace Guru.Editor | ||||
| { | ||||
|  | @ -16,6 +14,35 @@ namespace Guru.Editor | |||
|         private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"; | ||||
|         private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"; | ||||
| 
 | ||||
|         private const string PermissionReadPostNotifications = "android.permission.POST_NOTIFICATIONS"; | ||||
|         private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE"; | ||||
|         private const string PermissionAccessCoarseLocation = "android.permission.ACCESS_COARSE_LOCATION"; | ||||
|         private const string PermissionAccessFineLocation = "android.permission.ACCESS_FINE_LOCATION"; | ||||
|         private const string PermissionReadExternalStorage = "android.permission.READ_EXTERNAL_STORAGE"; | ||||
|         private const string PermissionReadLogs = "android.permission.READ_LOGS"; | ||||
|         private const string NetworkSecurityConfig = "networkSecurityConfig"; | ||||
|         private const string NetworkSecurityConfigValue = "@xml/network_security_config"; | ||||
|         private const string PermissionAdjustReadPermission = "com.adjust.preinstall.READ_PERMISSION"; // Adjust permission | ||||
|         private const string AdjustQueriesActionValue = "com.attribution.REFERRAL_PROVIDER"; // Adjust action | ||||
|          | ||||
|         // Add Permissions | ||||
|         private static string[] addPermissions = new[] | ||||
|         { | ||||
|             PermissionReadPostNotifications, | ||||
|             PermissionAdjustReadPermission, | ||||
|         }; | ||||
|          | ||||
|         // Remove Permissions | ||||
|         private static string[] removePermissions = new[] | ||||
|         { | ||||
|             PermissionReadPhoneState, | ||||
|             PermissionAccessCoarseLocation, | ||||
|             PermissionAccessFineLocation, | ||||
|             PermissionReadExternalStorage, | ||||
|             PermissionReadLogs, | ||||
|         }; | ||||
|          | ||||
| 
 | ||||
|         private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath); | ||||
|          | ||||
|         public static bool IsManifestExist() => File.Exists(TargetFullPath); | ||||
|  | @ -27,63 +54,53 @@ namespace Guru.Editor | |||
|                 CopyManifest(); | ||||
|             } | ||||
|              | ||||
|             var doc = new XmlDocument(); | ||||
|             doc.Load(TargetFullPath); | ||||
| 
 | ||||
|             var rootNode = doc.SelectSingleNode("manifest/application"); | ||||
|             int item1 = 0; | ||||
|             int item2 = 0; | ||||
|              | ||||
|             XmlNodeList metadatas = rootNode.SelectNodes("meta-data"); | ||||
|             if (metadatas != null && metadatas.Count > 0) | ||||
|             { | ||||
|                 bool isDirty = false; | ||||
|        | ||||
|                 foreach (XmlElement e in metadatas) | ||||
|                 { | ||||
|                     if (e != null) | ||||
|                     { | ||||
|                         if (e.HasAttribute("android:name")) | ||||
|                         { | ||||
|                             if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1; | ||||
|                             if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             string androidSP = "http://schemas.android.com/apk/res/android"; | ||||
|              | ||||
|             if (item1 == 0) | ||||
|             { | ||||
|                var e =  doc.CreateElement("meta-data"); | ||||
|                e.SetAttribute("name",androidSP, ValOptimizeInitialization); | ||||
|                e.SetAttribute("value",androidSP, "true"); | ||||
|                rootNode.AppendChild(e); | ||||
|             } | ||||
|              | ||||
|             if (item2 == 0) | ||||
|             { | ||||
|                 var e =  doc.CreateElement("meta-data"); | ||||
|                 e.SetAttribute("name",androidSP,ValOptimizeAdLoading); | ||||
|                 e.SetAttribute("value",androidSP, "true"); | ||||
|                 rootNode.AppendChild(e); | ||||
|             } | ||||
| 
 | ||||
|             var rootE = doc.SelectSingleNode("manifest") as XmlElement; | ||||
|             if (rootE != null) | ||||
|             { | ||||
|                 rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名 | ||||
|             } | ||||
| 
 | ||||
|             doc.Save(TargetFullPath); | ||||
|             FixAndroidManifest(); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// Fix Android Manifest | ||||
|         /// </summary> | ||||
|         private static void FixAndroidManifest() | ||||
|         { | ||||
|             var doc = AndroidManifestDoc.Load(TargetFullPath); | ||||
|              | ||||
|             // --- network_security_config --- | ||||
|             doc.SetApplicationAttribute(NetworkSecurityConfig, NetworkSecurityConfigValue); | ||||
|             doc.AddApplicationReplaceItem($"android:{NetworkSecurityConfig}"); | ||||
|             // ---- Metadata --- | ||||
|             doc.SetMetadata(ValOptimizeInitialization, "true"); | ||||
|             doc.SetMetadata(ValOptimizeAdLoading, "true"); | ||||
|             // ---- Permission --- | ||||
|             // Add needed permissions | ||||
|             foreach (var p in addPermissions) | ||||
|             { | ||||
|                 doc.AddPermission(p); | ||||
|             } | ||||
|             // Remove sensitive permissions | ||||
|             foreach (var p in removePermissions) | ||||
|             { | ||||
|                 doc.RemovePermission(p); | ||||
|             } | ||||
| 
 | ||||
|             // --- Bundle Id --- | ||||
|             doc.SetPackageName(PlayerSettings.applicationIdentifier); | ||||
|              | ||||
|             // --- Adjust Preinstall (Content provider) --- | ||||
|             doc.AddQueriesIntent(AdjustQueriesActionValue); | ||||
|              | ||||
|             doc.Save(); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 拷贝 AndroidManifest | ||||
|         /// </summary> | ||||
|         private static void CopyManifest() | ||||
|         { | ||||
|             if (File.Exists(TargetFullPath)) return; | ||||
|              | ||||
|             var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidManifestMod)} t:Script"); | ||||
|             var path = GuruEditorHelper.GetAssetPath(nameof(AndroidManifestMod), "Script", true); | ||||
|             if (!string.IsNullOrEmpty(path)) | ||||
|             { | ||||
|                 var from = Path.GetFullPath($"{path}/../../Files/AndroidManifest.txt"); | ||||
|  | @ -93,10 +110,7 @@ namespace Guru.Editor | |||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|         #region Testing | ||||
| 
 | ||||
|         [Test] | ||||
|  |  | |||
|  | @ -0,0 +1,178 @@ | |||
| using System.Collections; | ||||
| using Unity.EditorCoroutines.Editor; | ||||
| 
 | ||||
| namespace Guru.Editor | ||||
| { | ||||
|     using NUnit.Framework; | ||||
|     using UnityEditor; | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|     using System.IO; | ||||
|     using System.Xml; | ||||
|      | ||||
|     public static class AndroidManifestMod | ||||
|     { | ||||
|         private const string TargetPath = "Plugins/Android/AndroidManifest.xml"; | ||||
|         private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"; | ||||
|         private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"; | ||||
|         private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android"; | ||||
|         private const string NamespaceTools = "http://schemas.android.com/tools"; | ||||
| 
 | ||||
|         private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE"; | ||||
| 
 | ||||
|         private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath); | ||||
|          | ||||
|         public static bool IsManifestExist() => File.Exists(TargetFullPath); | ||||
| 
 | ||||
|         public static void Apply() | ||||
|         { | ||||
|             if (!IsManifestExist()) | ||||
|             { | ||||
|                 CopyManifest(); | ||||
|             } | ||||
|              | ||||
|             var doc = new XmlDocument(); | ||||
|             doc.Load(TargetFullPath); | ||||
| 
 | ||||
|             SetApplicationMod(doc); | ||||
|             SetPermissionMod(doc); | ||||
|              | ||||
|             doc.Save(TargetFullPath); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Fix all Elements in <Applicaiton> | ||||
|         /// </summary> | ||||
|         /// <param name="doc"></param> | ||||
|         private static void SetApplicationMod(XmlDocument doc) | ||||
|         { | ||||
|             string rootName = "manifest/application"; | ||||
|             var rootNode = doc.SelectSingleNode(rootName); | ||||
|             int item1 = 0; | ||||
|             int item2 = 0; | ||||
|              | ||||
|             if (rootNode == null) | ||||
|             { | ||||
|                 Debug.LogError($"Can't find root with name {rootName} ..."); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|              | ||||
|             XmlNodeList metadatas = rootNode.SelectNodes("meta-data"); | ||||
|             if (metadatas != null && metadatas.Count > 0) | ||||
|             { | ||||
|                 bool isDirty = false; | ||||
|        | ||||
|                 foreach (XmlElement e in metadatas) | ||||
|                 { | ||||
|                     if (e != null) | ||||
|                     { | ||||
|                         if (e.HasAttribute("name", NamespaceAndroid)) | ||||
|                         { | ||||
|                             if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1; | ||||
|                             if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if (item1 == 0) | ||||
|             { | ||||
|                 var e =  doc.CreateElement("meta-data"); | ||||
|                 e.SetAttribute("name",NamespaceAndroid, ValOptimizeInitialization); | ||||
|                 e.SetAttribute("value",NamespaceAndroid, "true"); | ||||
|                 rootNode.AppendChild(e); | ||||
|             } | ||||
|              | ||||
|             if (item2 == 0) | ||||
|             { | ||||
|                 var e =  doc.CreateElement("meta-data"); | ||||
|                 e.SetAttribute("name",NamespaceAndroid,ValOptimizeAdLoading); | ||||
|                 e.SetAttribute("value",NamespaceAndroid, "true"); | ||||
|                 rootNode.AppendChild(e); | ||||
|             } | ||||
| 
 | ||||
|             var rootE = doc.SelectSingleNode("manifest") as XmlElement; | ||||
|             if (rootE != null) | ||||
|             { | ||||
|                 rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Fix all permissions  | ||||
|         /// </summary> | ||||
|         /// <param name="doc"></param> | ||||
|         private static void SetPermissionMod(XmlDocument doc) | ||||
|         { | ||||
|             string attName = "uses-permission"; | ||||
|             string rootName = "manifest"; | ||||
|             bool isBuild = false; | ||||
|             var rootNode = doc.SelectSingleNode(rootName); | ||||
|              | ||||
|             XmlElement item1 = null; | ||||
|             if (rootNode == null) | ||||
|             { | ||||
|                 Debug.LogError($"Can't find root with name {rootName} ..."); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             XmlNodeList permissions = rootNode.SelectNodes(attName); | ||||
|             if (permissions != null && permissions.Count > 0) | ||||
|             { | ||||
|                 foreach (XmlElement e in permissions) | ||||
|                 { | ||||
|                     if (e != null) | ||||
|                     { | ||||
|                         if (e.HasAttribute("android:name")) | ||||
|                         { | ||||
|                             if (e.Attributes["android:name"].Value == PermissionReadPhoneState) item1 = e; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             isBuild = false; | ||||
|             if (item1 == null) | ||||
|             { | ||||
|                 isBuild = true; | ||||
|                 item1 =  doc.CreateElement(attName); | ||||
|             } | ||||
|             item1.SetAttribute("name",NamespaceAndroid, PermissionReadPhoneState); | ||||
|             item1.SetAttribute("node",NamespaceTools, "remove"); | ||||
|             if (isBuild) rootNode.AppendChild(item1); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         private static void CopyManifest() | ||||
|         { | ||||
|             if (File.Exists(TargetFullPath)) return; | ||||
|              | ||||
|             var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidManifestMod)} t:Script"); | ||||
|             if (!string.IsNullOrEmpty(path)) | ||||
|             { | ||||
|                 var from = Path.GetFullPath($"{path}/../../Files/AndroidManifest.txt"); | ||||
|                 if (File.Exists(from)) | ||||
|                 { | ||||
|                     File.Copy(from, TargetFullPath); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         #region Testing | ||||
| 
 | ||||
|         [Test] | ||||
|         public static void Test_Injection() | ||||
|         { | ||||
|             Apply(); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,5 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 3a35ac1e970714224ba4adaae5756f10 | ||||
| guid: f4547abfcddf84bc6b61e884ebfb30e2 | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|  | @ -10,27 +10,42 @@ namespace Guru.Editor | |||
|      | ||||
|     public class AndroidProjectMod | ||||
|     { | ||||
|         private static readonly int TargetSDKVersion = 33; | ||||
|         private const int TargetSDKVersion = 34; | ||||
|         private const string K_ANDROID_PLUGINS_NAME = "Plugins/Android"; | ||||
|          | ||||
|         private static readonly string LauncherName = "launcherTemplate"; | ||||
|         private static string LauncherFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{LauncherName}.gradle"); | ||||
|         private const string LauncherName = "launcherTemplate"; | ||||
|         private static readonly string LauncherFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{LauncherName}.gradle"); | ||||
|          | ||||
|         private static readonly string MainName = "mainTemplate"; | ||||
|         private static string MainFullPath = Path.Combine(Application.dataPath,  $"Plugins/Android/{MainName}.gradle"); | ||||
|         private const string MainName = "mainTemplate"; | ||||
|         private static readonly string MainFullPath = Path.Combine(Application.dataPath,  $"{K_ANDROID_PLUGINS_NAME}/{MainName}.gradle"); | ||||
|          | ||||
|         private static readonly string PropertiesName = "gradleTemplate"; | ||||
|         private static string PropertiesFullPath = Path.Combine(Application.dataPath,  $"Plugins/Android/{PropertiesName}.properties"); | ||||
|         private const string BaseProjectName = "baseProjectTemplate"; | ||||
|         private static readonly string BaseProjectFullPath = Path.Combine(Application.dataPath,  $"{K_ANDROID_PLUGINS_NAME}/{BaseProjectName}.gradle"); | ||||
|          | ||||
|         private const string PropertiesName = "gradleTemplate"; | ||||
|         private const string K_ENABLE_R8 = "android.enableR8"; | ||||
|         private static readonly string PropertiesFullPath = Path.Combine(Application.dataPath,  $"{K_ANDROID_PLUGINS_NAME}/{PropertiesName}.properties"); | ||||
|          | ||||
|         private const string SettingsName = "settingsTemplate"; | ||||
|         private static readonly string SettingsFullPath = Path.Combine(Application.dataPath,  $"Plugins/Android/{SettingsName}.gradle"); | ||||
|         private const string K_LINE_UNITY_PROJECT = "def unityProjectPath"; | ||||
|          | ||||
|          | ||||
|         private const string ProguardUserName = "proguard-user"; | ||||
|         private static readonly string ProguardUserFullPath = Path.Combine(Application.dataPath,  $"{K_ANDROID_PLUGINS_NAME}/{ProguardUserName}.txt"); | ||||
|          | ||||
|         public static void Apply() | ||||
|         { | ||||
|             FixLauncher(); | ||||
|             FixMain(); | ||||
|             FixProperties(); | ||||
|             CheckTargetSDKVersion(); | ||||
|             ApplyLauncher(); | ||||
|             ApplyBaseProjectTemplates(); | ||||
|             ApplyMainTemplates(); | ||||
|             ApplyGradleTemplate(); | ||||
|             ApplySettings(); | ||||
|             ApplyProguardUser(); | ||||
|             CheckTargetSDKVersion();  // 强制修复构建版本号 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static void FixLauncher() | ||||
|          | ||||
|         private static void ApplyLauncher() | ||||
|         { | ||||
|             if (!File.Exists(LauncherFullPath)) | ||||
|             { | ||||
|  | @ -43,10 +58,9 @@ namespace Guru.Editor | |||
|             var ptn2 = "abortOnError false"; | ||||
|             var lines = File.ReadAllLines(LauncherFullPath); | ||||
| 
 | ||||
|             string line = ""; | ||||
|             for (int i = 0; i < lines.Length; i++) | ||||
|             { | ||||
|                 line = lines[i]; | ||||
|                 var line = lines[i]; | ||||
|                 if (line.Contains(ptn1)) | ||||
|                 { | ||||
|                     lines[i] = line.Replace(ptn1, "\n\n\tpackagingOptions {\n\t\texclude(\"META-INF/*.kotlin_module\")\n\t}\n\n"); | ||||
|  | @ -64,7 +78,7 @@ namespace Guru.Editor | |||
|             File.WriteAllLines(LauncherFullPath, lines); | ||||
| 
 | ||||
|         } | ||||
|         private static void FixMain() | ||||
|         private static void ApplyMainTemplates() | ||||
|         { | ||||
|             if (!File.Exists(MainFullPath)) | ||||
|             { | ||||
|  | @ -72,13 +86,100 @@ namespace Guru.Editor | |||
|                 CopyFile($"{MainName}.txt", MainFullPath); | ||||
|             } | ||||
|         } | ||||
|         private static void FixProperties() | ||||
|          | ||||
|         private static void ApplyBaseProjectTemplates() | ||||
|         { | ||||
|             if (!File.Exists(BaseProjectFullPath)) | ||||
|             { | ||||
|                 Debug.Log($"[MOD] --- Copy file to: {BaseProjectFullPath}"); | ||||
|                 CopyFile($"{BaseProjectName}.txt", BaseProjectFullPath); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         private static void ApplyGradleTemplate() | ||||
|         { | ||||
|             if (!File.Exists(PropertiesFullPath)) | ||||
|             { | ||||
|                 Debug.Log($"[MOD] --- Copy file to: {PropertiesFullPath}"); | ||||
|                 CopyFile($"{PropertiesName}.txt", PropertiesFullPath); | ||||
|             } | ||||
| 
 | ||||
|             if (TargetSDKVersion > 33) | ||||
|             { | ||||
|                 FixGradleTemplate(PropertiesFullPath); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 该版本中不再使用 R8 | ||||
|         /// </summary> | ||||
|         /// <param name="filePath"></param> | ||||
|         private static void FixGradleTemplate(string filePath) | ||||
|         { | ||||
|             if (File.Exists(filePath)) | ||||
|             { | ||||
|                 bool isDirty = false; | ||||
|                 var lines = File.ReadAllLines(filePath); | ||||
| 
 | ||||
|                 for (int i = 0; i < lines.Length; i++) | ||||
|                 { | ||||
|                     if (lines[i].Contains(K_ENABLE_R8)) | ||||
|                     { | ||||
|                         lines[i] = $"# {lines[i]}"; // 禁用R8 | ||||
|                         isDirty = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (isDirty) File.WriteAllLines(filePath, lines); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 写入 settings.gradle 配置文件 | ||||
|         /// </summary> | ||||
|         private static void ApplySettings() | ||||
|         { | ||||
|             if (!File.Exists(SettingsFullPath)) | ||||
|             { | ||||
|                 CopyFile($"{SettingsName}.txt", SettingsFullPath); | ||||
|             } | ||||
|             FixProjectPathInSettings(SettingsFullPath); | ||||
|         } | ||||
|          | ||||
|         private static void FixProjectPathInSettings(string settingsPath) | ||||
|         { | ||||
|             bool isDirty = false; | ||||
|             if (File.Exists(settingsPath)) | ||||
|             { | ||||
|                 string projectPath = Path.GetFullPath($"{Application.dataPath}/../").Replace("\\", "/"); | ||||
|                 var lines = File.ReadAllLines(settingsPath); | ||||
|                 for (int i = 0; i < lines.Length; i++) | ||||
|                 { | ||||
|                     if (lines[i].Contains(K_LINE_UNITY_PROJECT)) | ||||
|                     { | ||||
|                         lines[i] = $"        def unityProjectPath = $/file:////{projectPath}/$.replace(\"\\\\\", \"/\")"; | ||||
|                         isDirty = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                 if(isDirty) | ||||
|                     File.WriteAllLines(settingsPath, lines); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 写入所有的配置文件 | ||||
|         /// </summary> | ||||
|         private static void ApplyProguardUser() | ||||
|         { | ||||
|             if (!File.Exists(ProguardUserFullPath)) | ||||
|             { | ||||
|                 CopyFile($"{ProguardUserName}.txt", ProguardUserFullPath); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private static void CheckTargetSDKVersion() | ||||
|  | @ -95,7 +196,7 @@ namespace Guru.Editor | |||
| 
 | ||||
|         private static string GetMoveFilePath(string fileName) | ||||
|         { | ||||
|             var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidProjectMod)} t:Script"); | ||||
|             var path = GuruEditorHelper.GetAssetPath(nameof(AndroidProjectMod), "Script", true); | ||||
|             var files = Path.GetFullPath($"{path}/../../Files"); | ||||
|             return $"{files}/{fileName}"; | ||||
|         } | ||||
|  |  | |||
|  | @ -0,0 +1,159 @@ | |||
| 
 | ||||
| using System.Net; | ||||
| using UnityEditor; | ||||
| 
 | ||||
| namespace Guru.Editor | ||||
| { | ||||
|     using UnityEngine; | ||||
|     using System.IO; | ||||
| 
 | ||||
|     public class AndroidPushIconHelper | ||||
|     { | ||||
|         public static readonly int targetWidth = 96; // 目标宽度 | ||||
|         public static readonly int targetHeight = 96; | ||||
|         private static readonly string LibName = "SDKRes"; | ||||
|         private static readonly string PackageName = "com.guru.unity.res"; | ||||
|         private static readonly string IconName = "ic_notification.png"; | ||||
| 
 | ||||
|         private static readonly string ColorContent = | ||||
|             "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorAccent\">#{0}</color>\n</resources>"; | ||||
|         private static readonly string ValueContent = | ||||
|             "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"default_notification_channel_id\" translatable=\"false\">{0}</string>\n</resources>"; | ||||
|         private static readonly string[] iconNames = new string[] | ||||
|         { | ||||
|             "drawable-mdpi", | ||||
|             "drawable-hdpi", | ||||
|             "drawable-xhdpi", | ||||
|             "drawable-xxhdpi", | ||||
|             "drawable-xxxhdpi" | ||||
|         }; | ||||
|          | ||||
|          | ||||
|         private static string FcmChannelId = "fcm_default_channel"; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 设置推送图标 | ||||
|         /// </summary> | ||||
|         /// <param name="source"></param> | ||||
|         /// <param name="color"></param> | ||||
|         public static bool SetPushIconAssets(Texture2D source, Color color = default(Color)) | ||||
|         { | ||||
|             if (source == null) | ||||
|             { | ||||
|                 Debug.LogError($"=== No Texture2D found ==="); | ||||
|                 return false; | ||||
|             } | ||||
|             return DeployAllIcons(source, color); | ||||
|         } | ||||
|          | ||||
|         private static Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight) | ||||
|         { | ||||
|             MakeTextureReadable(source); | ||||
|              | ||||
|             // Texture2D result = new Texture2D(newWidth, newHeight); | ||||
|             // Color[] newColors = new Color[newWidth * newHeight]; | ||||
|             // | ||||
|             // for (int y = 0; y < newHeight; y++) | ||||
|             // { | ||||
|             //     for (int x = 0; x < newWidth; x++) | ||||
|             //     { | ||||
|             //         // 应用一些缩放逻辑来获取新的颜色值 | ||||
|             //         newColors[x + y * newWidth] = source.GetPixelBilinear((float)x / newWidth * source.width, (float)y / newHeight * source.height); | ||||
|             //     } | ||||
|             // } | ||||
|             // | ||||
|             // result.SetPixels(newColors); | ||||
|             // result.Apply(); | ||||
|             // return result; | ||||
|              | ||||
|              | ||||
|             RenderTexture rt=new RenderTexture(newWidth, newHeight,24); | ||||
|             RenderTexture.active = rt; | ||||
|             Graphics.Blit(source,rt); | ||||
|             Texture2D result=new Texture2D(newWidth,newHeight); | ||||
|             result.ReadPixels(new Rect(0,0,newWidth,newHeight),0,0); | ||||
|             result.Apply(); | ||||
|             return result; | ||||
|         } | ||||
|          | ||||
|         private static void MakeTextureReadable(Texture2D source) | ||||
|         { | ||||
|             if (source.isReadable) return; | ||||
| 
 | ||||
|             var path = AssetDatabase.GetAssetPath(source); | ||||
|             TextureImporter ti = (TextureImporter)AssetImporter.GetAtPath(path); | ||||
|             if (!ti.isReadable) | ||||
|             { | ||||
|                 ti.isReadable = true; | ||||
|                 ti.SaveAndReimport(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|          | ||||
|         private static string ColorToHex(Color color) | ||||
|         { | ||||
|             return string.Format("{0:X2}{1:X2}{2:X2}", (int)(color.r * 255), (int)(color.g * 255), (int)(color.b * 255)); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         private static bool DeployAllIcons(Texture2D source, Color color) | ||||
|         { | ||||
|             var dir = AndroidLibHelper.CreateLibRoot(PackageName, LibName); | ||||
|             string path = ""; | ||||
|             string content = ""; | ||||
|              | ||||
|             var result = ResizeTexture(source, targetWidth, targetHeight); | ||||
|             byte[] bytes = result.EncodeToPNG(); | ||||
| 
 | ||||
| 
 | ||||
|             var resPath = $"{dir}/res"; | ||||
|             if (!Directory.Exists(resPath)) | ||||
|             { | ||||
|                 Directory.CreateDirectory(resPath); | ||||
|             } | ||||
|              | ||||
|             File.WriteAllBytes($"{resPath}/{IconName}", bytes); // Base Icon; | ||||
|             // ----- Build all  Icons ------ | ||||
|             foreach (var iconName in iconNames) | ||||
|             { | ||||
|                 var iconPath = $"{resPath}/{iconName}"; | ||||
|                 if (!Directory.Exists(iconPath)) | ||||
|                 { | ||||
|                     Directory.CreateDirectory(iconPath); | ||||
|                 } | ||||
|                 File.WriteAllBytes($"{iconPath}/{IconName}", bytes); | ||||
|             } | ||||
| 
 | ||||
|             var valuesPath = $"{resPath}/values"; | ||||
|             if (!Directory.Exists(valuesPath)) Directory.CreateDirectory(valuesPath); | ||||
|              | ||||
|             // ----- Build colors.xml ------ | ||||
|             path = $"{valuesPath}/colors.xml"; | ||||
|             content = ColorContent.Replace("{0}", ColorToHex(color)); | ||||
|             File.WriteAllText(path, content); | ||||
|             // ----- Build strings.xml ------ | ||||
|             path = $"{valuesPath}/strings.xml"; | ||||
|             content = ValueContent.Replace("{0}", FcmChannelId); | ||||
|             File.WriteAllText(path, content); | ||||
|              | ||||
|             // ----- Inject AndroidManifest.xml ------ | ||||
|             var doc = AndroidManifestDoc.Load(); | ||||
|             if (doc != null) | ||||
|             { | ||||
|                 doc.SetMetadata("com.google.firebase.messaging.default_notification_icon", "@drawable/ic_notification", valueName:"resource"); | ||||
|                 doc.SetMetadata("com.google.firebase.messaging.default_notification_color", "@color/colorAccent", valueName:"resource"); | ||||
|                 doc.SetMetadata("com.google.firebase.messaging.default_notification_channel_id", "@string/default_notification_channel_id"); | ||||
|                 doc.Save(); | ||||
|                 Debug.Log("<color=#88ff00> --- Push Icon Build Success --- </color>"); | ||||
|                  | ||||
|                 AssetDatabase.Refresh(); | ||||
|                 return true; | ||||
|             } | ||||
|              | ||||
|             Debug.LogError("AndroidManifest.xml not found ..."); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 39d65f42b9694641b0c3509262aca1d4 | ||||
| timeCreated: 1711724404 | ||||
|  | @ -6,38 +6,43 @@ namespace Guru.Editor | |||
|      | ||||
|     public class AndroidResMod | ||||
|     { | ||||
|         private static readonly string ResName = "GuruNetworkSecurity"; | ||||
|         private static string ResFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{ResName}.androidlib"); | ||||
|         private static string OldSecurityXml = Path.Combine(Application.dataPath, "Plugins/Android/res/xml/network_security_config.xml"); | ||||
|         private static readonly string NetworkSecurityXmlName = "network_security_config.xml"; | ||||
|         private static readonly string LibNetworkSecurity = "GuruNetworkSecurity"; | ||||
|         private static readonly string LibNetworkSecurityPackageName = "com.guru.unity.sdk.android.res.network.security"; | ||||
|         private static string OldSecurityXml = Path.Combine(Application.dataPath, $"Plugins/Android/res/xml/{NetworkSecurityXmlName}"); | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 应用补丁 | ||||
|         /// </summary> | ||||
|         public static void Apply() | ||||
|         { | ||||
|             DeployAndroidRes(); | ||||
|             DeployNetworkSecurity(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static void DeployAndroidRes() | ||||
|         /// <summary> | ||||
|         /// 部署网络安全配置 | ||||
|         /// </summary> | ||||
|         private static void DeployNetworkSecurity() | ||||
|         { | ||||
|             if(File.Exists(OldSecurityXml)) File.Delete(OldSecurityXml); | ||||
|              | ||||
|             var spath = GuruEditorHelper.GetFilePath($"{nameof(AndroidResMod)} t:Script"); | ||||
|             if (!string.IsNullOrEmpty(spath)) | ||||
|             { | ||||
|                 var from = Path.GetFullPath($"{spath}/../../Files/sdk_res"); | ||||
|                 if (Directory.Exists(from)) | ||||
|                 { | ||||
|                     var to = ResFullPath; | ||||
|                     if (Directory.Exists(to)) | ||||
|                     { | ||||
|                         FileUtil.DeleteFileOrDirectory(ResFullPath); | ||||
|                     } | ||||
|                     FileUtil.CopyFileOrDirectory(from, to); | ||||
|                 } | ||||
|             } | ||||
|             if(File.Exists(OldSecurityXml)) File.Delete(OldSecurityXml); // 清理旧文件 | ||||
| 
 | ||||
|             if (!AndroidLibHelper.IsEmbeddedAndroidLibExists(LibNetworkSecurity)) | ||||
|             { | ||||
|                 string dir = AndroidLibHelper.CreateLibRoot(LibNetworkSecurityPackageName, LibNetworkSecurity); | ||||
|                 var d = GuruEditorHelper.GetAssetPath(nameof(AndroidResMod), "Script", true); | ||||
|                 if (!string.IsNullOrEmpty(d)) | ||||
|                 { | ||||
|                     var from = $"{Directory.GetParent(d)?.FullName ?? ""}/../Files/{NetworkSecurityXmlName}"; | ||||
|                     if (File.Exists(from)) | ||||
|                     { | ||||
|                         string toDir = $"{dir}/res/xml"; | ||||
|                         if(!Directory.Exists(toDir))Directory.CreateDirectory(toDir); | ||||
|                         string to = $"{toDir}/{NetworkSecurityXmlName}"; | ||||
|                         FileUtil.CopyFileOrDirectory(from, to); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,17 +1,46 @@ | |||
| namespace Guru.Editor | ||||
| { | ||||
|     using UnityEditor; | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|     using System.IO; | ||||
|      | ||||
|     public class GuruEditorHelper | ||||
|     { | ||||
|         public static string GetFilePath(string filter) | ||||
|         public static string GetAssetPath(string filter, bool useFullPath = false) | ||||
|         { | ||||
|             var guids = AssetDatabase.FindAssets(filter); | ||||
|             string path = ""; | ||||
|             string fullPath = ""; | ||||
|             if (guids != null && guids.Length > 0) | ||||
|             { | ||||
|                 var path = AssetDatabase.GUIDToAssetPath(guids[0]); | ||||
|                 return path; | ||||
|                 path = AssetDatabase.GUIDToAssetPath(guids[0]); | ||||
|                 fullPath = path.Replace("Assets", Application.dataPath); | ||||
|                 if (File.Exists(fullPath)) | ||||
|                 { | ||||
|                     return useFullPath? fullPath : path; | ||||
|                 } | ||||
|             } | ||||
|             return ""; | ||||
|         } | ||||
|          | ||||
|         public static string GetAssetPath(string fileName, string typeName = "", bool useFullPath = false) | ||||
|         { | ||||
|             var filter = fileName; | ||||
|             if(!string.IsNullOrEmpty(typeName)) filter = $"{fileName} t:{typeName}"; | ||||
|             return GetAssetPath(filter, useFullPath); | ||||
|         } | ||||
| 
 | ||||
|         public static void OpenPath(string path) | ||||
|         { | ||||
| #if UNITY_EDITOR_OSX | ||||
|             EditorUtility.RevealInFinder(path); | ||||
|             return; | ||||
| #endif | ||||
|             Application.OpenURL($"file://{path}"); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 7f9d1111a9d94187bf583ae71d9192f0 | ||||
| timeCreated: 1711858346 | ||||
|  | @ -0,0 +1,116 @@ | |||
| namespace Guru.Editor | ||||
| { | ||||
|     using System; | ||||
|     using System.Collections.Generic; | ||||
|     using System.IO; | ||||
|      | ||||
|     public class EaseConfigFile | ||||
|     { | ||||
|         private Dictionary<string, string> _dataDict; | ||||
|         private string _filePath; | ||||
| 
 | ||||
|         protected bool ReadFile(string path) | ||||
|         { | ||||
|             _filePath = path; | ||||
|              | ||||
|             if (File.Exists(path)) | ||||
|             { | ||||
|                 var lines = File.ReadAllLines(path); | ||||
|                 int len = lines.Length; | ||||
|                 _dataDict = new Dictionary<string, string>(len); | ||||
| 
 | ||||
|                 string key = ""; | ||||
|                 string value = ""; | ||||
|                  | ||||
|                 for (int i=0; i< len; i++) | ||||
|                 { | ||||
|                     var line = lines[i]; | ||||
|                     if (line.Contains("=")) | ||||
|                     { | ||||
|                         key = ""; | ||||
|                         value = ""; | ||||
|                         var kv = line.Split('='); | ||||
|                         if(kv.Length > 0) key = kv[0]; | ||||
|                         if(kv.Length > 1) value = kv[1]; | ||||
|                         if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) | ||||
|                         { | ||||
|                             _dataDict[key] = value; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return true; | ||||
|             } | ||||
|              | ||||
|             _dataDict = new Dictionary<string, string>(10); | ||||
|             var dir = Directory.GetParent(path); | ||||
|             if(dir is { Exists: false }) dir.Create(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public void Save() | ||||
|         { | ||||
|             if (_dataDict == null || _dataDict.Count < 1) return; | ||||
| 
 | ||||
|             List<string> lines = new List<string>(_dataDict.Count); | ||||
|             foreach (var key in _dataDict.Keys) | ||||
|             { | ||||
|                 lines.Add($"{key}={_dataDict[key].ToString()}"); | ||||
|             } | ||||
|              | ||||
|             if(!string.IsNullOrEmpty(_filePath)) | ||||
|                 File.WriteAllLines(_filePath, lines); | ||||
|         } | ||||
|          | ||||
|         public void Set(string key, object value) | ||||
|         { | ||||
|             if (_dataDict == null) _dataDict = new Dictionary<string, string>(10); | ||||
|             _dataDict[key] = value.ToString(); | ||||
|             Save(); | ||||
|         } | ||||
| 
 | ||||
|         public string Get(string key) => _dataDict.ContainsKey(key) ? _dataDict[key] : ""; | ||||
| 
 | ||||
|         public bool TryGet(string key, out string value) | ||||
|         { | ||||
|             value = ""; | ||||
|             return _dataDict?.TryGetValue(key, out value) ?? false; | ||||
|         } | ||||
| 
 | ||||
|         public bool GetBool(string key, bool defaultVal = false) | ||||
|         { | ||||
|             if (TryGet(key, out var str)) | ||||
|             { | ||||
|                 return (str.ToLower() == "true" || str == "1"); | ||||
|             } | ||||
|             return defaultVal; | ||||
|         } | ||||
|          | ||||
|          | ||||
|         public int GetInt(string key, int defaultVal = 0) | ||||
|         { | ||||
|             if (TryGet(key, out var str)) | ||||
|             { | ||||
|                 var inVal = 0; | ||||
|                 if (int.TryParse(str, out inVal)) | ||||
|                 { | ||||
|                     return inVal; | ||||
|                 } | ||||
|             } | ||||
|             return defaultVal; | ||||
|         } | ||||
|          | ||||
|         public float GetFloat(string key, float defaultVal = 0) | ||||
|         { | ||||
|             if (TryGet(key, out var str)) | ||||
|             { | ||||
|                 float val = 0; | ||||
|                 if (float.TryParse(str, out val)) | ||||
|                 { | ||||
|                     return val; | ||||
|                 } | ||||
|             } | ||||
|             return defaultVal; | ||||
|         } | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 58ea378c89d742008a18f019afd54a27 | ||||
| timeCreated: 1711858357 | ||||
|  | @ -6,7 +6,9 @@ | |||
|         "GuruSDK", | ||||
|         "Guru.LitJson", | ||||
|         "Guru.Runtime", | ||||
|         "MaxSdk.Scripts.IntegrationManager.Editor" | ||||
|         "MaxSdk.Scripts.IntegrationManager.Editor", | ||||
|         "Guru.Editor", | ||||
|         "GuruAdjust.Editor" | ||||
|     ], | ||||
|     "includePlatforms": [ | ||||
|         "Editor" | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 1d20da1a87484f37bc53360cc2414030 | ||||
| timeCreated: 1712622721 | ||||
|  | @ -0,0 +1,23 @@ | |||
| namespace Guru | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 更新器 | ||||
|     /// </summary> | ||||
|     public interface IUpdater | ||||
|     { | ||||
|         UpdaterState State { get; } | ||||
|         void Start(); | ||||
|         void OnUpdate(); | ||||
|         void Pause(bool pause); | ||||
|         void Kill(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public enum UpdaterState | ||||
|     { | ||||
|         Prepare, | ||||
|         Running, | ||||
|         Pause, | ||||
|         Kill, | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 6ab7dd209c3a494eab0dfbedce0700d8 | ||||
| timeCreated: 1712622736 | ||||
|  | @ -0,0 +1,82 @@ | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using System.Collections; | ||||
|     using System.Collections.Generic; | ||||
| 
 | ||||
|      | ||||
|     public class ThreadHandler: IUpdater | ||||
|     { | ||||
|         private Queue<Action> _actions; | ||||
|         public Queue<Action> Actions | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_actions == null)  | ||||
|                     _actions = new Queue<Action>(10); | ||||
|                 return _actions; | ||||
|             } | ||||
| 
 | ||||
|             set | ||||
|             { | ||||
|                 if (value != null) _actions = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private UpdaterState _state; | ||||
|         public UpdaterState State => _state; | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动 Updater | ||||
|         /// </summary> | ||||
|         public void Start() | ||||
|         { | ||||
|             _state = UpdaterState.Running; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 执行方案 | ||||
|         /// </summary> | ||||
|         public void OnUpdate() | ||||
|         { | ||||
|             if (Actions.Count > 0) | ||||
|             { | ||||
|                 // 消耗对垒 | ||||
|                 while (Actions.Count > 0) | ||||
|                 { | ||||
|                     Actions.Dequeue()?.Invoke(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public void Pause(bool pause = true) | ||||
|         { | ||||
|             _state = pause ? UpdaterState.Pause : UpdaterState.Running; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 删除对象 | ||||
|         /// </summary> | ||||
|         public void Kill() | ||||
|         { | ||||
|             _state = UpdaterState.Kill; | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _actions.Clear(); | ||||
|             _state = UpdaterState.Kill; | ||||
|         } | ||||
|          | ||||
|         public void AddAction(Action action) | ||||
|         { | ||||
|             if (action == null) return; | ||||
|             Actions.Enqueue(action); | ||||
|         } | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: dc05f1b1ff5947c19ce6af1db301398d | ||||
| timeCreated: 1712624042 | ||||
|  | @ -1,12 +1,17 @@ | |||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System.Collections.Generic; | ||||
|     using System.Text; | ||||
|     using System; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// 启动参数配置 | ||||
|     /// </summary> | ||||
|     public partial class GuruSDKInitConfig | ||||
|     public class GuruSDKInitConfig | ||||
|     { | ||||
|         #region Properties | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 使用自定义的ConsentFlow启动流程 | ||||
|         /// </summary> | ||||
|  | @ -20,78 +25,189 @@ namespace Guru | |||
|         /// </summary> | ||||
|         public bool IAPEnabled = true; | ||||
|         /// <summary> | ||||
|         /// 自动申请推送授权信息 | ||||
|         /// </summary> | ||||
|         public bool AutoNotificationPermission = true; | ||||
|         /// <summary> | ||||
|         /// 自动记录完成的关卡 | ||||
|         /// </summary> | ||||
|         public bool AutoRecordFinishedLevels = true; | ||||
|         [Obsolete("Will be removed from InitConfig in next version. Use the <b_level> and <b_play> data from the GameUserData from game itself instead!")] | ||||
|         public bool AutoRecordFinishedLevels = false; | ||||
|         /// <summary> | ||||
|         /// 自定义 Service 云控 Key | ||||
|         /// </summary> | ||||
|         public string CustomServiceKey = ""; | ||||
|         /// <summary> | ||||
|         /// Banner 背景颜色 Hex 值 | ||||
|         /// </summary> | ||||
|         public string BannerBackgroundColor = "#00000040"; | ||||
|         /// <summary> | ||||
|         /// 已购买去广告道具 | ||||
|         /// </summary> | ||||
|         public bool IsBuyNoAds = false; | ||||
|         /// <summary> | ||||
|         /// Debug模式(默认关闭) | ||||
|         /// </summary> | ||||
|         public bool DebugMode = false; | ||||
|         /// <summary> | ||||
|         /// Debug模式下开启打点(默认关闭) | ||||
|         /// </summary> | ||||
|         public bool EnableDebugLogEvent = false; | ||||
| 
 | ||||
|         private Dictionary<string, object> _defaultRemoteData = new Dictionary<string, object>(); | ||||
|         /// <summary> | ||||
|         /// 云控参数的默认配置 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public Dictionary<string, object> DefaultRemoteData = new Dictionary<string, object>(); | ||||
|         public Dictionary<string, object> DefaultRemoteData | ||||
|         { | ||||
|             set | ||||
|             { | ||||
|                 if (value != null) | ||||
|                 { | ||||
|                     _defaultRemoteData = value; | ||||
|                 } | ||||
|             } | ||||
|             get => _defaultRemoteData; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启用 AdjustDeeplink | ||||
|         /// </summary> | ||||
|         public Action<string> OnAdjustDeeplinkCallback = null; | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 支付初始化Keys | ||||
|         /// </summary> | ||||
|         public byte[] GoogleKeys; | ||||
|         public byte[] AppleRootCerts; | ||||
|          | ||||
|         #region Initialization | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 构建启动配置 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static GuruSDKInitConfig Build( | ||||
|             bool useCustomConsent = false,  | ||||
|             bool autoLoadAds = true,  | ||||
|             bool iapEnabled = true,  | ||||
|             bool autoRecordFinishedLevels = true,  | ||||
|             bool debugMode = false, | ||||
|             Dictionary<string, object> defaultRemoteData = null, | ||||
|             byte[] googleKeys = null, | ||||
|             byte[] appleRootCerts = null) | ||||
|         { | ||||
|             // 创建启动用参数 | ||||
|             GuruSDKInitConfig config = new GuruSDKInitConfig() | ||||
|             { | ||||
|                 UseCustomConsent = useCustomConsent, | ||||
|                 AutoLoadWhenAdsReady = autoLoadAds, | ||||
|                 IAPEnabled = iapEnabled, | ||||
|                 AutoRecordFinishedLevels = autoRecordFinishedLevels, | ||||
|                 DebugMode = debugMode, | ||||
|                 GoogleKeys = googleKeys, | ||||
|                 AppleRootCerts = appleRootCerts, | ||||
|                 DefaultRemoteData = defaultRemoteData ?? new Dictionary<string, object>(), | ||||
|             }; | ||||
| #if UNITY_EDITOR | ||||
|             config.DebugMode = true; | ||||
| #endif | ||||
|             return config; | ||||
|         } | ||||
|          | ||||
|         public byte[] GoogleKeys;       // 数据取自 GooglePlayTangle.Data(); | ||||
|         public byte[] AppleRootCerts;   // 数据取自 AppleTangle.Data(); | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|          | ||||
|         #region Print | ||||
| 
 | ||||
|         public override string ToString() | ||||
|         { | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|             sb.AppendLine($"------- Custom init Config -------"); | ||||
|             sb.AppendLine($"\tUseCustomConsent: {UseCustomConsent}"); | ||||
|             sb.AppendLine($"\tAutoLoadWhenAdsReady: {AutoLoadWhenAdsReady}"); | ||||
|             sb.AppendLine($"\tIAPEnabled: {IAPEnabled}"); | ||||
|             sb.AppendLine($"\tShowDebugLog: {DebugMode}"); | ||||
|             sb.AppendLine($"------- Custom init Config -------"); | ||||
|             sb.AppendLine($"------- Custom InitConfig -------"); | ||||
|             sb.AppendLine($"\t  UseCustomConsent: {UseCustomConsent}"); | ||||
|             sb.AppendLine($"\t  AutoLoadWhenAdsReady: {AutoLoadWhenAdsReady}"); | ||||
|             sb.AppendLine($"\t  IAPEnabled: {IAPEnabled}"); | ||||
|             sb.AppendLine($"\t  AutoNotificationPermission: {AutoNotificationPermission}"); | ||||
|             sb.AppendLine($"\t  AutoRecordFinishedLevels: {AutoRecordFinishedLevels}"); | ||||
|             sb.AppendLine($"\t  CustomServiceKey: {CustomServiceKey}"); | ||||
|             sb.AppendLine($"\t  BannerBackgroundColor: {BannerBackgroundColor}"); | ||||
|             sb.AppendLine($"\t  IsBuyNoAds: {IsBuyNoAds}"); | ||||
|             sb.AppendLine($"\t  DebugMode: {DebugMode}"); | ||||
|             sb.AppendLine($"\t  DefaultRemote: Count: {DefaultRemoteData.Count}"); | ||||
|             sb.AppendLine($"------- Custom InitConfig -------"); | ||||
|             return sb.ToString(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Builder | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 构造器 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static GuruSDKInitConfigBuilder Builder() => new GuruSDKInitConfigBuilder(); | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     /// 构建器 | ||||
|     /// </summary> | ||||
|     public class GuruSDKInitConfigBuilder | ||||
|     { | ||||
| 
 | ||||
|         private GuruSDKInitConfig _config = new GuruSDKInitConfig(); | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 构建配置 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public GuruSDKInitConfig Build() | ||||
|         { | ||||
|             return _config; | ||||
|         } | ||||
| 
 | ||||
|         public GuruSDKInitConfigBuilder SetUseCustomConsent(bool value) | ||||
|         { | ||||
|             _config.UseCustomConsent = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetAutoLoadWhenAdsReady(bool value) | ||||
|         { | ||||
|             _config.AutoLoadWhenAdsReady = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetIAPEnabled(bool value) | ||||
|         { | ||||
|             _config.IAPEnabled = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetAutoRecordFinishedLevels(bool value) | ||||
|         { | ||||
|             _config.AutoRecordFinishedLevels = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetIsBuyNoAds(bool value) | ||||
|         { | ||||
|             _config.IsBuyNoAds = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetBannerBackgroundColor(string value) | ||||
|         { | ||||
|             _config.BannerBackgroundColor = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetDebugMode(bool value) | ||||
|         { | ||||
|             _config.DebugMode = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetOnAdjustDeeplinkCallback(Action<string> callback) | ||||
|         { | ||||
|             _config.OnAdjustDeeplinkCallback = callback; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetGoogleKeys(byte[] value) | ||||
|         { | ||||
|             _config.GoogleKeys = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetAppleRootCerts(byte[]  value) | ||||
|         { | ||||
|             _config.AppleRootCerts = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetDefaultRemoteData(Dictionary<string, object> value) | ||||
|         { | ||||
|             _config.DefaultRemoteData = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetEnableDebugLogEvent(bool value) | ||||
|         { | ||||
|             _config.EnableDebugLogEvent = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetCustomServiceKey(string value) | ||||
|         { | ||||
|             _config.CustomServiceKey = value; | ||||
|             return this; | ||||
|         } | ||||
|         public GuruSDKInitConfigBuilder SetAutoNotificationPermission(bool value) | ||||
|         { | ||||
|             _config.AutoNotificationPermission = value; | ||||
|             return this; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,15 +1,16 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using UnityEngine.Serialization; | ||||
| 
 | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine.Serialization; | ||||
|      | ||||
|     [Serializable] | ||||
|     public class GuruServicesConfig | ||||
|     { | ||||
|         public long version = 0; | ||||
|         public GuruAppSettings app_settings; | ||||
|         public GuruParameters parameters; | ||||
|         public GuruAdjustSettings adjust_settings; | ||||
|         public GuruFbSettings fb_settings; | ||||
|         public GuruAdSettings ad_settings; | ||||
|  | @ -34,10 +35,40 @@ namespace Guru | |||
|         public bool IsMolocoIOSEnabled() => ad_settings != null &&  | ||||
|                                                 ad_settings.moloco_ids_ios != null && | ||||
|                                                 ad_settings.moloco_ids_ios.Length > 0; | ||||
|         public bool IsTradplusAndroidEnabled() => ad_settings != null &&  | ||||
|                                             ad_settings.tradplus_ids_android != null && | ||||
|                                             ad_settings.tradplus_ids_android.Length > 0; | ||||
|         public bool IsTradplusIOSEnabled() => ad_settings != null &&  | ||||
|                                               ad_settings.tradplus_ids_ios != null && | ||||
|                                               ad_settings.tradplus_ids_ios.Length > 0; | ||||
|         public bool IsIAPEnabled() => app_settings != null && app_settings.enable_iap  | ||||
|                                                            && products != null && products.Length > 0; | ||||
|         public bool IsKeywordsEnabled() => app_settings != null && app_settings.enable_keywords;  | ||||
|         public bool UseCustomKeystore() => app_settings?.custom_keystore ?? false; | ||||
|          | ||||
|         public bool IsFirebaseEnabled() => app_settings?.enable_firebase ?? true; | ||||
|         public bool IsFacebookEnabled() => app_settings?.enable_facebook ?? true; | ||||
|         public bool IsAdjustEnabled() => app_settings?.enable_adjust ?? true; | ||||
|          | ||||
|         //-------------------------------- 配置检测 ------------------------------- | ||||
| 
 | ||||
|          | ||||
|          | ||||
|         //-------------------------------- Parameters -------------------------------- | ||||
|         public double Tch02Value() => parameters?.tch_020 ?? 0; | ||||
|         public bool IsAppReview() => parameters?.apple_review ?? false; | ||||
|         public bool EnableAnaErrorLog() => parameters?.enable_errorlog ?? false; | ||||
|         public bool IsAdsCompliance() => parameters?.ads_compliance ?? false; | ||||
|         public bool DMACountryCheck() => parameters?.dma_country_check ?? false; | ||||
|         public string DMAMapRule() => parameters?.dma_map_rule ?? ""; | ||||
|         public bool UseUUID() => parameters?.using_uuid ?? false; | ||||
|         public bool KeywordsEnabled() => parameters?.enable_keywords ?? false;  | ||||
|         public int TokenValidTime() => parameters?.token_valid_time ?? 604800; | ||||
|         public int LevelEndSuccessNum() => parameters?.level_end_success_num ?? 50; | ||||
|         public string CdnHost() => parameters?.cdn_host ?? ""; | ||||
|         public bool UsingUUID() => parameters?.using_uuid ?? true; | ||||
|         //-------------------------------- Parameters -------------------------------- | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|     [Serializable]     | ||||
|  | @ -51,25 +82,37 @@ namespace Guru | |||
|         public string terms_url; | ||||
|         public string android_store; | ||||
|         public string ios_store; | ||||
|         public int token_vaild_time = 604800; | ||||
|         public int level_end_success_num = 50; | ||||
|         public bool enable_keywords = true; | ||||
|         public bool enable_firebase = true; | ||||
|         public bool enable_facebook = true; | ||||
|         public bool enable_adjust = true; | ||||
|         public bool enable_iap = false; | ||||
|         public double tch020_val = 0; | ||||
|         public bool using_uuid = false; | ||||
|         public bool custom_keystore = false; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     [Serializable] | ||||
|     public class GuruParameters | ||||
|     { | ||||
|         public int token_valid_time = 604800; | ||||
|         public int level_end_success_num = 50; | ||||
|         public bool enable_keywords = false; | ||||
|         public double tch_020 = 0; | ||||
|         public bool using_uuid = false; | ||||
|         public string dma_map_rule = ""; | ||||
|         public bool dma_country_check = false; | ||||
|         public bool apple_review = false; // 苹果审核标志位 | ||||
|         public bool enable_errorlog = false; | ||||
|         public bool ads_compliance = false; | ||||
|         public string cdn_host = ""; | ||||
|     } | ||||
| 
 | ||||
|     [Serializable] | ||||
|     public class GuruAdjustSettings | ||||
|     { | ||||
|         public string[] app_token; | ||||
|         public string[] events; | ||||
| 
 | ||||
|         public string AndroidToken => app_token != null && app_token.Length > 0 ? app_token[0] : ""; | ||||
|         public string iOSToken => app_token != null && app_token.Length > 1 ? app_token[1] : ""; | ||||
|         public string AndroidToken() => app_token != null && app_token.Length > 0 ? app_token[0] : ""; | ||||
|         public string iOSToken() => app_token != null && app_token.Length > 1 ? app_token[1] : ""; | ||||
|     } | ||||
|      | ||||
|     [Serializable] | ||||
|  | @ -92,8 +135,8 @@ namespace Guru | |||
|         public string[] pubmatic_ids_ios; | ||||
|         public string[] moloco_ids_android; | ||||
|         public string[] moloco_ids_ios; | ||||
|         public string[] tradplus_ids_android; | ||||
|         public string[] tradplus_ids_ios; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|      | ||||
| } | ||||
|  | @ -7,14 +7,17 @@ namespace Guru | |||
|         /// 获取BLevel | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         protected override int GetBLevel() => GuruSDKModel.Instance.SuccessLevelCount; // BLevel | ||||
|         protected override int GetBLevel() => Model.BLevel; // BLevel | ||||
| 
 | ||||
|         private GuruSDKModel Model => GuruSDKModel.Instance; | ||||
|          | ||||
|         protected override void OnPurchaseOver(bool success, string productName) | ||||
|         { | ||||
|             if (success) | ||||
|             { | ||||
|                 GuruSDKModel.Instance.PurchasedCount++; // 记录成功购买次数 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void ClearData() | ||||
|         { | ||||
|             _model.ClearData(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -12,27 +12,17 @@ namespace Guru | |||
|             get => _value; | ||||
|             set | ||||
|             { | ||||
|                 if (_value.Equals(value)) return; | ||||
|                  | ||||
|                 _value = value; | ||||
|                 OnValueChanged?.Invoke(value); | ||||
|             } | ||||
|         } | ||||
|         public event Action<T> OnValueChanged; | ||||
|          | ||||
|          | ||||
|         public BindableProperty()  | ||||
|         { | ||||
|              | ||||
|         } | ||||
| 
 | ||||
|         public BindableProperty(Action<T> onChanged) | ||||
|         { | ||||
|             OnValueChanged = onChanged; | ||||
|         } | ||||
|          | ||||
|         public BindableProperty(T initValue, Action<T> onChanged) | ||||
|         public BindableProperty(T initValue) | ||||
|         { | ||||
|             _value = initValue; | ||||
|             OnValueChanged = onChanged; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,19 +1,46 @@ | |||
| 
 | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
|     using System.IO; | ||||
|     using System.Collections.Generic; | ||||
|     using System.Linq; | ||||
|      | ||||
|      | ||||
|     [Serializable] | ||||
|     class PurchasedProduct | ||||
|     { | ||||
|         public string productName; | ||||
|         public string productId; | ||||
|         public string receipt; | ||||
|         public bool appleProductIsRestored; | ||||
|     } | ||||
| 
 | ||||
|     [Serializable] | ||||
|     class GuruSDKSerializedModel | ||||
|     { | ||||
|         //-------------- data --------------- | ||||
|          | ||||
|         public string uid = ""; | ||||
|         public int b_level = 0; | ||||
|         public int b_play = 0; | ||||
|         public bool no_ads = false; | ||||
|         public List<PurchasedProduct> purchased = new List<PurchasedProduct>(10); | ||||
|          | ||||
|         //-------------- data --------------- | ||||
|     } | ||||
|      | ||||
|     [Serializable] | ||||
|     internal class GuruSDKModel | ||||
|     { | ||||
|         private const float SaveInterval = 3; | ||||
|         private const string SaveKey = "com.guru.sdk.model.save"; | ||||
|         private DateTime _lastSavedTime = new DateTime(1970,1,1); | ||||
|          | ||||
|         private bool _noAds = false; | ||||
|         private int _bLevel; | ||||
|         private int _bPlay; | ||||
|         private string _uid; | ||||
|         private List<PurchasedProduct> _purchased; | ||||
|          | ||||
|          | ||||
|         private static GuruSDKModel _instance; | ||||
|  | @ -21,97 +48,77 @@ namespace Guru | |||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (null == _instance) _instance = Load(); | ||||
|                 if (null == _instance) _instance = new GuruSDKModel(); | ||||
|                 return _instance; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         //-------------- data --------------- | ||||
|         public string uid = ""; | ||||
|         public int b_level = 0; | ||||
|         public int b_play = 0; | ||||
|         public int buy_count = 0; | ||||
| 
 | ||||
|         public List<PurchasedProduct> purchased; | ||||
|         //-------------- data --------------- | ||||
| 
 | ||||
|         private float _lastSavedTime = 0; | ||||
|         public GuruSDKModel() | ||||
|         { | ||||
|             // 读取内存值 | ||||
|             GuruSDKSerializedModel model = LoadModel(); | ||||
|             _uid = model.uid; | ||||
|             _noAds = model.no_ads; | ||||
|             _bLevel = model.b_level; | ||||
|             _bPlay = model.b_play; | ||||
|             _purchased = model.purchased; | ||||
|         } | ||||
|          | ||||
|         public int SuccessLevelCount | ||||
|         public int BLevel | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_successLevel == null) InitProperties(); | ||||
|                 return _successLevel.Value; | ||||
|             } | ||||
|             get => _bLevel; | ||||
|             set | ||||
|             { | ||||
|                 if(_successLevel == null) InitProperties(); | ||||
|                 _successLevel.Value = value; | ||||
|                 if (value < _bLevel) | ||||
|                 { | ||||
|                     // b_level 必须比上一次的值大 | ||||
|                     Debug.LogWarning($"[SDK] :: Set b_level [{value}] should not be less than original value [{_bLevel}]"); | ||||
|                     return; | ||||
|                 } | ||||
|                 _bLevel = value; | ||||
|                 Save(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public int TotalPlayedCount | ||||
|         public int BPlay | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_totalPlayed == null) InitProperties(); | ||||
|                 return _totalPlayed.Value; | ||||
|             } | ||||
|             get => _bPlay; | ||||
|             set | ||||
|             { | ||||
|                 if(_totalPlayed == null) InitProperties(); | ||||
|                 _totalPlayed.Value = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public int PurchasedCount | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_purchasedCount == null) InitProperties(); | ||||
|                 return _purchasedCount.Value; | ||||
|             } | ||||
|             set | ||||
|             { | ||||
|                 if(_purchasedCount == null) InitProperties(); | ||||
|                 _purchasedCount.Value = value; | ||||
|                 _bPlay = value; | ||||
|                 Save(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string UserId | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_uid == null) InitProperties(); | ||||
|                 return _uid.Value; | ||||
|             } | ||||
|             get => _uid; | ||||
|             set | ||||
|             { | ||||
|                 if(_uid == null) InitProperties(); | ||||
|                 _uid.Value = value; | ||||
|                 _uid = value; | ||||
|                 Save(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool IsIAPUser => PurchasedCount > 0; | ||||
| 
 | ||||
|         public bool IsIapUser => _purchased.Count > 0; | ||||
|          | ||||
|         private BindableProperty<int> _successLevel; | ||||
|         private BindableProperty<int> _totalPlayed; | ||||
|         private BindableProperty<int> _purchasedCount; | ||||
|         private BindableProperty<string> _uid; | ||||
|         public bool IsNoAds | ||||
|         { | ||||
|             get => _noAds; | ||||
|             set | ||||
|             { | ||||
|                 _noAds = value; | ||||
|                 Save(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public BindableProperty<int> PropBLevel => _successLevel; | ||||
|         public BindableProperty<int> PropBPlay => _totalPlayed; | ||||
|         public BindableProperty<int> PropBuyCount => _purchasedCount; | ||||
|         public BindableProperty<string> Uid => _uid; | ||||
| 
 | ||||
|         #region 初始化 | ||||
| 
 | ||||
|          | ||||
|         public static GuruSDKModel Load() | ||||
| 
 | ||||
|         private GuruSDKSerializedModel LoadModel() | ||||
|         { | ||||
|             GuruSDKModel model = null; | ||||
|             GuruSDKSerializedModel model = null; | ||||
|             if (PlayerPrefs.HasKey(SaveKey)) | ||||
|             { | ||||
|                 var json = PlayerPrefs.GetString(SaveKey, ""); | ||||
|  | @ -119,7 +126,7 @@ namespace Guru | |||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         model =  JsonUtility.FromJson<GuruSDKModel>(json); | ||||
|                         model =  JsonUtility.FromJson<GuruSDKSerializedModel>(json); | ||||
|                     } | ||||
|                     catch (Exception e) | ||||
|                     { | ||||
|  | @ -127,101 +134,54 @@ namespace Guru | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if(model == null) model = new GuruSDKModel(); | ||||
|             model.InitProperties(); | ||||
|             if(model == null) model = new GuruSDKSerializedModel(); | ||||
|             return model; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 保存至数据 | ||||
|         /// 保存至 PlayerPrefs 数据 | ||||
|         /// </summary> | ||||
|         private void SaveToPlayerPrefs() | ||||
|         private void SetToMemory() | ||||
|         { | ||||
|             var json = JsonUtility.ToJson(this); | ||||
|             PlayerPrefs.SetString(SaveKey, json); | ||||
|         } | ||||
| 
 | ||||
|         public void InitProperties() | ||||
|         { | ||||
|             _successLevel = new BindableProperty<int>(b_level, OnLevelChanged); | ||||
|             _totalPlayed = new BindableProperty<int>(b_play, OnPlayedChanged); | ||||
|             _purchasedCount = new BindableProperty<int>(buy_count, OnPurchasedNumChanged); | ||||
|             _uid = new BindableProperty<string>(uid, OnUidChanged); | ||||
| 
 | ||||
|             purchased = new List<PurchasedProduct>(20); | ||||
|             var model = new GuruSDKSerializedModel() | ||||
|             { | ||||
|                 uid = _uid, | ||||
|                 b_level = _bLevel, | ||||
|                 b_play = _bPlay, | ||||
|                 no_ads = _noAds, | ||||
|                 purchased = _purchased, | ||||
|             }; | ||||
|              | ||||
|             var json = JsonUtility.ToJson(model); | ||||
|             if (!string.IsNullOrEmpty(json)) | ||||
|             { | ||||
|                 PlayerPrefs.SetString(SaveKey, json); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 保存数据 | ||||
|         /// </summary> | ||||
|         /// <param name="force"></param> | ||||
|         public void Save(bool force = false) | ||||
|         /// <param name="forceSave"></param> | ||||
|         public void Save(bool forceSave = false) | ||||
|         { | ||||
|             bool save = force || (Time.realtimeSinceStartup - _lastSavedTime>= SaveInterval); | ||||
|             if (save) | ||||
|             { | ||||
|                 _lastSavedTime = Time.realtimeSinceStartup; | ||||
|                 SaveToPlayerPrefs(); | ||||
|             } | ||||
|         } | ||||
|             SetToMemory(); // 每次保存都要设置到 PlayerPrefs 内 | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 数据绑定变化 | ||||
|         private void OnLevelChanged(int value) | ||||
|         { | ||||
|             b_level = value; | ||||
|             Save(); | ||||
|         } | ||||
|         private void OnPlayedChanged(int value) | ||||
|         { | ||||
|             b_play = value; | ||||
|             Save(); | ||||
|         } | ||||
| 
 | ||||
|         private void OnPurchasedNumChanged(int value) | ||||
|         { | ||||
|             buy_count = value; | ||||
|             Save(); | ||||
|             bool shouldWriteToDisk = forceSave || (DateTime.Now - _lastSavedTime)>= TimeSpan.FromSeconds(SaveInterval); | ||||
|             if (!shouldWriteToDisk) return; | ||||
|             _lastSavedTime = DateTime.Now; // 更新最后保存时间 | ||||
|             PlayerPrefs.Save(); // 写入到磁盘 | ||||
|         } | ||||
|          | ||||
|         private void OnUidChanged(string value) | ||||
|         { | ||||
|             uid = value; | ||||
|             Save(); | ||||
|         } | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 启动配置 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 从 Streaming 加载 AppServices 配置 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public string LoadDefaltServicesConfigJson() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var txt = Resources.Load<TextAsset>(GuruSDK.ServicesConfigKey); | ||||
|                 return txt?.text ?? ""; | ||||
|             } | ||||
|             catch (Exception e) | ||||
|             { | ||||
|                 Log.Exception(e); | ||||
|             } | ||||
|             return ""; | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 订单记录 | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|         public bool HasPurchasedProduct(string receipt) | ||||
|         { | ||||
|             if(purchased == null || purchased.Count == 0) return false; | ||||
|             return purchased.Exists(p => p.receipt == receipt); | ||||
|             if(_purchased.Count == 0) return false; | ||||
|             return _purchased.Exists(p => p.receipt == receipt); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|  | @ -230,12 +190,12 @@ namespace Guru | |||
|         /// <param name="receipt"></param> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <param name="productId"></param> | ||||
|         /// <param name="appleProductIsRestored"></param> | ||||
|         public void AddReceipt(string receipt, string productName, string productId, bool appleProductIsRestored = false) | ||||
|         { | ||||
|             if (purchased == null) purchased = new List<PurchasedProduct>(20); | ||||
|             if (!HasPurchasedProduct(receipt)) | ||||
|             { | ||||
|                 purchased.Add(new PurchasedProduct() | ||||
|                 _purchased.Add(new PurchasedProduct() | ||||
|                 { | ||||
|                     receipt = receipt, | ||||
|                     productName = productName, | ||||
|  | @ -248,22 +208,16 @@ namespace Guru | |||
| 
 | ||||
|         public string[] GetReceipts(string productName) | ||||
|         { | ||||
|             int count = purchased?.Count ?? 0; | ||||
|             if (count == 0) count = 20; | ||||
|             if (purchased == null) purchased = new List<PurchasedProduct>(count); | ||||
|             var receipts = new List<string>(count); | ||||
|             receipts.AddRange(from purchasedProduct in purchased where purchasedProduct.productName == productName select purchasedProduct.receipt); | ||||
|             var receipts = new List<string>(); | ||||
|             receipts.AddRange(from purchasedProduct in _purchased where purchasedProduct.productName == productName select purchasedProduct.receipt); | ||||
|             return receipts.ToArray(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public string[] GetReceiptsById(string productId) | ||||
|         { | ||||
|             int count = purchased?.Count ?? 0; | ||||
|             if (count == 0) count = 20; | ||||
|             if (purchased  == null) purchased = new List<PurchasedProduct>(count); | ||||
|             var receipts = new List<string>(count); | ||||
|             receipts.AddRange(from purchasedProduct in purchased where purchasedProduct.productId == productId select purchasedProduct.receipt); | ||||
|             var receipts = new List<string>(); | ||||
|             receipts.AddRange(from purchasedProduct in _purchased where purchasedProduct.productId == productId select purchasedProduct.receipt); | ||||
|             return receipts.ToArray(); | ||||
|         } | ||||
|          | ||||
|  | @ -271,17 +225,18 @@ namespace Guru | |||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
| 
 | ||||
|         #region 清除数据 | ||||
| 
 | ||||
|         public void ClearData() | ||||
|         { | ||||
|             PlayerPrefs.DeleteKey(SaveKey); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|     } | ||||
| 
 | ||||
|     [Serializable] | ||||
|     internal class PurchasedProduct | ||||
|     { | ||||
|         public string productName; | ||||
|         public string productId; | ||||
|         public string receipt; | ||||
|         public bool appleProductIsRestored; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,47 +1,60 @@ | |||
| 
 | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|      | ||||
|     using Guru.Notification; | ||||
|      | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
| 
 | ||||
|         private const float CONSENT_FLOW_TIMEOUT = 10; // Consent Flow 超时时间(秒)    | ||||
|         private static AdsInitSpec _adInitSpec; | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 启动广告服务 | ||||
|         /// </summary> | ||||
|         public static void StartAds() | ||||
|         public static void StartAds(AdsInitSpec spec = null) | ||||
|         { | ||||
|             _adInitSpec = spec; | ||||
|             if (InitConfig.UseCustomConsent) | ||||
|             { | ||||
|                 Debug.Log($"{Tag} --- Call <color=orange>StartAdsWithCustomConsent</color> when you use custom consent, and pass the result (boolean) to the method."); | ||||
|                 return; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // 默认的启动顺序是先启动Consent后, 根据用户回调的结果来启动广告 | ||||
|                 Instance.StartConsentFlow(); | ||||
|             } | ||||
|              | ||||
|             // 默认的启动顺序是先启动Consent后, 根据用户回调的结果来启动广告 | ||||
|             Instance.StartConsentFlow(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 是否已经购买了去广告 | ||||
|         /// </summary> | ||||
|         /// <param name="buyNoAds"></param> | ||||
|         public static void StartAds(bool buyNoAds = false) | ||||
|         { | ||||
|             StartAds(AdsInitSpec.BuildWithNoAds());  // 按照默认的去广告来生成广告启动配置 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 使用自定义的Consent, 获取用户授权后, 调用此方法 | ||||
|         /// </summary> | ||||
|         /// <param name="userAllow"></param> | ||||
|         /// <param name="guideType">Consent 引导的类型, 如果使用了 MAX 的 consent 请填写 max </param> | ||||
|         public static void StartAdsWithCustomConsent(bool userAllow = true, string guideType = "custom") | ||||
|         /// <param name="consentName">Consent 引导的类型, 如果使用了 MAX 的 consent 请填写 max </param> | ||||
|         /// <param name="spec">广告启动配置</param> | ||||
|         public static void StartAdsWithCustomConsent(bool userAllow = true,  | ||||
|             string consentName = "custom", AdsInitSpec spec = null) | ||||
|         { | ||||
| #if UNITY_IOS | ||||
|             _attType = guideType; | ||||
|             _attType = consentName; | ||||
|             InitAttStatus(); | ||||
| #endif | ||||
|             if (userAllow) | ||||
|             { | ||||
| #if UNITY_IOS | ||||
|                 CheckAttStatus(); | ||||
|                 Instance.CheckAttStatus(); | ||||
| #else | ||||
|                 StartAdService(); | ||||
|                 StartAdService(spec); | ||||
| #endif | ||||
|             } | ||||
|             else | ||||
|  | @ -50,40 +63,102 @@ namespace Guru | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 使用自定义的Consent, 获取用户授权后, 调用此方法 | ||||
|         /// </summary> | ||||
|         /// <param name="userAllow">自定义 Consent 的用户授权结果</param> | ||||
|         /// <param name="consentName">Consent引导名称</param> | ||||
|         /// <param name="buyNoAds">是否已经购买了去广告</param> | ||||
|         public static void StartAdsWithCustomConsent(bool userAllow = true, string consentName = "custom", | ||||
|             bool buyNoAds = false) | ||||
|         { | ||||
|             StartAdsWithCustomConsent(userAllow, consentName, AdsInitSpec.BuildWithNoAds()); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         #region Guru Consent | ||||
|          | ||||
| 
 | ||||
|         private bool _hasConsentCalled = false; | ||||
|         private bool _adServiceHasStarted = false; | ||||
|         private string _notiStatue = ""; | ||||
|         private bool _hasNotiGranted = false; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动Consent流程 | ||||
|         /// 因为之后规划广告流程会放在 Consent 初始化之后, 因此请求广告的时候会需要先请求 Consent | ||||
|         /// </summary> | ||||
|         private void StartConsentFlow() | ||||
|         { | ||||
|             LogI($"StartConsentFlow"); | ||||
|             GuruConsent.StartConsent(OnConsentOver); | ||||
|             LogI($"#4.5 ---  StartConsentFlow ---"); | ||||
|              | ||||
|             float time = 1; | ||||
|             if (!_adServiceHasStarted && _appServicesConfig != null) | ||||
|             { | ||||
|                 time = _appServicesConfig.IsAdsCompliance() ? CONSENT_FLOW_TIMEOUT : 1f; // 启动合规判定后, 延迟最多 10 秒后启动广告 | ||||
|             } | ||||
|             Delay(time, AdServiceHandler); // 广告延迟启动 | ||||
|              | ||||
|             if (_hasConsentCalled) return; | ||||
|             _hasConsentCalled = true; | ||||
| 
 | ||||
|             bool enableCountryCheck = false; | ||||
|             string dmaMapRule = ""; | ||||
|              | ||||
|             if (_appServicesConfig != null && _appServicesConfig.parameters != null) | ||||
|             { | ||||
|                 enableCountryCheck = _appServicesConfig.DMACountryCheck(); | ||||
|                 dmaMapRule = _appServicesConfig.DMAMapRule(); | ||||
|             } | ||||
| 
 | ||||
| #if UNITY_IOS | ||||
|             InitAttStatus(); // Consent 启动前记录 ATT 初始值 | ||||
| #endif | ||||
|             UnityEngine.Debug.Log($"{Tag}  --- Call:StartConsentFlow ---"); | ||||
|             GuruConsent.StartConsent(OnGuruConsentOver, dmaMapRule:dmaMapRule, enableCountryCheck:enableCountryCheck); | ||||
|         } | ||||
| 
 | ||||
|         private void OnConsentOver(int code) | ||||
|         /// <summary> | ||||
|         /// Guru Consent flow is Over | ||||
|         /// </summary> | ||||
|         /// <param name="code"></param> | ||||
|         private void OnGuruConsentOver(int code) | ||||
|         { | ||||
|              | ||||
|             // 无论状态如何, 都在回调内启动广告初始化 | ||||
|             AdServiceHandler(); | ||||
| 
 | ||||
|             // 调用回调 | ||||
|             Callbacks.ConsentFlow.InvokeOnConsentResult(code); | ||||
|              | ||||
| #if UNITY_IOS | ||||
|             InitAttStatus(); | ||||
|             CheckAttStatus();  // [iOS] Consent 启动后检查 ATT 初始值 | ||||
| #elif UNITY_ANDROID | ||||
|             CheckNotiPermission(); // Consent 回调后检查 Notification 权限 | ||||
| #endif | ||||
|             Callbacks.ConsentFlow._onConsentResult?.Invoke(code); | ||||
|              | ||||
|             // 内部处理后继逻辑 | ||||
|             switch(code) | ||||
|             { | ||||
|                 case GuruConsent.StatusCode.OBTAINED: | ||||
|                 case GuruConsent.StatusCode.NOT_AVAILABLE: | ||||
|                     // 已获取授权, 或者地区不可用 | ||||
| #if UNITY_IOS | ||||
|                     CheckAttStatus(); | ||||
| #endif | ||||
|                     // 已获取授权, 或者地区不可用, ATT 尚未启动 | ||||
|                     // TODO: 添加后继处理逻辑 | ||||
|                     break; | ||||
|             } | ||||
|              | ||||
|             // 无论状态如何, 都在回调内启动广告初始化 | ||||
|             StartAdService(); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动广告服务 | ||||
|         /// </summary> | ||||
|         private void AdServiceHandler() | ||||
|         { | ||||
|             if (_adServiceHasStarted) return; | ||||
|             _adServiceHasStarted = true; | ||||
|             StartAdService(_adInitSpec); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region IOS ATT 广告授权流程 | ||||
|  | @ -92,12 +167,12 @@ namespace Guru | |||
|          | ||||
|         private static string _initialAttStatus; | ||||
|         private static String _attType = "admob"; | ||||
|          | ||||
|         private static bool _autoReCallAtt = false; | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 显示系统的 ATT 弹窗 | ||||
|         /// </summary> | ||||
|         public static void RequestAttDialog() | ||||
|         public void RequestAttDialog() | ||||
|         { | ||||
|             LogI($"RequestATTDialog"); | ||||
|             ATTManager.RequestATTDailog(ReportAttStatus); | ||||
|  | @ -108,32 +183,41 @@ namespace Guru | |||
|         /// </summary> | ||||
|         public static void InitAttStatus() | ||||
|         { | ||||
|             _attType = InitConfig.UseCustomConsent ? ATTManager.GUIDE_TYPE_CUSTOM : ATTManager.GUIDE_TYPE_ADMOB; // 点位属性确定 | ||||
|             _initialAttStatus = ATTManager.GetStatus(); | ||||
|             SetUserProperty(Analytics.ParameterATTStatus, _initialAttStatus); // 上报一个初始的状态 | ||||
|             SetATTStatus(_initialAttStatus); // 上报一个初始的状态 | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// iOS 平台检查 ATT 状态 | ||||
|         /// </summary> | ||||
|         private static void CheckAttStatus() => ATTManager.CheckStatus(ReportAttStatus); | ||||
|           | ||||
|         private static void ReportAttStatus(string status) | ||||
|         private void CheckAttStatus(bool autoReCall = false) | ||||
|         { | ||||
|             LogI($"{Tag} --- Get Att status: {status}"); | ||||
|             SetUserProperty(Analytics.ParameterATTStatus, status); // 当前的状态 | ||||
|             if (!string.IsNullOrEmpty(_initialAttStatus)  | ||||
|                 && status !=  ATTManager.ATT_STATUS_NOT_DETERMINED | ||||
|                 && _initialAttStatus != status) | ||||
|             _autoReCallAtt = autoReCall; | ||||
|              | ||||
|             // Delay 1s to get the user choice | ||||
|             Delay(1, () => ATTManager.CheckStatus(ReportAttStatus)); | ||||
|         } | ||||
| 
 | ||||
|         private void ReportAttStatus(string status) | ||||
|         { | ||||
|             LogI($"{Tag} --- Get Att status:{status}  att Type:{_attType}  recall:{_autoReCallAtt}"); | ||||
|             SetATTStatus(_initialAttStatus); // 上报一个初始的状态 | ||||
|             // SetUserProperty(Analytics.ParameterATTStatus, status); // 当前的状态 | ||||
|              | ||||
|             if (!string.IsNullOrEmpty(status)  | ||||
|                 && status != _initialAttStatus  | ||||
|                 && status !=  ATTManager.ATT_STATUS_NOT_DETERMINED) | ||||
|             { | ||||
|                 // 上报点位: | ||||
|                 Analytics.AttResult(status, _attType); | ||||
|                 SetATTStatus(_initialAttStatus); | ||||
|             } | ||||
| 
 | ||||
|             switch(status) | ||||
|             { | ||||
|                 case ATTManager.ATT_STATUS_NOT_DETERMINED: | ||||
|                     // ATT 状态未知, 请求弹窗 | ||||
|                     RequestAttDialog(); | ||||
|                     if(_autoReCallAtt) RequestAttDialog(); | ||||
|                     break; | ||||
|                 case ATTManager.ATT_STATUS_RESTRICTED: | ||||
|                 case ATTManager.ATT_STATUS_DENIED: | ||||
|  | @ -143,56 +227,203 @@ namespace Guru | |||
|                     // ATT 状态已授权 | ||||
|                     break; | ||||
|             } | ||||
|              | ||||
|             CheckNotiPermission(); // Consent 回调后检查 Notification 权限 | ||||
|         }  | ||||
| #endif | ||||
|          | ||||
|         #endregion    | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Notification Permission Check | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 初始化 Noti Service | ||||
|         /// </summary> | ||||
|         private void InitNotiPermission() | ||||
|         { | ||||
|             // bool hasNotiGranted = false; | ||||
|             _notiStatue = "no_determined"; | ||||
|             NotificationService.Initialize(); // 初始化 Noti 服务 | ||||
|             Analytics.SetNotiPerm(NotificationService.GetStatus()); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 检查 Noti 状态 | ||||
|         /// </summary> | ||||
|         private void CheckNotiPermission() | ||||
|         { | ||||
|             var status = NotificationService.GetStatus(); | ||||
|              | ||||
|             // 如果未启用自动 Noti 授权,则直接上报状态 | ||||
|             if (!_initConfig.AutoNotificationPermission) | ||||
|             { | ||||
|                 Debug.LogWarning($"[SDK] ---- AutoNotificationPermission is OFF, Project should request permission own."); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             bool isGranted = NotificationService.IsPermissionGranted(); | ||||
|             Debug.Log($"[SDK] ---- Check Noti Permission: {isGranted}"); | ||||
|             if (isGranted) | ||||
|             { | ||||
|                 Debug.Log($"[SDK] ---- Set Notification Permission: {status}"); | ||||
|                 Analytics.SetNotiPerm(status); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             RequestNotificationPermission(); // 请求授权 | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 请求推送授权 | ||||
|         /// </summary> | ||||
|         /// <param name="callback"></param> | ||||
|         public static void RequestNotificationPermission(Action<string> callback = null) | ||||
|         { | ||||
|             FirebaseUtil.StartFetchFcmToken(); | ||||
|              | ||||
|             Debug.Log($"[SDK] ---- RequestNotificationPermission"); | ||||
|             NotificationService.RequestPermission(status => | ||||
|             { | ||||
|                 Debug.Log($"[SDK] ---- Set Notification Permission: {status}"); | ||||
|                 if(!string.IsNullOrEmpty(status)) Analytics.SetNotiPerm(status); | ||||
|                  | ||||
|                 callback?.Invoke(status); | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 获取 Notification 状态值 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static string GetNotificationStatus() | ||||
|         { | ||||
|             return NotificationService.GetStatus(); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 用户是否已经获取了 Notification 授权了 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static bool IsNotificationPermissionGranted() | ||||
|         { | ||||
|             return NotificationService.IsPermissionGranted(); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region Ad Services | ||||
| 
 | ||||
|         private static bool _initAdsCompleted = false; | ||||
|         public static bool IsAdsReady => _initAdsCompleted; | ||||
|         private static int _preBannerAction = 0; | ||||
|          | ||||
|          | ||||
|         public static AdsInitSpec GetDefaultAdsSpec() | ||||
|         { | ||||
|             return AdsInitSpec.BuildDefault(InitConfig.AutoLoadWhenAdsReady, IsDebugMode); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动广告服务 | ||||
|         /// </summary> | ||||
|         public static void StartAdService() | ||||
|         private static void StartAdService(AdsInitSpec spec = null) | ||||
|         { | ||||
|             LogI($"StartAdService"); | ||||
|             ADService.Instance.StartService(OnAdsInitComplete,  | ||||
|                 InitConfig.AutoLoadWhenAdsReady, IsDebugMode); | ||||
|              | ||||
|             //--------- Callbacks ----------- | ||||
|             ADService.OnBannerLoaded = OnBannerLoaded; | ||||
|             ADService.OnInterstitialLoaded = OnInterstitialLoaded; | ||||
|             ADService.OnInterstitialFailed = OnInterstitialFailed; | ||||
|             ADService.OnRewardLoaded = OnRewardLoaded; | ||||
|             ADService.OnRewardFailed = OnRewardFailed; | ||||
|             //---------- Using InitConfig ---------- | ||||
|             if (InitConfig is { IsBuyNoAds: true }) SetBuyNoAds(true); | ||||
| 
 | ||||
|             LogI($"StartAdService"); | ||||
|             if (spec == null) | ||||
|             { | ||||
|                 spec = AdsInitSpec.BuildDefault(InitConfig.AutoLoadWhenAdsReady, IsDebugMode); | ||||
|                 if (ADService.Instance.IsBuyNoAds) | ||||
|                 { | ||||
|                     spec = AdsInitSpec.BuildWithNoAds(InitConfig.AutoLoadWhenAdsReady, IsDebugMode); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if(InitConfig != null && !string.IsNullOrEmpty(InitConfig.BannerBackgroundColor))  | ||||
|                 spec.bannerColorHex = InitConfig.BannerBackgroundColor; | ||||
|              | ||||
|             //--------- Add Callbacks ----------- | ||||
|             // BADS | ||||
|             ADService.Instance.OnBannerStartLoad = OnBannerStartLoad; | ||||
|             ADService.Instance.OnBannerLoaded = OnBannerLoaded; | ||||
|             // IADS | ||||
|             ADService.Instance.OnInterstitialStartLoad = OnInterstitialStartLoad; | ||||
|             ADService.Instance.OnInterstitialLoaded = OnInterstitialLoaded; | ||||
|             ADService.Instance.OnInterstitialFailed = OnInterstitialFailed; | ||||
|             ADService.Instance.OnInterstitialClosed = OnInterstitialClosed; | ||||
|             // RADS | ||||
|             ADService.Instance.OnRewardedStartLoad = OnRewardStartLoad; | ||||
|             ADService.Instance.OnRewardLoaded = OnRewardLoaded; | ||||
|             ADService.Instance.OnRewardFailed = OnRewardFailed; | ||||
|             ADService.Instance.OnRewardClosed = OnRewardClosed; | ||||
|              | ||||
|             // ---------- Start Services ---------- | ||||
|             ADService.Instance.StartService(OnAdsInitComplete, spec); | ||||
|              | ||||
|             // ---------- Life Cycle ---------- | ||||
|             Callbacks.App.OnAppPaused += OnAppPaused; | ||||
|         } | ||||
|          | ||||
|         private static void OnBannerLoaded()  | ||||
|             => Callbacks.Ads._onBannerADLoaded?.Invoke(); | ||||
|         /// <summary> | ||||
|         /// 生命周期回调 | ||||
|         /// </summary> | ||||
|         /// <param name="paused"></param> | ||||
|         private static void OnAppPaused(bool paused) | ||||
|         { | ||||
|             if(ADService.Instance.IsInitialized) | ||||
|                 ADService.Instance.OnAppPaused(paused); | ||||
|         } | ||||
| 
 | ||||
|         private static void OnBannerStartLoad(string adUnitId) | ||||
|             => Callbacks.Ads.InvokeOnBannerADStartLoad(adUnitId); | ||||
|         private static void OnBannerLoaded()  | ||||
|             => Callbacks.Ads.InvokeOnBannerADLoaded(); | ||||
|         private static void OnInterstitialStartLoad(string adUnitId)  | ||||
|             => Callbacks.Ads.InvokeOnInterstitialADStartLoad(adUnitId); | ||||
|         private static void OnInterstitialLoaded()  | ||||
|             => Callbacks.Ads._onInterstitialADLoaded?.Invoke(); | ||||
|             => Callbacks.Ads.InvokeOnInterstitialADLoaded(); | ||||
|         private static void OnInterstitialFailed() | ||||
|             => Callbacks.Ads._onInterstitialADFailed?.Invoke(); | ||||
|             => Callbacks.Ads.InvokeOnInterstitialADFailed(); | ||||
|         private static void OnInterstitialClosed() | ||||
|             => Callbacks.Ads.InvokeOnInterstitialADClosed(); | ||||
|         private static void OnRewardStartLoad(string adUnitId) | ||||
|             => Callbacks.Ads.InvokeOnRewardedADStartLoad(adUnitId);  | ||||
|         private static void OnRewardLoaded() | ||||
|             => Callbacks.Ads._onRewardedADLoaded?.Invoke();  | ||||
|             => Callbacks.Ads.InvokeOnRewardedADLoaded();  | ||||
|         private static void OnRewardFailed() | ||||
|             => Callbacks.Ads._onRewardADFailed?.Invoke(); | ||||
|             => Callbacks.Ads.InvokeOnRewardADFailed(); | ||||
|         private static void OnRewardClosed() | ||||
|             => Callbacks.Ads.InvokeOnRewardADClosed(); | ||||
| 
 | ||||
|         private static void OnAdsInitComplete() | ||||
|         { | ||||
|             _initAdsCompleted = true; | ||||
|             Callbacks.Ads._onAdsInitComplete?.Invoke(); | ||||
| 
 | ||||
|             if (_adInitSpec != null && _adInitSpec.loadBanner) | ||||
|             { | ||||
|                 // 预制动作处理 | ||||
|                 if (_preBannerAction == 1) | ||||
|                 { | ||||
|                     _preBannerAction = 0; | ||||
|                     ShowBanner(); | ||||
|                 } | ||||
|                 else if (_preBannerAction == -1) | ||||
|                 { | ||||
|                     _preBannerAction = 0; | ||||
|                     HideBanner(); | ||||
|                 } | ||||
|             } | ||||
|             Callbacks.Ads.InvokeOnAdsInitComplete(); | ||||
|         } | ||||
| 
 | ||||
|         private static bool CheckAdsReady() | ||||
|         { | ||||
|             if (!_initAdsCompleted) | ||||
|             if (!IsAdsReady) | ||||
|             { | ||||
|                 LogE("Ads is not ready. Call <GuruSDk.StartAdService> first."); | ||||
|                 LogW("[SDK] Ads is not ready. Call <GuruSDk.StartAdService> first."); | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|  | @ -204,15 +435,40 @@ namespace Guru | |||
|         /// <param name="placement"></param> | ||||
|         public static void ShowBanner(string placement = "") | ||||
|         { | ||||
|             if (!CheckAdsReady()) return; | ||||
|             if (!CheckAdsReady()) | ||||
|             { | ||||
|                 _preBannerAction = 1; | ||||
|                 return; | ||||
|             } | ||||
|             ADService.Instance.ShowBanner(placement); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 设置 Banner 背景颜色 | ||||
|         /// </summary> | ||||
|         /// <param name="color"></param> | ||||
|         public static void SetBannerBackgroundColor(Color color) | ||||
|         { | ||||
|             // if (!CheckAdsReady()) return; | ||||
|             ADService.Instance.SetBannerBackgroundColor(color); | ||||
|         } | ||||
|          | ||||
|         public static void SetBannerAutoRefresh(bool value = true) | ||||
|         { | ||||
|             if (!CheckAdsReady()) return; | ||||
|             ADService.Instance.SetBannerAutoRefresh(value); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 隐藏Banner广告 | ||||
|         /// </summary> | ||||
|         public static void HideBanner() | ||||
|         { | ||||
|             if (!CheckAdsReady()) return; | ||||
|             if (!CheckAdsReady()) | ||||
|             { | ||||
|                 _preBannerAction = -1; | ||||
|                 return; | ||||
|             } | ||||
|             ADService.Instance.HideBanner(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -291,5 +547,10 @@ namespace Guru | |||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,165 +0,0 @@ | |||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
|      | ||||
|      | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 回调参数类 | ||||
|         /// </summary> | ||||
|         public class Callbacks | ||||
|         { | ||||
|             /// <summary> | ||||
|             /// APP 事件 | ||||
|             /// </summary> | ||||
|             public static class App | ||||
|             { | ||||
|                 internal static Action<bool> _onAppPaused; | ||||
|                 public static event Action<bool> OnAppPaused | ||||
|                 { | ||||
|                     add => _onAppPaused += value; | ||||
|                     remove => _onAppPaused -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action _onAppQuit; | ||||
|                 public static event Action OnAppQuit | ||||
|                 { | ||||
|                     add => _onAppQuit += value; | ||||
|                     remove => _onAppQuit -= value; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// GDPR Consent | ||||
|             /// </summary> | ||||
|             public static class ConsentFlow | ||||
|             { | ||||
|                 /// <summary> | ||||
|                 /// 当Consent启动结束后返回状态码 | ||||
|                 /// </summary> | ||||
|                 public static event Action<int> OnConsentResult | ||||
|                 { | ||||
|                     add => _onConsentResult += value; | ||||
|                     remove => _onConsentResult -= value; | ||||
|                 } | ||||
|                 internal static Action<int> _onConsentResult; | ||||
| 
 | ||||
|                 /// <summary> | ||||
|                 /// ATT 状态返回 | ||||
|                 /// </summary> | ||||
|                 public static event Action<int> OnAttResult | ||||
|                 { | ||||
|                     add => _onAttResult += value; | ||||
|                     remove => _onAttResult -= value; | ||||
|                 } | ||||
|                 internal static Action<int> _onAttResult; | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 广告回调 | ||||
|             /// </summary> | ||||
|             public static class Ads | ||||
|             { | ||||
|                 internal static Action _onAdsInitComplete; | ||||
|                 public static event Action OnAdsInitComplete | ||||
|                 { | ||||
|                     add => _onAdsInitComplete += value; | ||||
|                     remove => _onAdsInitComplete -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action _onBannerADLoaded; | ||||
|                 public static event Action OnBannerADLoaded | ||||
|                 { | ||||
|                     add => _onBannerADLoaded += value; | ||||
|                     remove => _onBannerADLoaded -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action _onInterstitialADLoaded; | ||||
|                 public static event Action OnInterstitialADLoaded | ||||
|                 { | ||||
|                     add => _onInterstitialADLoaded += value; | ||||
|                     remove => _onInterstitialADLoaded -= value; | ||||
|                 } | ||||
| 
 | ||||
|                 internal static Action _onInterstitialADFailed; | ||||
|                 public static event Action OnInterstitialADFailed | ||||
|                 { | ||||
|                     add => _onInterstitialADFailed += value; | ||||
|                     remove => _onInterstitialADFailed -= value; | ||||
|                 } | ||||
| 
 | ||||
|                 internal static Action _onRewardedADLoaded; | ||||
|                 public static event Action OnRewardedADLoaded | ||||
|                 { | ||||
|                     add => _onRewardedADLoaded += value; | ||||
|                     remove => _onRewardedADLoaded -= value; | ||||
|                 } | ||||
| 
 | ||||
|                 internal static Action _onRewardADFailed; | ||||
|                 public static event Action OnRewardADFailed | ||||
|                 { | ||||
|                     add => _onRewardADFailed += value; | ||||
|                     remove => _onRewardADFailed -= value; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 云控参数 | ||||
|             /// </summary> | ||||
|             public static class Remote | ||||
|             { | ||||
|                 internal static Action<bool> _onRemoteFetchComplete; | ||||
|                 public static event Action<bool> OnRemoteFetchComplete | ||||
|                 { | ||||
|                     add => _onRemoteFetchComplete += value; | ||||
|                     remove => _onRemoteFetchComplete -= value; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 支付回调 | ||||
|             /// </summary> | ||||
|             public static class IAP | ||||
|             { | ||||
|                 internal static Action<bool> _onIAPInitComplete; | ||||
|                 public static event Action<bool> OnIAPInitComplete | ||||
|                 { | ||||
|                     add => _onIAPInitComplete += value; | ||||
|                     remove => _onIAPInitComplete -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action<string> _onPurchaseStart; | ||||
|                 public static event Action<string> OnPurchaseStart | ||||
|                 { | ||||
|                     add => _onPurchaseStart += value; | ||||
|                     remove => _onPurchaseStart -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action<string, string> _onPurchaseFailed; | ||||
|                 public static event Action<string, string> OnPurchaseFailed | ||||
|                 { | ||||
|                     add => _onPurchaseFailed += value; | ||||
|                     remove => _onPurchaseFailed -= value; | ||||
|                 } | ||||
|                  | ||||
|                 internal static Action<bool, string> _onIAPRestored; | ||||
|                 public static event Action<bool, string> OnIAPRestored | ||||
|                 { | ||||
|                     add => _onIAPRestored += value; | ||||
|                     remove => _onIAPRestored -= value; | ||||
|                 } | ||||
|                  | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,378 @@ | |||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|      | ||||
|      | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// 回调参数类 | ||||
|         /// </summary> | ||||
|         public class Callbacks | ||||
|         { | ||||
|             /// <summary> | ||||
|             /// APP 事件 | ||||
|             /// </summary> | ||||
|             public static class App | ||||
|             { | ||||
|                 private static Action<bool> _onAppPaused; | ||||
|                 public static event Action<bool> OnAppPaused | ||||
|                 { | ||||
|                     add => _onAppPaused += value; | ||||
|                     remove => _onAppPaused -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnAppPaused(bool isPaused) | ||||
|                 { | ||||
|                     _onAppPaused?.Invoke(isPaused); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onAppQuit; | ||||
|                 public static event Action OnAppQuit | ||||
|                 { | ||||
|                     add => _onAppQuit += value; | ||||
|                     remove => _onAppQuit -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnAppQuit() | ||||
|                 { | ||||
|                     _onAppQuit?.Invoke(); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             /// <summary> | ||||
|             /// GDPR Consent | ||||
|             /// </summary> | ||||
|             public static class ConsentFlow | ||||
|             { | ||||
|                 /// <summary> | ||||
|                 /// 当Consent启动结束后返回状态码 | ||||
|                 /// </summary> | ||||
|                 public static event Action<int> OnConsentResult | ||||
|                 { | ||||
|                     add => _onConsentResult += value; | ||||
|                     remove => _onConsentResult -= value; | ||||
|                 } | ||||
|                 private static Action<int> _onConsentResult; | ||||
|                 internal static void InvokeOnConsentResult(int code) | ||||
|                 { | ||||
|                     _onConsentResult?.Invoke(code); | ||||
|                 } | ||||
|                  | ||||
|                 /// <summary> | ||||
|                 /// ATT 状态返回 | ||||
|                 /// </summary> | ||||
|                 public static event Action<int> OnAttResult | ||||
|                 { | ||||
|                     add => _onAttResult += value; | ||||
|                     remove => _onAttResult -= value; | ||||
|                 } | ||||
|                 private static Action<int> _onAttResult; | ||||
|                 internal static void InvokeOnAttResultCallback(int code) | ||||
|                 { | ||||
|                     _onAttResult?.Invoke(code); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 广告回调 | ||||
|             /// </summary> | ||||
|             public static class Ads | ||||
|             { | ||||
|                 private static Action _onAdsInitComplete; | ||||
|                 public static event Action OnAdsInitComplete | ||||
|                 { | ||||
|                     add => _onAdsInitComplete += value; | ||||
|                     remove => _onAdsInitComplete -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnAdsInitComplete() | ||||
|                 { | ||||
|                     _onAdsInitComplete?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 //------------ BANNER ----------------- | ||||
|                 private static Action<string> _onBannerADStartLoad; | ||||
|                 public static event Action<string> OnBannerADStartLoad | ||||
|                 { | ||||
|                     add => _onBannerADStartLoad += value; | ||||
|                     remove => _onBannerADStartLoad -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnBannerADStartLoad(string adUnitId) | ||||
|                 { | ||||
|                     _onBannerADStartLoad?.Invoke(adUnitId); | ||||
|                 } | ||||
| 
 | ||||
|                 private static Action _onBannerADLoaded; | ||||
|                 public static event Action OnBannerADLoaded | ||||
|                 { | ||||
|                     add => _onBannerADLoaded += value; | ||||
|                     remove => _onBannerADLoaded -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnBannerADLoaded() | ||||
|                 { | ||||
|                     _onBannerADLoaded?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 //------------ INTER ----------------- | ||||
|                 private static Action<string> _onInterstitialADStartLoad; | ||||
|                 public static event Action<string> OnInterstitialADStartLoad | ||||
|                 { | ||||
|                     add => _onInterstitialADStartLoad += value; | ||||
|                     remove => _onInterstitialADStartLoad -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnInterstitialADStartLoad(string adUnitId) | ||||
|                 { | ||||
|                     _onInterstitialADStartLoad?.Invoke(adUnitId); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onInterstitialADLoaded; | ||||
|                 public static event Action OnInterstitialADLoaded | ||||
|                 { | ||||
|                     add => _onInterstitialADLoaded += value; | ||||
|                     remove => _onInterstitialADLoaded -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnInterstitialADLoaded() | ||||
|                 { | ||||
|                     _onInterstitialADLoaded?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onInterstitialADFailed; | ||||
|                 public static event Action OnInterstitialADFailed | ||||
|                 { | ||||
|                     add => _onInterstitialADFailed += value; | ||||
|                     remove => _onInterstitialADFailed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnInterstitialADFailed() | ||||
|                 { | ||||
|                     _onInterstitialADFailed?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onInterstitialADClosed; | ||||
|                 public static event Action OnInterstitialADClosed | ||||
|                 { | ||||
|                     add => _onInterstitialADClosed += value; | ||||
|                     remove => _onInterstitialADClosed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnInterstitialADClosed() | ||||
|                 { | ||||
|                     _onInterstitialADClosed?.Invoke(); | ||||
|                 } | ||||
| 
 | ||||
|                 //------------ REWARD ----------------- | ||||
|                 private static Action<string> _onRewardedADStartLoad; | ||||
|                 public static event Action<string> OnRewardedADStartLoad | ||||
|                 { | ||||
|                     add => _onRewardedADStartLoad += value; | ||||
|                     remove => _onRewardedADStartLoad -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnRewardedADStartLoad(string adUnitId) | ||||
|                 { | ||||
|                     _onRewardedADStartLoad?.Invoke(adUnitId); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onRewardedADLoaded; | ||||
|                 public static event Action OnRewardedADLoaded | ||||
|                 { | ||||
|                     add => _onRewardedADLoaded += value; | ||||
|                     remove => _onRewardedADLoaded -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnRewardedADLoaded() | ||||
|                 { | ||||
|                     _onRewardedADLoaded?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onRewardADClosed; | ||||
|                 public static event Action OnRewardedADClosed | ||||
|                 { | ||||
|                     add => _onRewardADClosed += value; | ||||
|                     remove => _onRewardADClosed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnRewardADClosed() | ||||
|                 { | ||||
|                     _onRewardADClosed?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onRewardADFailed; | ||||
|                 public static event Action OnRewardADFailed | ||||
|                 { | ||||
|                     add => _onRewardADFailed += value; | ||||
|                     remove => _onRewardADFailed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnRewardADFailed() | ||||
|                 { | ||||
|                     _onRewardADFailed?.Invoke(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 云控参数 | ||||
|             /// </summary> | ||||
|             public static class Remote | ||||
|             { | ||||
|                 private static Action<bool> _onRemoteFetchComplete; | ||||
|                 public static event Action<bool> OnRemoteFetchComplete | ||||
|                 { | ||||
|                     add => _onRemoteFetchComplete += value; | ||||
|                     remove => _onRemoteFetchComplete -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnRemoteFetchComplete(bool success) | ||||
|                 { | ||||
|                     _onRemoteFetchComplete?.Invoke(success); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// <summary> | ||||
|             /// 支付回调 | ||||
|             /// </summary> | ||||
|             public static class IAP | ||||
|             { | ||||
|                 private static Action _onIAPInitStart; | ||||
|                 public static event Action OnIAPInitStart | ||||
|                 { | ||||
|                     add => _onIAPInitStart += value; | ||||
|                     remove => _onIAPInitStart -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnIAPInitStart() | ||||
|                 { | ||||
|                     _onIAPInitStart?.Invoke(); | ||||
|                 } | ||||
|                  | ||||
|                  | ||||
|                 private static Action<bool> _onIAPInitComplete; | ||||
|                 public static event Action<bool> OnIAPInitComplete | ||||
|                 { | ||||
|                     add => _onIAPInitComplete += value; | ||||
|                     remove => _onIAPInitComplete -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnIAPInitComplete(bool success) | ||||
|                 { | ||||
|                     _onIAPInitComplete?.Invoke(success); | ||||
|                 } | ||||
|                  | ||||
|                  | ||||
|                 private static Action<string> _onPurchaseStart; | ||||
|                 public static event Action<string> OnPurchaseStart | ||||
|                 { | ||||
|                     add => _onPurchaseStart += value; | ||||
|                     remove => _onPurchaseStart -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnPurchaseStart(string productId) | ||||
|                 { | ||||
|                     _onPurchaseStart?.Invoke(productId); | ||||
|                 } | ||||
|                  | ||||
|                  | ||||
|                 private static Action<string, bool> _onPurchaseEnd; | ||||
|                 public static event Action<string, bool> OnPurchaseEnd | ||||
|                 { | ||||
|                     add => _onPurchaseEnd += value; | ||||
|                     remove => _onPurchaseEnd -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnPurchaseEnd(string productId, bool success) | ||||
|                 { | ||||
|                     _onPurchaseEnd?.Invoke(productId, success); | ||||
|                 } | ||||
|                  | ||||
|                  | ||||
|                 private static Action<string, string> _onPurchaseFailed; | ||||
|                 public static event Action<string, string> OnPurchaseFailed | ||||
|                 { | ||||
|                     add => _onPurchaseFailed += value; | ||||
|                     remove => _onPurchaseFailed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnPurchaseFailed(string productId, string error) | ||||
|                 { | ||||
|                     _onPurchaseFailed?.Invoke(productId, error); | ||||
|                 } | ||||
|                  | ||||
|         | ||||
|                 private static Action<bool, string> _onIAPRestored; | ||||
|                 public static event Action<bool, string> OnIAPRestored | ||||
|                 { | ||||
|                     add => _onIAPRestored += value; | ||||
|                     remove => _onIAPRestored -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnIAPRestored(bool success, string productId) | ||||
|                 { | ||||
|                     _onIAPRestored?.Invoke(success, productId); | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             public static class SDK | ||||
|             { | ||||
|                 private static Action<bool> _onFirebaseReady; | ||||
|                 public static event Action<bool> OnFirebaseReady | ||||
|                 { | ||||
|                     add => _onFirebaseReady += value; | ||||
|                     remove => _onFirebaseReady -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnFirebaseReady(bool success) | ||||
|                 { | ||||
|                     _onFirebaseReady?.Invoke(success); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action _onGuruServiceReady; | ||||
|                 public static event Action OnGuruServiceReady | ||||
|                 { | ||||
|                     add => _onGuruServiceReady += value; | ||||
|                     remove => _onGuruServiceReady -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnGuruServiceReady() | ||||
|                 { | ||||
|                     _onGuruServiceReady?.Invoke(); | ||||
|                 } | ||||
| 
 | ||||
|                 private static Action<bool> _onDebuggerDisplayed; | ||||
|                 public static event Action<bool> OnDisplayDebugger | ||||
|                 { | ||||
|                     add => _onDebuggerDisplayed += value; | ||||
|                     remove => _onDebuggerDisplayed -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnDebuggerDisplayed(bool success) | ||||
|                 { | ||||
|                     _onDebuggerDisplayed?.Invoke(success); | ||||
|                 } | ||||
|                  | ||||
|                 private static Action<bool> _onUserAuthResult; | ||||
|                 public static event Action<bool> OnGuruUserAuthResult | ||||
|                 { | ||||
|                     add => _onUserAuthResult += value; | ||||
|                     remove => _onUserAuthResult -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnGuruUserAuthResult(bool success) | ||||
|                 { | ||||
|                     _onUserAuthResult?.Invoke(success); | ||||
|                 } | ||||
|                  | ||||
|                 // DeepLink 回调  | ||||
|                 private static Action<string> _onDeeplinkCallback; | ||||
|                 public static event Action<string> OnDeeplinkCallback | ||||
|                 { | ||||
|                     add => _onDeeplinkCallback += value; | ||||
|                     remove => _onDeeplinkCallback -= value; | ||||
|                 } | ||||
|                 internal static void InvokeDeeplinkCallback(string deeplink) | ||||
|                 { | ||||
|                     _onDeeplinkCallback?.Invoke(deeplink); | ||||
|                 } | ||||
|                  | ||||
|                 // TODO: 之后需要添加 define 宏来控制是否可用 | ||||
|                 // Firebase Auth 回调 | ||||
|                 private static Action<bool, Firebase.Auth.FirebaseUser> _onFirebaseUserAuthResult; | ||||
|                 public static event Action<bool, Firebase.Auth.FirebaseUser> OnFirebaseUserAuthResult | ||||
|                 { | ||||
|                     add => _onFirebaseUserAuthResult += value; | ||||
|                     remove => _onFirebaseUserAuthResult -= value; | ||||
|                 } | ||||
|                 internal static void InvokeOnFirebaseAuthResult(bool success, Firebase.Auth.FirebaseUser firebaseUser = null) | ||||
|                 { | ||||
|                     _onFirebaseUserAuthResult?.Invoke(success, firebaseUser); | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,282 @@ | |||
| namespace Guru | ||||
| { | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
| 	     | ||||
| 		 | ||||
|         /// <summary> | ||||
|         /// Consts values | ||||
|         /// </summary> | ||||
|         public static class Consts | ||||
|         { | ||||
| 	        #region Firebase Defines | ||||
| 	         | ||||
| 	        public const string EventAdImpression = "ad_impression"; | ||||
| 			public const string EventAddPaymentInfo = "add_payment_info"; | ||||
| 			public const string EventAddShippingInfo = "add_shipping_info"; | ||||
| 			public const string EventAddToCart = "add_to_cart"; | ||||
| 			public const string EventAddToWishlist = "add_to_wishlist"; | ||||
| 			public const string EventAppOpen = "app_open"; | ||||
| 			public const string EventBeginCheckout = "begin_checkout"; | ||||
| 			public const string EventCampaignDetails = "campaign_details"; | ||||
| 			public const string EventEarnVirtualCurrency = "earn_virtual_currency"; | ||||
| 			public const string EventGenerateLead = "generate_lead"; | ||||
| 			public const string EventJoinGroup = "join_group"; | ||||
| 			public const string EventLevelEnd = "level_end"; | ||||
| 			public const string EventLevelStart = "level_start"; | ||||
| 			public const string EventLevelEndSuccessPrefix = "level_end_success_"; | ||||
| 			public const string EventLevelUp = "level_up"; | ||||
| 			public const string EventLogin = "login"; | ||||
| 			public const string EventPostScore = "post_score"; | ||||
| 			public const string EventPurchase = "purchase"; | ||||
| 			public const string EventRefund = "refund"; | ||||
| 			public const string EventRemoveFromCart = "remove_from_cart"; | ||||
| 			public const string EventScreenView = "screen_view"; | ||||
| 			public const string EventSearch = "search"; | ||||
| 			public const string EventSelectContent = "select_content"; | ||||
| 			public const string EventSelectItem = "select_item"; | ||||
| 			public const string EventSelectPromotion = "select_promotion"; | ||||
| 			public const string EventShare = "share"; | ||||
| 			public const string EventSignUp = "sign_up"; | ||||
| 			public const string EventSpendVirtualCurrency = "spend_virtual_currency"; | ||||
| 			public const string EventUnlockAchievement = "unlock_achievement"; | ||||
| 			public const string EventHpPoints = "hp_points"; | ||||
| 			 | ||||
| 			public const string EventAttReguideImp = "att_reguide_imp"; | ||||
| 			public const string EventAttReguideClk = "att_reguide_clk"; | ||||
| 			public const string EventAttReguideResult = "att_reguide_result"; | ||||
| 			 | ||||
| 			public const string EventTutorialBegin = "tutorial_begin"; | ||||
| 			public const string EventTutorialImp= "tutorial_{0}_imp"; | ||||
| 			public const string EventTutorialNextClick= "tutorial_{0}_next_clk"; | ||||
| 			public const string EventTutorialComplete= "tutorial_complete"; | ||||
| 			public const string EventTutorialClose = "tutorial_close"; | ||||
| 			 | ||||
| 			public const string EventNotiPermImp = "noti_perm_imp"; | ||||
| 			public const string EventNotiPermResult = "noti_perm_result"; | ||||
| 			public const string EventNotiPermRationaleImp = "noti_perm_rationale_imp"; | ||||
| 			public const string EventNotiPermRationaleResult = "noti_perm_rationale_result"; | ||||
| 			 | ||||
| 			public const string EventDevAudit = "dev_audit"; | ||||
| 			 | ||||
| 			public const string EventViewCart = "view_cart"; | ||||
| 			public const string EventViewItem = "view_item"; | ||||
| 			public const string EventViewItemList = "view_item_list"; | ||||
| 			public const string EventViewPromotion = "view_promotion"; | ||||
| 			public const string EventViewSearchResults = "view_search_results"; | ||||
| 
 | ||||
| 			public const string ParameterAchievementId = "achievement_id"; | ||||
| 			public const string ParameterAdFormat = "ad_format"; | ||||
| 			public const string ParameterAdNetworkClickID = "aclid"; | ||||
| 			public const string ParameterAdPlatform = "ad_platform"; | ||||
| 			public const string ParameterAdSource = "ad_source"; | ||||
| 			public const string ParameterAdUnitName = "ad_unit_name"; | ||||
| 			public const string ParameterAffiliation = "affiliation"; | ||||
| 			public const string ParameterCP1 = "cp1"; | ||||
| 			public const string ParameterCampaign = "campaign"; | ||||
| 			public const string ParameterCharacter = "character"; | ||||
| 			public const string ParameterContent = "content"; | ||||
| 			public const string ParameterContentType = "content_type"; | ||||
| 			public const string ParameterCoupon = "coupon"; | ||||
| 			public const string ParameterCreativeName = "creative_name"; | ||||
| 			public const string ParameterCreativeSlot = "creative_slot"; | ||||
| 			public const string ParameterCurrency = "currency"; | ||||
| 			public const string ParameterDestination = "destination"; | ||||
| 			public const string ParameterDiscount = "discount"; | ||||
| 			public const string ParameterEndDate = "end_date"; | ||||
| 			public const string ParameterExtendSession = "extend_session"; | ||||
| 			public const string ParameterFlightNumber = "flight_number"; | ||||
| 			public const string ParameterGroupId = "group_id"; | ||||
| 			public const string ParameterIndex = "index"; | ||||
| 			public const string ParameterItemBrand = "item_brand"; | ||||
| 			public const string ParameterItemCategory = "item_category"; | ||||
| 			public const string ParameterItemCategory2 = "item_category2"; | ||||
| 			public const string ParameterItemCategory3 = "item_category3"; | ||||
| 			public const string ParameterItemCategory4 = "item_category4"; | ||||
| 			public const string ParameterItemCategory5 = "item_category5"; | ||||
| 			public const string ParameterItemId = "item_id"; | ||||
| 			public const string ParameterItemList = "item_list"; | ||||
| 			public const string ParameterItemListID = "item_list_id"; | ||||
| 			public const string ParameterItemListName = "item_list_name"; | ||||
| 			public const string ParameterItemName = "item_name"; | ||||
| 			public const string ParameterLevel = "level"; | ||||
| 			public const string ParameterLevelName = "level_name"; | ||||
| 			public const string ParameterLocation = "location"; | ||||
| 			public const string ParameterLocationID = "location_id"; | ||||
| 			public const string ParameterMedium = "medium"; | ||||
| 			public const string ParameterMethod = "method"; | ||||
| 			public const string ParameterNumberOfNights = "number_of_nights"; | ||||
| 			public const string ParameterNumberOfPassengers = "number_of_passengers"; | ||||
| 			public const string ParameterNumberOfRooms = "number_of_rooms"; | ||||
| 			public const string ParameterOrigin = "origin"; | ||||
| 			public const string ParameterPaymentType = "payment_type"; | ||||
| 			public const string ParameterPrice = "price"; | ||||
| 			public const string ParameterPromotionID = "promotion_id"; | ||||
| 			public const string ParameterPromotionName = "promotion_name"; | ||||
| 			public const string ParameterQuantity = "quantity"; | ||||
| 			public const string ParameterScore = "score"; | ||||
| 			public const string ParameterScreenClass = "screen_class"; | ||||
| 			public const string ParameterScreenName = "screen_name"; | ||||
| 			public const string ParameterSearchTerm = "search_term"; | ||||
| 			public const string ParameterShipping = "shipping"; | ||||
| 			public const string ParameterShippingTier = "shipping_tier"; | ||||
| 			public const string ParameterSignUpMethod = "sign_up_method"; | ||||
| 			public const string ParameterSource = "source"; | ||||
| 			public const string ParameterStartDate = "start_date"; | ||||
| 			public const string ParameterSuccess = "success"; | ||||
| 			public const string ParameterTax = "tax"; | ||||
| 			public const string ParameterTerm = "term"; | ||||
| 			public const string ParameterTransactionId = "transaction_id"; | ||||
| 			public const string ParameterTravelClass = "travel_class"; | ||||
| 			public const string ParameterValue = "value"; | ||||
| 			 | ||||
| 	        #endregion | ||||
| 	         | ||||
| 	        #region Guru BI Events & Parameters | ||||
| 	         | ||||
|             public const string TAG = "Analytics"; | ||||
| 			// 美元符号 | ||||
| 			public const string USD = "USD";  | ||||
| 			// 广告平台 | ||||
| 			public const string AdMAX = "MAX";  | ||||
| 			 | ||||
| 			//IAP打点事件 | ||||
| 			public const string EventIAPFirst = "first_iap"; | ||||
| 			public const string EventIAPImp = "iap_imp"; | ||||
| 			public const string EventIAPClose = "iap_close"; | ||||
| 			public const string EventIAPClick = "iap_clk"; | ||||
| 			public const string EventIAPReturnTrue = "iap_ret_true"; | ||||
| 			public const string EventIAPReturnFalse = "iap_ret_false"; | ||||
| 			 | ||||
| 			//横幅广告打点事件 | ||||
| 			public const string EventBadsLoad = "bads_load"; | ||||
| 			public const string EventBadsLoaded = "bads_loaded"; | ||||
| 			public const string EventBadsFailed = "bads_failed"; | ||||
| 			public const string EventBadsClick = "bads_clk"; | ||||
| 			public const string EventBadsImp = "bads_imp"; | ||||
| 
 | ||||
| 			//插屏广告打点事件 | ||||
| 			public const string EventIadsLoad = "iads_load"; | ||||
| 			public const string EventIadsLoaded = "iads_loaded"; | ||||
| 			public const string EventIadsFailed = "iads_failed"; | ||||
| 			public const string EventIadsImp = "iads_imp"; | ||||
| 			public const string EventIadsClick = "iads_clk"; | ||||
| 			public const string EventIadsClose = "iads_close"; | ||||
| 	     | ||||
| 			//激励视频广告打点事件 | ||||
| 			public const string EventRadsLoad = "rads_load"; | ||||
| 			public const string EventRadsLoaded = "rads_loaded"; | ||||
| 			public const string EventRadsFailed = "rads_failed"; | ||||
| 			public const string EventRadsImp = "rads_imp"; | ||||
| 			public const string EventRadsRewarded = "rads_rewarded"; | ||||
| 			public const string EventRadsClick = "rads_clk"; | ||||
| 			public const string EventRadsClose = "rads_close"; | ||||
| 			public const string EventFirstRadsRewarded = "first_rads_rewarded"; | ||||
| 			 | ||||
| 			//广告收益打点事件 | ||||
| 			public const string EventTchAdRev001Impression = "tch_ad_rev_roas_001"; | ||||
| 			public const string EventTchAdRev02Impression = "tch_ad_rev_roas_02"; | ||||
| 			public const string EventTchAdRevAbnormal = "tch_ad_rev_value_abnormal"; | ||||
| 			 | ||||
| 			//内购成功事件上报 | ||||
| 			public const string EventIAPPurchase = "iap_purchase"; | ||||
| 			public const string EventSubPurchase = "sub_purchase"; | ||||
| 			public const string IAPStoreCategory = "Store"; | ||||
| 			public const string IAPTypeProduct = "product"; | ||||
| 			public const string IAPTypeSubscription = "subscription"; | ||||
| 			 | ||||
| 			//打点参数名 | ||||
| 			public const string ParameterResult = "result"; | ||||
| 			public const string ParameterStep = "step"; | ||||
| 			public const string ParameterDuration = "duration"; | ||||
| 			public const string ParameterErrorCode = "error_code"; | ||||
| 			public const string ParameterProductId = "product_id"; | ||||
| 			public const string ParameterPlatform = "platform"; | ||||
| 			public const string ParameterStartType = "start_type"; // 游戏启动类型 | ||||
| 			public const string ParameterReplay =  "replay"; // 游戏重玩 | ||||
| 			public const string ParameterContinue = "continue"; // 游戏继续 | ||||
| 			 | ||||
| 			// 评价参数 | ||||
| 			public const string EventRateImp = "rate_imp"; // 评价弹窗展示 | ||||
| 			public const string EventRateNow = "rate_now"; // 点击评分引导弹窗中的评分 | ||||
| 			 | ||||
| 			//打点内部执行错误 | ||||
| 			public static string ParameterEventError => "event_error"; | ||||
| 			 | ||||
| 			//ios ATT打点 | ||||
| 			public const string ATTGuideShow = "att_guide_show"; | ||||
| 			public const string ATTGuideOK = "att_guide_ok"; | ||||
| 			public const string ATTWindowShow = "att_window_show"; | ||||
| 			public const string ATTOptIn = "att_opt_in"; | ||||
| 			public const string ATTOpOut = "att_opt_out"; | ||||
| 			public const string ParameterATTStatus = "att_status"; | ||||
| 			public const string EventAttResult = "att_result"; | ||||
| 			 | ||||
| 			// 用户属性 | ||||
| 			public const string PropertyFirstOpenTime = "first_open_time"; 		//用户第一次first_open的时间 | ||||
| 			public const string PropertyDeviceID = "device_id"; //用户的设备ID | ||||
| 			public const string PropertyUserID = "user_id"; | ||||
| 			public const string PropertyLevel = "b_level"; //"每次完成通关上升一次,显示用户完成的最大关卡数。只针对主关卡和主玩法的局数做累加,初始值为0。" | ||||
| 			public const string PropertyPlay = "b_play"; //每完成一局或者游戏触发, | ||||
| 			public const string PropertyLastPlayedLevel = "last_played_level"; | ||||
| 			public const string PropertyGrade = "grade"; //当游戏玩家角色升级时触发 | ||||
| 			public const string PropertyIsIAPUser = "is_iap_user"; 		//付费成功后设置属性参数为true,如果没有发生付费可以不用设置该属性 | ||||
| 			public const string PropertyIAPCoin = "iap_coin"; //付费所得的总金币数(iap获取累计值)\ | ||||
| 			public const string PropertyNonIAPCoin = "noniap_coin"; //非付费iap获取累计值 | ||||
| 			public const string PropertyCoin = "coin"; //当前金币数 | ||||
| 			public const string PropertyExp = "exp"; // 经验值 | ||||
| 			public const string PropertyHp = "hp"; // 生命值/体力 | ||||
| 			public const string PropertyNetwork = "network"; // 网络状态 | ||||
| 			public const string PropertyAndroidID = "android_id"; // Android 平台 AndroidID | ||||
| 			public const string PropertyIDFV = "idfv"; // iOS  平台 IDFV | ||||
| 			public const string PropertyPicture = "picture"; // 玩家在主线的mapid | ||||
| 			public const string PropertyNoAds = "no_ads"; // 玩家是否去广告 | ||||
| 			public const string PropertyATTStatus = "att_status";  // ATT 状态 | ||||
| 			public const string PropertyNotiPerm = "noti_perm";  // Notification Permission 状态 | ||||
| 			public const string PropertyAdjustId = "adjust_id";  // AdjustId | ||||
| 			public const string PropertyGDPR = "gdpr"; // GDPR状态 | ||||
| 			 | ||||
| 			// 经济相关 | ||||
| 			public const string ParameterBalance = "balance"; // 用于余额 | ||||
| 			public const string ParameterDefaultScene = "in_game"; // 货币消费默认场景 | ||||
| 			public const string ParameterVirtualCurrencyName = "virtual_currency_name"; // 虚拟货币名称 | ||||
| 
 | ||||
| 			 | ||||
| 			public const string CurrencyNameProps = "props"; // props | ||||
| 			 | ||||
| 			public const string CurrencyCategoryReward = "reward"; // common, ads | ||||
| 			public const string CurrencyCategoryIAP = "iap_buy"; // In app purchase | ||||
| 			public const string CurrencyCategoryBonus = "bonus"; // ads+items, gift box, item group  | ||||
| 			public const string CurrencyCategoryIGC = "igc"; // In game currency | ||||
| 			public const string CurrencyCategoryIGB = "igb"; // In game barter | ||||
| 			public const string CurrencyCategoryProp = "prop"; // prop | ||||
| 			public const string CurrencyCategoryProps = "props"; // props | ||||
| 			public const string CurrencyCategoryBundle = "bundle"; // prop groups | ||||
| 			public const string CurrencyCategoryBoost = "boost"; // boost | ||||
| 			 | ||||
| 			// SDK  | ||||
| 			public const string EventSDKInfo = "sdk_info"; | ||||
| 			 | ||||
| 			//----------------- 关卡开始类型 --------------------- | ||||
| 			public const string EventLevelStartModePlay = "play"; | ||||
| 			public const string EventLevelStartModeReplay = "replay"; | ||||
| 			public const string EventLevelStartModeContinue= "continue"; | ||||
|          | ||||
| 			//----------------- 关卡结束类型 --------------------- | ||||
| 			public const string EventLevelEndSuccess = "success"; | ||||
| 			public const string EventLevelEndFail = "fail"; | ||||
| 			public const string EventLevelEndExit = "exit"; | ||||
| 			public const string EventLevelEndTimeout = "timeout"; | ||||
| 			 | ||||
| 			/// <summary> | ||||
| 			/// 主线关卡类型 | ||||
| 			/// 只有传入此类型时才会进行 Blevel 的累加 | ||||
| 			/// </summary> | ||||
| 			public const string LevelTypeMain = "main"; | ||||
| 
 | ||||
| 			#endregion | ||||
| 			 | ||||
|         } | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 35b764b09fe74efe8e2ebead9716279a | ||||
| timeCreated: 1709802191 | ||||
|  | @ -0,0 +1,60 @@ | |||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         private const string K_CMD_NAME_DEBUGGER = "gurusdk.unity.dbg"; | ||||
|         private const string K_CMD_NAME_WATERMARK = "gurusdk.unity.wm"; | ||||
|         private const string K_CMD_NAME_CONSOLE = "gurusdk.unity.con"; | ||||
|          | ||||
|         #region Android 测试入口 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 启动 Android 测试配置 | ||||
|         /// </summary> | ||||
|         private void StartAndroidDebugCmds() | ||||
|         { | ||||
| 
 | ||||
|             if (string.IsNullOrEmpty(AppBundleId)) | ||||
|             { | ||||
|                 UnityEngine.Debug.LogError("--- App Bundle Id is empty, please set it first. ---"); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             string val; | ||||
|             string key; | ||||
| 
 | ||||
|             key = K_CMD_NAME_DEBUGGER; | ||||
|             val = AndroidSystemPropertiesHelper.Get(key); | ||||
|             if (val == AppBundleId) | ||||
|             { | ||||
|                 // 显示应用调试状态栏 | ||||
|                 Debugger.ShowAdStatus(); | ||||
|             } | ||||
|              | ||||
|             key = K_CMD_NAME_WATERMARK; | ||||
|             val = AndroidSystemPropertiesHelper.Get(key); | ||||
|             if (val == AppBundleId) | ||||
|             { | ||||
|                 // 显示应用水印 | ||||
|                 // TODO | ||||
|             } | ||||
|              | ||||
|             key = K_CMD_NAME_CONSOLE; | ||||
|             val = AndroidSystemPropertiesHelper.Get(key); | ||||
|             if (val == AppBundleId) | ||||
|             { | ||||
|                 // 显示控制台 | ||||
|                  | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private bool IsCmdAvailable(string value) | ||||
|         { | ||||
|             return value == "1" || value == "true"; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 36f2d28a30ff46568d6adc0b728a3268 | ||||
| timeCreated: 1714354376 | ||||
|  | @ -0,0 +1,129 @@ | |||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|      | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         private static readonly bool _useBaseOptions = true; | ||||
| 
 | ||||
|         private static GuruDebugger _debugger; | ||||
| 
 | ||||
|         public static GuruDebugger Debugger | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (_debugger == null) | ||||
|                 { | ||||
|                     _debugger = GuruDebugger.Instance; | ||||
|                     if (_useBaseOptions) | ||||
|                     { | ||||
|                         InitDebuggerLayout(); | ||||
|                     } | ||||
|                 } | ||||
|                 return _debugger; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 显示广告状态 | ||||
|         /// </summary> | ||||
|         public static bool ShowAdStatus() | ||||
|         { | ||||
|             if (!IsServiceReady) return false; | ||||
|              | ||||
|             Debugger.ShowAdStatus(); | ||||
|             return true; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 显示 Debugger | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static bool ShowDebugger() | ||||
|         { | ||||
|             if (!IsServiceReady) return false; | ||||
|              | ||||
|             Debugger.ShowPage(); // 显示 Debugger 界面 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         private static void InitDebuggerLayout() | ||||
|         { | ||||
|             var settings = GuruSettings.Instance; | ||||
|             var v = GuruAppVersion.Load(); | ||||
|             var app_version = (v == null ? $"{Application.version} (unknown)" : $"{v.version}  ({v.code})"); | ||||
|             var uid = (string.IsNullOrEmpty(UID) ? "NULL" : UID); | ||||
|             var device_id = (string.IsNullOrEmpty(DeviceId) ? "NULL" : DeviceId); | ||||
|             var push_token = (string.IsNullOrEmpty(PushToken) ? "NULL" : PushToken); | ||||
|             var auth_token = (string.IsNullOrEmpty(AuthToken) ? "NULL" : AuthToken); | ||||
|             var fid = (string.IsNullOrEmpty(FirebaseId) ? "NULL" : FirebaseId); | ||||
|             var adjust_id = (string.IsNullOrEmpty(AdjustId) ? "NULL" : AdjustId); | ||||
|             var idfa = (string.IsNullOrEmpty(IDFA) ? "NULL" : IDFA); | ||||
|             var gsid = (string.IsNullOrEmpty(GSADID) ? "NULL" : GSADID); | ||||
|              | ||||
|             // ------------ Info Page -------------------- | ||||
|             Debugger.AddOption("Info/Guru SDK", GuruSDK.Version); | ||||
|             Debugger.AddOption("Info/Unity Version", Application.unityVersion); | ||||
|             Debugger.AddOption("Info/Name", settings.ProductName); | ||||
|             Debugger.AddOption("Info/Bundle Id", settings.GameIdentifier); | ||||
|             Debugger.AddOption("Info/Version", app_version); | ||||
|             Debugger.AddOption("Info/Uid", uid).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Device ID", device_id).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Push Token", push_token).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Auth Token", auth_token).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Firebase Id", fid).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Adjust Id", adjust_id).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/IDFA", idfa).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/GSADID", gsid).AddCopyButton(); | ||||
|             Debugger.AddOption("Info/Debug Mode", GuruSDK.IsDebugMode? "true" : "false"); | ||||
|             Debugger.AddOption("Info/Screen size", $"{Screen.width} x {Screen.height}"); | ||||
|              | ||||
|              | ||||
|             // ------------ Ads Page -------------------- | ||||
|             Debugger.AddOption("Ads/Show Ads Debug Panel", "", ShowMaxDebugPanel); | ||||
| 
 | ||||
|             var badsId = settings.ADSetting.GetBannerID(); | ||||
|             var iadsId = settings.ADSetting.GetInterstitialID(); | ||||
|             var radsId = settings.ADSetting.GetRewardedVideoID(); | ||||
|              | ||||
|             Debugger.AddOption("Ads/Banner Id", badsId); | ||||
|             Debugger.AddOption("Ads/Interstitial Id", iadsId); | ||||
|             Debugger.AddOption("Ads/Rewarded Id", radsId); | ||||
|              | ||||
|             GuruDebugger.OnClosed -= OnDebuggerClosed; | ||||
|             GuruDebugger.OnClosed += OnDebuggerClosed; | ||||
|             Callbacks.SDK.InvokeOnDebuggerDisplayed(true); | ||||
|         } | ||||
|          | ||||
|         private static void OnDebuggerClosed() | ||||
|         { | ||||
|             GuruDebugger.OnClosed -= OnDebuggerClosed; | ||||
|             Callbacks.SDK.InvokeOnDebuggerDisplayed(false); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 显示 Debugger | ||||
|         /// </summary> | ||||
|         /// <param name="debugger"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool ShowDebuggerWithData(out GuruDebugger debugger) | ||||
|         { | ||||
|             debugger = null; | ||||
|             bool res = ShowDebugger(); | ||||
|             if (res) | ||||
|             { | ||||
|                 debugger = GuruDebugger.Instance; | ||||
|             } | ||||
|             return res; | ||||
|         } | ||||
| 
 | ||||
|         public static GuruDebugger.OptionLayout AddOption(string uri, string content = "", Action clickHandler = null) | ||||
|         { | ||||
|             return Debugger.AddOption(uri, content, clickHandler); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 002605b9e408487bb69d27d07dda016c | ||||
| timeCreated: 1711078536 | ||||
|  | @ -1,6 +1,5 @@ | |||
| namespace Guru | ||||
| { | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|     using System.Linq; | ||||
|      | ||||
|  | @ -18,12 +17,12 @@ namespace Guru | |||
|         public const string BuyFail_DuplicateTransaction = "DuplicateTransaction"; | ||||
|         public const string BuyFail_Unknown = "Unknown"; | ||||
|          | ||||
|          | ||||
|         #region Start | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 初始化IAP 功能 | ||||
|         /// </summary> | ||||
|         public static void InitIAP(byte[] googleKey, byte[] appleRootCerts) | ||||
|         public static void InitIAP(string uid, byte[] googleKey, byte[] appleRootCerts) | ||||
|         { | ||||
|             GuruIAP.Instance.OnInitResult += OnIAPInitResult; | ||||
|             GuruIAP.Instance.OnRestored += OnRestored; | ||||
|  | @ -32,10 +31,11 @@ namespace Guru | |||
|             GuruIAP.Instance.OnBuyFailed += OnBuyFailed; | ||||
|             GuruIAP.Instance.OnGetProductReceipt += OnGetReceipt; | ||||
|              | ||||
|             GuruIAP.Instance.InitWithKeys(googleKey, appleRootCerts, IsDebugMode); | ||||
|             Callbacks.IAP.InvokeOnIAPInitStart(); // 初始化之前进行调用 | ||||
|              | ||||
|             GuruIAP.Instance.InitWithKeys(uid, googleKey, appleRootCerts, IsDebugMode); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 初始化结果 | ||||
|         /// </summary> | ||||
|  | @ -43,9 +43,8 @@ namespace Guru | |||
|         private static void OnIAPInitResult(bool success) | ||||
|         { | ||||
|             LogI($"IAP init result: {success}"); | ||||
|              | ||||
|             IsIAPReady = success; | ||||
|             Callbacks.IAP._onIAPInitComplete?.Invoke(success); | ||||
|             Callbacks.IAP.InvokeOnIAPInitComplete(success); | ||||
|         } | ||||
| 
 | ||||
|         private static bool CheckIAPReady() | ||||
|  | @ -59,6 +58,8 @@ namespace Guru | |||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
|         #region Data | ||||
| 
 | ||||
|         /// <summary> | ||||
|  | @ -68,7 +69,7 @@ namespace Guru | |||
|         /// <returns></returns> | ||||
|         public static ProductInfo GetProductInfo(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance?.GetInfo(productName) ?? null; | ||||
|             return GuruIAP.Instance?.GetInfo(productName); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|  | @ -78,7 +79,7 @@ namespace Guru | |||
|         /// <returns></returns> | ||||
|         public static ProductInfo GetProductInfoById(string productId) | ||||
|         { | ||||
|             return GuruIAP.Instance?.GetInfoById(productId) ?? null; | ||||
|             return GuruIAP.Instance?.GetInfoById(productId); | ||||
|         } | ||||
|          | ||||
|          | ||||
|  | @ -125,34 +126,47 @@ namespace Guru | |||
|          | ||||
|         #region Purchase | ||||
|          | ||||
|         private static Action<string, bool> _onPurchaseCallback; | ||||
|          | ||||
|         private static Action<string, bool> InvokeOnPurchaseCallback; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 老接口, 将会被废弃 | ||||
|         /// </summary> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <param name="purchaseCallback"></param> | ||||
|         [Obsolete("Will be discarded in next version. Using Purchase(string productName, string category, Action<string, bool> purchaseCallback) instead.")] | ||||
|         internal static void Purchase(string productName, Action<string, bool> purchaseCallback = null) | ||||
|         { | ||||
|             Purchase(productName, "", purchaseCallback); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 购买商品, 通过商品Name | ||||
|         /// </summary> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <param name="category"></param> | ||||
|         /// <param name="purchaseCallback"></param> | ||||
|         public static void Purchase(string productName, Action<string, bool> purchaseCallback = null) | ||||
|         public static void Purchase(string productName, string category = "", Action<string, bool> purchaseCallback = null) | ||||
|         { | ||||
|             if (CheckIAPReady()) | ||||
|             { | ||||
|                 _onPurchaseCallback = purchaseCallback; | ||||
|                 GuruIAP.Instance.Buy(productName); | ||||
|                 InvokeOnPurchaseCallback = purchaseCallback; | ||||
|                 GuruIAP.Instance.Buy(productName, category); | ||||
|             } | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 购买商品, 通过商品ID | ||||
|         /// </summary> | ||||
|         /// <param name="productId"></param> | ||||
|         /// <param name="category"></param> | ||||
|         /// <param name="purchaseCallback"></param> | ||||
|         public static bool PurchaseById(string productId, Action<string, bool> purchaseCallback = null) | ||||
|         public static bool PurchaseById(string productId, string category = "", Action<string, bool> purchaseCallback = null) | ||||
|         { | ||||
|             var productName = GetProductInfoById(productId)?.Name ?? ""; | ||||
|              | ||||
|             if (CheckIAPReady() && !string.IsNullOrEmpty(productName)) | ||||
|             { | ||||
|                 Purchase(productName, purchaseCallback); | ||||
|                 Purchase(productName, category, purchaseCallback); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|  | @ -162,11 +176,12 @@ namespace Guru | |||
|         /// <summary> | ||||
|         /// 支付回调 | ||||
|         /// </summary> | ||||
|         /// <param name="productId"></param> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <param name="success"></param> | ||||
|         private static void OnBuyEnd(string productName, bool success) | ||||
|         { | ||||
|             _onPurchaseCallback?.Invoke(productName, success); | ||||
|             InvokeOnPurchaseCallback?.Invoke(productName, success); | ||||
|             Callbacks.IAP.InvokeOnPurchaseEnd(productName, success); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|  | @ -175,7 +190,7 @@ namespace Guru | |||
|         /// <param name="productName"></param> | ||||
|         private static void OnBuyStart(string productName) | ||||
|         { | ||||
|             Callbacks.IAP._onPurchaseStart?.Invoke(productName); | ||||
|             Callbacks.IAP.InvokeOnPurchaseStart(productName); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|  | @ -185,7 +200,7 @@ namespace Guru | |||
|         /// <param name="reason"></param> | ||||
|         private static void OnBuyFailed(string productName, string reason) | ||||
|         { | ||||
|             Callbacks.IAP._onPurchaseFailed?.Invoke(productName, reason); | ||||
|             Callbacks.IAP.InvokeOnPurchaseFailed(productName, reason); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|  | @ -206,7 +221,7 @@ namespace Guru | |||
|         } | ||||
|         private static void OnRestored(bool success, string msg) | ||||
|         { | ||||
|             Callbacks.IAP._onIAPRestored?.Invoke(success, msg); // 更新回复购买回调 | ||||
|             Callbacks.IAP.InvokeOnIAPRestored(success, msg); // 更新回复购买回调 | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
|  | @ -228,5 +243,93 @@ namespace Guru | |||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Subscriptions | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 订阅是否被取消 | ||||
|         /// </summary> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool IsSubscriptionCancelled(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionCancelled(productName); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 订阅是否可用 | ||||
|         /// </summary> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool IsSubscriptionAvailable(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionAvailable(productName); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 订阅是否过期 | ||||
|         /// </summary> | ||||
|         /// <param name="productName"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool IsSubscriptionExpired(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionExpired(productName); | ||||
|         } | ||||
|          | ||||
|         public static bool IsSubscriptionFreeTrail(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionFreeTrail(productName); | ||||
|         } | ||||
|          | ||||
|         public static bool IsSubscriptionAutoRenewing(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionAutoRenewing(productName); | ||||
|         } | ||||
|          | ||||
|         public static bool IsSubscriptionIntroductoryPricePeriod(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.IsSubscriptionIntroductoryPricePeriod(productName); | ||||
|         } | ||||
|          | ||||
|         public DateTime GetSubscriptionExpireDate(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionExpireDate(productName); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         public DateTime GetSubscriptionPurchaseDate(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionPurchaseDate(productName); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         public DateTime GetSubscriptionCancelDate(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionCancelDate(productName); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         public TimeSpan GetSubscriptionRemainingTime(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionRemainingTime(productName); | ||||
|         } | ||||
|          | ||||
|         public TimeSpan GetSubscriptionIntroductoryPricePeriod(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionIntroductoryPricePeriod(productName); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         public TimeSpan GetSubscriptionFreeTrialPeriod(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionFreeTrialPeriod(productName); | ||||
|         } | ||||
|          | ||||
|         public string GetSubscriptionInfoJsonString(string productName) | ||||
|         { | ||||
|             return GuruIAP.Instance.GetSubscriptionInfoJsonString(productName); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         #endregion | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,108 @@ | |||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         // UID | ||||
|         public static string UID | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(Model != null && !string.IsNullOrEmpty(Model.UserId))  | ||||
|                     return Model.UserId; | ||||
|                 return IPMConfig.IPM_UID; | ||||
|             } | ||||
|         } | ||||
|         public static string UUID => IPMConfig.IPM_UUID ?? ""; | ||||
|         public static string DeviceId => IPMConfig.IPM_DEVICE_ID ?? "";  // TODO: change it to _model member later. | ||||
|         public static string PushToken => IPMConfig.IPM_PUSH_TOKEN ?? ""; // TODO: change it to _model member later. | ||||
|         public static string AuthToken => IPMConfig.IPM_TOKEN ?? ""; // TODO: change it to _model member later. | ||||
|         public static string SupportEmail => GuruSettings.SupportEmail ?? ""; | ||||
| 
 | ||||
|         public static string StoreUrl | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 string url = ""; | ||||
| #if UNITY_EDITOR | ||||
|                 url = "https://test@com.guru.ai"; | ||||
| #elif UNITY_ANDROID | ||||
|                 url = GuruSettings?.AndroidStoreUrl ?? ""; | ||||
| #elif UNITY_IOS | ||||
|                 url = GuruSettings?.IOSStoreUrl ?? ""; | ||||
| #endif | ||||
|                 return url; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public static string PrivacyUrl => GuruSettings.PriacyUrl ?? ""; | ||||
|          | ||||
|         public static string TermsUrl => GuruSettings.TermsUrl ?? ""; | ||||
| 
 | ||||
|         public static string AppVersion => GuruAppVersion.version; | ||||
| 
 | ||||
|         public static string AppVersionCode => GuruAppVersion.code; | ||||
|          | ||||
|         public static string AppVersionString => GuruAppVersion.ToString(); | ||||
| 
 | ||||
|         public static bool IsNewUser => IPMConfig.IPM_NEWUSER; | ||||
|          | ||||
|         public static string FirebaseId => IPMConfig.FIREBASE_ID; | ||||
|         public static string IDFA => IPMConfig.ADJUST_IDFA; | ||||
|         public static string AdjustId => IPMConfig.ADJUST_ID; | ||||
|         public static string GSADID => IPMConfig.ADJUST_ADID; | ||||
|         public static string CdnHost => _appServicesConfig?.CdnHost() ?? ""; | ||||
| 
 | ||||
|         private static GuruAppVersion _appVersion; | ||||
|         private static GuruAppVersion GuruAppVersion | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if(_appVersion == null) _appVersion = GuruAppVersion.Load(); | ||||
|                 return _appVersion; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private static string _appBundleId; | ||||
|         public static string AppBundleId => _appBundleId; | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 设置购买去广告道具的标志位 | ||||
|         /// </summary> | ||||
|         /// <param name="value"></param> | ||||
|         public static void SetBuyNoAds(bool value = true) | ||||
|         { | ||||
|             Model.IsNoAds = value; | ||||
|             ADService.Instance.IsBuyNoAds = value; | ||||
|             if (value) | ||||
|             { | ||||
|                 Analytics.SetIsIapUser(true); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 所有成功的主线关卡数量 (b_level) | ||||
|         /// </summary> | ||||
|         public static int BLevel | ||||
|         { | ||||
|             get => GuruSDKModel.Instance.BLevel; | ||||
|             set => GuruSDKModel.Instance.BLevel = value; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 成功关卡总计数量 (b_play) | ||||
|         /// </summary> | ||||
|         public static int BPlay | ||||
|         { | ||||
|             get => GuruSDKModel.Instance.BPlay; | ||||
|             set => GuruSDKModel.Instance.BPlay = value; | ||||
|         } | ||||
|          | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: ab7d65b5c8774a12924f3fe72a6e4a38 | ||||
| timeCreated: 1710154459 | ||||
|  | @ -1,9 +1,9 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Firebase.RemoteConfig; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using System.Collections.Generic; | ||||
|     using Firebase.RemoteConfig; | ||||
| 
 | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
| 
 | ||||
|  | @ -30,27 +30,45 @@ namespace Guru | |||
|         { | ||||
|             return RemoteConfigManager.GetConfig<T>(key); | ||||
|         } | ||||
|         public static string GetRemoteString(string key) => RemoteConfigManager.GetString(key); | ||||
|         public static int GetRemoteInt(string key) => RemoteConfigManager.GetInt(key); | ||||
|         public static long GetRemoteLong(string key) => RemoteConfigManager.GetLong(key); | ||||
|         public static double GetRemoteDouble(string key) => RemoteConfigManager.GetDouble(key); | ||||
|         public static float GetRemoteFloat(string key) => RemoteConfigManager.GetFloat(key); | ||||
|         public static bool GetRemoteBool(string key) => RemoteConfigManager.GetBool(key); | ||||
|         public static string GetRemoteString(string key, string defaultValue = "") => RemoteConfigManager.GetString(key, defaultValue); | ||||
|         public static int GetRemoteInt(string key, int defaultValue = 0) => RemoteConfigManager.GetInt(key, defaultValue); | ||||
|         public static long GetRemoteLong(string key, long defaultValue = 0 ) => RemoteConfigManager.GetLong(key, defaultValue); | ||||
|         public static double GetRemoteDouble(string key, double defaultValue = 0) => RemoteConfigManager.GetDouble(key, defaultValue); | ||||
|         public static float GetRemoteFloat(string key, float defaultValue = 0) => RemoteConfigManager.GetFloat(key, defaultValue); | ||||
|         public static bool GetRemoteBool(string key, bool defaultValue = false) => RemoteConfigManager.GetBool(key, defaultValue); | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 注册监听某个 Key 的变化 | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <param name="onValueChanged"></param> | ||||
|         public static void RegisterOnValueChanged(string key, Action<string,string> onValueChanged) | ||||
|         { | ||||
|             RemoteConfigManager.RegisterOnValueChanged(key, onValueChanged); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 注销监听某个 Key 的变化 | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <param name="onValueChanged"></param> | ||||
|         public static void UnRegisterOnValueChanged(string key, Action<string,string> onValueChanged) | ||||
|         { | ||||
|             RemoteConfigManager.UnRegisterOnValueChanged(key, onValueChanged); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 获取所有云控配置 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static Dictionary<string, ConfigValue> GetRemoteAllValues() => RemoteConfigManager.GetAllValues(); | ||||
|          | ||||
|          | ||||
|         /// <summary> | ||||
|         ///  | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <returns></returns> | ||||
|         public static string GetRemoteStaticValue(string key) => RemoteConfigManager.GetStaticValue(key); | ||||
|          | ||||
|     } | ||||
|  |  | |||
|  | @ -0,0 +1,97 @@ | |||
| using System; | ||||
| using UnityEngine; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         #region System | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 打开页面 | ||||
|         /// </summary> | ||||
|         /// <param name="url"></param> | ||||
|         public static void OpenURL(string url) | ||||
|         { | ||||
|             GuruWebview.OpenPage(url); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 打开隐私协议页面 | ||||
|         /// </summary> | ||||
|         public static void OpenPrivacyPage() | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(PrivacyUrl)) | ||||
|             { | ||||
|                 LogE("PrivacyUrl is null");  | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             OpenURL(PrivacyUrl); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 打开服务条款页面 | ||||
|         /// </summary> | ||||
|         public static void OpenTermsPage() | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(TermsUrl)) | ||||
|             { | ||||
|                 LogE("TermsUrl is null");  | ||||
|                 return; | ||||
|             } | ||||
|             OpenURL(TermsUrl); | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
|          | ||||
|         #region Android System | ||||
| 
 | ||||
| #if UNITY_ANDROID | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 获取 AndroidSDK 的系统版本号 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public static int GetAndroidSystemVersion() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 // sdkInt 是 Android SDK 的整数版本号,例如 Android 10 对应 29。 | ||||
|                 // release 是 Android 版本的字符串表示,例如 "10"。 | ||||
|                 using (AndroidJavaClass jc = new AndroidJavaClass("android.os.Build$VERSION")) | ||||
|                 { | ||||
|                     int sdkInt = jc.GetStatic<int>("SDK_INT"); | ||||
|                     Debug.LogWarning($"[SDK] --- Android SDK Version:{sdkInt}"); | ||||
|                     return sdkInt; | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Debug.LogError(ex); | ||||
|             } | ||||
| 
 | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Clear Data Cache | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 清除数据缓存 | ||||
|         /// </summary> | ||||
|         public static void ClearData() | ||||
|         { | ||||
|             Model.ClearData(); | ||||
|             GuruIAP.Instance.ClearData(); | ||||
|             PlayerPrefs.DeleteAll(); | ||||
|             PlayerPrefs.Save(); | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 80d35b4c8f0f4d23a7228fddfb06dd03 | ||||
| timeCreated: 1713238972 | ||||
|  | @ -0,0 +1,29 @@ | |||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
|      | ||||
|     public partial class GuruSDK | ||||
|     { | ||||
|         private ThreadHandler _threadHandler; | ||||
| 
 | ||||
|         private void InitThreadHandler() | ||||
|         { | ||||
|             _threadHandler = new ThreadHandler(); | ||||
|             RegisterUpdater(_threadHandler); | ||||
|         } | ||||
| 
 | ||||
|         private void AddActionToMainThread(Action action) | ||||
|         { | ||||
|             _threadHandler?.AddAction(action); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public static void RunOnMainThread(Action action) | ||||
|         { | ||||
|            Instance.AddActionToMainThread(action); | ||||
|         } | ||||
|          | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 8a0605c6b4b64b37a0036b34cbadb9ca | ||||
| timeCreated: 1712622534 | ||||
|  | @ -4,12 +4,17 @@ namespace Guru | |||
|     using System; | ||||
|     using System.Collections; | ||||
|     using System.Collections.Generic; | ||||
|     using System.IO; | ||||
|     using Debug = UnityEngine.Debug; | ||||
|     using Guru.Network; | ||||
|     using System.Linq; | ||||
|      | ||||
|     public partial class GuruSDK: MonoBehaviour | ||||
|     { | ||||
|         public const string Version = "1.0.6"; | ||||
|         public const string Tag = "[Guru]"; | ||||
|         // SDK_VERSION | ||||
|         public const string Version = "1.1.0";  | ||||
|          | ||||
|         // Const | ||||
|         private const string Tag = "[Guru]"; | ||||
|         public const string ServicesConfigKey = "guru_services"; | ||||
|          | ||||
|         private static GuruSDK _instance; | ||||
|  | @ -30,16 +35,11 @@ namespace Guru | |||
|         } | ||||
| 
 | ||||
|         private GuruSDKInitConfig _initConfig; | ||||
|         private Action<bool> _onCompleteCallback; | ||||
| 
 | ||||
|         private static GuruSDKModel _model; | ||||
| 
 | ||||
|         internal static GuruSDKInitConfig InitConfig => Instance._initConfig; | ||||
|         internal static GuruSDKModel Model => GuruSDKModel.Instance; | ||||
|         private static GuruSDKInitConfig InitConfig => Instance._initConfig; | ||||
|         private static GuruSDKModel Model => GuruSDKModel.Instance; | ||||
|         private static GuruServicesConfig _appServicesConfig; | ||||
| 
 | ||||
|         private static GuruSettings _guruSettings; | ||||
| 
 | ||||
|         private static GuruSettings GuruSettings | ||||
|         { | ||||
|             get | ||||
|  | @ -48,8 +48,8 @@ namespace Guru | |||
|                 return _guruSettings; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|         private static DateTime _initTime; | ||||
|         private static bool _isDebugEnabled = false; | ||||
|         /// <summary> | ||||
|         /// Debug Mode | ||||
|  | @ -69,7 +69,21 @@ namespace Guru | |||
|         /// 初始化成功标志位 | ||||
|         /// </summary> | ||||
|         public static bool IsInitialSuccess { get; private set; } = false; | ||||
|         /// <summary> | ||||
|         /// Firebase 就绪标志位 | ||||
|         /// </summary> | ||||
|         public static bool IsFirebaseReady { get; private set; } = false; | ||||
|         /// <summary> | ||||
|         /// 服务就绪标志位 | ||||
|         /// </summary> | ||||
|         public static bool IsServiceReady { get; private set; } = false; | ||||
| 
 | ||||
|         private Firebase.Auth.FirebaseUser _firebaseUser; | ||||
|         [Obsolete("获取 FirebaseUser 的属性接口即将废弃,请改用 <GuruSDK.Callbacks.SDK.OnFirebaseUserAuthResult += OnMyGetFirebaseUserCallback> 来异步获取该属性")] | ||||
|         public static Firebase.Auth.FirebaseUser FirebaseUser => Instance?._firebaseUser ?? null;  | ||||
|          | ||||
|          | ||||
|          | ||||
|         #region 初始化 | ||||
|          | ||||
|         private static GuruSDK CreateInstance() | ||||
|  | @ -79,34 +93,23 @@ namespace Guru | |||
|             _instance = go.AddComponent<GuruSDK>(); | ||||
|             return _instance; | ||||
|         } | ||||
| 
 | ||||
|         public static GuruSDKInitConfig BuildConfig( | ||||
|             bool useCustomConsent = false,  | ||||
|             bool autoLoadAds = true,  | ||||
|             bool iapEnabled = true,  | ||||
|             bool autoRecordFinishedLevels = true,  | ||||
|             bool debugMode = false, | ||||
|             Dictionary<string, object> defaultRemoteData = null, | ||||
|             byte[] googleKeys = null, | ||||
|             byte[] appleRootCerts = null) | ||||
|         { | ||||
|             var config = GuruSDKInitConfig.Build(useCustomConsent, autoLoadAds, iapEnabled,  | ||||
|                 autoRecordFinishedLevels, debugMode, defaultRemoteData, googleKeys, appleRootCerts); | ||||
|             return config; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         // TODO : 下个版本需要将 整个 GuruSDK 做功能性的拆分 | ||||
|          | ||||
|         public static void Init(Action<bool> onComplete) | ||||
|         { | ||||
|             Init(GuruSDKInitConfig.Build(), onComplete); | ||||
|             Init(GuruSDKInitConfig.Builder().Build(), onComplete); | ||||
|         } | ||||
|          | ||||
|         public static void Init(GuruSDKInitConfig config, Action<bool> onComplete) | ||||
|         { | ||||
|             LogI($"---- Guru SDK init ----\n{config.ToString()}"); | ||||
|             _initTime = DateTime.UtcNow; | ||||
|             // ----- First Open Time ----- | ||||
|             // SetFirstOpenTime(GetFirstOpenTime());  // FirstOpenTime  | ||||
|             LogI($"#1 ---- Guru SDK [{Version}] ----\n{config}"); | ||||
|             Instance.StartWithConfig(config, onComplete); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动SDK | ||||
|         /// </summary> | ||||
|  | @ -114,68 +117,57 @@ namespace Guru | |||
|         /// <param name="onComplete"></param> | ||||
|         private void StartWithConfig(GuruSDKInitConfig config, Action<bool> onComplete) | ||||
|         { | ||||
|             Model.PropBLevel.OnValueChanged += OnBLevelChanged; | ||||
|             Model.PropBPlay.OnValueChanged += OnBPlayChanged; | ||||
|              | ||||
|             IsInitialSuccess = false; | ||||
|             _initConfig = config; | ||||
|             _onCompleteCallback = onComplete; | ||||
|             _isDebugEnabled = config.DebugMode; | ||||
| 
 | ||||
|             //--- 之后的逻辑放在 Start 方法内 --- | ||||
|             if (config.EnableDebugLogEvent) Analytics.EnableDebugAnalytics = true; // 允许 Debug 模式下打点 | ||||
|             if (!config.AutoNotificationPermission) FirebaseUtil.SetAutoFetchFcmToken(false); // 不允许自动启动获取 FCM Token | ||||
|              | ||||
|             InitUpdaters(); // Updaters | ||||
|             InitThreadHandler(); // 初始化线程处理器 | ||||
|             InitServices(); // 初始化所有的服务 | ||||
|             InitNetworkMonitor(); // 网络状态 | ||||
|              | ||||
|             onComplete?.Invoke(true); | ||||
|         } | ||||
| 
 | ||||
|         void Start() | ||||
|         private void InitServices() | ||||
|         { | ||||
|             //---- Init All tools ---- | ||||
| 
 | ||||
|             //---------- Start Analytics ------------ | ||||
|             LogI($"#1.1 ---- Init Analytics ----"); | ||||
|             // 初始化打点类 | ||||
|             Analytics.Init();  | ||||
|             // 从 Model 中注入打点属性初始值 | ||||
|             Analytics.SetFirstOpenTime(IPMConfig.FIRST_OPEN_TIME); | ||||
|             Analytics.SetIsIapUser(Model.IsIapUser); | ||||
|             // Analytics.SetBLevel(Model.BLevel); | ||||
|             // Analytics.SetBPlay(Model.BPlay); | ||||
|              | ||||
|             //---- Start All tools ---- | ||||
|             LogI($"#2 --- InitFirebase ---"); | ||||
|             //---------- Start Firebase ------------ | ||||
|             FirebaseUtil.InitFirebase(OnFirebaseReady); | ||||
|             // FirebaseUtil.OnFetchRemoteSuccess+= OnFetchRemoteCallback; | ||||
|             StartFirebaseService(); | ||||
|             LogI($"#2.1 --- InitFacebook ---"); | ||||
|             //---------- Start Facebook ------------ | ||||
|             FBService.Instance.StartService(); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 开始各种组件初始化 | ||||
|         /// </summary> | ||||
|         private void OnFirebaseReady() | ||||
|         { | ||||
|             FBService.Instance.StartService(Analytics.OnFBInitComplete); | ||||
|              | ||||
|             IsInitialSuccess = true; | ||||
|     | ||||
|             if (!InitConfig.UseCustomConsent) | ||||
|             { | ||||
|                 // LogI($"--- #3 Start Consent Flow ---"); | ||||
|                 StartConsentFlow(); | ||||
|             } | ||||
|              | ||||
|             if(!string.IsNullOrEmpty(IPMConfig.IPM_UID)) SetUID(IPMConfig.IPM_UID); | ||||
| 
 | ||||
|             // 开始Remote Manager初始化  | ||||
|             RemoteConfigManager.Init(BuildDefaultRemoteData(_initConfig.DefaultRemoteData)); | ||||
|             RemoteConfigManager.OnFetchCompleted += OnFetchRemoteCallback; | ||||
|              | ||||
|             // 根据上次的云控配置来初始化参数 | ||||
|             SetupServicesConfig(); | ||||
|              | ||||
|             _onCompleteCallback?.Invoke(true); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 注入云控参数基础数据 | ||||
|         /// </summary> | ||||
|         /// <param name="dict"></param> | ||||
|         /// <returns></returns> | ||||
|         private Dictionary<string, object> BuildDefaultRemoteData(Dictionary<string, object> dict) | ||||
|         private string LoadDefaultGuruServiceJson() | ||||
|         { | ||||
|             if (dict == null) dict = new Dictionary<string, object>(3); | ||||
|              | ||||
|             // 注入默认的 Services 配置值 | ||||
|             string json = Model.LoadDefaltServicesConfigJson();  | ||||
|             if (!string.IsNullOrEmpty(json)) dict[ServicesConfigKey] = json; | ||||
|         | ||||
|             return dict; | ||||
|             // 加载本地 Services 配置值 | ||||
|             var txtAsset = Resources.Load<TextAsset>(ServicesConfigKey); | ||||
|             if (txtAsset != null) | ||||
|             { | ||||
|                 return txtAsset.text; | ||||
|             } | ||||
|             return ""; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|  | @ -184,163 +176,303 @@ namespace Guru | |||
|         /// <param name="success"></param> | ||||
|         private void OnFetchRemoteCallback(bool success) | ||||
|         { | ||||
|             LogI($"--- Remote fetch complete: {success} ---"); | ||||
|             LogI($"#6 --- Remote fetch complete: {success} ---"); | ||||
|             ABTestManager.Init(); // 启动AB测试解析器 | ||||
|             Callbacks.Remote._onRemoteFetchComplete?.Invoke(success); | ||||
|             Callbacks.Remote.InvokeOnRemoteFetchComplete(success); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         private void Update() | ||||
|         { | ||||
|             UpdateAllUpdates(); // 驱动所有的更新器 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region App Remote Update | ||||
| 
 | ||||
|         private void SetupServicesConfig() | ||||
|         /// <summary> | ||||
|         /// Apply Cloud guru-service configs for sdk assets | ||||
|         /// </summary> | ||||
|         private void InitAllGuruServices() | ||||
|         { | ||||
|             bool useKeywords = false; | ||||
|             bool useIAP = true; | ||||
|             // -------- Init Analytics --------- | ||||
|             SetSDKEventPriority(); | ||||
|             // -------- Init Notification ----------- | ||||
|             InitNotiPermission(); | ||||
|              | ||||
|             bool useKeywords = false; | ||||
|             bool useIAP = _initConfig.IAPEnabled; | ||||
|             bool appleReview = false; | ||||
|             // bool enableAnaErrorLog = false; | ||||
|              | ||||
|             //----------- Set GuruServices ---------------- | ||||
|             var services = GetRemoteServicesConfig(); | ||||
|             if (services != null) | ||||
|             { | ||||
|                 _appServicesConfig = services; | ||||
|                 useKeywords = _appServicesConfig.IsKeywordsEnabled(); | ||||
|                 useIAP = _appServicesConfig.IsIAPEnabled(); | ||||
|                 useKeywords = _appServicesConfig.KeywordsEnabled(); | ||||
|                 // 使用初始化配置中的 IAPEnable来联合限定, 如果本地写死关闭则不走云控开启 | ||||
|                 useIAP = _initConfig.IAPEnabled && _appServicesConfig.IsIAPEnabled();  | ||||
|                 // enableAnaErrorLog = _appServicesConfig.EnableAnaErrorLog(); | ||||
|                  | ||||
|            | ||||
|                 if (null != _appServicesConfig.adjust_settings && null != GuruSettings) | ||||
|                 Try(() => | ||||
|                 { | ||||
|                     // 更新 Adjust Tokens | ||||
|                     GuruSettings.UpdateAdjustTokens(_appServicesConfig.adjust_settings.AndroidToken | ||||
|                         , _appServicesConfig.adjust_settings.iOSToken); | ||||
|                     // 更新 Adjust Events | ||||
|                     GuruSettings.UpdateAdjustEvents(_appServicesConfig.adjust_settings.events); | ||||
|                 } | ||||
|                  | ||||
|                 if (null != _appServicesConfig.app_settings) | ||||
|                 { | ||||
|                     if (_appServicesConfig.app_settings.tch020_val > 0) | ||||
|                     { | ||||
|                         Analytics.EnableTch02Event = true; | ||||
|                         Analytics.SetTch02TargetValue(_appServicesConfig.app_settings.tch020_val); | ||||
|                     } | ||||
|                       | ||||
|                     // 设置获取设备 UUID 的方法 | ||||
|                     if (_appServicesConfig.app_settings.using_uuid) | ||||
|                     { | ||||
|                         IPMConfig.UsingUUID = true; // 开始使用 UUID 作为 DeviceID 标识 | ||||
|                     } | ||||
|                     LogI($"#4.1 --- Start apply services ---"); | ||||
|                     //---------------------------------------------------------------- | ||||
| 
 | ||||
|                     if (null !=  GuruSettings) | ||||
|                     // 自打点设置错误上报 | ||||
|                     // if(enableAnaErrorLog) GuruAnalytics.EnableErrorLog = true; | ||||
|                      | ||||
|                     // adjust 事件设置 | ||||
|                     if (null != _appServicesConfig.adjust_settings && null != GuruSettings) | ||||
|                     { | ||||
|                         // 更新和升级 GuruSettings 对应的值 | ||||
|                         GuruSettings.UpdateAppSettings( | ||||
|                             _appServicesConfig.app_settings.bundle_id, | ||||
|                             _appServicesConfig.fb_settings?.fb_app_id ?? "", | ||||
|                             _appServicesConfig.app_settings.support_email, | ||||
|                             _appServicesConfig.app_settings.privacy_url, | ||||
|                             _appServicesConfig.app_settings.terms_url, | ||||
|                             _appServicesConfig.app_settings.android_store, | ||||
|                             _appServicesConfig.app_settings.ios_store); | ||||
|                         // 更新 Adjust Tokens | ||||
|                         GuruSettings.UpdateAdjustTokens( | ||||
|                             _appServicesConfig.adjust_settings.AndroidToken(), | ||||
|                             _appServicesConfig.adjust_settings.iOSToken()); | ||||
|                         // 更新 Adjust Events | ||||
|                         GuruSettings.UpdateAdjustEvents(_appServicesConfig.adjust_settings.events); | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                     LogI($"#4.2 --- Start GuruSettings ---"); | ||||
|                     // GuruSettings 设置 | ||||
|                     if (null != _appServicesConfig.app_settings) | ||||
|                     { | ||||
|                         if (_appServicesConfig.Tch02Value() > 0) | ||||
|                         { | ||||
|                             Analytics.EnableTch02Event = true; | ||||
|                             Analytics.SetTch02TargetValue(_appServicesConfig.Tch02Value()); | ||||
|                         } | ||||
|                       | ||||
|                         // 设置获取设备 UUID 的方法 | ||||
|                         if (_appServicesConfig.UseUUID()) | ||||
|                         { | ||||
|                             IPMConfig.UsingUUID = true; // 开始使用 UUID 作为 DeviceID 标识 | ||||
|                         } | ||||
| 
 | ||||
| #if UNITY_IOS  | ||||
|                         // 苹果审核标志位 | ||||
|                         appleReview = _appServicesConfig.IsAppReview(); | ||||
| #endif | ||||
|                      | ||||
|                         if (null !=  GuruSettings) | ||||
|                         { | ||||
|                             // 更新和升级 GuruSettings 对应的值 | ||||
|                             GuruSettings.UpdateAppSettings( | ||||
|                                 _appServicesConfig.app_settings.bundle_id, | ||||
|                                 _appServicesConfig.fb_settings?.fb_app_id ?? "", | ||||
|                                 _appServicesConfig.app_settings.support_email, | ||||
|                                 _appServicesConfig.app_settings.privacy_url, | ||||
|                                 _appServicesConfig.app_settings.terms_url, | ||||
|                                 _appServicesConfig.app_settings.android_store, | ||||
|                                 _appServicesConfig.app_settings.ios_store,  | ||||
|                                 _appServicesConfig.parameters?.using_uuid ?? false, | ||||
|                                 _appServicesConfig.parameters?.cdn_host ?? ""); | ||||
|                              | ||||
|                             _appBundleId = _appServicesConfig.app_settings.bundle_id; // 配置预设的 BundleId | ||||
|                         } | ||||
|                     } | ||||
|                     //--------------------------------- | ||||
|                 }, ex => | ||||
|                 { | ||||
|                     Debug.LogError($"--- ERROR on apply services: {ex.Message}"); | ||||
|                 }); | ||||
|            | ||||
|                  | ||||
|             } | ||||
|              | ||||
|             //----------- Set IAP ---------------- | ||||
|             if (useIAP) | ||||
|             { | ||||
|                 InitIAP(_initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP | ||||
|                 // InitIAP(_initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP | ||||
|                 Try(() => | ||||
|                 { | ||||
|                     LogI($"#4.3 --- Start IAP ---"); | ||||
|                     if (_initConfig.GoogleKeys == null || _initConfig.AppleRootCerts == null) | ||||
|                     { | ||||
|                         LogEx("[IAP] GoogleKeys is null when using IAPService! Integration failed. App will Exit"); | ||||
|                     } | ||||
|                      | ||||
|                     InitIAP(UID, _initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP | ||||
|                 }, ex => | ||||
|                 { | ||||
|                     Debug.LogError($"--- ERROR on useIAP: {ex.Message}"); | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             //----------- Set Keywords ---------------- | ||||
|             if (useKeywords) | ||||
|             { | ||||
|                 KeywordsManager.Install(Model.IsIAPUser, Model.SuccessLevelCount); // 启动Keyword管理器 | ||||
|                 // KeywordsManager.Install(Model.IsIAPUser, Model.SuccessLevelId); // 启动Keyword管理器 | ||||
|                 Try(() => | ||||
|                 { | ||||
|                     LogI($"#4.4 --- Start Keywords ---"); | ||||
|                     KeywordsManager.Install(Model.IsIapUser, Model.BLevel); // 启动Keyword管理器 | ||||
|                 }, ex => | ||||
|                 { | ||||
|                     Debug.LogError($"--- ERROR on Keywords: {ex.Message}"); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| #if UNITY_IOS | ||||
|             if (appleReview) | ||||
|             { | ||||
|                 // StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程 | ||||
|                 Try(() => | ||||
|                 { | ||||
|                     LogI($"#4.5 ---  StartAppleReviewFlow ---"); | ||||
|                     StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程 | ||||
|                 }, ex => | ||||
|                 { | ||||
|                     Debug.LogError($"--- ERROR on StartAppleReviewFlow: {ex.Message}"); | ||||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
| #endif | ||||
|             //----------- Set Consent ---------------- | ||||
|             if (!InitConfig.UseCustomConsent && !appleReview) | ||||
|             { | ||||
|                 LogI($"#4.6 --- Start Consent Flow ---"); | ||||
|                 Try(StartConsentFlow, ex => | ||||
|                 { | ||||
|                     Debug.LogError($"--- ERROR on StartConsentFlow: {ex.Message}"); | ||||
|                 }); | ||||
|             } | ||||
|              | ||||
| #if UNITY_ANDROID | ||||
|             LogI($"#5.1 --- Android StartAndroidDebug Cmd lines---"); | ||||
|             // Android 命令行调试 | ||||
|             StartAndroidDebugCmds();            | ||||
| #endif | ||||
|              | ||||
|             IsServiceReady = true; | ||||
|              | ||||
|             // 中台服务初始化结束 | ||||
|             Callbacks.SDK.InvokeOnGuruServiceReady(); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// Get the guru-service cloud config value; | ||||
|         /// User can fetch the cloud guru-service config by using Custom Service Key | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         private GuruServicesConfig GetRemoteServicesConfig() | ||||
|         { | ||||
|             var json = GetRemoteString(ServicesConfigKey); | ||||
| 
 | ||||
|             string defaultJson = GetRemoteString(ServicesConfigKey); | ||||
|              | ||||
|             bool useCustomKey = false; | ||||
|             string key = ServicesConfigKey; | ||||
|             if (!string.IsNullOrEmpty(_initConfig.CustomServiceKey)) | ||||
|             { | ||||
|                 key = _initConfig.CustomServiceKey; | ||||
|                 useCustomKey = true; | ||||
|             } | ||||
|             var json = GetRemoteString(key); // Cloud cached data | ||||
| 
 | ||||
|             if (string.IsNullOrEmpty(json) && useCustomKey && !string.IsNullOrEmpty(defaultJson)) | ||||
|             { | ||||
|                 // No remote data fetched from cloud, should use default values | ||||
|                 json = defaultJson; | ||||
|                 Debug.Log($"{Tag} --- No remote data found with: {key}  -> Using default key {ServicesConfigKey} and local data!!!"); | ||||
|             } | ||||
| 
 | ||||
|             if (!string.IsNullOrEmpty(json)) | ||||
|             { | ||||
|                 return JsonParser.ToObject<GuruServicesConfig>(json); | ||||
|             } | ||||
|              | ||||
|             return null; | ||||
|         } | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 数据 | ||||
| 
 | ||||
|         private void OnBLevelChanged(int blevel) | ||||
|         private void Try(Action method, Action<Exception> onException = null, Action onFinal = null) | ||||
|         { | ||||
|             SetUserBLevel(blevel); | ||||
|         } | ||||
|             if (method == null) return; | ||||
| 
 | ||||
|         private void OnBPlayChanged(int bplay) | ||||
|         { | ||||
|             SetUserBPlay(bplay); | ||||
|         } | ||||
| 
 | ||||
|         public static string UID => _model?.UserId ?? ""; | ||||
| 
 | ||||
|         public static string SupportEmail => GuruSettings.SupportEmail ?? ""; | ||||
| 
 | ||||
|         public static string StoreUrl | ||||
|         { | ||||
|             get | ||||
|             try | ||||
|             { | ||||
|                 string url = ""; | ||||
| #if UNITY_EDITOR | ||||
|                 url = "https://test@com.guru.ai"; | ||||
| #elif UNITY_ANDROID | ||||
|                 url = GuruSettings?.AndroidStoreUrl ?? ""; | ||||
| #elif UNITY_IOS | ||||
|                 url = GuruSettings?.IOSStoreUrl ?? ""; | ||||
| #endif | ||||
|                 return url; | ||||
|                 method.Invoke(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 LogEx(ex); | ||||
|                 // ignored | ||||
|                 onException?.Invoke(ex); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 // Finally | ||||
|                 onFinal?.Invoke(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
|          | ||||
|         #region Misc | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 打开页面 | ||||
|         /// </summary> | ||||
|         /// <param name="url"></param> | ||||
|         public static void OpenURL(string url) | ||||
|         { | ||||
|             GuruWebview.OpenPage(url); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Logging | ||||
|         #region Apple 审核流程逻辑 | ||||
| 
 | ||||
| #if UNITY_IOS | ||||
|         private void StartAppleReviewFlow() | ||||
|         { | ||||
|             CheckAttStatus(); | ||||
|         } | ||||
| #endif | ||||
|         #endregion | ||||
|          | ||||
|         internal static void LogI(object message) | ||||
|         #region Logging | ||||
| 
 | ||||
|         private static void LogI(object message) | ||||
|         { | ||||
|             Debug.Log($"{Tag} {message}"); | ||||
|         } | ||||
|          | ||||
|         internal static void LogW(object message) | ||||
| 
 | ||||
|         private static void LogW(object message) | ||||
|         { | ||||
|             Debug.LogWarning($"{Tag} {message}"); | ||||
|         } | ||||
|          | ||||
|         internal static void LogE(object message) | ||||
| 
 | ||||
|         private static void LogE(object message) | ||||
|         { | ||||
|             Debug.LogError($"{Tag} {message}"); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         internal static void LogException(string message) | ||||
| 
 | ||||
|         private static void LogEx(string message) | ||||
|         { | ||||
|             LogException( new Exception($"{Tag} {message}")); | ||||
|             LogEx( new Exception($"{Tag} {message}")); | ||||
|         } | ||||
|          | ||||
|         internal static void LogException(Exception e) | ||||
| 
 | ||||
|         private static void LogEx(Exception e) | ||||
|         { | ||||
|             Debug.LogException(e); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 上报崩溃信息 | ||||
|         /// </summary> | ||||
|         /// <param name="message"></param> | ||||
|         public static void Report(string message) | ||||
|         { | ||||
|             Analytics.LogCrashlytics(message, false); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 上报异常 | ||||
|         /// </summary> | ||||
|         /// <param name="message"></param> | ||||
|         public static void ReportException(string message) | ||||
|         { | ||||
|             Analytics.LogCrashlytics(message); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 上报异常 Exception | ||||
|         /// </summary> | ||||
|         /// <param name="ex"></param> | ||||
|         public static void ReportException(Exception ex) | ||||
|         { | ||||
|             Analytics.LogCrashlytics(ex); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 生命周期 | ||||
|  | @ -352,7 +484,7 @@ namespace Guru | |||
|         private void OnAppPauseHandler(bool paused) | ||||
|         { | ||||
|             if(paused) Model.Save(true); // 强制保存数据 | ||||
|             Callbacks.App._onAppPaused?.Invoke(paused); | ||||
|             Callbacks.App.InvokeOnAppPaused(paused); | ||||
|         } | ||||
|          | ||||
|         private void OnApplicationPause(bool paused) | ||||
|  | @ -368,7 +500,7 @@ namespace Guru | |||
|         private void OnApplicationQuit() | ||||
|         { | ||||
|             Model.Save(true); | ||||
|             Callbacks.App._onAppQuit?.Invoke(); | ||||
|             Callbacks.App.InvokeOnAppQuit(); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|  | @ -396,9 +528,9 @@ namespace Guru | |||
|         /// </summary> | ||||
|         /// <param name="seconds"></param> | ||||
|         /// <param name="callback"></param> | ||||
|         public static void Delay(float seconds, Action callback) | ||||
|         public static Coroutine Delay(float seconds, Action callback) | ||||
|         { | ||||
|             DoCoroutine(Instance.OnDelayCall(seconds, callback)); | ||||
|             return DoCoroutine(Instance.OnDelayCall(seconds, callback)); | ||||
|         } | ||||
| 
 | ||||
|         private IEnumerator OnDelayCall(float delay, Action callback) | ||||
|  | @ -415,6 +547,297 @@ namespace Guru | |||
|         } | ||||
|          | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 帧更新 Updater | ||||
| 
 | ||||
|          | ||||
|         private List<IUpdater> _updaterRunningList; | ||||
|         private List<IUpdater> _updaterRemoveList; | ||||
| 
 | ||||
|         private void InitUpdaters() | ||||
|         { | ||||
|             _updaterRunningList = new List<IUpdater>(20); | ||||
|             _updaterRemoveList = new List<IUpdater>(20); | ||||
|         } | ||||
| 
 | ||||
|         private void UpdateAllUpdates() | ||||
|         { | ||||
|             int i = 0; | ||||
|             // ---- Updater Trigger ---- | ||||
|             if (_updaterRunningList.Count > 0) | ||||
|             { | ||||
|                 i = 0; | ||||
|                 while (i < _updaterRunningList.Count) | ||||
|                 { | ||||
|                     var updater = _updaterRunningList[i]; | ||||
|                     if (updater != null)  | ||||
|                     { | ||||
|                         if (updater.State == UpdaterState.Running) | ||||
|                         { | ||||
|                             updater.OnUpdate(); | ||||
|                         } | ||||
|                         else if(updater.State == UpdaterState.Kill) | ||||
|                         { | ||||
|                             _updaterRemoveList.Add(updater); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         _updaterRunningList.RemoveAt(i); | ||||
|                         i--; | ||||
|                     } | ||||
|                     i++; | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             if (_updaterRemoveList.Count > 0) | ||||
|             { | ||||
|                 i = 0; | ||||
|                 while (i < _updaterRemoveList.Count) | ||||
|                 { | ||||
|                     RemoveUpdater(_updaterRemoveList[i]); | ||||
|                     i++; | ||||
|                 } | ||||
|                 _updaterRemoveList.Clear(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 注册帧更新器 | ||||
|         /// </summary> | ||||
|         /// <param name="updater"></param> | ||||
|         public static void RegisterUpdater(IUpdater updater) | ||||
|         { | ||||
|             Instance.AddUpdater(updater); | ||||
|             updater.Start(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private void AddUpdater(IUpdater updater) | ||||
|         { | ||||
|             if (_updaterRunningList == null) _updaterRunningList = new List<IUpdater>(20); | ||||
|             _updaterRunningList.Add(updater); | ||||
|         } | ||||
| 
 | ||||
|         private void RemoveUpdater(IUpdater updater) | ||||
|         { | ||||
|             if (_updaterRunningList != null && updater != null) | ||||
|             { | ||||
|                 _updaterRunningList.Remove(updater); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 中台推送管理 | ||||
| 
 | ||||
|         private static int _messageRetry = 0; | ||||
|         public static void SetPushNotificationEnabled(bool enabled) | ||||
|         { | ||||
|             DeviceInfoUploadRequest request = (DeviceInfoUploadRequest) new DeviceInfoUploadRequest() | ||||
|                 .SetRetryTimes(1) | ||||
|                 .SetSuccessCallBack(() => | ||||
|                 { | ||||
|                     _messageRetry = 0; | ||||
|                     Debug.Log($"[SDK] --- Set Push Enabled: {enabled} success"); | ||||
|                 }) | ||||
|                 .SetFailCallBack(() => | ||||
|                 { | ||||
|                     double retryDelay = Math.Pow(2, _messageRetry); | ||||
|                     _messageRetry++; | ||||
|                     CoroutineHelper.Instance.StartDelayed((float)retryDelay, ()=> SetPushNotificationEnabled(enabled)); | ||||
|                 }); | ||||
| 
 | ||||
|             if (request == null) return; | ||||
|              | ||||
|             request.SetPushEnabled(enabled); | ||||
|             request.Send(); | ||||
|         } | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Deeplink | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 添加回调链接 | ||||
|         /// </summary> | ||||
|         /// <param name="deeplink"></param> | ||||
|         private void OnDeeplinkCallback(string deeplink) | ||||
|         { | ||||
|            Callbacks.SDK.InvokeDeeplinkCallback(deeplink); // 尝试调用回调 | ||||
|         } | ||||
|          | ||||
|         #endregion | ||||
| 
 | ||||
|         #region 网络状态上报 | ||||
| 
 | ||||
|         private NetworkStatusMonitor _networkStatusMonitor; | ||||
|         private string _lastNetworkStatus; | ||||
|          | ||||
|         private void InitNetworkMonitor() | ||||
|         { | ||||
|             _networkStatusMonitor = new NetworkStatusMonitor(Analytics.SetNetworkStatus,  | ||||
|                 lastStatus => | ||||
|             { | ||||
|                 LogEvent("guru_offline", new Dictionary<string, dynamic>() | ||||
|                 { | ||||
|                     ["from"] = lastStatus | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 获取当前的网络状态 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         private string GetNetworkStatus() => _networkStatusMonitor.GetNetworkStatus(); | ||||
| 
 | ||||
|          | ||||
|         #endregion | ||||
| 
 | ||||
|         #region Firebase 服务 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 启动 Firebase 服务 | ||||
|         /// </summary> | ||||
|         private void StartFirebaseService() | ||||
|         { | ||||
|             FirebaseUtil.Init(OnFirebaseDepsCheckResult,  | ||||
|                 OnGetFirebaseId,  | ||||
|                 OnGetGuruUID,  | ||||
|                 OnFirebaseLoginResult); // 确保所有的逻辑提前被调用到 | ||||
|         } | ||||
| 
 | ||||
|         private void OnGetGuruUID(bool success) | ||||
|         { | ||||
|             if (success) | ||||
|             { | ||||
|                 Model.UserId = IPMConfig.IPM_UID; | ||||
|                 if (GuruIAP.Instance != null) | ||||
|                 { | ||||
|                     GuruIAP.Instance.SetUID(UID); | ||||
|                     GuruIAP.Instance.SetUUID(UUID); | ||||
|                 } | ||||
|                  | ||||
|                 // 自打点设置用户 ID | ||||
|                 Analytics.SetUid(UID); | ||||
|                 // Crashlytics 设置 uid | ||||
|                 CrashlyticsAgent.SetUserId(UID); | ||||
|                 // 上报所有的事件 | ||||
|                 Analytics.ShouldFlushGuruEvents(); | ||||
|             } | ||||
|              | ||||
|             Callbacks.SDK.InvokeOnGuruUserAuthResult(success); | ||||
|         } | ||||
|          | ||||
|         private void OnGetFirebaseId(string fid) | ||||
|         { | ||||
|             // 初始化 Adjust 服务 | ||||
|             InitAdjustService(fid, InitConfig.OnAdjustDeeplinkCallback); | ||||
|             // 初始化自打点 | ||||
|             Analytics.InitGuruAnalyticService(fid); | ||||
|              | ||||
|             //---------- Event SDK Info ------------ | ||||
|             LogI($"#6.0 --- SDK is ready, report Info ---"); | ||||
|             LogSDKInfo((DateTime.UtcNow - _initTime).TotalSeconds); | ||||
|         } | ||||
|          | ||||
|         // TODO: 需要之后用宏隔离应用和实现 | ||||
|         // Auth 登录认证 | ||||
|         private void OnFirebaseLoginResult(bool success, Firebase.Auth.FirebaseUser firebaseUser) | ||||
|         { | ||||
|             _firebaseUser = firebaseUser; | ||||
|             Callbacks.SDK.InvokeOnFirebaseAuthResult(success, firebaseUser); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 开始各种组件初始化 | ||||
|         /// </summary> | ||||
|         private void OnFirebaseDepsCheckResult(bool success) | ||||
|         { | ||||
|             LogI($"#3 --- On FirebaseDeps: {success} ---"); | ||||
|             IsFirebaseReady = success; | ||||
|             Callbacks.SDK.InvokeOnFirebaseReady(success); | ||||
| 
 | ||||
|             Analytics.OnFirebaseInitCompleted(); | ||||
| 
 | ||||
|             LogI($"#3.5 --- Call InitRemoteConfig ---"); | ||||
|             // 开始Remote Manager初始化  | ||||
|              | ||||
|             var defaultGuruServiceJson = LoadDefaultGuruServiceJson(); | ||||
| 
 | ||||
|             var dict = _initConfig.DefaultRemoteData.ToDictionary( | ||||
|                 entry => entry.Key, | ||||
|                 entry => entry.Value); | ||||
|              | ||||
|             if (!string.IsNullOrEmpty(defaultGuruServiceJson)) | ||||
|             { | ||||
|                 dict[ServicesConfigKey] = defaultGuruServiceJson; | ||||
|             } | ||||
|              | ||||
|             RemoteConfigManager.Init(dict); | ||||
|             RemoteConfigManager.OnFetchCompleted += OnFetchRemoteCallback; | ||||
| 
 | ||||
|             LogI($"#4 --- Apply remote services config ---"); | ||||
|             // 根据缓存的云控配置来初始化参数 | ||||
|             InitAllGuruServices(); | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|         		 | ||||
|         #region Adjust服务 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 启动 Adjust 服务 | ||||
|         /// </summary> | ||||
|         private static void InitAdjustService(string firebaseId, Action<string> onDeeplinkCallback = null) | ||||
|         { | ||||
|             // 启动 AdjustService | ||||
|             string appToken = GuruSettings.Instance.AdjustSetting?.GetAppToken() ?? ""; | ||||
|             string fbAppId = GuruSettings.Instance.IPMSetting.FacebookAppId; | ||||
| 
 | ||||
|             // if (!string.IsNullOrEmpty(IPMConfig.ADJUST_ID)) | ||||
|             //     Analytics.SetAdjustId(IPMConfig.ADJUST_ID); // 二次启动后,若有值则立即上报属性 | ||||
|              | ||||
|             AdjustService.Instance.Start(appToken, fbAppId, firebaseId, DeviceId, | ||||
|                 OnAdjustInitComplete, onDeeplinkCallback ,OnGetGoogleAdId ); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adjust 初始化结束 | ||||
|         /// </summary> | ||||
|         /// <param name="adjustId"></param> | ||||
|         /// <param name="idfv"></param> | ||||
|         /// <param name="idfa"></param> | ||||
|         private static void OnAdjustInitComplete(string adjustId, string idfv, string idfa) | ||||
|         { | ||||
|             Debug.Log($"{Tag} --- OnAdjustInitComplete:  adjustId:{adjustId}  idfv:{idfv}  idfa:{idfa}"); | ||||
|              | ||||
|             // 获取 ADID  | ||||
|             if (string.IsNullOrEmpty(adjustId)) adjustId = "not_set"; | ||||
|             if (string.IsNullOrEmpty(idfv)) idfv = "not_set"; | ||||
|             if (string.IsNullOrEmpty(idfa)) idfa = "not_set"; | ||||
|              | ||||
|             IPMConfig.ADJUST_ID = adjustId; | ||||
|             IPMConfig.ADJUST_IDFV = idfv; | ||||
|             IPMConfig.ADJUST_IDFA = idfa; | ||||
|              | ||||
|             Analytics.SetAdjustId(adjustId); | ||||
|             Analytics.SetIDFV(idfv); | ||||
|             Analytics.SetIDFA(idfa); | ||||
|             Analytics.OnAdjustInitComplete(); | ||||
|         } | ||||
| 
 | ||||
|         private static void OnGetGoogleAdId(string googleAdId) | ||||
|         { | ||||
|             Debug.Log($"{Tag} --- OnGetGoogleAdId: {googleAdId}"); | ||||
|             // IPMConfig.ADJUST_GOOGLE_ADID = googleAdId; | ||||
|             Analytics.SetGoogleAdId(googleAdId); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         | ||||
|         #endregion | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 45ff6fac409a4698ada70e0d8d5e8269 | ||||
| timeCreated: 1714353187 | ||||
|  | @ -0,0 +1,67 @@ | |||
| 
 | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Android 系统属性获取器 | ||||
|     /// </summary> | ||||
|     public class AndroidSystemPropertiesHelper | ||||
|     { | ||||
|          | ||||
|         private static string _appBundleId; | ||||
|         public static string AppBundleId | ||||
|         { | ||||
|             get => _appBundleId; | ||||
|             set => _appBundleId = value; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get the system property value by key | ||||
|         /// </summary> | ||||
|         /// <param name="key"></param> | ||||
|         /// <returns></returns> | ||||
|         public static string Get(string key) | ||||
|         { | ||||
| #if UNITY_ANDROID | ||||
|             return GetPropValue(key); | ||||
| #endif | ||||
|             return ""; | ||||
|         } | ||||
|          | ||||
| #if UNITY_ANDROID | ||||
| 
 | ||||
|         private static AndroidJavaClass _systemPropsCls; | ||||
|         private const string SYSTEM_PROPS_CLASS = "android.os.SystemProperties"; | ||||
| 
 | ||||
|         private static string GetPropValue(string key) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (_systemPropsCls == null) | ||||
|                 { | ||||
|                     _systemPropsCls = new AndroidJavaClass(SYSTEM_PROPS_CLASS); | ||||
|                 } | ||||
| 
 | ||||
|                 if (_systemPropsCls != null) | ||||
|                 { | ||||
|                     return _systemPropsCls.CallStatic<string>("get", key); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception e) | ||||
|             { | ||||
|                 Debug.LogException(e); | ||||
|             } | ||||
| 
 | ||||
|             return ""; | ||||
|         } | ||||
| #endif | ||||
|          | ||||
|          | ||||
|          | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 41272ed6104b4d80be5322135a811186 | ||||
| timeCreated: 1714353340 | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 75760985db114e44bfcd901ed50af96c | ||||
| timeCreated: 1712580213 | ||||
|  | @ -0,0 +1,23 @@ | |||
| { | ||||
|     "name": "GuruSDK.Tests", | ||||
|     "rootNamespace": "Guru.Tests", | ||||
|     "references": [ | ||||
|         "UnityEngine.TestRunner", | ||||
|         "UnityEditor.TestRunner", | ||||
|         "Guru.Runtime", | ||||
|         "GuruSDK" | ||||
|     ], | ||||
|     "includePlatforms": [], | ||||
|     "excludePlatforms": [], | ||||
|     "allowUnsafeCode": false, | ||||
|     "overrideReferences": true, | ||||
|     "precompiledReferences": [ | ||||
|         "nunit.framework.dll" | ||||
|     ], | ||||
|     "autoReferenced": false, | ||||
|     "defineConstraints": [ | ||||
|         "UNITY_INCLUDE_TESTS" | ||||
|     ], | ||||
|     "versionDefines": [], | ||||
|     "noEngineReferences": false | ||||
| } | ||||
|  | @ -1,6 +1,6 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 038b314e2834b4b3f80537a3c7832481 | ||||
| DefaultImporter: | ||||
| guid: 9eaf5abb3054d452ba2776169a31aa91 | ||||
| AssemblyDefinitionImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|  | @ -0,0 +1,51 @@ | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Guru.Tests | ||||
| { | ||||
|     using UnityEditor; | ||||
|     using NUnit.Framework; | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|      | ||||
|     public class Test_IAP | ||||
|     { | ||||
| 
 | ||||
|         [Test] | ||||
|         public void Test__AppleOrders() | ||||
|         { | ||||
|             var model = IAPModel.Load(); | ||||
|             int level = 1; | ||||
|             int orderType = 0; | ||||
|             for (int i = 0; i < 5; i++) | ||||
|             { | ||||
|                 model.AddAppleOrder(new AppleOrderData(orderType,  | ||||
|                     $"i.iap.test.icon_{i}",  | ||||
|                     $"receipt_{i}",  | ||||
|                     $"order_id_{i}",  | ||||
|                     DateTime.Now.ToString("g"),  | ||||
|                     level,  | ||||
|                     "RMB", 6.99d, "Store")); | ||||
|                  | ||||
|                 level++; | ||||
|             } | ||||
|              | ||||
|             if (model.HasUnreportedAppleOrder) | ||||
|             { | ||||
|                 int i = 0; | ||||
|                 while (model.appleOrders.Count > 0  | ||||
|                        && i < model.appleOrders.Count) | ||||
|                 { | ||||
|                     var o = model.appleOrders[i]; | ||||
|                     model.RemoveAppleOrder(o); | ||||
|                     i++; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 7181a5508c604c439d3f4585ff49a5e0 | ||||
| timeCreated: 1712580254 | ||||
|  | @ -0,0 +1,49 @@ | |||
| 
 | ||||
| 
 | ||||
| namespace Guru.Tests | ||||
| { | ||||
|     using NUnit.Framework; | ||||
|     using System.Threading; | ||||
|     using UnityEngine; | ||||
|      | ||||
|     public class Test_Threading | ||||
|     { | ||||
| 
 | ||||
|         private int TestCount | ||||
|         { | ||||
|             get => PlayerPrefs.GetInt(nameof(TestCount), 0); | ||||
|             set => PlayerPrefs.SetInt(nameof(TestCount), value); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         [Test] | ||||
|         public void Test_ThreadingCall() | ||||
|         { | ||||
|             GuruSDK.Init(success => | ||||
|             { | ||||
|                 GuruSDK.Delay(0.1f, () => | ||||
|                 { | ||||
|                     CallThreading(); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private void CallThreading() | ||||
|         { | ||||
|             Debug.Log($"--------- CallThreading -------------"); | ||||
|             var t = new Thread(() => | ||||
|             { | ||||
|                 Debug.Log($"--------- Thread Start -------------"); | ||||
|                 Thread.Sleep(2000); | ||||
|                 GuruSDK.RunOnMainThread(() => | ||||
|                 { | ||||
|                     TestCount++; | ||||
|                     Debug.Log($">>>>> CallThreading: {TestCount}"); | ||||
|                 }); | ||||
|             }); | ||||
|              | ||||
|             t.Start(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 67d9cfd9949c48f3a8f6e6821b733ff1 | ||||
| timeCreated: 1712649738 | ||||
|  | @ -1,11 +1,11 @@ | |||
| { | ||||
|   "name": "com.guru.unity.sdk", | ||||
|   "displayName": "Guru SDK", | ||||
|   "version": "1.0.6", | ||||
|   "version": "1.1.0", | ||||
|   "description": "Guru SDK for unity project", | ||||
|   "unity": "2020.3", | ||||
|   "author":{ | ||||
|     "name": "Guru Games" | ||||
|     "name": "Guru Game" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "category": "Game,Tool,Development", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue