vr-app/unity-overlay/Assets/Scripts/Net/ContentService.cs
Thomas Fransolet c70c572aed Menu du casque : la grille bento, et le choix de la langue
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>
2026-09-17 11:37:06 +02:00

124 lines
4.4 KiB
C#

using System;
using System.Threading.Tasks;
using UnityEngine;
namespace MyInfoMate.Vr.Net
{
/// <summary>
/// Le contenu d'un lieu, servi <b>cache d'abord</b> — item <b>E4</b> du lot XR-4.
///
/// Deux chemins, jamais mélangés :
/// <list type="bullet">
/// <item><b>Au démarrage</b> — le cache s'il existe, affiché tout de suite ;
/// le réseau seulement s'il n'y a rien en cache.</item>
/// <item><b>Ensuite</b> — un rafraîchissement en tâche de fond, dont l'échec est
/// silencieux : une borne hors ligne affiche son contenu de la veille, et
/// ce n'est pas une erreur à montrer au visiteur.</item>
/// </list>
///
/// C'est aussi ce qui rendra la reprise après coupure gratuite : au redémarrage, le
/// cache est déjà là.
/// </summary>
public class ContentService
{
readonly ApiClient _client;
readonly string _configurationId;
/// <summary>
/// Ce casque. Il désigne le lien application/configuration côté serveur, donc
/// l'agencement qui lui est propre : ordre des sections, tailles de panneaux et
/// sections masquées pour le canal VR seulement. Sans lui, l'export rend la
/// configuration nue, telle que la voient le mobile et le web.
/// </summary>
readonly string _deviceId;
public ContentService(ApiClient client, string configurationId, string deviceId = null)
{
_client = client;
_configurationId = configurationId;
_deviceId = deviceId;
}
public class Loaded
{
public ConfigurationExport Export;
/// <summary>Vrai si le contenu vient du disque et non du serveur.</summary>
public bool FromCache;
/// <summary>Date du cache servi, pour l'afficher en mode dégradé.</summary>
public DateTime? CachedAt;
public string Error;
public bool Ok => Error == null;
}
public async Task<Loaded> LoadAsync()
{
var cached = ContentCache.ReadExport(_configurationId);
if (cached != null)
{
var parsed = ConfigurationExport.Parse(cached);
if (parsed.Ok)
return new Loaded
{
Export = parsed.Value,
FromCache = true,
CachedAt = ContentCache.ExportDate(_configurationId)
};
// Un cache illisible est un cache mort : on ne le garde pas pour
// rejouer le même échec au prochain démarrage.
Debug.LogWarning("[Content] Cache illisible, il est écarté.");
}
return await FetchAsync();
}
string ExportPath => string.IsNullOrEmpty(_deviceId)
? $"/api/configuration/{_configurationId}/export"
: $"/api/configuration/{_configurationId}/export?deviceId={_deviceId}";
/// <summary>
/// Rafraîchit le cache sans bloquer l'affichage. Renvoie le contenu neuf, ou null
/// si rien n'a changé ou si le serveur n'a pas répondu — dans les deux cas
/// l'appelant garde ce qu'il montre.
/// </summary>
public async Task<ConfigurationExport> RefreshAsync()
{
var before = ContentCache.ReadExport(_configurationId);
var raw = await _client.GetRawAsync(ExportPath);
if (!raw.Ok)
{
Debug.Log($"[Content] Rafraîchissement impossible ({raw.Error}) — " +
"le contenu en cache reste affiché.");
return null;
}
if (raw.Value == before) return null;
var parsed = ConfigurationExport.Parse(raw.Value);
if (!parsed.Ok) return null;
ContentCache.WriteExport(_configurationId, raw.Value);
return parsed.Value;
}
async Task<Loaded> FetchAsync()
{
var raw = await _client.GetRawAsync(ExportPath);
if (!raw.Ok) return new Loaded { Error = raw.Error };
var parsed = ConfigurationExport.Parse(raw.Value);
if (!parsed.Ok) return new Loaded { Error = parsed.Error };
ContentCache.WriteExport(_configurationId, raw.Value);
return new Loaded { Export = parsed.Value };
}
}
}