analyitcs

This commit is contained in:
Savya Bikram Shah
2026-06-01 13:38:28 +05:45
parent 6f79bcd018
commit 0bf6f64548
20 changed files with 257 additions and 64 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 89ff3ec1d970c4031b6e9f765b93c4e7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Darkmatter.Core.Contracts.Services.Analytics
{
public interface IAnalyticsService
{
void LogEvent(string name);
void LogEvent(string name, string paramName, string paramValue);
void LogEvent(string name, IReadOnlyDictionary<string, object> parameters);
void SetUserProperty(string name, string value);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a36684ef8a2124c469c6ac2c8c4fef2e

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a60852842d4a74363904d1aa6e2c8fb5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.GameplayFlow
{
public record struct DrawingCompletedSignal(string TemplateId, int CompletionCount);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4e2a47489fe7c4886993338327772aac

View File

@@ -14,6 +14,7 @@ using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Core.Data.Signals.Features.GameplayFlow;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Enums.Features.Progression;
using Darkmatter.Core.Enums.Services.Audio;
@@ -133,6 +134,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_sfx.Play(SfxId.LevelComplete);
await _coloring.PlayCompletionAnimationAsync(ct);
_progression.MarkCompleted(_templateId);
var progressAfter = _progression.GetProgress(_templateId);
_bus.Publish(new DrawingCompletedSignal(_templateId, progressAfter?.completionCount ?? 1));
var nextId = _catalog.GetNextTemplate(_templateId);
if (string.IsNullOrEmpty(nextId))

View File

@@ -1,3 +1,4 @@
using Darkmatter.Core.Contracts.Services.Analytics;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
@@ -9,7 +10,8 @@ namespace Darkmatter.Services.Analytics
{
public void Register(IContainerBuilder builder)
{
builder.RegisterEntryPoint<FirebaseAnalyticsSystem>();
builder.RegisterEntryPoint<FirebaseAnalyticsSystem>().As<IAnalyticsService>();
builder.RegisterEntryPoint<AnalyticsTracker>();
}
}
}

View File

@@ -7,7 +7,9 @@
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f8c64bb88d959406689053ae3f31183d",
"GUID:a0b1547602fc44f6da0a5e755ab3a7ef"
"GUID:a0b1547602fc44f6da0a5e755ab3a7ef",
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Services.Analytics;
using Darkmatter.Core.Data.Signals.Features.AppBoot;
using Darkmatter.Core.Data.Signals.Features.Capture;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Core.Data.Signals.Features.GameplayFlow;
using Darkmatter.Core.Data.Signals.Features.MainMenu;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Services.Analytics
{
public sealed class AnalyticsTracker : IStartable, IDisposable
{
private readonly IEventBus _bus;
private readonly IAnalyticsService _analytics;
private readonly List<IDisposable> _subs = new();
public AnalyticsTracker(IEventBus bus, IAnalyticsService analytics)
{
_bus = bus;
_analytics = analytics;
}
public void Start()
{
_subs.Add(_bus.Subscribe<IntroCompletedSignal>(_ => _analytics.LogEvent("intro_completed")));
_subs.Add(_bus.Subscribe<PlayBtnClickedSignal>(_ => _analytics.LogEvent("play_clicked")));
_subs.Add(_bus.Subscribe<OpenColorBookSignal>(_ => _analytics.LogEvent("colorbook_opened")));
_subs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => _analytics.LogEvent("artbook_opened")));
_subs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => _analytics.LogEvent("main_menu_returned")));
_subs.Add(_bus.Subscribe<DrawingSelectedSignal>(s =>
_analytics.LogEvent("drawing_selected", "template_id", s.TemplateId)));
_subs.Add(_bus.Subscribe<ShapeBuilderStartedSignal>(s =>
_analytics.LogEvent("shape_builder_started", "template_id", s.TemplateId)));
_subs.Add(_bus.Subscribe<ShapeAssembledSignal>(s =>
_analytics.LogEvent("shape_assembled", "template_id", s.TemplateId)));
_subs.Add(_bus.Subscribe<DrawingCompletedSignal>(s => _analytics.LogEvent("drawing_completed",
new Dictionary<string, object>
{
["template_id"] = s.TemplateId,
["completion_count"] = s.CompletionCount,
})));
_subs.Add(_bus.Subscribe<GallerySaveStartedSignal>(_ => _analytics.LogEvent("gallery_save_started")));
_subs.Add(_bus.Subscribe<GallerySaveCompletedSignal>(s =>
_analytics.LogEvent("gallery_save_completed", "success", s.Success ? "true" : "false")));
}
public void Dispose()
{
foreach (var s in _subs) s?.Dispose();
_subs.Clear();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ad16bfd960ac4a7391ffffcc6e7bd59

View File

@@ -1,42 +1,100 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Services.Analytics;
using Firebase;
using Firebase.Analytics;
using Firebase.Crashlytics;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Services.Analytics
{
public class FirebaseAnalyticsSystem : IAsyncStartable
public class FirebaseAnalyticsSystem : IAnalyticsService, IAsyncStartable
{
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
await Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
var dependencyStatus = task.Result;
if (dependencyStatus == Firebase.DependencyStatus.Available)
{
// Create and hold a reference to your FirebaseApp,
// where app is a Firebase.FirebaseApp property of your application class.
// Crashlytics will use the DefaultInstance, as well;
// this ensures that Crashlytics is initialized.
Firebase.FirebaseApp app = Firebase.FirebaseApp.DefaultInstance;
#if DEVELOPMENT_BUILD
Firebase.Crashlytics.Crashlytics.SetCustomKey("environment", "dev");
#else
Firebase.Crashlytics.Crashlytics.SetCustomKey("environment", "prod");
#endif
// When this property is set to true, Crashlytics will report all
// uncaught exceptions as fatal events. This is the recommended behavior.
Crashlytics.ReportUncaughtExceptionsAsFatal = true;
private bool _ready;
private bool _failed;
private readonly Queue<Action> _pending = new();
// Set a flag here for indicating that your project is ready to use Firebase.
}
else
{
UnityEngine.Debug.LogError(System.String.Format(
"Could not resolve all Firebase dependencies: {0}", dependencyStatus));
// Firebase Unity SDK is not safe to use here.
}
}, cancellation);
public async UniTask StartAsync(CancellationToken cancellation = default)
{
DependencyStatus status;
try
{
status = await FirebaseApp.CheckAndFixDependenciesAsync().AsUniTask();
}
catch (Exception e)
{
Debug.LogError($"[FirebaseAnalytics] Init failed: {e}");
_failed = true;
_pending.Clear();
return;
}
if (status != DependencyStatus.Available)
{
Debug.LogError($"[FirebaseAnalytics] Deps unavailable: {status}");
_failed = true;
_pending.Clear();
return;
}
_ = FirebaseApp.DefaultInstance;
#if DEVELOPMENT_BUILD
Crashlytics.SetCustomKey("environment", "dev");
#else
Crashlytics.SetCustomKey("environment", "prod");
#endif
Crashlytics.ReportUncaughtExceptionsAsFatal = true;
_ready = true;
while (_pending.Count > 0)
{
try { _pending.Dequeue().Invoke(); }
catch (Exception e) { Debug.LogError($"[FirebaseAnalytics] Queued event failed: {e}"); }
}
}
public void LogEvent(string name) =>
Run(() => FirebaseAnalytics.LogEvent(name));
public void LogEvent(string name, string paramName, string paramValue) =>
Run(() => FirebaseAnalytics.LogEvent(name, paramName, paramValue ?? string.Empty));
public void LogEvent(string name, IReadOnlyDictionary<string, object> parameters)
{
if (parameters == null || parameters.Count == 0)
{
LogEvent(name);
return;
}
var arr = new Parameter[parameters.Count];
int i = 0;
foreach (var kv in parameters) arr[i++] = ToParameter(kv.Key, kv.Value);
Run(() => FirebaseAnalytics.LogEvent(name, arr));
}
public void SetUserProperty(string name, string value) =>
Run(() => FirebaseAnalytics.SetUserProperty(name, value ?? string.Empty));
private void Run(Action action)
{
if (_failed) return;
if (_ready) { try { action(); } catch (Exception e) { Debug.LogError($"[FirebaseAnalytics] Event failed: {e}"); } }
else _pending.Enqueue(action);
}
private static Parameter ToParameter(string key, object value) => value switch
{
null => new Parameter(key, string.Empty),
long l => new Parameter(key, l),
int i => new Parameter(key, i),
double d => new Parameter(key, d),
float f => new Parameter(key, (double)f),
bool b => new Parameter(key, b ? 1L : 0L),
_ => new Parameter(key, value.ToString())
};
}
}
}