diff --git a/App.tsx b/App.tsx index 2f7abd1..906b2f5 100644 --- a/App.tsx +++ b/App.tsx @@ -1,17 +1,26 @@ /** * BehördenKlar — Behördenbriefe verstehen, übersetzen, beantworten. * Einstiegspunkt: Navigation + Initialisierung (Archiv, Benachrichtigungen). + * + * Aufbau: untere Tab-Leiste (Fristen · Scannen · Archiv) mit darüber + * liegenden Detail-Screens (Analyse, Antwort, Einstellungen …). */ import React, { useEffect } from 'react'; +import { Pressable, StyleSheet, View } from 'react-native'; import { StatusBar } from 'expo-status-bar'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { RootStackParamList } from './src/types'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator, NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { HauptTabParamList, RootStackParamList } from './src/types'; import { useAppStore } from './src/store/useAppStore'; import { initialisiereBenachrichtigungen } from './src/services/erinnerungen'; +import { holeConsent } from './src/services/storage'; import { AppSchutz } from './src/components/AppSchutz'; +import { Ikone } from './src/components/Ikone'; import { farben, schrift } from './src/theme'; -import { HomeScreen } from './src/screens/HomeScreen'; +import { FristenScreen } from './src/screens/FristenScreen'; +import { ArchivScreen } from './src/screens/ArchivScreen'; import { ConsentScreen } from './src/screens/ConsentScreen'; import { ScanScreen } from './src/screens/ScanScreen'; import { AnalyseScreen } from './src/screens/AnalyseScreen'; @@ -20,6 +29,78 @@ import { GlossarScreen } from './src/screens/GlossarScreen'; import { EinstellungenScreen } from './src/screens/EinstellungenScreen'; const Stack = createNativeStackNavigator(); +const Tab = createBottomTabNavigator(); + +/** Platzhalter für den mittleren Scan-Tab — seine Taste öffnet den Scan-Flow, + * der Inhalt wird nie angezeigt (der Tab-Wechsel wird abgefangen). */ +const LeererScreen = () => null; + +/** Erhabener runder Scan-Knopf in der Mitte der Tab-Leiste. */ +function ScanKnopf({ onPress }: { onPress: () => void }) { + return ( + + [styles.scanKnopf, pressed && { opacity: 0.85 }]} + > + + + + ); +} + +function HauptTabs() { + const navigation = useNavigation>(); + const insets = useSafeAreaInsets(); + + const scanStarten = async () => { + const ok = await holeConsent(); + navigation.navigate(ok ? 'Scan' : 'Consent'); + }; + + return ( + + , + }} + /> + }} + listeners={{ tabPress: (e) => e.preventDefault() }} + /> + , + }} + /> + + ); +} export default function App() { const initialisiere = useAppStore((s) => s.initialisiere); @@ -33,26 +114,45 @@ export default function App() { - - - - - - - - + + + + + + + + ); } + +const styles = StyleSheet.create({ + scanWrap: { flex: 1, alignItems: 'center', justifyContent: 'flex-start' }, + scanKnopf: { + width: 56, + height: 56, + borderRadius: 28, + marginTop: -18, + backgroundColor: farben.primaer, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 3, + borderColor: farben.flaeche, + shadowColor: '#000', + shadowOffset: { width: 0, height: 3 }, + shadowOpacity: 0.2, + shadowRadius: 5, + elevation: 5, + }, +}); diff --git a/package-lock.json b/package-lock.json index cd85eee..95becb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/bottom-tabs": "^7.18.14", "@react-navigation/native": "^7.3.8", "@react-navigation/native-stack": "^7.17.10", "expo": "^57.0.0", @@ -2344,13 +2345,31 @@ } } }, - "node_modules/@react-navigation/core": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.21.5.tgz", - "integrity": "sha512-3hpV7uR41LBW+GHDoLhztZCb/i5ySRJISZ/rez4d7DCHSZo6ej4gNxYclaS6LRguoLiKG7SOCNa6O390AQklZQ==", + "node_modules/@react-navigation/bottom-tabs": { + "version": "7.18.14", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.14.tgz", + "integrity": "sha512-A3V9rDSut459TBPtkD7rb0npUUBlJBfMunyRT5nOGKqPguhuXWk1h91NfQMuqyW2DCUvvFZChzsbLbj42rXTdQ==", "license": "MIT", "dependencies": { - "@react-navigation/routers": "^7.6.0", + "@react-navigation/elements": "^2.9.36", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^7.3.14", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/core": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.21.11.tgz", + "integrity": "sha512-bCW1PsLA/eOXDOukcJFEzlcL3Zpy8DJuDCfkDDwAQlAgoSZ/J9+ZeDRUMmCUi6xbnFgvFEEIMertaLeErOFP0Q==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.6.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", @@ -2364,15 +2383,15 @@ } }, "node_modules/@react-navigation/core/node_modules/react-is": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT" }, "node_modules/@react-navigation/elements": { - "version": "2.9.30", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.30.tgz", - "integrity": "sha512-2isleieiRMmP4WNMV2Q1u3qP1M47ZqsJ2hJ/Og11FeKXK8YmUTHya7PW7ecsgAh2CXKTxAcbfJDFTSvq2D++Tw==", + "version": "2.9.36", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.36.tgz", + "integrity": "sha512-+10x9s5v2Q7FwAYdSmPMgILtxZyC5e4hWJQu8g5o3u4p8DUToTBmGvys/UvmEr+h9xmm0Go42qw9Ff2ape53kQ==", "license": "MIT", "dependencies": { "color": "^4.2.3", @@ -2381,7 +2400,7 @@ }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.3.8", + "@react-navigation/native": "^7.3.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -2393,16 +2412,16 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.8.tgz", - "integrity": "sha512-zHmQcxWBT8GOwsofEOmHqpdM5twkwE/esa9JFGlW4hpXeQTTe/dRcPSLjwsvePtolbDKz0YZbK+I5KrW3j63LQ==", + "version": "7.3.14", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.14.tgz", + "integrity": "sha512-hcKTDNBuuAA1/xW6QeKYmMPVhk5W9dKGQpPmn5dQeeePwMpu5OZ14NOgwKH0w9D3tg2jupojTcVL0tsx5DTFXg==", "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.21.5", + "@react-navigation/core": "^7.21.11", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", - "standard-navigation": "^0.0.7", + "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { @@ -2430,9 +2449,9 @@ } }, "node_modules/@react-navigation/routers": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.0.tgz", - "integrity": "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.4.tgz", + "integrity": "sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w==", "license": "MIT", "dependencies": { "nanoid": "^3.3.11" @@ -9108,10 +9127,13 @@ } }, "node_modules/standard-navigation": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.7.tgz", - "integrity": "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==", - "license": "MIT" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.8.tgz", + "integrity": "sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } }, "node_modules/statuses": { "version": "1.5.0", diff --git a/package.json b/package.json index 226bad2..b9e3148 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "main": "index.ts", "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/bottom-tabs": "^7.18.14", "@react-navigation/native": "^7.3.8", "@react-navigation/native-stack": "^7.17.10", "expo": "^57.0.0", diff --git a/src/components/Ikone.tsx b/src/components/Ikone.tsx index 169a5a2..6dd6c72 100644 --- a/src/components/Ikone.tsx +++ b/src/components/Ikone.tsx @@ -27,7 +27,12 @@ export type IkonenName = | 'warnung' | 'wecker' | 'info' - | 'funken'; + | 'funken' + | 'uhr' + | 'archiv' + | 'schloss' + | 'lupe' + | 'pfeilLinks'; // `as const` hält die Namen als Literal-Typen — so prüft TypeScript gegen // die offizielle SF-Symbol-/Material-Namensliste von expo-symbols @@ -52,6 +57,11 @@ const NAMEN = { wecker: { ios: 'alarm.fill', android: 'alarm' }, info: { ios: 'info.circle.fill', android: 'info' }, funken: { ios: 'wand.and.stars', android: 'auto_awesome' }, + uhr: { ios: 'clock.fill', android: 'schedule' }, + archiv: { ios: 'archivebox.fill', android: 'inventory_2' }, + schloss: { ios: 'lock.fill', android: 'lock' }, + lupe: { ios: 'magnifyingglass', android: 'search' }, + pfeilLinks: { ios: 'chevron.left', android: 'chevron_left' }, } as const; interface Props { diff --git a/src/screens/ArchivScreen.tsx b/src/screens/ArchivScreen.tsx new file mode 100644 index 0000000..4c4dc3c --- /dev/null +++ b/src/screens/ArchivScreen.tsx @@ -0,0 +1,188 @@ +/** + * Archiv — alle Briefe durchsuchbar und nach Behörde filterbar. + * (Übernimmt die Listen-/Suchfunktion, die vorher auf dem Start-Screen lag.) + */ +import React, { useState } from 'react'; +import { Alert, Pressable, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { BriefEintrag, RootStackParamList } from '../types'; +import { useAppStore } from '../store/useAppStore'; +import { berechneAmpel } from '../utils/ampel'; +import { formatiereDatum } from '../services/erinnerungen'; +import { Ikone } from '../components/Ikone'; +import { farben, schrift, abstand, TOUCH_TARGET } from '../theme'; + +type Nav = NativeStackNavigationProp; + +/** Kurzschlüssel einer Behörde: erstes Wort des Absenders (z. B. „Jobcenter"). */ +function behoerdenSchluessel(absender: string): string { + return absender.trim().split(/\s+/)[0] || absender; +} + +export function ArchivScreen() { + const navigation = useNavigation