com.amanotes.gdk 0.2.56 → 0.2.58

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 (35) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/Editor/AmaGDKEditor.cs +12 -22
  3. package/Editor/Extra/GDKLocalBuild.cs +7 -6
  4. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  5. package/Extra/CheckDiskSpace.unitypackage +0 -0
  6. package/Extra/ForceUpdate.unitypackage +0 -0
  7. package/Extra/PostProcessor.unitypackage +0 -0
  8. package/Extra/SDKVersionTracking.unitypackage +0 -0
  9. package/Packages/AmaGDKConfig.unitypackage +0 -0
  10. package/Packages/AmaGDKExample.unitypackage +0 -0
  11. package/Packages/AmaGDKTest.unitypackage +0 -0
  12. package/Packages/AppsFlyerAdapter.unitypackage +0 -0
  13. package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
  14. package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
  15. package/Packages/IronSourceAdapter.unitypackage +0 -0
  16. package/Packages/RevenueCatAdapter.unitypackage +0 -0
  17. package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
  18. package/Runtime/AmaGDK.Adapters.cs +1 -1
  19. package/Runtime/AmaGDK.Ads.cs +69 -45
  20. package/Runtime/AmaGDK.Analytics.cs +8 -10
  21. package/Runtime/AmaGDK.RemoteConfig.cs +5 -16
  22. package/Runtime/AmaGDK.UserProfile.cs +15 -2
  23. package/Runtime/AmaGDK.cs +36 -3
  24. package/Runtime/Internal/AmaGDK.Internal.cs +29 -0
  25. package/Runtime/Internal/AmaGDK.Utils.cs +42 -0
  26. package/Runtime/Internal/AmaGDK.WebUtils.cs +2 -1
  27. package/Runtime/Internal/GDKGeoLocationcs.cs +119 -0
  28. package/Runtime/Internal/GDKGeoLocationcs.cs.meta +3 -0
  29. package/Runtime/Internal/GDKServerTime.cs +211 -0
  30. package/Runtime/Internal/GDKServerTime.cs.meta +3 -0
  31. package/Runtime/Klavar/Attributes/ToStringAttribute.cs +15 -7
  32. package/Runtime/Klavar/KlavarContainer.cs +4 -3
  33. package/Runtime/Klavar/KlavarEvent.cs +1 -1
  34. package/Runtime/Klavar/KlavarEventUtils.cs +3 -3
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.58 - 2024-04-09]
4
+ - [DEV] combine code coverage report
5
+ - [Dev] Improve GDK Utils
6
+ - [Dev] Add hints to AdCallback APIs
7
+ - [Fix] timeDiffLastFire always returns 0 in adShowCalled callbacks
8
+ - [Fix] Various warnings fixes due to invalid examples
9
+ - [Fix] Warning on AdContext destroyed caused by invalid editor ad callback
10
+
11
+
12
+ ## [0.2.57 - 2024-04-08]
13
+ - [Dev] Fix bug in value conversion within KlavarEvent
14
+ - [Dev] Auto get change log
15
+ - [Dev] Auto publish command
16
+ - [Dev] Upgrade to Unity 2021.3.35f1
17
+ - [Dev] Add support for various banner sizes in IronsourceAdapter
18
+ - [Dev] Clean up and add tooltip for options
19
+ - [Fix] Null exception in LocalBuild Version
20
+
21
+
3
22
  ## [0.2.56 - 2024-04-03]
4
23
  - Release AmaGDK v0.2.56
5
24
 
@@ -369,20 +369,7 @@ namespace Amanotes.Editor
369
369
  {
370
370
  configOpen.Add(adapterId, isOpen = false);
371
371
  }
372
-
373
- // if (adapter == null)
374
- // {
375
- // // EditorGUILayout.HelpBox("Adapter<" + adapterId + "> not imported!", MessageType.Info);
376
- // string adapterName = FindBestAdapterFor(adapterId);
377
- // var package = $"{Res.PACKAGE_PATH}{adapterName}";
378
- //
379
- // if (GUILayout.Button(adapterName))
380
- // {
381
- // AssetDatabase.ImportPackage(package, false);
382
- // }
383
- // return;
384
- // }
385
-
372
+
386
373
  SDKStatus status = SDKStatus.Get(adapterId);
387
374
  bool changed = AmaGUI.Foldout(ObjectNames.NicifyVariableName(adapterId), ref isOpen, () =>
388
375
  {
@@ -461,18 +448,21 @@ namespace Amanotes.Editor
461
448
  EditorGUILayout.HelpBox($"{adapterId} SDK not installed", MessageType.Warning);
462
449
  return;
463
450
  }
451
+
464
452
  if (!status.adapterInstalled)
465
453
  {
466
454
  EditorGUILayout.HelpBox($"{adapterId}-GDKAdapter not installed", MessageType.Warning);
455
+ if (adapterId == IRONSOURCE)
456
+ {
457
+ AmaGUI.Foldout("Enabled AdNetworks", ref showIRSAdapter, () =>
458
+ {
459
+ DrawIRSAdapterVersions();
460
+ GUILayout.Space(8f);
461
+ });
462
+ }
467
463
  return;
468
- }
469
-
470
- if (!status.adapterInstalled)
471
- {
472
- EditorGUILayout.HelpBox($"{adapterId}-GDKAdapter not installed", MessageType.Warning);
473
- return;
474
- }
475
-
464
+ }
465
+
476
466
  DrawModuleConfig(adapter, adapterId);
477
467
  }
478
468
 
@@ -11,7 +11,7 @@ using UnityObject = UnityEngine.Object;
11
11
 
12
12
  namespace Amanotes.Editor
13
13
  {
14
- [CreateAssetMenu(menuName = "Ama GDK/Build Config", fileName = "LocalBuild")]
14
+ [CreateAssetMenu(menuName = "Ama GDK/Local Build Config", fileName = "LocalBuild")]
15
15
  public class GDKLocalBuild : ScriptableObject
16
16
  {
17
17
  private const string DEFAULT_BUILD_FOLDER = "../../_Build";
@@ -37,7 +37,6 @@ namespace Amanotes.Editor
37
37
  public List<UnityObject> listUnused;
38
38
  public BuildRequestTS buildRQ;
39
39
  public UnityEvent beforeBuild;
40
- public TextAsset shTemplate;
41
40
 
42
41
  [NonSerialized] private BuildInfo _buildInfo;
43
42
  [NonSerialized] private int _lockCount;
@@ -493,12 +492,14 @@ namespace Amanotes.Editor
493
492
  buildNumber++;
494
493
  if (string.IsNullOrEmpty(buildVersion)) buildVersion = "1.0.0";
495
494
 
496
- string[] arr = buildVersion.Split('.');
497
- int last = int.Parse(arr[2]);
495
+ List<string> list = buildVersion.Split('.').ToList();
496
+ if (list.Count < 2) list.Add("0");
497
+ if (list.Count < 3) list.Add("0");
498
+ int last = int.Parse(list[2]);
498
499
  last++;
499
500
 
500
- arr[2] = last.ToString();
501
- buildVersion = string.Join(".", arr);
501
+ list[2] = last.ToString();
502
+ buildVersion = string.Join(".", list);
502
503
  }
503
504
  }
504
505
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -13,7 +13,7 @@ namespace Amanotes.Core.Internal
13
13
  [HideInInspector] [SerializeField] internal bool enabled = true;
14
14
  [NonSerialized] internal string adapterId;
15
15
  [NonSerialized] internal string adapterVersion;
16
- [NonSerialized] protected internal Status status = Status.None;
16
+ [NonSerialized] internal protected Status status = Status.None;
17
17
 
18
18
  protected abstract void Init(Action<bool> onComplete);
19
19
 
@@ -9,10 +9,11 @@ using static Amanotes.Core.AmaGDK;
9
9
 
10
10
  namespace Amanotes.Core
11
11
  {
12
-
13
-
14
12
  public static class AdExtension
15
13
  {
14
+ /// <summary>
15
+ /// Invoke when ad view has opened
16
+ /// </summary>
16
17
  public static IAdCallback OnAdOpened(this IAdCallback ad, Action callback)
17
18
  {
18
19
  if (!MakeSureAdNotNull(ad)) return null;
@@ -21,6 +22,9 @@ namespace Amanotes.Core
21
22
  return ad;
22
23
  }
23
24
 
25
+ /// <summary>
26
+ /// The user finished watching the video, and should be rewarded.
27
+ /// </summary>
24
28
  public static IAdCallback OnRewardReceived(this IAdCallback ad, Action callback)
25
29
  {
26
30
  if (!MakeSureAdNotNull(ad)) return null;
@@ -29,6 +33,9 @@ namespace Amanotes.Core
29
33
  return ad;
30
34
  }
31
35
 
36
+ /// <summary>
37
+ /// Invoked when the video ad was clicked.
38
+ /// </summary>
32
39
  public static IAdCallback OnAdClicked(this IAdCallback ad, Action callback)
33
40
  {
34
41
  if (!MakeSureAdNotNull(ad)) return null;
@@ -37,6 +44,9 @@ namespace Amanotes.Core
37
44
  return ad;
38
45
  }
39
46
 
47
+ /// <summary>
48
+ /// Callback when the rewarded video ad was failed to show
49
+ /// </summary>
40
50
  public static IAdCallback OnAdShowFailed(this IAdCallback ad, Action callback)
41
51
  {
42
52
  if (!MakeSureAdNotNull(ad)) return null;
@@ -45,6 +55,9 @@ namespace Amanotes.Core
45
55
  return ad;
46
56
  }
47
57
 
58
+ /// <summary>
59
+ /// Invoked before OnAdOpened but is not reliable (not supported by all adNetworks)
60
+ /// </summary>
48
61
  public static IAdCallback OnAdShowSuccess(this IAdCallback ad, Action callback)
49
62
  {
50
63
  if (!MakeSureAdNotNull(ad)) return null;
@@ -53,6 +66,9 @@ namespace Amanotes.Core
53
66
  return ad;
54
67
  }
55
68
 
69
+ /// <summary>
70
+ /// Invoke when ad view is about to be closed
71
+ /// </summary>
56
72
  public static IAdCallback OnAdClosed(this IAdCallback ad, Action callback)
57
73
  {
58
74
  if (!MakeSureAdNotNull(ad)) return null;
@@ -61,6 +77,11 @@ namespace Amanotes.Core
61
77
  return ad;
62
78
  }
63
79
 
80
+ /// <summary>
81
+ /// This callback may invokes several times according to the AdShowReadyStatus
82
+ /// - When there is no ad cached (wifi disabled / no internet / wait4Ad / timeout)
83
+ /// - When the ad is cached & ready to show
84
+ /// </summary>
64
85
  public static IAdCallback OnAdShowReadyStatus(this IAdCallback ad, Action<AdShowReadyStatus> callback)
65
86
  {
66
87
  if (!MakeSureAdNotNull(ad)) return null;
@@ -69,6 +90,10 @@ namespace Amanotes.Core
69
90
  return ad;
70
91
  }
71
92
 
93
+ /// <summary>
94
+ /// The finalize callback that will always trigger to mark an end for the AdShowRoutine
95
+ ///
96
+ /// </summary>
72
97
  public static IAdCallback OnAdResult(this IAdCallback ad, Action<bool> callback)
73
98
  {
74
99
  if (!MakeSureAdNotNull(ad)) return null;
@@ -79,7 +104,7 @@ namespace Amanotes.Core
79
104
 
80
105
  /// <summary>
81
106
  /// Call right before the ad actually open (that means Ad has already been cached & available to show).
82
- /// This callback will not be triggered in case Ad is not available or no internet
107
+ /// This callback will not be triggered in case Ad is not available or no internet (in those case use OnAdShowReadyStatus callback)
83
108
  /// </summary>
84
109
  public static IAdCallback OnBeforeAdOpen(this IAdCallback ad, Action callback)
85
110
  {
@@ -126,7 +151,8 @@ namespace Amanotes.Core
126
151
  internal class AdStat
127
152
  {
128
153
  // per session
129
- internal float lastCallTime;
154
+ internal float prevCallTime;
155
+ internal float currentCallTime;
130
156
  internal int callCount;
131
157
 
132
158
  internal float lastSuccessTime;
@@ -156,7 +182,18 @@ namespace Amanotes.Core
156
182
 
157
183
  internal int TimeSinceLastShowCalled(AdType adType)
158
184
  {
159
- return (int)(Time.realtimeSinceStartup - GetStat(adType).lastCallTime);
185
+ AdStat stat = GetStat(adType);
186
+ AdShowContext context = Ads.context;
187
+
188
+ // Right inside XXX_ShowCalled() callbacks
189
+ if (context != null && context.adType == adType)
190
+ {
191
+ if (stat.callCount == 1) return 0; // first call
192
+ return (int)(stat.currentCallTime - stat.prevCallTime);
193
+ }
194
+
195
+ if (stat.callCount == 0) return 0; // not yet called
196
+ return (int)(Time.realtimeSinceStartup - stat.prevCallTime);
160
197
  }
161
198
 
162
199
  internal int TimeSinceLastShowSuccess(AdType adType)
@@ -173,20 +210,7 @@ namespace Amanotes.Core
173
210
  [Serializable]
174
211
  public class AdAdapterConfig
175
212
  {
176
- // public bool manualInit;
177
- public int adLoadTimeoutInSecs = 60;
178
- public int adShowTimeoutInSecs = 5;
179
- public BannerPosition bannerPosition = BannerPosition.BOTTOM;
180
- public bool autoLogEvent = true;
181
-
182
- public bool enableInterstitial = true;
183
- public bool enableRewarded = true;
184
- public bool enableBanner = true;
185
- public bool autoMute = true;
186
- public bool autoTimeScale = true;
187
213
 
188
- [Header("EDITOR")]
189
- [Range(0f, 1f)] public float rewardAdShowSuccessRate = 1;
190
214
  }
191
215
 
192
216
  public partial class Ads
@@ -255,7 +279,7 @@ namespace Amanotes.Core
255
279
  if (_isBannerShowing)
256
280
  {
257
281
  _isBannerShowing = false;
258
- ShowBanner(_showingBannerPosition);
282
+ ShowBanner();
259
283
  }
260
284
  }
261
285
 
@@ -298,22 +322,22 @@ namespace Amanotes.Core
298
322
 
299
323
 
300
324
  private static bool _isBannerShowing;
301
- private static BannerPosition _showingBannerPosition;
302
325
 
303
- public static void ShowBanner(BannerPosition? bannerPosition = null)
326
+ public static void ShowBanner()
304
327
  {
328
+ if (_config.ad.enableBanner) return;
329
+
305
330
  if (_isBannerShowing) return;
306
331
  _isBannerShowing = true;
307
- _showingBannerPosition = bannerPosition ?? Config.ad.bannerPosition;
308
-
309
332
  if (_adapter == null) return;
310
333
 
311
334
  dispatcher.Dispatch(Event.BANNER_SHOW);
312
- _adapter.ShowBanner(_showingBannerPosition);
335
+ _adapter.ShowBanner();
313
336
  }
314
337
 
315
338
  public static void HideBanner()
316
339
  {
340
+ if (_config.ad.enableBanner) return;
317
341
  if (!_isBannerShowing) return;
318
342
  _isBannerShowing = false;
319
343
  if (_adapter == null) return;
@@ -369,22 +393,27 @@ namespace Amanotes.Core.Internal
369
393
  {
370
394
  [Serializable] public class AdModuleConfig
371
395
  {
372
- // [Header("----- MODULE CONFIG -----")]
373
- // public bool manualInit;
396
+ [Tooltip("Automatically retry requesting ad if an ad failed to response in this amount of time (in secs)")]
374
397
  public int adLoadTimeoutInSecs = 60;
398
+
399
+ [Tooltip("When an ad is not ready to show, keep waiting for this amount of time (in secs)\n\nIf the ad is ready during that time, it would be shown normally.")]
375
400
  public int adShowTimeoutInSecs = 5;
376
- public BannerPosition bannerPosition = BannerPosition.BOTTOM;
377
- public bool autoLogEvent = true;
401
+
402
+ [Tooltip("When an ad is not ready to show, verify that there is an internet connection by ping the internet check URL .\nIf there is no internet connection, we will not need to wait for adShowTimeout secs")]
378
403
  public bool checkInternet = true;
379
-
380
- public bool enableInterstitial = true;
381
- public bool enableRewarded = true;
382
- public bool enableBanner = true;
383
-
404
+
405
+ [HideInInspector] public bool enableInterstitial = true;
406
+ [HideInInspector] public bool enableRewarded = true;
407
+ [HideInInspector] public bool enableBanner = true;
408
+
409
+ [Tooltip("Before showing an ad: set AudioListener.volume to 0\nAfter the ad is closed: restore the previously saved AudioListener.volume")]
384
410
  public bool autoMute = true;
411
+
412
+ [Tooltip("Before showing an ad: set Time.timeScale to 0\nAfter the ad is closed: restore the previously saved Time.timeScale")]
385
413
  public bool autoTimeScale = true;
386
414
 
387
415
  [Header("EDITOR")]
416
+ [Tooltip("Only applicable in Editor\n0 = 0% success\n1 = 100% success")]
388
417
  [Range(0f, 1f)] public float rewardAdShowSuccessRate = 1;
389
418
  }
390
419
 
@@ -399,14 +428,14 @@ namespace Amanotes.Core.Internal
399
428
  protected AdModuleConfig moduleConfig => Config.ad;
400
429
 
401
430
  // APIs
402
- protected internal abstract AdLogic interstitial { get; }
403
- protected internal abstract AdLogic rewardVideo { get; }
431
+ internal protected abstract AdLogic interstitial { get; }
432
+ internal protected abstract AdLogic rewardVideo { get; }
404
433
 
405
- public abstract void ShowBanner(BannerPosition bannerPosition);
434
+ public abstract void ShowBanner();
406
435
  public abstract void HideBanner();
407
436
 
408
437
  // Internally used
409
- protected internal abstract void SetUserId(string userId);
438
+ internal protected abstract void SetUserId(string userId);
410
439
  }
411
440
 
412
441
  public interface IAdCallback { }
@@ -443,13 +472,6 @@ namespace Amanotes.Core.Internal
443
472
  Fail = 256
444
473
  }
445
474
 
446
- public enum BannerPosition
447
- {
448
- TOP = 1,
449
- BOTTOM = 2,
450
- RECTANGLE = 3
451
- }
452
-
453
475
  public enum AdType
454
476
  {
455
477
  Interstitial,
@@ -643,7 +665,9 @@ namespace Amanotes.Core.Internal
643
665
 
644
666
  AdStat stat = Ads.localData.GetStat(adType);
645
667
  stat.callCount++;
646
- stat.lastCallTime = Time.realtimeSinceStartup;
668
+ stat.prevCallTime = stat.currentCallTime;
669
+ stat.currentCallTime = Time.realtimeSinceStartup;
670
+
647
671
  dispatcher.Dispatch(
648
672
  _isInterstitial ? AmaGDK.Event.INTER_SHOW_CALLED : AmaGDK.Event.REWARD_VIDEO_SHOW_CALLED
649
673
  );
@@ -2,7 +2,6 @@ using System;
2
2
  using System.Collections.Concurrent;
3
3
  using System.Collections.Generic;
4
4
  using System.Text;
5
- using System.Text.RegularExpressions;
6
5
  using Amanotes.Core.Internal;
7
6
  using UnityEngine;
8
7
  using UnityEngine.Profiling;
@@ -1050,10 +1049,10 @@ namespace Amanotes.Core.Internal
1050
1049
  {
1051
1050
  // [Header("----- MODULE CONFIG -----")]
1052
1051
  public bool showAnalyticsLog;
1053
- public bool normalizeEventName = true;
1054
- public bool checkMigrationIntegrity = true;
1055
- public bool migrateAccumulatedCount = true;
1056
- public bool enableEventStat;
1052
+ [HideInInspector] public bool normalizeEventName = true;
1053
+ [HideInInspector] public bool checkMigrationIntegrity = true;
1054
+ [HideInInspector] public bool migrateAccumulatedCount = true;
1055
+ [HideInInspector] public bool enableEventStat;
1057
1056
  public AnalyticQualityConfig quality;
1058
1057
  }
1059
1058
 
@@ -1079,11 +1078,10 @@ namespace Amanotes.Core.Internal
1079
1078
 
1080
1079
  public abstract class AnalyticsAdapter : Adapter2
1081
1080
  {
1082
- public bool SendEventByDefault;
1083
-
1084
- public List<string> NeverSend;
1085
- public List<string> AlwaysSend;
1086
-
1081
+ [HideInInspector] public List<string> NeverSend = new List<string>();
1082
+ [HideInInspector] public List<string> AlwaysSend = new List<string>();
1083
+ protected abstract bool SendEventByDefault { get; }
1084
+
1087
1085
  public bool IsForbidden(string eventName)
1088
1086
  {
1089
1087
  return SendEventByDefault
@@ -361,20 +361,6 @@ namespace Amanotes.Core
361
361
  }
362
362
  }
363
363
  }
364
-
365
- // public static class RemoteConfigExtension
366
- // {
367
- // public static void InitRemoteConfigModule(this RemoteConfigAdapter ap, Action<bool> onCompleteCallback)
368
- // {
369
- // if (AmaGDK.RemoteConfig.fetchState != AmaGDK.RemoteConfig.FetchState.None)
370
- // {
371
- // LogWarning($"[RemoteConfig] Current state {AmaGDK.RemoteConfig.fetchState.ToString()}\nCannot start to initialize");
372
- // return;
373
- // }
374
- //
375
- // AmaGDK.RemoteConfig.NextState();
376
- // }
377
- // }
378
364
  }
379
365
 
380
366
  namespace Amanotes.Core.Internal
@@ -382,10 +368,13 @@ namespace Amanotes.Core.Internal
382
368
  [Serializable]
383
369
  public class RemoteConfigConfig
384
370
  {
385
- // [Header("----- MODULE CONFIG -----")]
386
-
371
+ [Tooltip("Fetch timeout for first session (in secs)")]
387
372
  public float firstFetchTimeOutInSeconds = 10;
373
+
374
+ [Tooltip("Fetch timeout for 2nd+ session (in secs)")]
388
375
  public float defaultTimeOutInSeconds = 3;
376
+
377
+ [Tooltip("To create embed Remote config:\n\n- Right click in Project panel\n- Create > AmaGDK > Embed Remote Config\n- Drag & drop the asset here")]
389
378
  [SerializeField] internal EmbedRemoteConfigAsset embedRemoteConfig;
390
379
  }
391
380
 
@@ -120,7 +120,17 @@ namespace Amanotes.Core
120
120
  {
121
121
  get => _session;
122
122
  }
123
-
123
+
124
+ public string Country
125
+ {
126
+ get => _country;
127
+ set
128
+ {
129
+ _country = ValidateId(value, COUNTRY_CODE_PATTERN);
130
+ SaveIfDirty();
131
+ }
132
+ }
133
+
124
134
  public void UpdateUserId()
125
135
  {
126
136
  #if UNITY_ANDROID
@@ -199,12 +209,15 @@ namespace Amanotes.Core
199
209
  private string _appsFlyerId;
200
210
  [SerializeField]
201
211
  private string _installationId;
202
- [SerializeField]
212
+ [SerializeField]
203
213
  private int _session;
214
+ [SerializeField]
215
+ private string _country;
204
216
 
205
217
  private const string ALPHANUMERIC_HYPHEN_PATTERN = @"^(?=.*\d)(?=.*[a-zA-Z])(?=.*-).+$";
206
218
  private const string ALPHANUMERIC_PATTERN = @"^(?=.*\d)(?=.*[a-zA-Z])[a-zA-Z\d]+$";
207
219
  private const string NUMERIC_HYPHEN_PATTERN = @"^(?=.*\d)(?=.*-)[\d-]+$";
220
+ private const string COUNTRY_CODE_PATTERN = @"^[A-Z]{2}$";
208
221
 
209
222
  private const string AMA_DEVICE_ID_KEY = "ama_device_id";
210
223
 
package/Runtime/AmaGDK.cs CHANGED
@@ -17,13 +17,14 @@ namespace Amanotes.Core
17
17
  {
18
18
  public partial class AmaGDK : MonoBehaviour
19
19
  {
20
- public const string VERSION = "0.2.56";
20
+ public const string VERSION = "0.2.58";
21
21
 
22
22
  internal static AmaGDK _instance;
23
23
  internal static Status _status = Status.None;
24
24
  internal static bool _allowInit = false;
25
25
  public bool autoInit = true;
26
-
26
+ internal static readonly GDKGeoLocation GeoLocation = new GDKGeoLocation();
27
+ internal static readonly GDKServerTime ServerTime = new GDKServerTime();
27
28
  private static ConfigAsset _config = null;
28
29
 
29
30
  public static partial class Event
@@ -173,7 +174,6 @@ namespace Amanotes.Core
173
174
  }
174
175
  }
175
176
 
176
-
177
177
  IEnumerator InitRoutine()
178
178
  {
179
179
  if (Config == null)
@@ -192,12 +192,39 @@ namespace Amanotes.Core
192
192
  yield return InitAdapters(sb);
193
193
 
194
194
  StartModules();
195
+
196
+ ConfigGeoLocation();
197
+ if (Config.enableServerTime)
198
+ {
199
+ ServerTime.UpdateTime();
200
+ }
201
+
195
202
  dispatcher.Dispatch(Event.GDK_INIT_END);
196
203
 
197
204
  _status = Status.Ready;
198
205
  dispatcher.Dispatch(Event.GDK_READY);
199
206
  Debug.Log($"AmaGDK v{VERSION} | {READY} in {Time.realtimeSinceStartup - startTime:#0.00}s\n\n{sb}");
200
207
  }
208
+
209
+ private void ConfigGeoLocation()
210
+ {
211
+ if (Config.enableGeoLocation)
212
+ {
213
+ var countryCode = User.Country;
214
+ if (string.IsNullOrWhiteSpace(countryCode))
215
+ {
216
+ GeoLocation.GetCountryCode(remoteCountryCode =>
217
+ {
218
+ User.Country = remoteCountryCode;
219
+ GeoLocation.SetCountryCode(remoteCountryCode);
220
+ });
221
+ }
222
+ else
223
+ {
224
+ GeoLocation.SetCountryCode(countryCode);
225
+ }
226
+ }
227
+ }
201
228
  }
202
229
 
203
230
  public partial class AmaGDK // PUBLIC APIS
@@ -461,4 +488,10 @@ namespace Amanotes.Core
461
488
  }
462
489
  }
463
490
  }
491
+
492
+ public class GDKException : Exception
493
+ {
494
+ public GDKException() { }
495
+ public GDKException(string message) : base(message) { }
496
+ }
464
497
  }
@@ -182,4 +182,33 @@ namespace Amanotes.Core.Internal
182
182
  public interface IOnApplicationQuit { void OnApplicationQuit(); }
183
183
  public interface IOnApplicationFocus { void OnApplicationFocus(bool hasFocus); }
184
184
  public interface IOnFrameUpdate { void OnFrameUpdate(); }
185
+
186
+ public static class Utils
187
+ {
188
+ /// <summary>
189
+ /// Registers the list of server time providers.
190
+ /// </summary>
191
+ /// <param name="providers">List of server time providers implementing IServerTimeProvider interface or extending BaseServerTimeProvider.</param>
192
+ /// <remarks>
193
+ /// It is mandatory to call RegisterServerTimeProvider before initializing AmaGDK by calling AmaGDK.Init().
194
+ /// </remarks>
195
+ public static void RegisterServerTimeProvider(List<IServerTimeProvider> providers)
196
+ {
197
+ GDKUtils.ThrowIf(AmaGDK._status != Status.None, "Please call RegisterServerTimeProvider() before initializing AmaGDK by calling AmaGDK.Init()");
198
+ ServerTime.SetTimeProviders(providers);
199
+ }
200
+
201
+ /// <summary>
202
+ /// Set the GeoLocation Provider.
203
+ /// </summary>
204
+ /// <param name="provider">provider implementing IGeoLocationProvider interface.</param>
205
+ /// <remarks>
206
+ /// It is mandatory to call SetGeoLocationProvider before initializing AmaGDK by calling AmaGDK.Init().
207
+ /// </remarks>
208
+ public static void SetGeoLocationProvider(IGeoLocationProvider provider)
209
+ {
210
+ GDKUtils.ThrowIf(AmaGDK._status != Status.None, "Please call SetGeoLocationProvider() before initializing AmaGDK by calling AmaGDK.Init()");
211
+ GeoLocation.SetGeoLocationProvider(provider);
212
+ }
213
+ }
185
214
  }
@@ -77,6 +77,42 @@ namespace Amanotes.Core.Internal
77
77
  // Check if each segment exists in the output string in the correct order
78
78
  return AreSegmentsPresentInOrder(segments, formattedString);
79
79
  }
80
+
81
+ /// <summary>
82
+ /// Retrieves the current server time adjusted to the user's timezone.
83
+ /// </summary>
84
+ /// <returns>The current server time adjusted to the user's timezone if available; otherwise, null.</returns>
85
+ public static DateTime? GetServerTime()
86
+ {
87
+ return AmaGDK.ServerTime.GetTime();
88
+ }
89
+
90
+ /// <summary>
91
+ /// Asynchronously retrieves the current server time with a callback function, adjusted to the user's timezone.
92
+ /// </summary>
93
+ /// <param name="result">Callback function invoked with the server time adjusted to the user's timezone if available; otherwise, null is passed in case of network or server errors.</param>
94
+ public static void GetServerTime(Action<DateTime?> result)
95
+ {
96
+ AmaGDK.ServerTime.GetTime(result);
97
+ }
98
+
99
+ /// <summary>
100
+ /// Gets the country code.
101
+ /// </summary>
102
+ /// <returns>The country code if available; otherwise, null.</returns>
103
+ public static string GetCountryCode()
104
+ {
105
+ return AmaGDK.GeoLocation.GetCountryCode();
106
+ }
107
+
108
+ /// <summary>
109
+ /// Gets the country code asynchronously with a callback function.
110
+ /// </summary>
111
+ /// <param name="result">Callback function to be invoked with the country code if available; otherwise, null will be passed in case of network or server errors.</param>
112
+ public static void GetCountryCode(Action<string> result)
113
+ {
114
+ AmaGDK.GeoLocation.GetCountryCode(result);
115
+ }
80
116
 
81
117
  static bool AreSegmentsPresentInOrder(string[] segments, string outputString)
82
118
  {
@@ -117,6 +153,12 @@ namespace Amanotes.Core.Internal
117
153
  segments[matches.Count] = template.Substring(lastIndex);
118
154
  return segments;
119
155
  }
156
+
157
+ internal static void ThrowIf(bool condition, string message)
158
+ {
159
+ if (condition)
160
+ throw new GDKException(message);
161
+ }
120
162
  }
121
163
 
122
164
  public static class GDKReflection
@@ -151,6 +151,7 @@ namespace Amanotes.Core.Internal
151
151
 
152
152
  public partial class SDKConfig
153
153
  {
154
- public string internetCheckUrl = "http://www.google.com";
154
+ [Tooltip("The URL we would use to ping to verify if the app is actually connected to the internet")]
155
+ public string internetCheckUrl = "http://clients3.google.com/generate_204";
155
156
  }
156
157
  }
@@ -0,0 +1,119 @@
1
+ using System;
2
+ using System.Collections;
3
+ using UnityEngine;
4
+ using UnityEngine.Networking;
5
+
6
+ namespace Amanotes.Core.Internal
7
+ {
8
+ public partial class ConfigAsset
9
+ {
10
+ [SerializeField] public bool enableGeoLocation = true;
11
+ }
12
+
13
+ internal class GDKGeoLocation
14
+ {
15
+ private IGeoLocationProvider provider = new CountryIS_GeoLocationProvider();
16
+ private string localCountryCode;
17
+
18
+ public void SetGeoLocationProvider(IGeoLocationProvider provider)
19
+ {
20
+ this.provider = provider;
21
+ }
22
+ public string GetCountryCode()
23
+ {
24
+ GDKUtils.ThrowIf(!AmaGDK.Config.enableGeoLocation, "GeoLocation is disabled, Please enable it in the AmaGDKConfig");
25
+ return localCountryCode;
26
+ }
27
+
28
+ public void GetCountryCode(Action<string> result)
29
+ {
30
+ GDKUtils.ThrowIf(!AmaGDK.Config.enableGeoLocation, "GeoLocation is disabled, Please enable it in the AmaGDKConfig");
31
+ if (!string.IsNullOrWhiteSpace(localCountryCode))
32
+ {
33
+ result?.Invoke(localCountryCode);
34
+ return;
35
+ }
36
+ provider.FetchGeoLocation(data =>
37
+ {
38
+ result?.Invoke(data.countryCode?.ToUpper());
39
+ if (data.errorMessage != null)
40
+ Logging.Log(data.errorMessage);
41
+ });
42
+ }
43
+
44
+ internal void SetCountryCode(string countryCode)
45
+ {
46
+ localCountryCode = countryCode;
47
+ }
48
+ }
49
+ }
50
+
51
+ namespace Amanotes.Core
52
+ {
53
+ public class GeoLocationData
54
+ {
55
+ public string countryCode;
56
+ public string errorMessage;
57
+
58
+ public GeoLocationData(string countryCode, string errorMessage = null)
59
+ {
60
+ this.countryCode = countryCode;
61
+ this.errorMessage = errorMessage;
62
+ }
63
+
64
+ public static GeoLocationData WithErrorMessage(string message)
65
+ {
66
+ return new GeoLocationData(null, message);
67
+ }
68
+ }
69
+
70
+ public interface IGeoLocationProvider
71
+ {
72
+ public void FetchGeoLocation(Action<GeoLocationData> result);
73
+ }
74
+
75
+ public class CountryIS_GeoLocationProvider: IGeoLocationProvider
76
+ {
77
+ public const string URL = "https://api.country.is/";
78
+ [Serializable]
79
+ private class LocationData
80
+ {
81
+ public string country;
82
+ }
83
+
84
+ void IGeoLocationProvider.FetchGeoLocation(Action<GeoLocationData> result)
85
+ {
86
+ AmaGDK._instance.StartCoroutine(FetchGeoLocationRoutine(result));
87
+ }
88
+
89
+ private IEnumerator FetchGeoLocationRoutine(Action<GeoLocationData> result)
90
+ {
91
+ using (var request = UnityWebRequest.Get(URL))
92
+ {
93
+ yield return request.SendWebRequest();
94
+
95
+ if (request.result != UnityWebRequest.Result.Success)
96
+ {
97
+ result?.Invoke(GeoLocationData.WithErrorMessage($"Error fetching location: {request.error}"));
98
+ yield break;
99
+ }
100
+
101
+ try
102
+ {
103
+ var data = ParseResponse(request.downloadHandler.text);
104
+ result?.Invoke(data);
105
+ }
106
+ catch (Exception e)
107
+ {
108
+ result?.Invoke(GeoLocationData.WithErrorMessage($"Error parsing location data: {e.Message}"));
109
+ }
110
+ }
111
+ }
112
+
113
+ private GeoLocationData ParseResponse(string response)
114
+ {
115
+ var locationData = JsonUtility.FromJson<LocationData>(response);
116
+ return new GeoLocationData(locationData.country);
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: aaf2bb28b9484523af7d43aa2f6fb36e
3
+ timeCreated: 1711956851
@@ -0,0 +1,211 @@
1
+ using System;
2
+ using System.Collections;
3
+ using System.Collections.Generic;
4
+ using UnityEngine;
5
+ using UnityEngine.Networking;
6
+
7
+ namespace Amanotes.Core
8
+ {
9
+ public class ServerTimeData
10
+ {
11
+ public DateTime? time;
12
+ public string errorMessage;
13
+
14
+ public ServerTimeData(DateTime time)
15
+ {
16
+ this.time = time;
17
+ errorMessage = null;
18
+ }
19
+
20
+ public ServerTimeData(string errorMessage)
21
+ {
22
+ time = null;
23
+ this.errorMessage = errorMessage;
24
+ }
25
+ }
26
+
27
+ public interface IServerTimeProvider
28
+ {
29
+ public void FetchServerTime(Action<ServerTimeData> result);
30
+ }
31
+
32
+ public abstract class BaseServerTimeProvider : IServerTimeProvider
33
+ {
34
+ public abstract string GetURL();
35
+ public abstract ServerTimeData ParseResponse(string response);
36
+
37
+ public void FetchServerTime(Action<ServerTimeData> result)
38
+ {
39
+ AmaGDK._instance.StartCoroutine(FetchTimeRoutine(result));
40
+ }
41
+
42
+ private IEnumerator FetchTimeRoutine(Action<ServerTimeData> result)
43
+ {
44
+ using (var request = UnityWebRequest.Get(GetURL()))
45
+ {
46
+ yield return request.SendWebRequest();
47
+
48
+ if (request.result != UnityWebRequest.Result.Success)
49
+ {
50
+ result?.Invoke(new ServerTimeData($"Error fetching time: {request.error}"));
51
+ yield break;
52
+ }
53
+
54
+ result?.Invoke(ParseResponse(request.downloadHandler.text));
55
+ }
56
+ }
57
+ }
58
+
59
+ internal class WorldTimeServer_TimeProvider : BaseServerTimeProvider
60
+ {
61
+ private const string URL = "https://www.worldtimeserver.com/current_time_in_LA.aspx";
62
+
63
+ public override ServerTimeData ParseResponse(string response)
64
+ {
65
+ string startStr = "id=\"serverTimeStamp\" value=\"";
66
+ int startIndex = response.IndexOf(startStr) + startStr.Length;
67
+ string unixTimeStr = string.Empty;
68
+ char currentChar = char.MinValue;
69
+ for (int i = startIndex, n = response.Length; i < n; i++)
70
+ {
71
+ currentChar = response[i];
72
+ if (currentChar == '.' || currentChar == '\"') break;
73
+ unixTimeStr += currentChar;
74
+ }
75
+ if (long.TryParse(unixTimeStr, out long unixTime))
76
+ {
77
+ DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(unixTime);
78
+ return new ServerTimeData(dateTimeOffset.DateTime);
79
+ }
80
+ else
81
+ {
82
+ return new ServerTimeData("Error parsing WorldTimeServer data");
83
+ }
84
+ }
85
+ public override string GetURL()
86
+ {
87
+ return URL;
88
+ }
89
+ }
90
+
91
+ internal class WorldTimeAPI_TimeProvider : BaseServerTimeProvider
92
+ {
93
+ private const string URL = "https://worldtimeapi.org/api/ip";
94
+
95
+ [Serializable]
96
+ private class TimeData
97
+ {
98
+ public string datetime;
99
+ }
100
+
101
+ public override string GetURL()
102
+ {
103
+ return URL;
104
+ }
105
+
106
+ public override ServerTimeData ParseResponse(string response)
107
+ {
108
+ try
109
+ {
110
+ var data = JsonUtility.FromJson<TimeData>(response);
111
+ DateTime utcTime = DateTime.ParseExact(data.datetime, "yyyy-MM-dd'T'HH:mm:ss.ffffffzzz", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal);
112
+ return new ServerTimeData(utcTime);
113
+ }
114
+ catch (Exception e)
115
+ {
116
+ return new ServerTimeData($"Error parsing time data: {e.Message}");
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ namespace Amanotes.Core.Internal
123
+ {
124
+ public partial class ConfigAsset
125
+ {
126
+ [SerializeField] public bool enableServerTime = true;
127
+ }
128
+
129
+ internal class GDKServerTime
130
+ {
131
+ private const float MAX_WAIT_TIME = 60;
132
+ private List<IServerTimeProvider> providers = new List<IServerTimeProvider>() { new WorldTimeAPI_TimeProvider(), new WorldTimeServer_TimeProvider() };
133
+ private int currentIndex = 0;
134
+ private IServerTimeProvider provider
135
+ {
136
+ get { return providers[currentIndex]; }
137
+ }
138
+
139
+ private DateTime? serverTime;
140
+ private float timeSinceFetchServerTime;
141
+ private int retryCount = 0;
142
+
143
+ internal GDKServerTime()
144
+ {
145
+ }
146
+
147
+ public void SetTimeProviders(List<IServerTimeProvider> providers)
148
+ {
149
+ GDKUtils.ThrowIf(providers == null || providers.Count == 0, "Server time providers list cannot be null or empty.");
150
+ this.providers = providers;
151
+ currentIndex = 0;
152
+ }
153
+
154
+ public DateTime? GetTime()
155
+ {
156
+ GDKUtils.ThrowIf(!AmaGDK.Config.enableServerTime, "ServerTime is disabled, Please enable it in the AmaGDKConfig");
157
+ if (serverTime == null)
158
+ return null;
159
+ float duration = Time.realtimeSinceStartup - timeSinceFetchServerTime;
160
+ return ((DateTime)serverTime).AddSeconds(duration);
161
+ }
162
+
163
+ public void GetTime(Action<DateTime?> result)
164
+ {
165
+ GDKUtils.ThrowIf(!AmaGDK.Config.enableServerTime, "ServerTime is disabled, Please enable it in the AmaGDKConfig");
166
+ if (serverTime == null)
167
+ {
168
+ UpdateTime(result);
169
+ }
170
+ else
171
+ {
172
+ result(GetTime());
173
+ }
174
+ }
175
+
176
+ public void UpdateTime(Action<DateTime?> result = null)
177
+ {
178
+ provider.FetchServerTime(data =>
179
+ {
180
+ if (data.time != null)
181
+ {
182
+ serverTime = data.time;
183
+ timeSinceFetchServerTime = Time.realtimeSinceStartup;
184
+ result?.Invoke(data.time);
185
+ }
186
+ else
187
+ {
188
+ result?.Invoke(null);
189
+ Logging.Log(data.errorMessage);
190
+ FetchTimeWithOtherProvider();
191
+ }
192
+ });
193
+ }
194
+
195
+ private void FetchTimeWithOtherProvider()
196
+ {
197
+ retryCount++;
198
+ currentIndex = (currentIndex + 1) % providers.Count;
199
+ if (retryCount < providers.Count)
200
+ {
201
+ UpdateTime();
202
+ return;
203
+ }
204
+ float waitTime = Mathf.Min(MAX_WAIT_TIME, Mathf.Pow(2, retryCount - providers.Count + 1));
205
+ GDKUtils.DelayCall(waitTime, () =>
206
+ {
207
+ UpdateTime();
208
+ });
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 04a5b0922db443b39ec9a3edeb2e3337
3
+ timeCreated: 1711943237
@@ -43,14 +43,22 @@ namespace Amanotes.Core
43
43
  {
44
44
  this.converter = converter;
45
45
  }
46
-
47
- private string GetValueAsString(object enumVal)
46
+
47
+ private string GetValueAsString(object fieldValue)
48
48
  {
49
- // get the field
50
- var field = enumVal.GetType().GetField(enumVal.ToString());
51
- var attr = field.GetCustomAttribute<ValueAttribute>();
52
-
53
- return attr != null ? convertMethods[converter](attr.sValue) : convertMethods[converter](enumVal.ToString());
49
+ var fieldStringValue = fieldValue.ToString();
50
+ var fieldType = fieldValue.GetType();
51
+ if (fieldType.IsEnum)
52
+ {
53
+ var members = fieldType.GetMember(fieldStringValue);
54
+ if (members.Length > 0)
55
+ {
56
+ var attribute = (ValueAttribute)GetCustomAttribute(members[0], typeof(ValueAttribute));
57
+ if (attribute != null)
58
+ return convertMethods[converter](attribute.sValue);
59
+ }
60
+ }
61
+ return convertMethods[converter](fieldStringValue);
54
62
  }
55
63
 
56
64
  public override object Convert(object parent, FieldInfo fieldInfo, object param)
@@ -11,9 +11,10 @@ namespace Amanotes.Core
11
11
  public string lastEventName;
12
12
 
13
13
  public static void LogEventError(string message)
14
- {
15
- if (!Application.isBatchMode)
16
- Logging.LogError(message);
14
+ {
15
+ #if !AMAGDK_DEV
16
+ Logging.LogError(message);
17
+ #endif
17
18
  throw new KlavarException(message);
18
19
  }
19
20
  }
@@ -87,7 +87,7 @@ namespace Amanotes.Core
87
87
  {
88
88
  return this
89
89
  .GetType()
90
- .GetFields()
90
+ .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
91
91
  .Where(field => field.GetCustomAttribute<IgnoreAttribute>() == null)
92
92
  .Select(field => Validate(field))
93
93
  .ToDictionary(
@@ -39,14 +39,14 @@ namespace Amanotes.Core
39
39
  public static string ToCamelCase(string text)
40
40
  {
41
41
  var tokens = ToTokens(text);
42
- return string.Join("", tokens.Select(t => Char.ToUpper(t[0]) + t.Substring(1).ToLower()));
42
+ return string.Join("", tokens.Select((t, i) =>
43
+ (i == 0 ? Char.ToLower(t[0]) : Char.ToUpper(t[0])) + t.Substring(1).ToLower()));
43
44
  }
44
45
 
45
46
  public static string ToPascalCase(string text)
46
47
  {
47
48
  var tokens = ToTokens(text);
48
- return string.Join("", tokens.Select((t, i) =>
49
- (i == 0 ? Char.ToLower(t[0]) : Char.ToUpper(t[0])) + t.Substring(1).ToLower()));
49
+ return string.Join("", tokens.Select(t => Char.ToUpper(t[0]) + t.Substring(1).ToLower()));
50
50
  }
51
51
 
52
52
  public static string ToKebabCase(string text)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.amanotes.gdk",
3
- "version": "0.2.56",
3
+ "version": "0.2.58",
4
4
  "displayName": "AmaGDK",
5
5
  "description": "Amanotes Game Development Kit",
6
6
  "unity": "2019.4",