import 'dart:convert'; import 'dart:math'; import 'package:manager_api_new/api.dart'; import 'package:mymuseum_visitapp/client.dart'; class StatisticsService { final Client clientAPI; final String? instanceId; final String? configurationId; final String? appType; // "Mobile" or "Tablet" final String? language; final String sessionId; StatisticsService({ required this.clientAPI, required this.instanceId, required this.configurationId, this.appType = 'Mobile', this.language, }) : sessionId = _generateSessionId(); static String _generateSessionId() { final rand = Random(); final bytes = List.generate(16, (_) => rand.nextInt(256)); return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); } /// [isVoice] marque un événement **consommé à la voix** — le guide a lu la section /// à haute voix, par déclenchement proactif ou en réponse à une question. /// /// ⚠️ C'est un attribut de l'événement, **pas un `appType`**. Un visiteur passe des /// lunettes à l'écran dans une même session — c'est tout l'objet du miroir — et /// `AppTypeDistribution` ne compte qu'une entrée par session, tranchée sur /// l'événement le plus ancien : commencer au doigt puis mettre les lunettes aurait /// rendu le vocal invisible, l'inverse aurait compté la navigation tactile comme /// vocale. Le KPI serveur devient « part des sessions ayant utilisé le vocal ». /// /// Même conception que `VisitorQuestion`, qui porte un booléen `IsVoice` **à côté** /// de son `AppType`. Future track( String eventType, { String? sectionId, int? durationSeconds, Map? metadata, bool isVoice = false, }) async { final payload = isVoice ? {...?metadata, 'voice': true} : metadata; try { await clientAPI.statsApi!.statsTrackEvent(VisitEventDTO( instanceId: instanceId ?? '', configurationId: configurationId, sectionId: sectionId, sessionId: sessionId, eventType: eventType, appType: appType, language: language, durationSeconds: durationSeconds, metadata: payload != null ? jsonEncode(payload) : null, timestamp: DateTime.now(), )); } catch (_) { // fire-and-forget — never block the UI on stats errors } } }