com.amanotes.gdk 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/Editor/AmaGDKEditor.cs +21 -4
  3. package/Editor/AssemblyInfo.cs +5 -0
  4. package/Editor/AssemblyInfo.cs.meta +11 -0
  5. package/Editor/EmbedRemoteConfigAssetInspector.cs +191 -0
  6. package/Editor/EmbedRemoteConfigAssetInspector.cs.meta +11 -0
  7. package/Editor/Utils/AmaGDKEditor.Utils.cs +49 -0
  8. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  9. package/Extra/AmaGDKProject.unitypackage +0 -0
  10. package/Packages/AdjustAdapter_v4.33.0.unitypackage +0 -0
  11. package/Packages/AmaGDKConfig.unitypackage +0 -0
  12. package/Packages/AmaGDKExample.unitypackage +0 -0
  13. package/Packages/AmaGDKTest.unitypackage +0 -0
  14. package/Packages/AmaPassport.ATTSupport.unitypackage +0 -0
  15. package/Packages/AmaPassportAdapter_v1.0.0.unitypackage +0 -0
  16. package/Packages/AppsFlyerAdapter_v6.5.4.unitypackage +0 -0
  17. package/Packages/FirebaseAnalyticsAdapter_v9.1.0.unitypackage +0 -0
  18. package/Packages/FirebaseRemoteConfigAdapter_v9.1.0.unitypackage +0 -0
  19. package/Packages/IronsourceAdapter_v7.2.6.unitypackage +0 -0
  20. package/Runtime/AmaGDK.Ads.cs +46 -14
  21. package/Runtime/AmaGDK.Analytics.cs +23 -4
  22. package/Runtime/AmaGDK.Internal.cs +0 -9
  23. package/Runtime/AmaGDK.RemoteConfig.cs +77 -47
  24. package/Runtime/AmaGDK.UserProfile.cs +2 -2
  25. package/Runtime/AmaGDK.Utils.cs +34 -6
  26. package/Runtime/AmaGDK.cs +50 -2
  27. package/Runtime/AnalyticQualityAsset.cs +218 -0
  28. package/Runtime/AnalyticQualityAsset.cs.meta +11 -0
  29. package/Runtime/AssemblyInfo.cs +2 -1
  30. package/Runtime/EmbedRemoteConfigAsset.cs +59 -0
  31. package/Runtime/EmbedRemoteConfigAsset.cs.meta +11 -0
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.14 - 2023-08-08]
4
+ ### Support multiple embed remote config and load config by id
5
+ ### Clear cached remote config on new app update
6
+ ### Override remote config value in editor mode
7
+ ### New feature: Analytics Quality
8
+
9
+ ## [0.2.13 - 2023-07-20]
10
+ ### Fix error Remote Config JSON caused by special chars (", ', \)
11
+
3
12
  ## [0.2.12 - 2023-07-07]
4
13
  ### Restructure GDK - symlink GDK source to unity projects
5
14
  ### Unity 2019 compatible - no null coalescing
@@ -3,6 +3,7 @@ using System.Text;
3
3
  using Amanotes.Core;
4
4
  using Amanotes.Core.Internal;
5
5
  using System.Collections.Generic;
6
+ using System.IO;
6
7
  using UnityEditor;
7
8
  using UnityEngine;
8
9
  using static Amanotes.Core.Internal.Logging;
@@ -23,6 +24,8 @@ namespace Amanotes.Editor
23
24
  private static bool showConfigDetail;
24
25
  private static bool showAdapterInstaller;
25
26
  private static bool showExampleDetail;
27
+ private bool adapterScanned;
28
+ private readonly List<string> lstAdapter = new List<string>();
26
29
 
27
30
  public static Color btnColor;
28
31
  public static float btnLerp;
@@ -119,14 +122,28 @@ namespace Amanotes.Editor
119
122
  EditorGUILayout.ObjectField(configAsset, configAsset.GetType(), false);
120
123
  }
121
124
 
125
+ private void UpdateListAdapter()
126
+ {
127
+ if(adapterScanned)
128
+ return;
129
+
130
+ adapterScanned = true;
131
+
132
+ var adapterPath = Directory.GetFiles(Res.PACKAGE_PATH, "*Adapter*.unitypackage", SearchOption.AllDirectories);
133
+
134
+ for (int i = 0; i < adapterPath.Length; i++)
135
+ {
136
+ lstAdapter.Add(Path.GetFileName(adapterPath[i]));
137
+ }
138
+ }
122
139
  private void DrawGUI_AdapterList()
123
140
  {
124
- for (var i = 0; i < Res.AMAGDK_ADAPTERS.Length; i++)
141
+ UpdateListAdapter();
142
+ foreach (var adapter in lstAdapter)
125
143
  {
126
- string adapterName = Res.AMAGDK_ADAPTERS[i];
127
- if (GUILayout.Button(ObjectNames.NicifyVariableName(adapterName)))
144
+ if (GUILayout.Button(ObjectNames.NicifyVariableName(adapter)))
128
145
  {
129
- var package = $"{Res.PACKAGE_PATH}{adapterName}.unitypackage";
146
+ var package = $"{Res.PACKAGE_PATH}{adapter}";
130
147
  AssetDatabase.ImportPackage(package, false);
131
148
  }
132
149
  }
@@ -0,0 +1,5 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ #if UNITY_EDITOR
4
+ [assembly: InternalsVisibleTo("AmaGDK.Dev.Editor")]
5
+ #endif
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 702e20d6e8ffb452b83787a36bd81d81
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,191 @@
1
+ using System;
2
+ using System.Text;
3
+ using Amanotes.Core.Internal;
4
+ using UnityEditor;
5
+ using UnityEngine;
6
+
7
+ using static Amanotes.Core.Internal.Logging;
8
+
9
+ namespace Amanotes.Editor
10
+ {
11
+ [CustomEditor(typeof(EmbedRemoteConfigAsset))]
12
+ internal class EmbedRemoteConfigAssetInspector : UnityEditor.Editor
13
+ {
14
+ private EmbedRemoteConfigAsset remoteConfigAsset;
15
+ private SerializedProperty overrideInEditorProperty;
16
+ private SerializedProperty overrideConfigProperty;
17
+ private SerializedProperty configsProperty;
18
+ private bool[] foldouts;
19
+
20
+ private void OnEnable()
21
+ {
22
+ remoteConfigAsset = (EmbedRemoteConfigAsset)target;
23
+ overrideInEditorProperty = serializedObject.FindProperty("overrideInEditor");
24
+ overrideConfigProperty = serializedObject.FindProperty("overrideConfig");
25
+ configsProperty = serializedObject.FindProperty("configs");
26
+ foldouts = new bool[configsProperty.arraySize];
27
+ }
28
+
29
+ public override void OnInspectorGUI()
30
+ {
31
+ EditorGUILayout.BeginHorizontal();
32
+ EditorGUILayout.LabelField("Active Id:");
33
+ EditorGUILayout.LabelField(remoteConfigAsset.activeId);
34
+ EditorGUILayout.EndHorizontal();
35
+
36
+ serializedObject.Update();
37
+
38
+ DrawPropertiesExcluding(serializedObject, "m_Script", "activeId", "configs", "overrideConfig");
39
+
40
+ if (overrideInEditorProperty.boolValue)
41
+ {
42
+ EditorGUILayout.PropertyField(overrideConfigProperty);
43
+ }
44
+
45
+ if (GUILayout.Button("Add a config set"))
46
+ {
47
+ configsProperty.arraySize++;
48
+ int lastIndex = configsProperty.arraySize - 1;
49
+ var config= configsProperty.GetArrayElementAtIndex(lastIndex);
50
+ config.FindPropertyRelative("id").stringValue = $"config{lastIndex}";
51
+ foldouts = AddElementToArray(foldouts, false);
52
+ }
53
+
54
+ for (int i = 0; i < configsProperty.arraySize; i++)
55
+ {
56
+ SerializedProperty configProperty = configsProperty.GetArrayElementAtIndex(i);
57
+ SerializedProperty idProperty = configProperty.FindPropertyRelative("id");
58
+ SerializedProperty fetchUrlProperty = configProperty.FindPropertyRelative("fetchUrl");
59
+ SerializedProperty itemsProperty = configProperty.FindPropertyRelative("items");
60
+
61
+ EditorGUILayout.BeginVertical(GUI.skin.box);
62
+ foldouts[i] = EditorGUILayout.Foldout(foldouts[i], idProperty.stringValue);
63
+ if (foldouts[i])
64
+ {
65
+ EditorGUI.indentLevel++;
66
+ EditorGUILayout.BeginHorizontal();
67
+ if (GUILayout.Button("Set active", GUILayout.MinWidth(100)))
68
+ {
69
+ remoteConfigAsset.SetActiveConfig(idProperty.stringValue);
70
+ }
71
+ if (GUILayout.Button("Fetch", GUILayout.MinWidth(80)))
72
+ {
73
+ var url = fetchUrlProperty.stringValue;
74
+ var currentConfigIndex = i;
75
+ if (string.IsNullOrWhiteSpace(url))
76
+ {
77
+ ShowError("Please fill the fetch URL first");
78
+ }
79
+ else
80
+ {
81
+ WebRequest.Get(url, onProgress: FetchProgress, onSuccess: s => FillEmbedConfig(currentConfigIndex, s), onError:ShowError);
82
+ }
83
+ }
84
+ if (GUILayout.Button("Remove", GUILayout.MinWidth(80)))
85
+ {
86
+ if (remoteConfigAsset.ActiveConfig?.id == idProperty.stringValue)
87
+ {
88
+ remoteConfigAsset.SetActiveConfig(string.Empty);
89
+ }
90
+ configsProperty.DeleteArrayElementAtIndex(i);
91
+ foldouts = RemoveElementAtIndex(foldouts, i);
92
+ }
93
+ if (GUILayout.Button("Copy", GUILayout.MinWidth(80)))
94
+ {
95
+ GUIUtility.systemCopyBuffer = KeyValueToText(itemsProperty);
96
+ }
97
+ if (GUILayout.Button("Paste", GUILayout.MinWidth(80)))
98
+ {
99
+ FillEmbedConfig(i, GUIUtility.systemCopyBuffer);
100
+ }
101
+ EditorGUILayout.EndHorizontal();
102
+ EditorGUILayout.PropertyField(idProperty);
103
+ EditorGUILayout.PropertyField(fetchUrlProperty);
104
+ EditorGUILayout.PropertyField(itemsProperty);
105
+
106
+ EditorGUI.indentLevel--;
107
+ }
108
+ EditorGUILayout.EndVertical();
109
+ }
110
+
111
+ serializedObject.ApplyModifiedProperties();
112
+ }
113
+
114
+ private T[] AddElementToArray<T>(T[] array, T element)
115
+ {
116
+ T[] newArray = new T[array.Length + 1];
117
+ array.CopyTo(newArray, 0);
118
+ newArray[array.Length] = element;
119
+ return newArray;
120
+ }
121
+
122
+ private T[] RemoveElementAtIndex<T>(T[] array, int index)
123
+ {
124
+ T[] newArray = new T[array.Length - 1];
125
+ for (int i = 0, j = 0; i < array.Length; i++)
126
+ {
127
+ if (i != index)
128
+ {
129
+ newArray[j] = array[i];
130
+ j++;
131
+ }
132
+ }
133
+ return newArray;
134
+ }
135
+
136
+ private void FetchProgress(float progress)
137
+ {
138
+ EditorUtility.DisplayProgressBar("Fetching default remote config", "Please wait...", progress);
139
+ }
140
+
141
+ private void FillEmbedConfig(int configIndex, string rawText)
142
+ {
143
+ try
144
+ {
145
+ string[] lines = rawText.Split("\n");
146
+ KeyValue[] arrKeyValue = new KeyValue[lines.Length];
147
+ for (int i = 0; i < lines.Length; i++)
148
+ {
149
+ var line = lines[i];
150
+ var idx = line.IndexOf('\t');
151
+ if (idx == -1)
152
+ {
153
+ LogWarning($"[RemoteConfig] Cannot get (key, value) at line {i+1}. Content:\n<{line}>");
154
+ continue;
155
+ }
156
+ arrKeyValue[i] = new KeyValue
157
+ {
158
+ key = line.Substring(0, idx),
159
+ value = line.Substring(idx + 1)
160
+ };
161
+ }
162
+ remoteConfigAsset.configs[configIndex].items = arrKeyValue;
163
+ }
164
+ catch (Exception e)
165
+ {
166
+ LogWarning(e.Message);
167
+ }
168
+ finally
169
+ {
170
+ EditorUtility.ClearProgressBar();
171
+ }
172
+ }
173
+
174
+ private string KeyValueToText(SerializedProperty items)
175
+ {
176
+ StringBuilder sb = new StringBuilder();
177
+ for (int i = 0; i < items.arraySize; i++)
178
+ {
179
+ SerializedProperty c = items.GetArrayElementAtIndex(i);
180
+ sb.AppendLine($"{c.FindPropertyRelative("key").stringValue}\t{c.FindPropertyRelative("value").stringValue}");
181
+ }
182
+ return sb.ToString();
183
+ }
184
+
185
+ private void ShowError(string error)
186
+ {
187
+ EditorUtility.ClearProgressBar();
188
+ EditorUtility.DisplayDialog("Error", error, "OK");
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: cc3262a8477b7444e8ba53e7174756f6
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -1,7 +1,10 @@
1
1
  using System;
2
2
  using System.Collections;
3
+ using System.Collections.Generic;
3
4
  using UnityEditor;
4
5
  using UnityEngine;
6
+ using UnityEngine.Networking;
7
+
5
8
  namespace Amanotes.Editor
6
9
  {
7
10
  internal static class Utils
@@ -31,4 +34,50 @@ namespace Amanotes.Editor
31
34
  EditorApplication.update += closureCallback;
32
35
  }
33
36
  }
37
+
38
+ internal static class WebRequest
39
+ {
40
+ internal static void Get(string url, Dictionary<string, string> headers = null, Action<float> onProgress = null, Action<string> onSuccess = null, Action<string> onError = null)
41
+ {
42
+ GetCoroutine(url, headers, onProgress, onSuccess, onError).StartEditorCoroutine();
43
+ }
44
+
45
+ private static IEnumerator GetCoroutine(string url, Dictionary<string, string> headers = null, Action<float> onProgress = null, Action<string> onSuccess = null, Action<string> onError = null)
46
+ {
47
+ using var request = UnityWebRequest.Get(url);
48
+ // Add headers to the request
49
+ if (headers != null)
50
+ {
51
+ foreach (var header in headers)
52
+ {
53
+ request.SetRequestHeader(header.Key, header.Value);
54
+ }
55
+ }
56
+
57
+ // Send the request
58
+ request.timeout = 60;
59
+ yield return request.SendWebRequest();
60
+
61
+ while (request.isDone == false)
62
+ {
63
+ onProgress?.Invoke(request.downloadProgress);
64
+ yield return null;
65
+ }
66
+
67
+ // Check for errors
68
+ #pragma warning disable CS0618
69
+ if (request.isNetworkError || request.isHttpError)
70
+ #pragma warning restore CS0618
71
+ {
72
+ Debug.LogError("Error: " + request.error);
73
+ onError?.Invoke(request.error);
74
+ }
75
+ else
76
+ {
77
+ // Process the response
78
+ string responseText = request.downloadHandler.text;
79
+ onSuccess?.Invoke(responseText);
80
+ }
81
+ }
82
+ }
34
83
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -85,7 +85,7 @@ namespace Amanotes.Core
85
85
  public AdStat interstitial = new AdStat();
86
86
  public AdStat reward = new AdStat();
87
87
 
88
- internal int GetLastShowInSeconds(AdType adType)
88
+ internal int TimeSinceLastShow(AdType adType)
89
89
  {
90
90
  return (int)(Time.realtimeSinceStartup - (adType == AdType.Interstitial ? interstitial.lastSuccessTime : reward.lastSuccessTime));
91
91
  }
@@ -138,11 +138,11 @@ namespace Amanotes.Core
138
138
  _adapter?.rewardVideo.StartLoadAd();
139
139
  }
140
140
 
141
- public static bool hasInterstitial => _adapter.interstitial.hasAd;
142
- public static bool hasRewardedVideo => _adapter.rewardVideo.hasAd;
143
- public static bool showingBanner => _showingBanner;
144
- public static bool showingAd => context != null;
145
-
141
+ public static bool HasInterstitial => _adapter.interstitial.hasAd;
142
+ public static bool HasRewardedVideo => _adapter.rewardVideo.hasAd;
143
+ public static bool IsBannerShowing => _isBannerShowing;
144
+ public static bool IsAdShowing => context != null;
145
+
146
146
  private static bool ClearPrevContext()
147
147
  {
148
148
  if (context == null) return true;
@@ -176,6 +176,15 @@ namespace Amanotes.Core
176
176
  return context;
177
177
  }
178
178
 
179
+ public static bool IsInterstitialShowing
180
+ {
181
+ get
182
+ {
183
+ if (context == null) return false;
184
+ return context.adType == AdType.Interstitial;
185
+ }
186
+ }
187
+
179
188
  public static IAdCallback ShowRewardedVideo(string placementName = null, Dictionary<string, object> userData = null)
180
189
  {
181
190
  if (!ClearPrevContext()) return null;
@@ -184,19 +193,36 @@ namespace Amanotes.Core
184
193
  _adapter.rewardVideo.StartShowAd();
185
194
  return context;
186
195
  }
196
+
197
+ public static bool IsRewardedVideoShowing
198
+ {
199
+ get
200
+ {
201
+ if (context == null) return false;
202
+ return context.adType == AdType.VideoReward;
203
+ }
204
+ }
187
205
 
188
- private static bool _showingBanner;
189
- public static void ShowBanner()
206
+ private static bool _isBannerShowing;
207
+ public static void ShowBanner(BannerPosition? bannerPosition = null)
190
208
  {
191
- if (_showingBanner) return;
192
- _showingBanner = true;
193
- _adapter.ShowBanner(_adConfig.bannerPosition);
209
+ if (_isBannerShowing) return;
210
+ _isBannerShowing = true;
211
+
212
+ if (bannerPosition.HasValue)
213
+ {
214
+ _adapter.ShowBanner(bannerPosition.Value);
215
+ }
216
+ else
217
+ {
218
+ _adapter.ShowBanner(_adConfig.bannerPosition);
219
+ }
194
220
  }
195
221
 
196
222
  public static void HideBanner()
197
223
  {
198
- if (!_showingBanner) return;
199
- _showingBanner = false;
224
+ if (!_isBannerShowing) return;
225
+ _isBannerShowing = false;
200
226
  _adapter.HideBanner();
201
227
  }
202
228
 
@@ -210,6 +236,12 @@ namespace Amanotes.Core
210
236
  else
211
237
  _adapter.rewardVideo.StopShowAd();
212
238
  }
239
+
240
+ public static int TimeSinceLastInterstitial => localData.TimeSinceLastShow(AdType.Interstitial);
241
+
242
+ public static int TimeSinceLastVideoReward => localData.TimeSinceLastShow(AdType.VideoReward);
243
+
244
+ public static int TimeSinceLastAd => Math.Min(TimeSinceLastInterstitial, TimeSinceLastVideoReward) ;
213
245
  }
214
246
  }
215
247
  }
@@ -287,7 +319,7 @@ namespace Amanotes.Core.Internal
287
319
  // Stats
288
320
  public float showCallAt;
289
321
  public float showFinishAt;
290
-
322
+
291
323
  public ShowAdContext(AdType adType, string placementName, Dictionary<string, object> userData)
292
324
  {
293
325
  this.adType = adType;
@@ -301,7 +301,8 @@ namespace Amanotes.Core
301
301
  internal static void ProcessEventQueue()
302
302
  {
303
303
  if (eventQueue.Count <= 0) return;
304
- bool showAnalyticsLog = Config.common.showAnalyticsLog;
304
+ SDKConfig common = Config.common;
305
+ bool showAnalyticsLog = common.showAnalyticsLog;
305
306
  sessionStat.ClearLastFrameEventInfo();
306
307
 
307
308
  while (eventQueue.Count > 0)
@@ -317,9 +318,11 @@ namespace Amanotes.Core
317
318
  }
318
319
 
319
320
  string eventNameToSend = GetEventNameToSend(eventData.eventName, countInSession, totalCount);
320
- if (Config.common.normalizeEventName) NormalizeEventName(ref eventNameToSend);
321
+ if (common.normalizeEventName) NormalizeEventName(ref eventNameToSend);
321
322
 
322
- if (showAnalyticsLog) Debug.Log($"AmaGDK | Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventData.parameters, true)}");
323
+ if (showAnalyticsLog) Debug.Log($"AmaGDK | Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventData.parameters, true)}");
324
+
325
+ common.quality.CheckEventQuality(eventData.eventName, eventData.parameters);
323
326
 
324
327
  var inUseAdapterIDs = new List<string>();
325
328
  foreach (AdapterContext m in listAdapters)
@@ -585,7 +588,7 @@ namespace Amanotes.Core
585
588
  contents.AppendLine(stat.ToTsv());
586
589
  }
587
590
 
588
- FileUtils.SaveAppend(fileName, contents.ToString());
591
+ GDKFileUtils.SaveAppend(fileName, contents.ToString());
589
592
  }
590
593
  }
591
594
  }
@@ -731,12 +734,28 @@ namespace Amanotes.Core
731
734
 
732
735
  namespace Amanotes.Core.Internal
733
736
  {
737
+ [Serializable]
738
+ public class AnalyticQualityConfig
739
+ {
740
+ public bool enable = false;
741
+ public AnalyticQualityAsset qualityAsset;
742
+
743
+ public void CheckEventQuality(string eventName, Dictionary<string, object> parameters)
744
+ {
745
+ if (enable && qualityAsset != null)
746
+ {
747
+ qualityAsset.CheckEventQuality(eventName, parameters);
748
+ }
749
+ }
750
+ }
751
+
734
752
  public partial class SDKConfig
735
753
  {
736
754
  public bool showAnalyticsLog = true;
737
755
  public bool normalizeEventName = true;
738
756
  public bool checkMigrationIntegrity = true;
739
757
  public bool enableEventStat;
758
+ public AnalyticQualityConfig quality;
740
759
  }
741
760
 
742
761
  public interface IAnalyticAdapter
@@ -40,15 +40,6 @@ namespace Amanotes.Core.Internal
40
40
  public const string AMAGDK_PREFAB = BASE_PATH + "Runtime/AmaGDK.prefab";
41
41
  public const string AMAGDK_CONFIG_PACKAGE = PACKAGE_PATH + "AmaGDKConfig.unitypackage";
42
42
 
43
- public static readonly string[] AMAGDK_ADAPTERS =
44
- {
45
- "FirebaseAnalyticsAdapter_v9.1.0",
46
- "AppsFlyerAdapter_v6.5.4",
47
- "AdjustAdapter_v4.33.0",
48
- "IronsourceAdapter_v7.2.6",
49
- "FirebaseRemoteConfigAdapter_v9.1.0"
50
- };
51
-
52
43
  #if UNITY_EDITOR
53
44
 
54
45
  [MenuItem("GameObject/AmaGDK/Add AmaGDK Prefab", false, 10)]
@@ -16,8 +16,7 @@ namespace Amanotes.Core
16
16
  {
17
17
  public enum RemoteConfigSource
18
18
  {
19
- DefaultAsset,
20
- DefaultFile,
19
+ EmbedConfig,
21
20
  Cache,
22
21
  Internet
23
22
  }
@@ -26,7 +25,7 @@ namespace Amanotes.Core
26
25
  public partial class RemoteConfig //Public
27
26
  {
28
27
  public static Action<bool> onFetchCompleted;
29
- public static Dictionary<string, string> DictConfig { get { return dictConfig; } }
28
+ public static Dictionary<string, string> DictConfig => dictConfig;
30
29
  public static RemoteConfigSource Source { private set; get; }
31
30
  static bool? _cacheExisted = null;
32
31
  public static bool HasCache
@@ -34,7 +33,7 @@ namespace Amanotes.Core
34
33
  get
35
34
  {
36
35
  if (_cacheExisted == null)
37
- _cacheExisted = FileUtils.Exist(fileName);
36
+ _cacheExisted = GDKFileUtils.Exist(fileName);
38
37
  return _cacheExisted.GetValueOrDefault();
39
38
  }
40
39
  }
@@ -64,6 +63,15 @@ namespace Amanotes.Core
64
63
 
65
64
  public static T Get<T>(string key, T defaultValue = default(T))
66
65
  {
66
+ #if UNITY_EDITOR
67
+ //Override value in editor environment
68
+ if (adapterConfig.embedRemoteConfig.overrideInEditor)
69
+ {
70
+ foreach (var c in adapterConfig.embedRemoteConfig.overrideConfig)
71
+ if (c.key == key)
72
+ return (T)Convert.ChangeType(c.value, typeof(T));
73
+ }
74
+ #endif
67
75
  if (dictConfig.ContainsKey(key))
68
76
  return (T)Convert.ChangeType(dictConfig[key], typeof(T));
69
77
  return defaultValue;
@@ -78,6 +86,13 @@ namespace Amanotes.Core
78
86
  {
79
87
  return JsonUtils.DictionaryToJson(dictConfig);
80
88
  }
89
+
90
+ public static void SetActiveEmbedConfig(string id)
91
+ {
92
+ if (adapterConfig == null || adapterConfig.embedRemoteConfig == null)
93
+ LogWarning("[RemoteConfig] No embed remote config available");
94
+ adapterConfig.embedRemoteConfig.SetActiveConfig(id);
95
+ }
81
96
  }
82
97
 
83
98
  public partial class RemoteConfig //Internal
@@ -91,7 +106,7 @@ namespace Amanotes.Core
91
106
  SaveCache,
92
107
 
93
108
  LoadCache,
94
- LoadDefaultAsset,
109
+ LoadEmbedConfig,
95
110
 
96
111
  Success,
97
112
  Fail
@@ -100,9 +115,15 @@ namespace Amanotes.Core
100
115
  static Dictionary<string, string> dictConfig = new Dictionary<string, string>();
101
116
  static string fileName = $"{nameof(RemoteConfig)}.kv";
102
117
  static string base64Prefix = "base64:";
118
+ const string VERSION_PREFIX = "version:";
119
+ static int EmbedVersion =>
120
+ adapterConfig != null && adapterConfig.embedRemoteConfig != null
121
+ ? adapterConfig.embedRemoteConfig.version
122
+ : 1;
103
123
 
104
124
  static FetchState _state = FetchState.None;
105
- internal static FetchState State { get { return _state; } }
125
+ internal static FetchState State => _state;
126
+
106
127
  static readonly Dictionary<FetchState, Action[]> fetchSM = new Dictionary<FetchState, Action[]>
107
128
  {
108
129
  { FetchState.None, new Action[]{ ResolveDependency }},
@@ -110,8 +131,8 @@ namespace Amanotes.Core
110
131
  { FetchState.CheckInternet, new Action[]{ LoadCache, Fetch}},
111
132
  { FetchState.Fetch, new Action[]{ LoadCache, SaveCache}},
112
133
  { FetchState.SaveCache, new Action[]{ SetDone }},
113
- { FetchState.LoadCache, new Action[]{ LoadDefaultAsset, SetDone}},
114
- { FetchState.LoadDefaultAsset, new Action[]{ SetFail, SetDone}}
134
+ { FetchState.LoadCache, new Action[]{ LoadEmbedConfig, SetDone}},
135
+ { FetchState.LoadEmbedConfig, new Action[]{ SetFail, SetDone}}
115
136
  };
116
137
 
117
138
  internal static IRemoteConfig adapter;
@@ -177,30 +198,17 @@ namespace Amanotes.Core
177
198
  {
178
199
  _state = FetchState.SaveCache;
179
200
  StringBuilder sb = new StringBuilder();
180
-
181
-
182
- bool needUpdateDefault = Application.isEditor && adapterConfig.autoUpdateDefaultValue;
183
- List<KeyValue> list = needUpdateDefault ? new List<KeyValue>() : null;
184
201
 
202
+ //Add embedded remote config version to force load from embedded config after app update
203
+ sb.AppendLine($"{VERSION_PREFIX}{EmbedVersion}");
204
+
185
205
  foreach (var c in dictConfig)
186
206
  {
187
207
  sb.AppendLine($"{c.Key}\t{Encode(c.Value)}");
188
- if (!needUpdateDefault) continue;
189
- list.Add(new KeyValue() { key = c.Key, value = c.Value });
190
- }
191
-
192
- #if UNITY_EDITOR
193
- if (needUpdateDefault)
194
- {
195
- list.Sort((item1, item2) => string.Compare(item1.key, item2.key, StringComparison.Ordinal));
196
- adapterConfig.defaultValue = list.ToArray();
197
- EditorUtility.SetDirty(_config);
198
- Log($"[RemoteConfig] Updated default remote config!");
199
208
  }
200
- #endif
201
209
 
202
- string filePath = FileUtils.GetPath(fileName);
203
- FileUtils.Save(filePath, sb.ToString());
210
+ string filePath = GDKFileUtils.GetPath(fileName);
211
+ GDKFileUtils.Save(filePath, sb.ToString());
204
212
  Log($"[RemoteConfig] Saved remote config to {filePath}");
205
213
  NextState();
206
214
  }
@@ -218,17 +226,34 @@ namespace Amanotes.Core
218
226
  try
219
227
  {
220
228
  dictConfig.Clear();
221
- using (StreamReader sr = new StreamReader(FileUtils.GetPath(fileName)))
229
+ using (StreamReader sr = new StreamReader(GDKFileUtils.GetPath(fileName)))
222
230
  {
231
+ //Check new app update
232
+ string firstLine = sr.ReadLine();
233
+ if (firstLine == null)
234
+ {
235
+ NextState(false);
236
+ return;
237
+ }
238
+ if (ShouldClearCache(firstLine))
239
+ {
240
+ GDKFileUtils.Delete(fileName);
241
+ string oldVersion = firstLine.StartsWith(VERSION_PREFIX) ? firstLine : "<none>";
242
+ Log($"[RemoteConfig] Cached remote config cleared due to embed version changed:\n{oldVersion} -> {EmbedVersion}");
243
+ NextState(false);
244
+ return;
245
+ }
246
+
247
+ //Read key-value
223
248
  string line;
224
- int lineIdx = 0;
249
+ int lineNo = 1;
225
250
  while ((line = sr.ReadLine()) != null)
226
251
  {
227
- lineIdx++;
252
+ lineNo++;
228
253
  var idx = line.IndexOf('\t');
229
254
  if (idx == -1)
230
255
  {
231
- LogWarning($"[RemoteConfig] Cannot get (key, value) at line {lineIdx} of cache");
256
+ LogWarning($"[RemoteConfig] Cannot get (key, value) at line {lineNo} of cache. Content:\n<{line}>");
232
257
  continue;
233
258
  }
234
259
  string key = line.Substring(0, idx);
@@ -250,24 +275,30 @@ namespace Amanotes.Core
250
275
  NextState(true);
251
276
  }
252
277
 
253
- static void LoadDefaultAsset()
278
+ static void LoadEmbedConfig()
254
279
  {
255
- _state = FetchState.LoadDefaultAsset;
280
+ _state = FetchState.LoadEmbedConfig;
281
+
282
+ if (adapterConfig == null || adapterConfig.embedRemoteConfig == null)
283
+ {
284
+ NextState(false);
285
+ return;
286
+ }
256
287
 
257
- var defaultValue = ((AdapterContext)adapter).GetAdapterConfig<RemoteConfigConfig>().defaultValue;
288
+ var config = adapterConfig.embedRemoteConfig.ActiveConfig;
258
289
 
259
- if (defaultValue.Length == 0)
290
+ if (config == null || config.items.Length == 0)
260
291
  {
261
292
  NextState(false);
262
293
  return;
263
294
  }
264
295
 
265
- foreach (var item in defaultValue)
296
+ foreach (var item in config.items)
266
297
  {
267
298
  dictConfig.Add(item.key, item.value);
268
299
  }
269
- Source = RemoteConfigSource.DefaultAsset;
270
- Log("[RemoteConfig] Load from default asset");
300
+ Source = RemoteConfigSource.EmbedConfig;
301
+ Log("[RemoteConfig] Load from embed asset");
271
302
  NextState(true);
272
303
  }
273
304
 
@@ -301,6 +332,12 @@ namespace Amanotes.Core
301
332
  s = s.Remove(0, base64Prefix.Length);
302
333
  return Encoding.UTF8.GetString(Convert.FromBase64String(s));
303
334
  }
335
+
336
+ static bool ShouldClearCache(string cacheVersion)
337
+ {
338
+ if (!cacheVersion.StartsWith(VERSION_PREFIX)) return true;
339
+ return cacheVersion != VERSION_PREFIX + EmbedVersion;
340
+ }
304
341
  }
305
342
  }
306
343
 
@@ -324,19 +361,12 @@ namespace Amanotes.Core.Internal
324
361
  [Serializable]
325
362
  public class RemoteConfigConfig
326
363
  {
327
- public bool autoUpdateDefaultValue;
328
- public KeyValue[] defaultValue;
364
+ [SerializeField]
365
+ internal EmbedRemoteConfigAsset embedRemoteConfig;
329
366
  public float firstFetchTimeOutInSeconds = 10;
330
367
  public float defaultTimeOutInSeconds = 3;
331
368
  }
332
-
333
- [Serializable]
334
- public class KeyValue
335
- {
336
- public string key;
337
- public string value;
338
- }
339
-
369
+
340
370
  public interface IRemoteConfig
341
371
  {
342
372
  void ResolveDependencies(Action<bool> onComplete);
@@ -15,12 +15,12 @@ namespace Amanotes.Core
15
15
 
16
16
  public void Save()
17
17
  {
18
- FileUtils.Save(FILE_NAME, JsonUtility.ToJson(this));
18
+ GDKFileUtils.Save(FILE_NAME, JsonUtility.ToJson(this));
19
19
  }
20
20
 
21
21
  public UserProfile Load()
22
22
  {
23
- string json = FileUtils.Load(FILE_NAME);
23
+ string json = GDKFileUtils.Load(FILE_NAME);
24
24
  if (string.IsNullOrEmpty(json)) return this;
25
25
  JsonUtility.FromJsonOverwrite(json, this);
26
26
  return this;
@@ -3,12 +3,13 @@ using System.Collections;
3
3
  using System.Collections.Generic;
4
4
  using System.IO;
5
5
  using System.Text;
6
+ using System.Text.RegularExpressions;
6
7
  using UnityEngine;
7
8
  using static Amanotes.Core.Internal.Logging;
8
9
 
9
10
  namespace Amanotes.Core.Internal
10
11
  {
11
- public class Utils
12
+ public class GDKUtils
12
13
  {
13
14
  public static void DelayCall(float seconds, Action action)
14
15
  {
@@ -22,7 +23,7 @@ namespace Amanotes.Core.Internal
22
23
  }
23
24
  }
24
25
 
25
- internal class FileUtils
26
+ internal class GDKFileUtils
26
27
  {
27
28
 
28
29
  private static string _basePath;
@@ -39,6 +40,7 @@ namespace Amanotes.Core.Internal
39
40
  return _basePath;
40
41
  }
41
42
  }
43
+
42
44
  internal static string GetPath(string fileName)
43
45
  {
44
46
  return Path.Combine(basePath, fileName);
@@ -161,6 +163,26 @@ namespace Amanotes.Core.Internal
161
163
  LogWarningOnce(path + "\n" + ex);
162
164
  }
163
165
  }
166
+
167
+ internal static void ClearCacheData()
168
+ {
169
+ StringBuilder logBuilder = new StringBuilder();
170
+ logBuilder.AppendLine("[AmaGDK] All cached data & stats cleared");
171
+
172
+ if (!Directory.Exists(_basePath))
173
+ {
174
+ logBuilder.AppendLine("Cache path not exists: " + _basePath);
175
+ return;
176
+ }
177
+
178
+ string[] filePaths = Directory.GetFiles(_basePath);
179
+ foreach (string path in filePaths)
180
+ {
181
+ Delete(path);
182
+ logBuilder.AppendLine($"[deleted] - {path}");
183
+ }
184
+ Debug.Log(logBuilder);
185
+ }
164
186
  }
165
187
 
166
188
  public class ActionQueue
@@ -238,7 +260,7 @@ namespace Amanotes.Core.Internal
238
260
  instance = result;
239
261
  }
240
262
  string fileName = instance.GetType().Name;
241
- string json = FileUtils.Load(fileName);
263
+ string json = GDKFileUtils.Load(fileName);
242
264
  JsonUtility.FromJsonOverwrite(json, instance);
243
265
  return instance;
244
266
  }
@@ -247,7 +269,7 @@ namespace Amanotes.Core.Internal
247
269
  {
248
270
  string fileName = instance.GetType().Name;
249
271
  string json = JsonUtility.ToJson(instance);
250
- FileUtils.Save(fileName, json);
272
+ GDKFileUtils.Save(fileName, json);
251
273
  }
252
274
  }
253
275
 
@@ -342,8 +364,7 @@ namespace Amanotes.Core.Internal
342
364
  return;
343
365
  }
344
366
  }
345
-
346
- jsonSb.Append($"\"{value}\"");
367
+ jsonSb.Append($"\"{EscapeJsonString(s)}\"");
347
368
  return;
348
369
  }
349
370
 
@@ -374,6 +395,13 @@ namespace Amanotes.Core.Internal
374
395
  jsonSb.Append($"{JsonUtility.ToJson(value, format)}");
375
396
  }
376
397
 
398
+ private static string EscapeJsonString(string input)
399
+ {
400
+ //Match any occurrence of a backslash(\), single quotation('), or double quotation (")
401
+ string pattern = "[\\\'\"]";
402
+ return Regex.Replace(input, pattern, match => $"\\{match.Value}");
403
+ }
404
+
377
405
  //public static T[] FromJsonToArray<T>(string json)
378
406
  //{
379
407
  // if (!json.StartsWith("{\"items\":"))
package/Runtime/AmaGDK.cs CHANGED
@@ -1,16 +1,20 @@
1
1
  using System;
2
2
  using System.Collections;
3
+ using System.Diagnostics;
4
+ using System.IO;
3
5
  using System.Text;
6
+ using System.Text.RegularExpressions;
4
7
  using Amanotes.Core.Internal;
5
8
  using UnityEngine;
6
9
  using static Amanotes.Core.Internal.Logging;
7
10
  using static Amanotes.Core.Internal.Messsage;
11
+ using Debug = System.Diagnostics.Debug;
8
12
 
9
13
  namespace Amanotes.Core
10
14
  {
11
15
  public partial class AmaGDK : MonoBehaviour
12
16
  {
13
- public const string VERSION = "0.2.12";
17
+ public const string VERSION = "0.2.14";
14
18
 
15
19
  internal static AmaGDK _instance;
16
20
  internal static Status _status = Status.None;
@@ -109,7 +113,7 @@ namespace Amanotes.Core
109
113
  _status = Status.Ready;
110
114
  _onReadyCallback?.Invoke();
111
115
  _onReadyCallback = null;
112
- Debug.Log($"AmaGDK v{VERSION} | {READY} in {Time.realtimeSinceStartup - startTime}s\n\n{adapterInfo.ToString()}");
116
+ Log($"AmaGDK v{VERSION} | {READY} in {Time.realtimeSinceStartup - startTime}s\n\n{adapterInfo.ToString()}");
113
117
  }
114
118
 
115
119
  void RegisterUnityCallback(AdapterContext adapter)
@@ -231,4 +235,48 @@ namespace Amanotes.Core
231
235
  applicationQuit?.Invoke();
232
236
  }
233
237
  }
238
+
239
+ public partial class AmaGDK // Switch dev mode
240
+ {
241
+ [ContextMenu("Change Dev Mode")]
242
+ public void ChangeDevMode()
243
+ {
244
+ string variableName = "GDK_HOME";
245
+ string output = RunShellCommand($"source ~/.zshrc && echo ${variableName}");
246
+ output = output.Trim();
247
+
248
+ if (string.IsNullOrEmpty(output))
249
+ {
250
+ LogWarning($"{variableName} environment variable is not set.");
251
+ return;
252
+ }
253
+
254
+ UpdateManifest(output);
255
+ }
256
+
257
+ private string RunShellCommand(string command)
258
+ {
259
+ Process process = new Process();
260
+ process.StartInfo.FileName = "/bin/zsh";
261
+ process.StartInfo.Arguments = $"-c \"{command}\"";
262
+ process.StartInfo.RedirectStandardOutput = true;
263
+ process.StartInfo.UseShellExecute = false;
264
+ process.StartInfo.CreateNoWindow = true;
265
+ process.Start();
266
+
267
+ string output = process.StandardOutput.ReadToEnd();
268
+ process.WaitForExit();
269
+
270
+ return output;
271
+ }
272
+
273
+ private void UpdateManifest(string strDesired)
274
+ {
275
+ const string manifestPath = "Packages/manifest.json";
276
+ string manifestJson = File.ReadAllText(manifestPath);
277
+
278
+ manifestJson = Regex.Replace(manifestJson, "\"com\\.amanotes\\.gdk\"\\s*:\\s*\"\\d+\\.\\d+\\.\\d+\"", $"\"com.amanotes.gdk\": \"{strDesired}\"");
279
+ File.WriteAllText(manifestPath, manifestJson);
280
+ }
281
+ }
234
282
  }
@@ -0,0 +1,218 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+ using System.Text;
5
+ using UnityEngine;
6
+ using static Amanotes.Core.Internal.Logging;
7
+ #if UNITY_EDITOR
8
+ using UnityEditor;
9
+ #endif
10
+
11
+ namespace Amanotes.Core
12
+ {
13
+ public enum ParamType
14
+ {
15
+ String,
16
+ Integer,
17
+ RealNumber
18
+ }
19
+
20
+ [Serializable]
21
+ public class EventParam
22
+ {
23
+ public string paramName;
24
+ public ParamType type;
25
+ public bool isOptional;
26
+ }
27
+
28
+ [Serializable]
29
+ public class EventQuality
30
+ {
31
+ public string eventName;
32
+ public List<EventParam> lstParam;
33
+
34
+ private Dictionary<string, EventParam> dicParam = null;
35
+
36
+ internal Dictionary<string, EventParam> DicParam
37
+ {
38
+ get
39
+ {
40
+ if (dicParam == null) BuildCache();
41
+ return dicParam;
42
+ }
43
+ }
44
+
45
+ private void BuildCache()
46
+ {
47
+ dicParam = new Dictionary<string, EventParam>();
48
+ foreach (var param in lstParam)
49
+ {
50
+ if (dicParam.ContainsKey(param.paramName)) continue;
51
+
52
+ dicParam.Add(param.paramName, param);
53
+ }
54
+ }
55
+
56
+ private bool IsNumericType(Type type)
57
+ {
58
+ return type == typeof(int) ||
59
+ type == typeof(long) ||
60
+ type == typeof(short) ||
61
+ type == typeof(byte) ||
62
+ type == typeof(double) ||
63
+ type == typeof(float) ||
64
+ type == typeof(decimal) ||
65
+ type == typeof(uint) ||
66
+ type == typeof(ulong) ||
67
+ type == typeof(ushort) ||
68
+ type == typeof(sbyte);
69
+ }
70
+
71
+ private bool IsIntegerType(Type type)
72
+ {
73
+ return type == typeof(sbyte) ||
74
+ type == typeof(byte) ||
75
+ type == typeof(short) ||
76
+ type == typeof(ushort) ||
77
+ type == typeof(int) ||
78
+ type == typeof(uint) ||
79
+ type == typeof(long) ||
80
+ type == typeof(ulong);
81
+ }
82
+
83
+ internal void ValidateParams(string eventName, Dictionary<string, object> eventParams)
84
+ {
85
+ StringBuilder logBuilder = new StringBuilder();
86
+ logBuilder.AppendLine($"Analytics quality violation found for event <{eventName}>");
87
+ bool isValid = true;
88
+ HashSet<string> checkedParameters = new HashSet<string>();
89
+
90
+ foreach (var kvp in DicParam)
91
+ {
92
+ if (!eventParams.ContainsKey(kvp.Key))
93
+ {
94
+ if (!kvp.Value.isOptional)
95
+ {
96
+ isValid = false;
97
+ logBuilder.AppendLine($"[-] {kvp.Key}");
98
+ }
99
+ continue;
100
+ }
101
+
102
+ object paramValue = eventParams[kvp.Key];
103
+
104
+ if (!ValidateParamType(paramValue, kvp.Value.type))
105
+ {
106
+ isValid = false;
107
+ logBuilder.AppendLine($"[X] {kvp.Key}. Input type {paramValue.GetType()}, Expected type: {kvp.Value.type.ToString()}.");
108
+ }
109
+
110
+ checkedParameters.Add(kvp.Key);
111
+ }
112
+
113
+ foreach (var parameter in eventParams)
114
+ {
115
+ if (checkedParameters.Contains(parameter.Key)) continue;
116
+
117
+ isValid = false;
118
+ logBuilder.AppendLine($"[+] {parameter.Key}");
119
+ }
120
+
121
+ if (!isValid) LogWarningOnce(logBuilder.ToString());
122
+ }
123
+
124
+ private bool ValidateParamType(object paramValue, ParamType paramType)
125
+ {
126
+ bool isValidType = false;
127
+
128
+ switch (paramType)
129
+ {
130
+ case ParamType.String:
131
+ isValidType = paramValue is string;
132
+ break;
133
+ case ParamType.Integer:
134
+ isValidType = IsIntegerType(paramValue.GetType());
135
+ break;
136
+ case ParamType.RealNumber:
137
+ isValidType = IsNumericType(paramValue.GetType());
138
+ break;
139
+ default:
140
+ LogWarningOnce($"Unsupported EventParam type: + {paramType}");
141
+ break;
142
+ }
143
+
144
+ return isValidType;
145
+ }
146
+ }
147
+
148
+ [CreateAssetMenu(fileName = "AmaGDKAnalyticsQuality", menuName = "Ama GDK/Analytics Quality", order = 3)]
149
+ [Serializable]
150
+ public class AnalyticQualityAsset : ScriptableObject
151
+ {
152
+ [SerializeField] private List<EventQuality> lstEvent = new List<EventQuality>();
153
+
154
+ private Dictionary<string, EventQuality> dictEvent = null;
155
+ private void BuildCache()
156
+ {
157
+ dictEvent = new Dictionary<string, EventQuality>();
158
+ foreach (var evt in lstEvent)
159
+ {
160
+ if (dictEvent.ContainsKey(evt.eventName))
161
+ {
162
+ LogWarningOnce($"Duplicate event: {evt.eventName}");
163
+ continue;
164
+ }
165
+
166
+ dictEvent.Add(evt.eventName, evt);
167
+ }
168
+ }
169
+
170
+ private Dictionary<string, EventQuality> DictEvent
171
+ {
172
+ get
173
+ {
174
+ if (dictEvent == null) BuildCache();
175
+ return dictEvent;
176
+ }
177
+ }
178
+ #if UNITY_EDITOR
179
+ [ContextMenu("Validate Unique EventNames")]
180
+ private void ValidateUniqueEventNames()
181
+ {
182
+ HashSet<string> eventNames = new HashSet<string>();
183
+
184
+ lstEvent = lstEvent.OrderBy(evt => evt.eventName).ToList();
185
+
186
+ foreach (EventQuality eventQuality in lstEvent)
187
+ {
188
+ if (string.IsNullOrWhiteSpace(eventQuality.eventName)) Debug.LogWarning("Event name cannot be empty or whitespace.");
189
+
190
+ if (!eventNames.Add(eventQuality.eventName)) Debug.LogWarning($"Duplicate event name found: {eventQuality.eventName}. Event names must be unique.");
191
+
192
+ HashSet<string> paramNames = new HashSet<string>();
193
+
194
+ eventQuality.lstParam = eventQuality.lstParam.OrderBy(param => param.paramName).ToList();
195
+
196
+ foreach (var param in eventQuality.lstParam)
197
+ {
198
+ if (string.IsNullOrWhiteSpace(param.paramName)) Debug.LogWarning("Event parameter name cannot be empty or whitespace.");
199
+
200
+ if (!paramNames.Add(param.paramName)) Debug.LogWarning($"Duplicate parameter name found in event {eventQuality.eventName}: {param.paramName}. Parameter names must be unique within an event.");
201
+ }
202
+ }
203
+ EditorUtility.SetDirty(this);
204
+ }
205
+ #endif
206
+
207
+ public void CheckEventQuality(string eventName, Dictionary<string, object> parameters)
208
+ {
209
+ if (!DictEvent.ContainsKey(eventName))
210
+ {
211
+ LogWarningOnce($"Event not found in AnalyticQuality: {eventName}");
212
+ return;
213
+ }
214
+
215
+ DictEvent[eventName].ValidateParams(eventName, parameters);
216
+ }
217
+ }
218
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: fc7588fb4889749c8ba1469aeaf0d42d
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -4,4 +4,5 @@
4
4
  #if UNITY_EDITOR
5
5
  [assembly: InternalsVisibleTo("AmaGDK.Editor")]
6
6
  [assembly: InternalsVisibleTo("AmaGDK.Test.Editor")]
7
- #endif
7
+ [assembly: InternalsVisibleTo("AmaGDK.Dev.Editor")]
8
+ #endif
@@ -0,0 +1,59 @@
1
+ using System;
2
+ using System.Linq;
3
+ using UnityEngine;
4
+
5
+ namespace Amanotes.Core.Internal
6
+ {
7
+ [CreateAssetMenu(fileName = "AmaGDKEmbedRemoteConfig", menuName = "Ama GDK/Embed Remote Config", order = 2)]
8
+ [Serializable]
9
+ internal class EmbedRemoteConfigAsset : ScriptableObject
10
+ {
11
+ public int version = 1;
12
+ [SerializeField]
13
+ internal string activeId;
14
+ public bool overrideInEditor;
15
+ public KeyValue[] overrideConfig;
16
+ [SerializeField]
17
+ internal EmbedConfig[] configs;
18
+ private EmbedConfig activeConfig;
19
+ internal EmbedConfig ActiveConfig
20
+ {
21
+ get
22
+ {
23
+ if (activeConfig != null) return activeConfig;
24
+ activeConfig = GetConfigById(activeId);
25
+ return activeConfig;
26
+ }
27
+ }
28
+
29
+ internal EmbedConfig GetConfigById(string id)
30
+ {
31
+ var config = configs?.FirstOrDefault(a => a.id == id);
32
+ if (config == null)
33
+ Logging.LogWarningOnce($"Not found embedded config <{id}>");
34
+ return config;
35
+ }
36
+
37
+ internal void SetActiveConfig(string id)
38
+ {
39
+ activeId = id;
40
+ activeConfig = null;
41
+ }
42
+ }
43
+
44
+ [Serializable]
45
+ internal class EmbedConfig
46
+ {
47
+ public string id;
48
+ public string fetchUrl;
49
+ public KeyValue[] items;
50
+ }
51
+
52
+ [Serializable]
53
+ internal class KeyValue
54
+ {
55
+ public string key;
56
+ public string value;
57
+ }
58
+ }
59
+
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: c9b85abfe1c1641eb8627956b9ef33af
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.amanotes.gdk",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "displayName": "AmaGDK",
5
5
  "description": "Amanotes Game Development Kit",
6
6
  "unity": "2020.3",