using MijnBesluit.Client; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace MijnBesluit.AppUi; /// /// Gedeelde basis voor de schermen achter het inloggen: haalt de gegevens op, houdt één /// foutmelding bij en stuurt wie niet is aangemeld naar het inlogscherm. /// public abstract class SchermBasis : ComponentBase, IDisposable { [Inject] protected AppToestand Toestand { get; set; } = default!; [Inject] protected NavigationManager Navigatie { get; set; } = default!; [Inject] private VernieuwSignaal Vernieuwen { get; set; } = default!; /// Wordt getoond in een rode balk bovenaan het scherm. protected string? Fout { get; set; } /// Er loopt iets; knoppen zijn dan uit zodat er niet twee keer wordt gedrukt. protected bool Bezig { get; set; } protected override async Task OnInitializedAsync() { Toestand.Veranderd += OpVerandering; // Op PersoonId en niet op IsAangemeld: een bewaard token zegt alleen dat we ooit zijn // ingelogd, niet dat de app al weet wie hij is. Lukte het oppakken van de sessie de // vorige keer niet omdat er even geen verbinding was, dan staat het token er nog en // proberen we het hier gewoon opnieuw. // // En naar het inlogscherm alleen als er daarna echt geen token meer is. Dat betekent // dat de server de sessie niet meer kent - de enige goede reden om iemand opnieuw te // laten inloggen. Een haperende verbinding hoort dat niet te doen: dan komt er een rode // balk "geen verbinding" en blijft de sessie staan. if (Toestand.PersoonId == 0 && !await Toestand.HervatAsync() && !Toestand.IsAangemeld) { Navigatie.NavigateTo("/inloggen"); return; } await LaadAsync(); // Alleen het scherm dat nu open staat luistert naar het trek-naar-beneden-gebaar; zie // VernieuwSignaal. Er wordt niet naar Bezig gekeken - dat gebruiken de schermen zelf al // voor hun eigen knoppen en zou hier de verversing kunnen blokkeren zonder reden. Vernieuwen.Gevraagd += OpVernieuwenGevraagdAsync; } /// Hier haalt het scherm zijn eigen gegevens op. protected virtual Task LaadAsync() => Task.CompletedTask; private async Task OpVernieuwenGevraagdAsync() { await LaadAsync(); await InvokeAsync(StateHasChanged); } /// /// Voert iets uit dat kan mislukken en zet de melding klaar. Zo staat in elk scherm maar /// één keer de afhandeling van fouten, en blijft de knop niet hangen als het misgaat. /// protected async Task ProbeerAsync(Func> werk) { Bezig = true; Fout = null; StateHasChanged(); try { var uitkomst = await werk(); if (!uitkomst.Gelukt) Fout = uitkomst.Melding; return uitkomst.Gelukt; } finally { Bezig = false; StateHasChanged(); } } /// /// Enter of spatie op een tegel die je normaal aantikt. Zo'n tegel is geen knop voor de /// browser, dus zonder dit is hij alleen met een vinger of muis te bedienen - en niet met /// een toetsenbord of een schermlezer. /// protected static Task ToetsAsync(KeyboardEventArgs toets, Func doen) => toets.Key is "Enter" or " " ? doen() : Task.CompletedTask; private void OpVerandering() => InvokeAsync(StateHasChanged); public virtual void Dispose() { Toestand.Veranderd -= OpVerandering; Vernieuwen.Gevraagd -= OpVernieuwenGevraagdAsync; } } /// Kleine hulpmiddelen om waarden leesbaar op het scherm te zetten. public static class Tonen { // De maandnamen komen niet uit de taalinstelling van het toestel. Die bepaalde ze eerst, en // op een telefoon die op Engels staat las de lijst "9 June 2031" terwijl de rest van het // scherm Nederlands is. Een app die maar in één taal bestaat hoort ook maar in één taal te // rekenen. // // De lijst zelf staat in MijnBesluit.Client, naast de website-versie in het domein; een // zelfcontrole houdt die twee gelijk. De website liep tegen precies hetzelfde aan. private static string[] Maanden => MijnBesluit.Client.Datums.Maanden; private static string[] KorteMaanden => MijnBesluit.Client.Datums.KorteMaanden; public static string Datum(DateTime? moment) { if (moment is null) return ""; var d = moment.Value.ToLocalTime(); return $"{d.Day} {Maanden[d.Month - 1]} {d.Year}"; } public static string DatumTijd(DateTime? moment) { if (moment is null) return ""; var d = moment.Value.ToLocalTime(); return $"{Datum(d)} om {d:HH:mm}"; } /// /// De datum zoals hij op een tegel staat: "9 Jun. 2031". Daar telt elke pixel, en de maand /// voluit duwde het jaartal van de regel af - "22 Septem...". /// public static string Kort(DateTime? moment) { if (moment is null) return ""; var d = moment.Value.ToLocalTime(); return $"{d.Day} {KorteMaanden[d.Month - 1]} {d.Year}"; } /// /// Hetzelfde voor een kale datum zonder tijd - een geboortedag, een sterfdag. Die hoort /// niet omgerekend te worden naar een tijdzone: 9 juni is 9 juni, waar je ook bent. /// public static string Kort(DateOnly? dag) => dag is null ? "" : $"{dag.Value.Day} {KorteMaanden[dag.Value.Month - 1]} {dag.Value.Year}"; /// /// De ingebouwde naam bij een soortnummer. Een soort die de beheerder zelf maakte staat hier niet /// in; die naam reist met het item mee (). /// public static string Categorie(int categorie) => MijnBesluit.Client.IngebouwdeSoorten.Naam(categorie); /// /// Hoe een relatie op het scherm heet. De lijst staat in MijnBesluit.Client, zodat de app en /// de website er niet ieder een eigen versie van hebben. /// public static string Relatie(int relatie) => MijnBesluit.Client.Relaties.Naam(relatie); public static string Vrijgave(int beleid) => beleid switch { 0 => "Alleen voor mijzelf", 1 => "Na mijn overlijden", 2 => "Op een datum", _ => "" }; /// /// Het tekentje bij de vrijgave. Het takje is hetzelfde als dat van Nalatenschap onderin, /// en de kalender staat voor een datum - zo hoeft niemand een nieuwe taal te leren. /// /// Voor "Alleen voor mijzelf" staat hier niets: dat ben jij, en daar hoort je eigen /// profielfoto, geen plaatje. Het scherm zet daar een penning neer. /// public static string Vrijgaveteken(int beleid) => beleid switch { 1 => "\U0001F33F", // takje, net als bij Nalatenschap 2 => "\U0001F4C5", // kalender _ => "" }; }