Files
beh-rde-brief/App.tsx
JUSN 779261fbad Design: warme Palette aus dem Entwurf (Beige/Gold/Tinte) statt iOS-Blau
Korrektur zur Rueckmeldung des Nutzers: der Lesbarkeits-Einwand galt nur der
duennen Serifenschrift, nicht den Farben. Die warmen Design-Farben sind gut
lesbar und vereinheitlichen App + Website.

- theme.ts: Papier-Beige-Grund, warme Karten, Gold-Akzent (#B68235) fuer
  Icons/Tabs/Rahmen, dunkles Bronze (#7D5411, neu: primaerFuellung) als
  Fuellung gefuellter Buttons (weisse Schrift kontrastreich), warme
  Ampel-Toene (Backstein/Bronze/Salbei), warme Trennlinien/Graustufen
- GrossButton: Fuellung + Sekundaer-Text auf primaerFuellung (Kontrast)
- FAB-Scan-Knopf auf Bronze
- FristenScreen: ueberfaellige Zeilen zeigen 'faellig' statt 'Tage'

Im Simulator geprueft: Fristen-Start + Scan-Screen in warmen Farben, gut
lesbar. Typecheck + 30 Tests gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 21:29:19 +02:00

159 lines
5.8 KiB
TypeScript

/**
* 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, 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 { 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';
import { AntwortScreen } from './src/screens/AntwortScreen';
import { GlossarScreen } from './src/screens/GlossarScreen';
import { EinstellungenScreen } from './src/screens/EinstellungenScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<HauptTabParamList>();
/** 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 (
<View style={styles.scanWrap}>
<Pressable
onPress={onPress}
accessibilityRole="button"
accessibilityLabel="Brief scannen"
style={({ pressed }) => [styles.scanKnopf, pressed && { opacity: 0.85 }]}
>
<Ikone name="kamera" groesse={26} farbe={farben.primaerText} />
</Pressable>
</View>
);
}
function HauptTabs() {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const insets = useSafeAreaInsets();
const scanStarten = async () => {
const ok = await holeConsent();
navigation.navigate(ok ? 'Scan' : 'Consent');
};
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
tabBarActiveTintColor: farben.primaer,
tabBarInactiveTintColor: farben.textTertiaer,
tabBarLabelStyle: { fontSize: 11, fontWeight: '600' },
tabBarStyle: {
backgroundColor: farben.flaeche,
borderTopColor: farben.rand,
height: 56 + insets.bottom,
paddingTop: 6,
paddingBottom: insets.bottom,
},
}}
>
<Tab.Screen
name="Fristen"
component={FristenScreen}
options={{
tabBarLabel: 'Fristen',
tabBarIcon: ({ color }) => <Ikone name="uhr" groesse={23} farbe={color} />,
}}
/>
<Tab.Screen
name="ScanTab"
component={LeererScreen}
options={{ tabBarButton: () => <ScanKnopf onPress={scanStarten} /> }}
listeners={{ tabPress: (e) => e.preventDefault() }}
/>
<Tab.Screen
name="Archiv"
component={ArchivScreen}
options={{
tabBarLabel: 'Archiv',
tabBarIcon: ({ color }) => <Ikone name="archiv" groesse={23} farbe={color} />,
}}
/>
</Tab.Navigator>
);
}
export default function App() {
const initialisiere = useAppStore((s) => s.initialisiere);
useEffect(() => {
initialisiereBenachrichtigungen();
initialisiere();
}, [initialisiere]);
return (
<AppSchutz>
<NavigationContainer>
<StatusBar style="dark" />
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: farben.hintergrund },
headerShadowVisible: false,
headerTintColor: farben.primaer,
headerTitleStyle: { fontSize: schrift.gross, fontWeight: '700', color: farben.text },
headerBackTitle: 'Zurück',
contentStyle: { backgroundColor: farben.hintergrund },
}}
>
<Stack.Screen name="Tabs" component={HauptTabs} options={{ headerShown: false }} />
<Stack.Screen name="Consent" component={ConsentScreen} options={{ title: 'Datenschutz' }} />
<Stack.Screen name="Scan" component={ScanScreen} options={{ title: 'Brief scannen' }} />
<Stack.Screen name="Analyse" component={AnalyseScreen} options={{ title: 'Ihr Brief erklärt' }} />
<Stack.Screen name="Antwort" component={AntwortScreen} options={{ title: 'Antwort erstellen' }} />
<Stack.Screen name="Glossar" component={GlossarScreen} options={{ title: 'Behörden-Glossar' }} />
<Stack.Screen name="Einstellungen" component={EinstellungenScreen} options={{ title: 'Einstellungen' }} />
</Stack.Navigator>
</NavigationContainer>
</AppSchutz>
);
}
const styles = StyleSheet.create({
scanWrap: { flex: 1, alignItems: 'center', justifyContent: 'flex-start' },
scanKnopf: {
width: 56,
height: 56,
borderRadius: 28,
marginTop: -18,
backgroundColor: farben.primaerFuellung,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 3,
borderColor: farben.flaeche,
shadowColor: '#000',
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.2,
shadowRadius: 5,
elevation: 5,
},
});