/** * Radice dell'app: apre il database, sceglie la lingua, monta la navigazione. * * Il database si apre una volta all'avvio. Se non si apre, l'app lo dice e si * ferma: non parte vuota fingendo che vada tutto bene, perché un ricettario * vuoto sembra un ricettario perso (spec, sezione 9). * * Per lo stesso motivo qui NON si monta ``: il provider di * expo-sqlite apre il database per conto suo e trasforma il fallimento in un * throw che noi non intercettiamo, mentre a noi serve mostrare `errore.db`. * `apriDb()` la chiamiamo noi, e la connessione la distribuiamo con ContestoApp. * * La rotta iniziale è Categorie: la prima cosa che si vede aprendo l'app sono i * raggruppamenti, non l'elenco. * * Questo file esporta solo il componente: i tipi delle rotte stanno in * navigazione.ts, che le schermate importano. */ import { useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; import type { SQLiteDatabase } from 'expo-sqlite'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useFonts } from 'expo-font'; // Dal percorso del peso, non dalla radice del pacchetto: l'indice di // @expo-google-fonts/caveat fa un `require` per ognuno dei quattro pesi, e chi // importa la radice se li porta dentro tutti e quattro — tre quarti di mega di // carattere che l'app non usa. `useFonts` è quello di expo-font: quello del // pacchetto dei font ne è una copia, e importarlo dalla radice rimetterebbe // dentro gli altri tre. import { Caveat_700Bold } from '@expo-google-fonts/caveat/700Bold'; import { apriDb } from '../data/db.ts'; import type { Lingua } from '../domain/lingua/index.ts'; import type { Chiave } from '../i18n/index.ts'; import { linguaDispositivo, t } from '../i18n/index.ts'; import { ContestoApp } from './contesto.ts'; import type { StatoApp } from './contesto.ts'; import type { ParametriNav } from './navigazione.ts'; import Categorie from './schermate/Categorie.tsx'; import Elenco from './schermate/Elenco.tsx'; import Dosatore from './schermate/Dosatore.tsx'; import GestioneCategorie from './schermate/GestioneCategorie.tsx'; import Modifica from './schermate/Modifica.tsx'; import type { Colori } from './tema.ts'; import { MANO, TESTO, useColori } from './tema.ts'; const Stack = createNativeStackNavigator(); type Fase = | { nome: 'apertura' } | { nome: 'pronto'; db: SQLiteDatabase } | { nome: 'errore' }; export default function App() { const lingua = useMemo(() => linguaDispositivo(), []); const [fase, setFase] = useState({ nome: 'apertura' }); const colori = useColori(); // Il serif dei titoli si carica dentro la stessa attesa che apre il database, // così non si vede il titolo cambiare carattere a schermata già aperta. Se il // caricamento fallisce si va avanti lo stesso col carattere di sistema: per un // font non si tiene fuori l'utente dal suo ricettario. const [fontPronto, erroreFont] = useFonts({ [MANO]: Caveat_700Bold }); useEffect(() => { let vivo = true; apriDb() .then((db) => { if (vivo) setFase({ nome: 'pronto', db }); }) .catch((errore) => { console.warn('apertura del database fallita', errore); if (vivo) setFase({ nome: 'errore' }); }); return () => { vivo = false; }; }, []); const contesto = useMemo( () => fase.nome === 'pronto' ? { db: fase.db, lingua, testo: (chiave: Chiave, valori?: Record) => t(chiave, lingua, valori), } : null, [fase, lingua], ); if (fase.nome === 'apertura' || (!fontPronto && !erroreFont)) { return ( ); } if (contesto === null) { return ( {t('errore.db', lingua)} ); } return ( : il // perché sta nell'intestazione di quel componente. Qui resta al // navigatore quello che sa fare meglio di noi — la transizione // fra le schermate e il gesto di trascinare dal bordo. headerShown: false, contentStyle: { backgroundColor: colori.fondo }, }} > ); } /** * Il marchio nell'intestazione della schermata principale: il nome scritto a * mano e, sotto, la riga che dice cosa fa l'app. * * "Quanto Basta" non si traduce, è il nome; il sottotitolo sì, ed è anche la * riga con cui l'app si trova cercando negli store. Restano attaccati perché * separati non vogliono dire niente: il nome da solo non spiega, la riga da * sola non è un marchio. */ export function Marchio({ colori, lingua }: { colori: Colori; lingua: Lingua }) { return ( {t('app.nome', lingua)} {t('app.sottotitolo', lingua)} ); } const stili = StyleSheet.create({ centro: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 32 }, marchio: { alignItems: 'center', justifyContent: 'center' }, errore: { fontSize: 16, textAlign: 'center' }, });