/** * Una riga di ingrediente in scrittura: quantità, unità, nome, e il tasto per * toglierla. Le righe che il parser non ha capito sono in evidenza, con * `attenzione` (si può fare, ma guarda) e non `errore`: la riga si salva * anche così, va solo controllata. * * Il testo dell'utente non viene mai interpretato: TextInput e Text rendono * testo semplice, non markup. Niente WebView, niente HTML. * * I tre campi sono collegati fra loro: il tasto della tastiera dice «avanti» e * porta al successivo senza chiudere niente, come il tabulatore in un modulo. * Sull'ultimo campo dell'ultima riga dice «fine» e chiude. * * C'era anche una barra sopra la tastiera con le frecce avanti e indietro, ed * è stata tolta: su iOS si smontava ad ogni cambio di campo e le frecce * sparivano dopo il primo, su Android non esisteva affatto, e il tasto della * tastiera fa già la stessa cosa su tutti e due i sistemi. * * Chi tiene il registro dei campi e sa qual è il prossimo è Modifica.tsx, che * passa `registra` e `onAvanti`: la riga non sa di essere in una lista. * * I colori non si scrivono qui: vengono da tema.ts, come nelle altre schermate. */ import { useMemo } from 'react'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import { t } from '../../i18n/index.ts'; import type { CampoRiga } from '../logica-modifica.ts'; import { etichettaTogliRiga } from '../logica-modifica.ts'; import type { Lingua } from '../../domain/lingua/index.ts'; import type { Colori } from '../tema.ts'; import { SPAZIO, TESTO, useColori } from '../tema.ts'; import type { RigaBozza } from '../bozza.ts'; export type PropsRigaIngrediente = { riga: RigaBozza; lingua: Lingua; onCambia: (campi: Partial>) => void; onTogli: () => void; /** Consegna il campo a chi tiene il registro, per poterlo mettere a fuoco. */ registra: (campo: CampoRiga, elemento: TextInput | null) => void; /** Porta al campo dopo, o chiude la tastiera se non ce n'è. */ onAvanti: (campo: CampoRiga) => void; /** Se dopo questo campo ce n'è un altro: decide cosa scrive il tasto invio. */ haSeguito: (campo: CampoRiga) => boolean; }; export function RigaIngrediente({ riga, lingua, onCambia, onTogli, registra, onAvanti, haSeguito, }: PropsRigaIngrediente) { const colori = useColori(); const stili = useMemo(() => creaStili(colori), [colori]); return ( registra('quantita', el)} style={[stili.campo, stili.quantita]} value={riga.quantita} onChangeText={(v) => onCambia({ quantita: v })} // «q.tà» e non «q.b.»: una quantità lasciata vuota è davvero un // quanto basta, ma un segnaposto deve dire cosa scrivere lì, non // cosa succede se non ci si scrive niente. placeholder={t('modifica.quantita.segnaposto', lingua)} placeholderTextColor={colori.testoTenue} // Su iOS dà la tastiera coi numeri ma lascia scrivere 1/2 e 1,5; // su Android non esiste e si ricade sulla tastiera normale. keyboardType="numbers-and-punctuation" returnKeyType={haSeguito('quantita') ? 'next' : 'done'} // `blurOnSubmit={false}` solo finché si prosegue: senza, passando al // campo dopo la tastiera si chiude e si riapre, e il contenuto salta. // Sull'ultimo invece la si lascia chiudere, che è quello che «fine» // promette. blurOnSubmit={!haSeguito('quantita')} onSubmitEditing={() => onAvanti('quantita')} /> registra('unita', el)} style={[stili.campo, stili.unita]} value={riga.unita} onChangeText={(v) => onCambia({ unita: v })} autoCapitalize="none" autoCorrect={false} returnKeyType={haSeguito('unita') ? 'next' : 'done'} // `blurOnSubmit={false}` solo finché si prosegue: senza, passando al // campo dopo la tastiera si chiude e si riapre, e il contenuto salta. // Sull'ultimo invece la si lascia chiudere, che è quello che «fine» // promette. blurOnSubmit={!haSeguito('unita')} onSubmitEditing={() => onAvanti('unita')} /> registra('nome', el)} style={[stili.campo, stili.nome]} value={riga.nome} onChangeText={(v) => onCambia({ nome: v })} returnKeyType={haSeguito('nome') ? 'next' : 'done'} // `blurOnSubmit={false}` solo finché si prosegue: senza, passando al // campo dopo la tastiera si chiude e si riapre, e il contenuto salta. // Sull'ultimo invece la si lascia chiudere, che è quello che «fine» // promette. blurOnSubmit={!haSeguito('nome')} onSubmitEditing={() => onAvanti('nome')} /> {/* 44x44 dichiarati, non un hitSlop: righe consecutive sono vicine, e un hitSlop generoso sconfinerebbe nella riga sopra o sotto (vedi GestioneCategorie.tsx, dove tre hitSlop in fila si sovrapponevano). L'etichetta è il comando di questa riga, non 'modifica.elimina', che è «Elimina ricetta»: sul gesto più distruttivo della riga diceva il contrario di quello che fa. */} × {riga.incerta ? {t('modifica.riga.incerta', lingua)} : null} ); } const creaStili = (colori: Colori) => StyleSheet.create({ riga: { paddingVertical: SPAZIO.xs }, rigaIncerta: { borderLeftWidth: 3, borderLeftColor: colori.attenzione, paddingLeft: SPAZIO.s, }, campi: { flexDirection: 'row', alignItems: 'center', gap: SPAZIO.s }, campo: { borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colori.bordo, paddingVertical: SPAZIO.s, ...TESTO.corpo, color: colori.testo, }, quantita: { width: 64, textAlign: 'right' }, unita: { width: 72 }, nome: { flex: 1 }, togli: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, togliSegno: { fontSize: 22, color: colori.testoTenue }, avviso: { ...TESTO.piccolo, color: colori.attenzione, paddingLeft: SPAZIO.s, paddingTop: 2, }, });