/** * Una ricetta nella vista a griglia: la foto grande, e sotto il titolo su fondo * scuro pieno. * * La forma viene da una scheda che Paolo aveva gia' disegnato per i suoi giochi * da tavolo, e risolve un problema che i miei tentativi precedenti non * risolvevano. Scrivere in bianco SOPRA una fotografia vuol dire che il * contrasto dipende dalla fotografia, e le fotografie non le ho ancora viste: * per garantire 4,5:1 su una foto bianca — il caso peggiore — servirebbe un * velo nero al 54% steso su tutto il testo, che scurisce l'immagine e si vede. * * La foto occupa TUTTA la tessera e il testo ci sta sopra: sotto di lui la * sfumatura e' arrivata al nero pieno, quindi di fotografico non resta niente e * il contrasto e' sempre lo stesso — qualunque foto ci sia, e per quanto * l'utente ingrandisca i caratteri. * * Un primo tentativo divideva la tessera in due, foto sopra e fascia nera piena * sotto, con in mezzo una sfumatura di raccordo. Era sbagliato per due motivi, * e si e' visto solo guardandolo: la fascia tagliava di netto la fotografia a * meta' altezza, e la sfumatura sopra di lei non serviva a niente, perche' li' * non c'era nessun testo da far leggere. Due elementi per un lavoro solo. * * Il nero della sfumatura e' un letterale e non un colore della tavolozza, per * la stessa ragione dell'ombra in tema.ts: non e' una tinta scelta, e' il buio * sotto una fotografia, e vale uguale col tema chiaro e con quello scuro. * * Proprio perche' vale uguale serve il contorno. Sul fondo crema la tessera si * stacca da sola, ma sul tema scuro il buio in fondo e il fondo della schermata * sono lo stesso colore: senza il filo di contorno la griglia notturna sarebbe * un blocco unico in cui non si capisce dove finisce una ricetta. * * Senza foto la tessera non prova a sembrare una foto mancante: prende la tinta * piena della categoria, che e' un colore che quella categoria ha gia'. I * contrasti del testo su ognuna delle otto tinte forti stanno fra 8,05:1 e * 9,15:1, verificati col calcolo come tutti gli altri. */ import { Image, Pressable, StyleSheet, Text, View } from 'react-native'; import type { Categoria, Ricetta } from '../../domain/types.ts'; import Icona from './Icona.tsx'; import type { Colori } from '../tema.ts'; import { RAGGIO, SPAZIO, TESTO } from '../tema.ts'; import type { Tinta } from '../tinte.ts'; /** Il buio sotto la fotografia. Non e' della tavolozza: vedi l'intestazione. */ const BUIO = '#17120F'; const SOPRA_BUIO = '#FFFFFF'; const SOPRA_BUIO_TENUE = '#CFC5BC'; /** Quanto e' alta una tessera. Foto e testo stanno tutti e due qui dentro. */ const ALTEZZA = 210; /** * La sfumatura: alta i quattro quinti della tessera, dal nulla in cima al nero * pieno in fondo. * * Va molto piu' su di quanto serva al testo, e non e' spreco: una sfumatura * corta si vede tutta, e la riga dove comincia taglia l'immagine in due. Lunga, * invece, non si nota il passaggio — smette di sembrare un velo messo li' per * far leggere il testo e diventa il modo in cui quella foto finisce. * * Alta cosi', dove sta il titolo il nero copre gia' oltre il settanta per * cento: sulla foto piu' chiara che esista fa 8,5:1, quasi il doppio della * soglia. Anche col testo ingrandito al massimo il titolo resta dentro. */ const ALTEZZA_VELO = 170; export interface PropsTessera { ricetta: Ricetta; /** La sua categoria, se ce l'ha e se e' stata letta. */ categoria: Categoria | null; /** Il percorso della foto sul disco, o null se non c'e'. */ foto: string | null; /** La tinta con cui disegnare la tessera quando la foto manca. */ tinta: Tinta; /** Gia' tradotto e unito: «Dolci · 8 ingredienti · 35 porzioni». */ dati: string; colori: Colori; onPress: () => void; } export default function TesseraRicetta({ ricetta, categoria, foto, tinta, dati, colori, onPress, }: PropsTessera) { const stili = creaStili(colori); const conFoto = foto !== null; return ( [stili.tessera, pressed && stili.premuta]} onPress={onPress} accessibilityRole="button" accessibilityLabel={`${ricetta.titolo}, ${dati}`} > {conFoto ? ( <> {/* Una PNG di un pixel, stirata. React Native 0.86 sa fare un gradiente vero solo dietro `experimental_backgroundImage`, che la sua documentazione dice di non usare in produzione. */} ) : ( /* Senza foto la tessera non prova a sembrare una foto mancante: prende la tinta piena della categoria, con la sua icona in mezzo, ed e' la stessa cosa che si vede nella schermata principale. */ {categoria !== null && ( )} )} {ricetta.titolo} {dati} ); } const creaStili = (colori: Colori) => StyleSheet.create({ tessera: { flex: 1, height: ALTEZZA, borderRadius: RAGGIO.foto, overflow: 'hidden', justifyContent: 'flex-end', // Si vede nell'attimo prima che la foto sia pronta: meglio il buio che un // lampo bianco sopra il crema. backgroundColor: BUIO, borderWidth: StyleSheet.hairlineWidth, borderColor: colori.bordo, }, premuta: { opacity: 0.7 }, senzaFoto: { alignItems: 'center', justifyContent: 'center' }, velo: { position: 'absolute', left: 0, right: 0, bottom: 0, height: ALTEZZA_VELO, width: undefined, }, testi: { paddingHorizontal: SPAZIO.m, paddingBottom: SPAZIO.m, gap: 3 }, titolo: { ...TESTO.etichetta, fontWeight: '700', color: SOPRA_BUIO, letterSpacing: -0.2 }, dati: { ...TESTO.piccolo, fontSize: 12, color: SOPRA_BUIO_TENUE }, });