/** * Il foglio che sale dal basso, sull'iPhone: quello di sistema (la `sheet` * di SwiftUI), di vetro come le altre cose di iOS 26. Si stacca dai bordi, * lascia intravedere la pagina sotto, si chiude trascinandolo giù, e in cima * ha i comandi di Apple: la X di vetro a sinistra, il titolo, la spunta * colorata a destra. * * Chi lo usa non cambia niente: passa a Foglio lo stesso contenuto di prima, * con dentro la sua TestaFoglio. Qui TestaFoglio non disegna niente e * consegna titolo e comandi al foglio (ContestoFoglio), che li mette nella * testata di sistema. Il fondo del contenuto lo toglie vesteFoglio: sul vetro * un fondo pieno coprirebbe proprio il vetro. * * Su Android resta il Foglio nostro, com'era: la scelta sta in * logica-intestazione.ts. */ import type { ReactNode } from 'react'; import { createContext, useContext, useEffect, useRef, useState } from 'react'; import { useWindowDimensions, View } from 'react-native'; import type { ViewStyle } from 'react-native'; import { BottomSheet, Button as BottoneSwift, Group as GruppoSwift, HStack, Host, RNHostView, Spacer, Text as TestoSwift, VStack, } from '@expo/ui/swift-ui'; import { buttonStyle, disabled, font, frame, interactiveDismissDisabled, labelStyle, onAppear, padding, presentationDragIndicator, tint, } from '@expo/ui/swift-ui/modifiers'; import { useColori, useSchema } from '../tema.ts'; import { INTESTAZIONE_DI_SISTEMA } from './Intestazione.tsx'; import { ACCENTO_SOTTO_IL_BIANCO } from '../logica-intestazione.ts'; import { APPENA_APERTO, dopoEvento, improntaTesta } from '../logica-foglio.ts'; import type { EventoFoglio, StatoChiusura, TestaDiSistema } from '../logica-foglio.ts'; export type { TestaDiSistema }; /** Dove TestaFoglio consegna titolo e comandi, quando il foglio è quello di sistema. */ export const ContestoFoglio = createContext<((testa: TestaDiSistema) => void) | null>(null); export function useConsegnaTesta(): ((testa: TestaDiSistema) => void) | null { return useContext(ContestoFoglio); } /** * Da aggiungere in coda allo stile del contenuto di un foglio. Nel foglio di * sistema toglie il fondo pieno e gli angoli (sul vetro coprirebbero il * vetro, e gli angoli li fa già il foglio) e il margine per l'indicatore home, * che il foglio di sistema lascia già da sé. Su Android non cambia niente. */ export const VESTE_FOGLIO: ViewStyle | null = INTESTAZIONE_DI_SISTEMA ? { backgroundColor: 'transparent', borderTopLeftRadius: 0, borderTopRightRadius: 0, paddingBottom: 16 } : null; export interface PropsFoglioDiSistema { visibile: boolean; onChiudi: () => void; onChiuso?: () => void; onAperto?: () => void; children: ReactNode; } export function FoglioDiSistema({ visibile, onChiudi, onChiuso, onAperto, children }: PropsFoglioDiSistema) { const colori = useColori(); const schema = useSchema(); const larghezza = useWindowDimensions().width; const [testa, setTesta] = useState(null); // Le azioni passano dal ref: la testata disegnata resta quella, il tocco // arriva alla funzione dell'ultimo disegno del contenuto. Lo stato cambia // solo quando cambia qualcosa che si vede: TestaFoglio consegna a ogni // disegno del contenuto, e ridisegnare il foglio a ogni consegna farebbe // ridisegnare il contenuto, che consegnerebbe di nuovo, all'infinito. const ultima = useRef(null); const consegna = useRef((nuova: TestaDiSistema) => { ultima.current = nuova; setTesta((prima) => (prima !== null && improntaTesta(prima) === improntaTesta(nuova) ? prima : nuova)); }).current; const aperto = useRef(onAperto); aperto.current = onAperto; const chiudi = useRef(onChiudi); chiudi.current = onChiudi; const chiuso = useRef(onChiuso); chiuso.current = onChiuso; // Quando avvisare che il foglio è stato chiuso col dito: dopoEvento, in // logica-foglio.ts, col perché. const chiusura = useRef(APPENA_APERTO); const evento = (e: EventoFoglio) => { const esito = dopoEvento(chiusura.current, e); chiusura.current = esito.stato; if (esito.avvisa) chiudi.current(); }; // Ogni apertura riparte da capo. useEffect(() => { if (visibile) chiusura.current = APPENA_APERTO; }, [visibile]); // Il cursore nel campo appena il contenuto del foglio compare (onAppear di // SwiftUI, sotto), come faceva il Foglio nostro: la tastiera sale insieme // al foglio. Un tempo fisso dipendeva da quanto ci mette il telefono. const compare = () => aperto.current?.(); const sinistra = testa?.sinistra; const destra = testa?.destra; return ( { if (!v) evento('gesto'); }} onDismiss={() => { evento('sparito'); chiuso.current?.(); }} fitToContents > {sinistra !== undefined ? ( ultima.current?.sinistra?.onPress()} modifiers={[buttonStyle('glass'), tint(colori.testo), labelStyle('iconOnly')]} /> ) : ( )} {testa?.titolo ?? ''} {destra !== undefined ? ( ultima.current?.destra?.onPress()} modifiers={[ buttonStyle('glassProminent'), // La spunta la disegna iOS in bianco: il fondo è l'accento // che regge il bianco, anche col tema scuro (tema.ts). tint(ACCENTO_SOTTO_IL_BIANCO), disabled(destra.spento === true), labelStyle('iconOnly'), ]} /> ) : ( )} {/* La larghezza è quella della finestra: il foglio di iOS 26, staccato dai bordi, è più stretto di qualche punto, e i bordi del contenuto (che ha già il suo margine) restano fuori. */} {children} ); }