com.amanotes.gdk 0.2.13 → 0.2.16
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.
- package/CHANGELOG.md +6 -0
- package/Editor/EmbedRemoteConfigAssetInspector.cs +191 -0
- package/Editor/EmbedRemoteConfigAssetInspector.cs.meta +11 -0
- package/Editor/MatchSDKVersion.cs +151 -0
- package/Editor/MatchSDKVersion.cs.meta +11 -0
- package/Editor/Utils/AmaGDKEditor.ExtractVersion.cs +1 -1
- package/Editor/Utils/AmaGDKEditor.Utils.cs +52 -0
- package/Extra/AmaGDKInstaller.unitypackage +0 -0
- package/Extra/AmaGDKProject.unitypackage +0 -0
- package/Packages/AdjustAdapter_v4.33.0.unitypackage +0 -0
- package/Packages/AmaGDKConfig.unitypackage +0 -0
- package/Packages/AmaGDKExample.unitypackage +0 -0
- package/Packages/AmaGDKTest.unitypackage +0 -0
- package/Packages/AmaPassport.ATTSupport.unitypackage +0 -0
- package/Packages/AmaPassportAdapter_v1.0.0.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter_v6.5.4.unitypackage +0 -0
- package/Packages/FirebaseAnalyticsAdapter_v9.1.0.unitypackage +0 -0
- package/Packages/FirebaseRemoteConfigAdapter_v9.1.0.unitypackage +0 -0
- package/Packages/IronsourceAdapter_v7.2.6.unitypackage +0 -0
- package/Runtime/AmaGDK.AmaPassport.cs +1 -0
- package/Runtime/AmaGDK.Analytics.cs +22 -3
- package/Runtime/AmaGDK.IAP.cs +423 -0
- package/Runtime/AmaGDK.IAP.cs.meta +11 -0
- package/Runtime/AmaGDK.RemoteConfig.cs +73 -43
- package/Runtime/AmaGDK.cs +3 -2
- package/Runtime/AnalyticQualityAsset.cs +218 -0
- package/Runtime/AnalyticQualityAsset.cs.meta +11 -0
- package/Runtime/EmbedRemoteConfigAsset.cs +59 -0
- package/Runtime/EmbedRemoteConfigAsset.cs.meta +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
using Amanotes.Core.Internal;
|
|
6
|
+
|
|
7
|
+
using static Amanotes.Core.AmaGDK.IAP;
|
|
8
|
+
using static Amanotes.Core.Internal.Logging;
|
|
9
|
+
|
|
10
|
+
namespace Amanotes.Core
|
|
11
|
+
{
|
|
12
|
+
public partial class AmaGDK //In-app purchase
|
|
13
|
+
{
|
|
14
|
+
public partial class IAP //Public
|
|
15
|
+
{
|
|
16
|
+
public static bool IsVIP => !string.IsNullOrEmpty(localData.activeSubscriptionId);
|
|
17
|
+
|
|
18
|
+
public static string ActiveSubscriptionId => localData.activeSubscriptionId;
|
|
19
|
+
|
|
20
|
+
public static Dictionary<string, ProductInfo> AllProducts => dicProducts;
|
|
21
|
+
|
|
22
|
+
public static ISetupBuilder GetProducts()
|
|
23
|
+
{
|
|
24
|
+
var result = new SetupContext();
|
|
25
|
+
ExecuteInNextFrame(result.GetProducts);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static IPurchaseBuilder Purchase(string productId)
|
|
30
|
+
{
|
|
31
|
+
var result = new PurchaseContext();
|
|
32
|
+
ExecuteInNextFrame(() => result.Purchase(productId));
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static IPurchaseBuilder RestorePurchase()
|
|
37
|
+
{
|
|
38
|
+
var result = new PurchaseContext();
|
|
39
|
+
ExecuteInNextFrame(result.RestorePurchase);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static ProductInfo GetProduct(string productId)
|
|
44
|
+
{
|
|
45
|
+
if (!dicProducts.ContainsKey(productId))
|
|
46
|
+
{
|
|
47
|
+
LogWarning($"[IAP] Not found product with id <{productId}>. Make sure you have called Setup() first.");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return dicProducts[productId];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static List<string> GetActiveSubscriptions()
|
|
55
|
+
{
|
|
56
|
+
return IAPAdapter?.GetActiveSubscriptions();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static List<string> GetPurchasedProductIds()
|
|
60
|
+
{
|
|
61
|
+
return localData.purchasedProducts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static bool IsPurchased(string productId)
|
|
65
|
+
{
|
|
66
|
+
var purchasedProductIds = GetPurchasedProductIds();
|
|
67
|
+
return purchasedProductIds != null && purchasedProductIds.Contains(productId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public partial class IAP //Internal
|
|
72
|
+
{
|
|
73
|
+
internal static IIAPAdapter adapter;
|
|
74
|
+
internal static readonly IAPData localData = new IAPData().LoadJsonFromFile();
|
|
75
|
+
internal static IIAPAdapter IAPAdapter
|
|
76
|
+
{
|
|
77
|
+
get
|
|
78
|
+
{
|
|
79
|
+
if (adapter != null) return adapter;
|
|
80
|
+
LogWarning("[IAP] Adapter is not available.");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static IAPAdapterConfig _adapterConfig;
|
|
86
|
+
internal static IAPAdapterConfig AdapterConfig
|
|
87
|
+
{
|
|
88
|
+
get
|
|
89
|
+
{
|
|
90
|
+
if (_adapterConfig != null) return _adapterConfig;
|
|
91
|
+
LogWarning("[IAP] Adapter config is not available.");
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
internal static Dictionary<string, ProductInfo> dicProducts = new Dictionary<string, ProductInfo>();
|
|
97
|
+
|
|
98
|
+
internal static void Init()
|
|
99
|
+
{
|
|
100
|
+
adapter = Adapter.GetSingleAdapter<IIAPAdapter>();
|
|
101
|
+
var adapterContext = adapter as AdapterContext;
|
|
102
|
+
|
|
103
|
+
if (adapterContext == null) return;
|
|
104
|
+
_adapterConfig = adapterContext?.GetAdapterConfig<IAPAdapterConfig>();
|
|
105
|
+
|
|
106
|
+
if (!adapterContext.IsReady) return;
|
|
107
|
+
localData.Sync();
|
|
108
|
+
if (_adapterConfig.autoGetProducts)
|
|
109
|
+
GetProducts();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
internal static void ExecuteInNextFrame(Action method)
|
|
113
|
+
{
|
|
114
|
+
_instance.StartCoroutine(ExecuteRoutine(method));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static IEnumerator ExecuteRoutine(Action method)
|
|
118
|
+
{
|
|
119
|
+
yield return null;
|
|
120
|
+
method?.Invoke();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public partial class IAP //Persistent
|
|
125
|
+
{
|
|
126
|
+
[Serializable]
|
|
127
|
+
internal class IAPData : IJsonFile
|
|
128
|
+
{
|
|
129
|
+
public string activeSubscriptionId;
|
|
130
|
+
public List<string> purchasedProducts;
|
|
131
|
+
|
|
132
|
+
public void Sync()
|
|
133
|
+
{
|
|
134
|
+
activeSubscriptionId = adapter.GetActiveSubscriptionId();
|
|
135
|
+
purchasedProducts = adapter.GetPurchasedProducts();
|
|
136
|
+
this.SaveJsonToFile();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public enum ProductType
|
|
143
|
+
{
|
|
144
|
+
Subscription,
|
|
145
|
+
Consumable,
|
|
146
|
+
NonConsumable
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[Serializable]
|
|
150
|
+
public class ProductInfo
|
|
151
|
+
{
|
|
152
|
+
public string id;
|
|
153
|
+
public ProductType type;
|
|
154
|
+
[HideInInspector] public string title;
|
|
155
|
+
[HideInInspector] public string description;
|
|
156
|
+
[HideInInspector] public float price;
|
|
157
|
+
[HideInInspector] public string currencyCode;
|
|
158
|
+
[HideInInspector] public string[] discountIds;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public class PurchaseSuccess
|
|
162
|
+
{
|
|
163
|
+
public string productId;
|
|
164
|
+
public string transactionId;
|
|
165
|
+
public string receipt;
|
|
166
|
+
public string purchaseToken;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public class PurchaseError
|
|
170
|
+
{
|
|
171
|
+
public string productId;
|
|
172
|
+
public bool isCancelled;
|
|
173
|
+
public string error;
|
|
174
|
+
|
|
175
|
+
public PurchaseError()
|
|
176
|
+
{
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public PurchaseError(string errorMessage)
|
|
180
|
+
{
|
|
181
|
+
error = errorMessage;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public static class IAPExtension
|
|
186
|
+
{
|
|
187
|
+
//GetProducts extensions
|
|
188
|
+
public static ISetupBuilder SetSubscriptions(this ISetupBuilder sc, params string[] subs)
|
|
189
|
+
{
|
|
190
|
+
if (sc == null) return null;
|
|
191
|
+
|
|
192
|
+
foreach (var pId in subs)
|
|
193
|
+
{
|
|
194
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.Subscription });
|
|
195
|
+
}
|
|
196
|
+
return sc;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public static ISetupBuilder SetConsumables(this ISetupBuilder sc, params string[] cons)
|
|
200
|
+
{
|
|
201
|
+
if (sc == null) return null;
|
|
202
|
+
|
|
203
|
+
foreach (var pId in cons)
|
|
204
|
+
{
|
|
205
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.Consumable });
|
|
206
|
+
}
|
|
207
|
+
return sc;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public static ISetupBuilder SetNonComsumables(this ISetupBuilder sc, params string[] nons)
|
|
211
|
+
{
|
|
212
|
+
if (sc == null) return null;
|
|
213
|
+
|
|
214
|
+
foreach (var pId in nons)
|
|
215
|
+
{
|
|
216
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.NonConsumable });
|
|
217
|
+
}
|
|
218
|
+
return sc;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public static ISetupBuilder OnSuccess(this ISetupBuilder sc, Action<Dictionary<string, ProductInfo>> callback)
|
|
222
|
+
{
|
|
223
|
+
if (sc == null) return null;
|
|
224
|
+
|
|
225
|
+
var setupContext = sc as SetupContext;
|
|
226
|
+
setupContext.onSuccess = callback;
|
|
227
|
+
return sc;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public static ISetupBuilder OnFail(this ISetupBuilder sc, Action<string> callback)
|
|
231
|
+
{
|
|
232
|
+
if (sc == null) return null;
|
|
233
|
+
|
|
234
|
+
var setupContext = sc as SetupContext;
|
|
235
|
+
setupContext.onFail = callback;
|
|
236
|
+
return sc;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//Purchase extensions
|
|
240
|
+
public static IPurchaseBuilder OnSuccess(this IPurchaseBuilder pb, Action<PurchaseSuccess> callback)
|
|
241
|
+
{
|
|
242
|
+
if (pb == null) return null;
|
|
243
|
+
|
|
244
|
+
var purchaseContext = pb as PurchaseContext;
|
|
245
|
+
purchaseContext.onSuccess = callback;
|
|
246
|
+
return pb;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public static IPurchaseBuilder OnFail(this IPurchaseBuilder pb, Action<PurchaseError> callback)
|
|
250
|
+
{
|
|
251
|
+
if (pb == null) return null;
|
|
252
|
+
|
|
253
|
+
var purchaseContext = pb as PurchaseContext;
|
|
254
|
+
purchaseContext.onFail = callback;
|
|
255
|
+
return pb;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public static IPurchaseBuilder SetDiscount(this IPurchaseBuilder pb, string discountId)
|
|
259
|
+
{
|
|
260
|
+
if (pb == null) return null;
|
|
261
|
+
|
|
262
|
+
var purchaseContext = pb as PurchaseContext;
|
|
263
|
+
purchaseContext.discountId = discountId;
|
|
264
|
+
return pb;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
namespace Amanotes.Core.Internal
|
|
270
|
+
{
|
|
271
|
+
[Serializable]
|
|
272
|
+
public class IAPAdapterConfig
|
|
273
|
+
{
|
|
274
|
+
[SerializeField] internal bool allPurchaseSuccessInEditor;
|
|
275
|
+
[SerializeField] internal List<ProductInfo> products;
|
|
276
|
+
[SerializeField] internal bool autoGetProducts;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public interface ISetupBuilder { }
|
|
280
|
+
|
|
281
|
+
public class SetupContext: ISetupBuilder
|
|
282
|
+
{
|
|
283
|
+
public Action<Dictionary<string, ProductInfo>> onSuccess;
|
|
284
|
+
public Action<string> onFail;
|
|
285
|
+
|
|
286
|
+
private readonly Queue<(List<string> pIds, ProductType pType)> getProdQueue = new Queue<(List<string> pIds, ProductType pType)>();
|
|
287
|
+
|
|
288
|
+
public void GetProducts()
|
|
289
|
+
{
|
|
290
|
+
if (AdapterConfig?.products.Count == 0)
|
|
291
|
+
{
|
|
292
|
+
LogWarning("There is no IAP product");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getProdQueue.Clear();
|
|
297
|
+
var subs = new List<string>();
|
|
298
|
+
var cons = new List<string>();
|
|
299
|
+
var nons = new List<string>();
|
|
300
|
+
|
|
301
|
+
foreach (var p in AdapterConfig.products)
|
|
302
|
+
{
|
|
303
|
+
switch (p.type)
|
|
304
|
+
{
|
|
305
|
+
case ProductType.Subscription:
|
|
306
|
+
subs.Add(p.id);
|
|
307
|
+
break;
|
|
308
|
+
case ProductType.Consumable:
|
|
309
|
+
cons.Add(p.id);
|
|
310
|
+
break;
|
|
311
|
+
case ProductType.NonConsumable:
|
|
312
|
+
nons.Add(p.id);
|
|
313
|
+
break;
|
|
314
|
+
default:
|
|
315
|
+
LogWarning($"[IAP] Product <{p.id}> has not specified ProductType.");
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
getProdQueue.Enqueue((subs, ProductType.Subscription));
|
|
321
|
+
getProdQueue.Enqueue((cons, ProductType.Consumable));
|
|
322
|
+
getProdQueue.Enqueue((nons, ProductType.NonConsumable));
|
|
323
|
+
GetNextProductsInQueue();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
void GetNextProductsInQueue()
|
|
327
|
+
{
|
|
328
|
+
if (getProdQueue.Count <= 0)
|
|
329
|
+
{
|
|
330
|
+
onSuccess?.Invoke(dicProducts);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
var item = getProdQueue.Dequeue();
|
|
335
|
+
GetProductsByType(item.pIds, item.pType);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
void GetProductsByType(List<string> lsProduct, ProductType type)
|
|
339
|
+
{
|
|
340
|
+
try
|
|
341
|
+
{
|
|
342
|
+
IAPAdapter?.GetProducts(lsProduct.ToArray(), type, (pList) =>
|
|
343
|
+
{
|
|
344
|
+
foreach (var pI in pList)
|
|
345
|
+
{
|
|
346
|
+
dicProducts.Add(pI.id, pI);
|
|
347
|
+
}
|
|
348
|
+
ExecuteInNextFrame(GetNextProductsInQueue);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
catch (Exception e)
|
|
352
|
+
{
|
|
353
|
+
LogWarning($"[IAP] Error on getting products of type <{type}>. Error detail:\n{e.Message}");
|
|
354
|
+
onFail?.Invoke(e.Message);
|
|
355
|
+
throw;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public interface IPurchaseBuilder { }
|
|
361
|
+
|
|
362
|
+
public class PurchaseContext : IPurchaseBuilder
|
|
363
|
+
{
|
|
364
|
+
public Action<PurchaseSuccess> onSuccess;
|
|
365
|
+
public Action<PurchaseError> onFail;
|
|
366
|
+
public string discountId;
|
|
367
|
+
|
|
368
|
+
public void Purchase(string productId)
|
|
369
|
+
{
|
|
370
|
+
if (!dicProducts.ContainsKey(productId))
|
|
371
|
+
{
|
|
372
|
+
LogWarning($"[IAP] Product {productId} is not found");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#if UNITY_EDITOR
|
|
377
|
+
if (AdapterConfig.allPurchaseSuccessInEditor)
|
|
378
|
+
{
|
|
379
|
+
DoOnSuccess(new PurchaseSuccess
|
|
380
|
+
{
|
|
381
|
+
productId = productId
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
#endif
|
|
386
|
+
|
|
387
|
+
if (!string.IsNullOrWhiteSpace(discountId))
|
|
388
|
+
{
|
|
389
|
+
IAPAdapter?.PurchaseDiscount(productId, dicProducts[productId].type, discountId, DoOnSuccess, DoOnFail);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
IAPAdapter?.Purchase(productId, dicProducts[productId].type, DoOnSuccess, DoOnFail);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public void RestorePurchase()
|
|
397
|
+
{
|
|
398
|
+
IAPAdapter?.RestorePurchase(DoOnSuccess, DoOnFail);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
void DoOnSuccess(PurchaseSuccess success)
|
|
402
|
+
{
|
|
403
|
+
localData.Sync();
|
|
404
|
+
onSuccess?.Invoke(success);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
void DoOnFail(PurchaseError error)
|
|
408
|
+
{
|
|
409
|
+
onFail?.Invoke(error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
public interface IIAPAdapter
|
|
414
|
+
{
|
|
415
|
+
void GetProducts(string[] productIds, ProductType productType, Action<ProductInfo[]> onComplete);
|
|
416
|
+
void Purchase(string productId, ProductType productType, Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
417
|
+
void PurchaseDiscount(string productId, ProductType productType, string discountId, Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
418
|
+
void RestorePurchase(Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
419
|
+
List<string> GetActiveSubscriptions();
|
|
420
|
+
List<string> GetPurchasedProducts();
|
|
421
|
+
string GetActiveSubscriptionId();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -16,8 +16,7 @@ namespace Amanotes.Core
|
|
|
16
16
|
{
|
|
17
17
|
public enum RemoteConfigSource
|
|
18
18
|
{
|
|
19
|
-
|
|
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
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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[]{
|
|
114
|
-
{ FetchState.
|
|
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,27 +198,14 @@ 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
210
|
string filePath = GDKFileUtils.GetPath(fileName);
|
|
203
211
|
GDKFileUtils.Save(filePath, sb.ToString());
|
|
@@ -220,15 +228,32 @@ namespace Amanotes.Core
|
|
|
220
228
|
dictConfig.Clear();
|
|
221
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
|
|
249
|
+
int lineNo = 1;
|
|
225
250
|
while ((line = sr.ReadLine()) != null)
|
|
226
251
|
{
|
|
227
|
-
|
|
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 {
|
|
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
|
|
278
|
+
static void LoadEmbedConfig()
|
|
254
279
|
{
|
|
255
|
-
_state = FetchState.
|
|
280
|
+
_state = FetchState.LoadEmbedConfig;
|
|
281
|
+
|
|
282
|
+
if (adapterConfig == null || adapterConfig.embedRemoteConfig == null)
|
|
283
|
+
{
|
|
284
|
+
NextState(false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
256
287
|
|
|
257
|
-
var
|
|
288
|
+
var config = adapterConfig.embedRemoteConfig.ActiveConfig;
|
|
258
289
|
|
|
259
|
-
if (
|
|
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
|
|
296
|
+
foreach (var item in config.items)
|
|
266
297
|
{
|
|
267
298
|
dictConfig.Add(item.key, item.value);
|
|
268
299
|
}
|
|
269
|
-
Source = RemoteConfigSource.
|
|
270
|
-
Log("[RemoteConfig] Load from
|
|
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
|
-
|
|
328
|
-
|
|
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);
|
package/Runtime/AmaGDK.cs
CHANGED
|
@@ -14,7 +14,7 @@ namespace Amanotes.Core
|
|
|
14
14
|
{
|
|
15
15
|
public partial class AmaGDK : MonoBehaviour
|
|
16
16
|
{
|
|
17
|
-
public const string VERSION = "0.2.
|
|
17
|
+
public const string VERSION = "0.2.14";
|
|
18
18
|
|
|
19
19
|
internal static AmaGDK _instance;
|
|
20
20
|
internal static Status _status = Status.None;
|
|
@@ -109,6 +109,7 @@ namespace Amanotes.Core
|
|
|
109
109
|
AmaPassport.Init();
|
|
110
110
|
Analytics.Init();
|
|
111
111
|
Ads.Init();
|
|
112
|
+
IAP.Init();
|
|
112
113
|
|
|
113
114
|
_status = Status.Ready;
|
|
114
115
|
_onReadyCallback?.Invoke();
|
|
@@ -242,7 +243,7 @@ namespace Amanotes.Core
|
|
|
242
243
|
public void ChangeDevMode()
|
|
243
244
|
{
|
|
244
245
|
string variableName = "GDK_HOME";
|
|
245
|
-
string output = RunShellCommand($"source ~/.zshrc && echo
|
|
246
|
+
string output = RunShellCommand($"source ~/.zshrc && echo file:${variableName}");
|
|
246
247
|
output = output.Trim();
|
|
247
248
|
|
|
248
249
|
if (string.IsNullOrEmpty(output))
|