@inherits LayoutComponentBase @implements IDisposable @inject ServiceLivresApi Api @inject EtatReseau Reseau @* Mise en page pensée mobile d'abord : un bandeau, une colonne, rien à gauche. Le PC hérite de la même colonne, simplement centrée et limitée en largeur. *@
MaBibli @if (!Reseau.EnLigne) { @* Une pastille dans le bandeau, visible sur tous les écrans : l'état du réseau change l'usage de l'application, il ne doit pas se découvrir au premier clic qui échoue. *@ Hors ligne } @if (_utilisateur?.Identifiant is not null) { @_utilisateur.Affichage@(_utilisateur.Simule ? " (dev)" : "") }
@if (!Reseau.EnLigne) { @* Dire d'où viennent les données et de quand elles datent. Sans cette phrase, une bibliothèque affichée hors-ligne est indiscernable d'une bibliothèque à jour — et un livre ajouté depuis un autre appareil manquerait sans explication. *@

Hors ligne. @(Reseau.DerniereSynchro is { } synchro ? $" Données enregistrées {Quand(synchro)}. " : " Aucune donnée n'a encore pu être enregistrée sur cet appareil. ") Consultation et recherche fonctionnent ; les modifications sont impossibles.

}
@Body
@* Navigation permanente, en bas de l'écran : quatre onglets, toujours au même endroit. Elle remplace les listes de liens que chaque page portait dans sa barre d'actions — cinq boutons y passaient sur deux ou trois lignes sur un téléphone, et l'entrée « Envies » ne figurait pas partout. Les barres d'actions restantes ne portent plus que des ACTIONS. ⚠️ Le pouce atteint le bas de l'écran, pas le haut : c'est ce qui justifie le bas plutôt qu'un rang d'onglets sous le bandeau. Les actions de page se placent juste au-dessus (variable --mb-onglets-hauteur, partagée avec .actions-flottantes). Les onglets restent actifs hors-ligne : les quatre écrans se consultent depuis leurs instantanés. Ce sont les écritures qui se désactivent, pas la navigation. *@ @code { private UtilisateurCourant? _utilisateur; private bool _etaitEnLigne = true; private bool _synchroEnCours; protected override async Task OnInitializedAsync() { Reseau.Change += SurChangementReseau; Reseau.SynchroChange += SurSynchro; // Écoute des bascules online/offline avant tout appel : un démarrage hors-ligne doit // aller directement au cache, sans attendre l'échec d'une requête. await Reseau.DemarrerAsync(); _etaitEnLigne = Reseau.EnLigne; _utilisateur = await Api.ObtenirUtilisateurAsync(); // Rafraîchit tout le fonds, pas seulement l'écran ouvert : c'est ce qui rend la // bibliothèque entière consultable et cherchable après la coupure. await SynchroniserAsync(); } /// /// Au retour du réseau, on recharge : la bibliothèque a pu changer depuis un autre appareil, /// et les actions d'écriture redeviennent disponibles dans la foulée. /// private void SurChangementReseau() { var revenu = Reseau.EnLigne && !_etaitEnLigne; _etaitEnLigne = Reseau.EnLigne; _ = InvokeAsync(async () => { StateHasChanged(); if (revenu) { _utilisateur = await Api.ObtenirUtilisateurAsync(); await SynchroniserAsync(); StateHasChanged(); } }); } private async Task SynchroniserAsync() { if (_synchroEnCours) { return; } _synchroEnCours = true; try { await Api.SynchroniserAsync(); } finally { _synchroEnCours = false; } } /// Date de synchronisation en clair : l'heure suffit le jour même. private static string Quand(DateTimeOffset instant) { var local = instant.ToLocalTime(); return local.Date == DateTimeOffset.Now.Date ? $"aujourd'hui à {local:HH:mm}" : $"le {local:dd/MM/yyyy} à {local:HH:mm}"; } /// La date affichée vient de changer : rien à recharger, juste à redessiner. private void SurSynchro() => _ = InvokeAsync(StateHasChanged); public void Dispose() { Reseau.Change -= SurChangementReseau; Reseau.SynchroChange -= SurSynchro; } }