/** * La barra in alto di sistema, sull'iPhone: titolo e comandi passati alla * pila di navigazione, che li disegna su vetro (Liquid Glass). La freccia per * tornare indietro la mette la pila da sola, in un cerchio di vetro. * * Su Android `useIntestazione(null)` non fa niente e la schermata disegna la * sua Barra.tsx, com'era. Il perché della divisione e le regole di Apple * stanno in logica-intestazione.ts. * * Le azioni dei comandi si rifanno a ogni disegno della schermata e possono * leggere uno stato appena cambiato: la barra chiama sempre l'ultima versione * (il ref), ma si riscrive solo quando cambia qualcosa che si vede * (improntaComandi). Riscriverla a ogni disegno farebbe ridisegnare il * navigatore a ogni tasto premuto. */ import { HeaderHeightContext } from '@react-navigation/elements'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import type { ReactNode } from 'react'; import { useContext, useLayoutEffect, useRef } from 'react'; import { Platform, PlatformColor } from 'react-native'; import type { ComandoIntestazione } from '../logica-intestazione.ts'; import { ACCENTO_SOTTO_IL_BIANCO, elementiDestra, elementoChiudi, improntaComandi, intestazioneDiSistema, opzioniPila, } from '../logica-intestazione.ts'; import { useColori } from '../tema.ts'; /** Vero sull'iPhone: la schermata non disegna la sua Barra e chiama useIntestazione. */ export const INTESTAZIONE_DI_SISTEMA = intestazioneDiSistema(Platform.OS); /** * Le opzioni delle pile di navigazione (opzioniPila), con il colore «label» di * sistema per i simboli della barra: un colore che il vetro sa girare quando * sotto c'è una foto chiara. React Navigation lo tipizza come stringa, ma * passa il valore a React Native così com'è, e PlatformColor ci arriva intero. */ export function useOpzioniPila() { const colori = useColori(); const tinta = INTESTAZIONE_DI_SISTEMA ? (PlatformColor('label') as unknown as string) : colori.testo; return opzioniPila(Platform.OS, colori, tinta); } /** * Quanto è alta la barra di vetro, sull'iPhone: il contenuto le passa sotto, * e quello che non scorre (una rotella, un messaggio, una copertina) deve * sapere da dove cominciare. Zero su Android, dove la barra è nostra e occupa * il suo posto, e zero dove una barra non c'è (il pannello degli schermi * larghi). */ export function useAltezzaBarra(): number { const altezza = useContext(HeaderHeightContext) ?? 0; return INTESTAZIONE_DI_SISTEMA ? altezza : 0; } export interface OpzioniIntestazione { /** Il titolo al centro. Stringa vuota: niente titolo. */ titolo?: string; /** Il colore del titolo, per l'elenco che prende la tinta della categoria. */ coloreTitolo?: string; /** Al posto del titolo: il marchio della schermata principale. */ centro?: ReactNode; /** * Quando `centro` cambia aspetto senza che cambi il titolo (la lingua del * sottotitolo del marchio): una stringa nuova ridisegna la barra. */ chiaveCentro?: string; /** I comandi a destra, nell'ordine in cui si leggono. */ destra?: ComandoIntestazione[]; /** * La X a sinistra, nelle schermate che salgono dal basso (l'anteprima * dell'import): lì non c'è la freccia, si chiude. */ chiudi?: { etichetta: string; azione: () => void }; } export function useIntestazione(opzioni: OpzioniIntestazione | null) { const navigation = useNavigation(); const colori = useColori(); const ultime = useRef(opzioni); ultime.current = opzioni; const attiva = opzioni !== null && INTESTAZIONE_DI_SISTEMA; const impronta = opzioni === null ? '' : improntaComandi([ ...(opzioni.destra ?? []), ...(opzioni.chiudi !== undefined ? [{ etichetta: `chiudi:${opzioni.chiudi.etichetta}` }] : []), ]); const titolo = opzioni?.titolo ?? ''; const coloreTitolo = opzioni?.coloreTitolo; const conCentro = opzioni?.centro !== undefined; const chiaveCentro = opzioni?.chiaveCentro; const conChiudi = opzioni?.chiudi !== undefined; useLayoutEffect(() => { if (!attiva) return; // Ogni azione passa dal ref: la barra resta quella di prima, ma il tocco // arriva alla funzione dell'ultimo disegno. const comandi: ComandoIntestazione[] = (ultime.current?.destra ?? []).map((c, i) => ({ ...c, azione: () => ultime.current?.destra?.[i]?.azione?.(), voci: c.voci?.map((v, j) => ({ ...v, azione: () => ultime.current?.destra?.[i]?.voci?.[j]?.azione() })), })); const etichettaChiudi = ultime.current?.chiudi?.etichetta ?? ''; const opzioniPila: NativeStackNavigationOptions = { title: titolo, headerTitle: conCentro ? () => ultime.current?.centro ?? null : undefined, headerTitleStyle: { color: coloreTitolo ?? colori.testo }, // Il comando in evidenza ha la scritta bianca di iOS: il suo fondo è // l'accento che regge il bianco, con tutt'e due i temi. unstable_headerRightItems: () => elementiDestra(comandi, ACCENTO_SOTTO_IL_BIANCO), unstable_headerLeftItems: conChiudi ? () => [elementoChiudi(etichettaChiudi, () => ultime.current?.chiudi?.azione())] : undefined, }; navigation.setOptions(opzioniPila); }, [attiva, navigation, impronta, titolo, coloreTitolo, conCentro, chiaveCentro, conChiudi, colori]); }