Les tuiles suivent maintenant les mêmes colSpan × rowSpan que les autres canaux, sur six colonnes — environ 83° d'ouverture, dans le confort du regard — avec un écart serré assumé : espacées, les tuiles se lisent comme des panneaux sans rapport plutôt que comme une planche. Le pas angulaire se déduit de la largeur de cellule et du rayon de l'arc, plutôt que d'être posé en dur. S'y ajoute le sélecteur de langue avec ses drapeaux, et les libellés de l'interface sortis du code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
4.1 KiB
C#
96 lines
4.1 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace MyInfoMate.Vr.Net
|
|
{
|
|
/// <summary>
|
|
/// Télémétrie de visite — item <b>E9</b> du lot XR-4.
|
|
///
|
|
/// <b>Les stats du manager fonctionnent sans une ligne de plus côté serveur</b> :
|
|
/// <c>VisitEvent</c> porte déjà un <c>AppType</c>, et l'écran Statistiques filtre
|
|
/// génériquement sur <c>AppType.values</c> — le canal VR y apparaîtra tout seul au
|
|
/// premier événement reçu.
|
|
///
|
|
/// ⚠️ Deux différences avec le reste de l'API, vérifiées dans <c>StatsController</c> :
|
|
/// la route est <c>POST /api/stats/event</c> (pas <c>/api/visitevent</c>), et
|
|
/// <c>appType</c> comme <c>eventType</c> partent en <b>chaîne</b>, parsés par nom
|
|
/// côté serveur. Un nom inconnu d'<c>eventType</c> est un 400 ; un <c>appType</c>
|
|
/// inconnu retombe <b>silencieusement sur Mobile</b> — c'est-à-dire qu'une faute de
|
|
/// frappe ici ferait compter les visites du casque dans le canal mobile.
|
|
///
|
|
/// L'envoi est « tire et oublie » : une borne ne doit jamais attendre une statistique,
|
|
/// et un événement perdu ne vaut pas un message d'erreur au visiteur.
|
|
/// </summary>
|
|
public class Telemetry
|
|
{
|
|
/// <summary>Le nom, pas la valeur : le serveur parse par nom (<c>Enum.TryParse</c>).</summary>
|
|
const string AppTypeVr = "VR";
|
|
|
|
readonly ApiClient _client;
|
|
readonly string _instanceId;
|
|
readonly string _configurationId;
|
|
|
|
/// <summary>
|
|
/// La langue de la visite en cours. Elle n'est pas fixée à la construction : le
|
|
/// visiteur la choisit au début de <b>sa</b> visite, sur un casque qui tourne
|
|
/// depuis le matin — et une visite comptée dans la mauvaise langue vaut mieux
|
|
/// corrigée que figée.
|
|
/// </summary>
|
|
public string Language { get; set; }
|
|
|
|
/// <summary>
|
|
/// Une session = une visite. Sur une borne, elle se renouvelle à chaque retour au
|
|
/// menu ou à la repose du casque (E10), pas au lancement de l'app : sinon une
|
|
/// journée entière de borne compterait pour un seul visiteur.
|
|
/// </summary>
|
|
public string SessionId { get; private set; } = NewSessionId();
|
|
|
|
public Telemetry(ApiClient client, string instanceId, string configurationId, string language)
|
|
{
|
|
_client = client;
|
|
_instanceId = instanceId;
|
|
_configurationId = configurationId;
|
|
Language = language;
|
|
}
|
|
|
|
public void StartNewSession() => SessionId = NewSessionId();
|
|
|
|
public void SectionView(string sectionId) => Send("SectionView", sectionId);
|
|
|
|
public void SectionLeave(string sectionId, int seconds) =>
|
|
Send("SectionLeave", sectionId, seconds);
|
|
|
|
public void MenuItemTap(string sectionId) => Send("MenuItemTap", sectionId);
|
|
|
|
public void MapPoiTap(string sectionId, string poiTitle) =>
|
|
Send("MapPoiTap", sectionId, metadata: $"{{\"poi\":\"{Escape(poiTitle)}\"}}");
|
|
|
|
async void Send(string eventType, string sectionId,
|
|
int? durationSeconds = null, string metadata = null)
|
|
{
|
|
var result = await _client.PostAsync<object>("/api/stats/event", new
|
|
{
|
|
instanceId = _instanceId,
|
|
configurationId = _configurationId,
|
|
sectionId,
|
|
sessionId = SessionId,
|
|
eventType,
|
|
appType = AppTypeVr,
|
|
language = Language,
|
|
durationSeconds,
|
|
metadata,
|
|
timestamp = DateTime.UtcNow
|
|
});
|
|
|
|
// La réponse est un 204 sans corps : la désérialisation ne dit rien d'utile,
|
|
// seul un échec réseau mérite une trace — et rien ne remonte au visiteur.
|
|
if (!result.Ok) Debug.Log($"[Stats] {eventType} non envoyé : {result.Error}");
|
|
}
|
|
|
|
static string NewSessionId() => Guid.NewGuid().ToString("N");
|
|
|
|
static string Escape(string value) =>
|
|
string.IsNullOrEmpty(value) ? "" : value.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
|
}
|
|
}
|