Il comando della barra si adatta alla schermata
Il tasto al centro perde l'etichetta: non ne ha una sola, perche' quello che fa lo decide la schermata sotto. Dentro un'ispezione apre le sue azioni (nuova scheda, concludi, elimina); altrove apre una nuova ispezione. Il glifo resta lo stesso e resta nello stesso punto: quello che cambia e' cosa c'e' dietro, non dove mettere il dito. Le azioni dell'ispezione lasciano quindi la testata, dove stavano dietro un menu a tre punti, e tornano sotto il pollice. Il collegamento passa da PageActionsService: la pagina si registra finche' e' a video e si sfila uscendo, cosi' la barra non tiene in vita schermate che non ci sono piu'. Chi non registra niente lascia al comando il suo significato di default — un'ispezione conclusa non registra, e il tasto non resta li' a non fare nulla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,7 @@ public static class CoreModule
|
|||||||
builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>();
|
builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>();
|
||||||
builder.Services.AddSingleton<OnScannerService>();
|
builder.Services.AddSingleton<OnScannerService>();
|
||||||
builder.Services.AddSingleton<BackButtonService>();
|
builder.Services.AddSingleton<BackButtonService>();
|
||||||
|
builder.Services.AddSingleton<PageActionsService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterDbServices()
|
public void RegisterDbServices()
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@inject IDialogService Dialog
|
@inject IDialogService Dialog
|
||||||
|
@inject PageActionsService PageActions
|
||||||
|
@using SteUp.Shared.Core.Messages.System
|
||||||
|
|
||||||
@*
|
@*
|
||||||
Navigazione flottante in basso (STILE_UI §6.15): il contenuto le scorre
|
Navigazione flottante in basso (STILE_UI §6.15): il contenuto le scorre
|
||||||
@@ -7,11 +9,11 @@
|
|||||||
riposo, piena da selezionata, etichetta sempre visibile in entrambe.
|
riposo, piena da selezionata, etichetta sempre visibile in entrambe.
|
||||||
Da 720px diventa una colonna laterale (vedi components.css).
|
Da 720px diventa una colonna laterale (vedi components.css).
|
||||||
|
|
||||||
In mezzo alle due destinazioni sta l'unica azione dell'app: aprire una
|
In mezzo alle due destinazioni sta il comando. Non e' un pulsante che
|
||||||
nuova ispezione. Non e' un pulsante che galleggia sopra la barra e ne
|
galleggia sopra la barra e ne copre il centro: e' la casella centrale,
|
||||||
copre il centro, e' la terza casella della barra, alla stessa altezza
|
alla stessa altezza delle altre due. Sta sempre nello stesso punto, ma
|
||||||
delle altre due. Vale una cosa sola dappertutto: se cambiasse significato
|
quello che fa lo decide la schermata sotto (vedi PageActionsService):
|
||||||
da una pagina all'altra sarebbe un tasto da indovinare.
|
dentro un'ispezione apre le sue azioni, altrove apre una nuova ispezione.
|
||||||
*@
|
*@
|
||||||
|
|
||||||
<nav class="navbar @(IsVisible ? "" : "navbar--hidden")" aria-label="Navigazione principale">
|
<nav class="navbar @(IsVisible ? "" : "navbar--hidden")" aria-label="Navigazione principale">
|
||||||
@@ -22,12 +24,11 @@
|
|||||||
<span class="navbar__label">Ispezioni</span>
|
<span class="navbar__label">Ispezioni</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<button type="button" class="navbar__action" @onclick="NewInspection"
|
<button type="button" class="navbar__action" @onclick="RunAction"
|
||||||
disabled="@(!NetworkService.IsNetworkAvailable())"
|
disabled="@(!NetworkService.IsNetworkAvailable())"
|
||||||
aria-label="Nuova ispezione"
|
aria-label="@ActionLabel"
|
||||||
title="@(NetworkService.IsNetworkAvailable() ? null : "Serve la connessione per aprire una nuova ispezione")">
|
title="@(NetworkService.IsNetworkAvailable() ? ActionLabel : "Serve la connessione")">
|
||||||
<i class="ri-add-line" aria-hidden="true"></i>
|
<i class="ri-add-line" aria-hidden="true"></i>
|
||||||
<span class="navbar__action-label">Nuova</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<NavLink class="navbar__item pressable" href="user" Match="NavLinkMatch.All">
|
<NavLink class="navbar__item pressable" href="user" Match="NavLinkMatch.All">
|
||||||
@@ -49,12 +50,17 @@
|
|||||||
|
|
||||||
private bool IsOn(string route) => CurrentRoute.EqualsIgnoreCase(route);
|
private bool IsOn(string route) => CurrentRoute.EqualsIgnoreCase(route);
|
||||||
|
|
||||||
|
private string ActionLabel => PageActions.HasActions ? "Azioni" : "Nuova ispezione";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
NavigationManager.LocationChanged += OnLocationChanged;
|
NavigationManager.LocationChanged += OnLocationChanged;
|
||||||
|
PageActions.Changed += OnPageActionsChanged;
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPageActionsChanged() => InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
private void OnLocationChanged(object? sender, LocationChangedEventArgs args) => Refresh();
|
private void OnLocationChanged(object? sender, LocationChangedEventArgs args) => Refresh();
|
||||||
|
|
||||||
private void Refresh()
|
private void Refresh()
|
||||||
@@ -63,6 +69,10 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Il comando vale quello che la schermata gli ha dato; se non gliel'ha
|
||||||
|
/// dato nessuno, vale la cosa che l'app fa piu' spesso.
|
||||||
|
private Task RunAction() => PageActions.HasActions ? PageActions.Invoke() : NewInspection();
|
||||||
|
|
||||||
private async Task NewInspection()
|
private async Task NewInspection()
|
||||||
{
|
{
|
||||||
if (!await SteupDataService.CanOpenNewInspection())
|
if (!await SteupDataService.CanOpenNewInspection())
|
||||||
@@ -77,5 +87,9 @@
|
|||||||
await Dialog.OpenSelectShop();
|
await Dialog.OpenSelectShop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged;
|
public void Dispose()
|
||||||
|
{
|
||||||
|
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||||
|
PageActions.Changed -= OnPageActionsChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,27 +13,20 @@
|
|||||||
@using SteUp.Shared.Core.Interface.IntegryApi
|
@using SteUp.Shared.Core.Interface.IntegryApi
|
||||||
@using SteUp.Shared.Core.Interface.LocalDb
|
@using SteUp.Shared.Core.Interface.LocalDb
|
||||||
@using SteUp.Shared.Core.Interface.System
|
@using SteUp.Shared.Core.Interface.System
|
||||||
|
@using SteUp.Shared.Core.Messages.System
|
||||||
@inject IIspezioniService IspezioniService
|
@inject IIspezioniService IspezioniService
|
||||||
@inject IDialogService Dialog
|
@inject IDialogService Dialog
|
||||||
@inject IIntegrySteupService IntegrySteupService
|
@inject IIntegrySteupService IntegrySteupService
|
||||||
@inject IFileManager FileManager
|
@inject IFileManager FileManager
|
||||||
@inject ILogger<IspezionePage> Logger
|
@inject ILogger<IspezionePage> Logger
|
||||||
|
@inject PageActionsService PageActions
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
<HeaderLayout Title="@Titolo" Subtitle="@Sottotitolo" Back="true">
|
@* Le azioni non stanno nella testata: le apre il comando al centro della
|
||||||
<Actions>
|
barra in basso, che finche' questa schermata e' a video significa "azioni
|
||||||
@* Su un'ispezione conclusa resta solo la consultazione: niente azioni. *@
|
di questa ispezione" (vedi PageActionsService). Su un'ispezione conclusa
|
||||||
@if (!IsCompleted)
|
non si registra nulla e resta solo la consultazione. *@
|
||||||
{
|
<HeaderLayout Title="@Titolo" Subtitle="@Sottotitolo" Back="true"/>
|
||||||
@* Qui le azioni sono piu' d'una e non hanno lo stesso peso: lo
|
|
||||||
sheet le mostra con nome, icona e tono, invece di nasconderle
|
|
||||||
tutte dietro lo stesso glifo. *@
|
|
||||||
<button type="button" class="appbar__icon-action" @onclick="@(() => _actionsOpen = true)"
|
|
||||||
aria-label="Azioni sull'ispezione" title="Azioni">
|
|
||||||
<i class="ri-more-2-fill" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</Actions>
|
|
||||||
</HeaderLayout>
|
|
||||||
|
|
||||||
<ActionSheet @bind-Open="_actionsOpen" Title="Azioni ispezione" Subtitle="@Ispezione.CodMdep">
|
<ActionSheet @bind-Open="_actionsOpen" Title="Azioni ispezione" Subtitle="@Ispezione.CodMdep">
|
||||||
<div class="stack stack-xs">
|
<div class="stack stack-xs">
|
||||||
@@ -253,7 +246,26 @@
|
|||||||
|
|
||||||
private string Sottotitolo => Ispezione.CodMdep;
|
private string Sottotitolo => Ispezione.CodMdep;
|
||||||
|
|
||||||
protected override void OnInitialized() => LoadSchede();
|
private IDisposable? _actionsRegistration;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
LoadSchede();
|
||||||
|
|
||||||
|
// Su un'ispezione conclusa non c'e' niente da fare: il comando della
|
||||||
|
// barra si tiene il suo significato di default invece di aprire uno
|
||||||
|
// sheet di azioni tutte spente.
|
||||||
|
if (!IsCompleted)
|
||||||
|
_actionsRegistration = PageActions.Register(() => InvokeAsync(OpenActions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenActions()
|
||||||
|
{
|
||||||
|
_actionsOpen = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => _actionsRegistration?.Dispose();
|
||||||
|
|
||||||
private void LoadSchede()
|
private void LoadSchede()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
namespace SteUp.Shared.Core.Messages.System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ponte fra il comando al centro della barra di navigazione e la schermata
|
||||||
|
/// che sta sotto. Il comando sta sempre nello stesso punto — sotto il pollice,
|
||||||
|
/// dove il rilevatore lo cerca — ma quello che fa appartiene alla schermata:
|
||||||
|
/// dentro un'ispezione apre le azioni di quell'ispezione, altrove apre una
|
||||||
|
/// nuova ispezione.
|
||||||
|
///
|
||||||
|
/// La pagina si registra finché è a video e si sfila uscendo, così la barra
|
||||||
|
/// non tiene in vita un riferimento a schermate che non ci sono più. Chi non
|
||||||
|
/// registra niente lascia al comando il suo significato di default: la barra
|
||||||
|
/// non resta mai con un tasto che non fa nulla.
|
||||||
|
/// </summary>
|
||||||
|
public class PageActionsService
|
||||||
|
{
|
||||||
|
private Func<Task>? _handler;
|
||||||
|
|
||||||
|
/// Avvisa la barra che il significato del comando è cambiato, così si ridisegna.
|
||||||
|
public event Action? Changed;
|
||||||
|
|
||||||
|
public bool HasActions => _handler is not null;
|
||||||
|
|
||||||
|
public IDisposable Register(Func<Task> handler)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
Changed?.Invoke();
|
||||||
|
|
||||||
|
return new Registration(this, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Invoke() => _handler?.Invoke() ?? Task.CompletedTask;
|
||||||
|
|
||||||
|
private sealed class Registration(PageActionsService owner, Func<Task> handler) : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Solo chi ha registrato per ultimo può sfilare: se una schermata
|
||||||
|
// nuova si è già registrata, la vecchia uscendo non deve spegnerla.
|
||||||
|
if (owner._handler != handler) return;
|
||||||
|
|
||||||
|
owner._handler = null;
|
||||||
|
owner.Changed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -568,30 +568,28 @@
|
|||||||
transition: background-color var(--duration-base) var(--ease-out);
|
transition: background-color var(--duration-base) var(--ease-out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* L'azione al centro: e' una casella della barra come le altre, alla stessa
|
/* Il comando al centro: una casella della barra come le altre, alla stessa
|
||||||
altezza, solo piena d'accento. L'unico accento della schermata sta sul solo
|
altezza, solo piena d'accento. Senza etichetta, perche' non ne ha una sola:
|
||||||
comando che crea qualcosa. */
|
quello che fa lo decide la schermata sotto. L'unico accento della schermata
|
||||||
|
sta sull'unico comando. */
|
||||||
.navbar__action {
|
.navbar__action {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--space-6);
|
aspect-ratio: 1;
|
||||||
min-width: var(--touch-min);
|
min-width: var(--touch-min);
|
||||||
padding-inline: var(--space-md);
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
color: var(--on-primary);
|
color: var(--on-primary);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: var(--label-lg-size);
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: transform var(--duration-fast) var(--ease-out),
|
transition: transform var(--duration-fast) var(--ease-out),
|
||||||
background-color var(--duration-fast) var(--ease-out);
|
background-color var(--duration-fast) var(--ease-out);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar__action i { font-size: 22px; }
|
.navbar__action i { font-size: 26px; }
|
||||||
|
|
||||||
.navbar__action:active { transform: scale(0.96); background-color: var(--primary-pressed); }
|
.navbar__action:active { transform: scale(0.96); background-color: var(--primary-pressed); }
|
||||||
|
|
||||||
@@ -644,8 +642,8 @@
|
|||||||
qualcosa incastrato fra due destinazioni. */
|
qualcosa incastrato fra due destinazioni. */
|
||||||
.navbar__action {
|
.navbar__action {
|
||||||
order: -1;
|
order: -1;
|
||||||
align-self: stretch;
|
align-self: center;
|
||||||
height: 48px;
|
height: 52px;
|
||||||
margin-bottom: var(--space-xs);
|
margin-bottom: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user