Apple-natives Design: SF Symbols statt Emojis, iOS-System-Optik

- Neue Ikone-Komponente (expo-symbols): echte SF Symbols auf iOS,
  Material Symbols auf Android — alle Emojis in Buttons/Kacheln/
  Checkboxen/Ampel/Chevrons ersetzt
- Theme: iOS-Systemgrau (#F2F2F7) statt Papier-Ton, weiße Karten ohne
  Rahmen und Schatten (wie Einstellungen/Health), "graue Taste" für
  Sekundär-Buttons statt Umrandung
- Kernaussage-Karte: dezenter Marken-Tint statt Textmarker-Gelb
- Frist/Termin-Zeilen ohne Emoji-Präfixe
- Layout unverändert (Kacheln, Ampel-Streifen, großer Titel)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
JUSNandClaude Fable 5 committed 2026-07-16 21:32:18 +02:00
1 parent a670cc1758
commit 4ce397026c
12 files changed
+209 -130

No files matched your search

+30 -28
View File
@@ -23,7 +23,8 @@ import { formatiereDatum } from '../services/erinnerungen';
import { berechneAmpel } from '../utils/ampel';
import { Ampel } from '../components/Ampel';
import { GrossButton } from '../components/GrossButton';
import { farben, schrift, abstand, kartenSchatten, TOUCH_TARGET } from '../theme';
import { Ikone } from '../components/Ikone';
import { farben, schrift, abstand, TOUCH_TARGET } from '../theme';
type Props = NativeStackScreenProps<RootStackParamList, 'Analyse'>;
@@ -133,9 +134,13 @@ export function AnalyseScreen({ navigation, route }: Props) {
accessibilityRole="button"
accessibilityLabel={`Sprache ändern. Aktuell: ${sprache.name}`}
>
<Text style={styles.sprachButtonText}>
🌐 {sprache.eigenname} {uebersetzt ? ' (übersetzt…)' : ''}
</Text>
<View style={styles.sprachZeileInhalt}>
<Ikone name="globus" groesse={20} farbe={farben.primaer} />
<Text style={styles.sprachButtonText}>
{sprache.eigenname} {uebersetzt ? ' (übersetzt…)' : ''}
</Text>
<Ikone name="pfeilRechts" groesse={16} farbe={farben.textTertiaer} />
</View>
</Pressable>
{/* Kernaussage: Was will das Amt von mir? */}
@@ -145,7 +150,7 @@ export function AnalyseScreen({ navigation, route }: Props) {
<View style={{ marginTop: abstand.s }}>
<GrossButton
titel={liest ? 'Vorlesen stoppen' : 'Vorlesen'}
symbol={liest ? '⏹️' : '🔊'}
ikone={liest ? 'stopp' : 'vorlesen'}
variante="sekundaer"
onPress={vorlesen}
/>
@@ -162,7 +167,7 @@ export function AnalyseScreen({ navigation, route }: Props) {
>
{analyse.frist && (
<Text style={styles.fristText}>
📅 Frist: <Text style={styles.fett}>{formatiereDatum(analyse.frist.datum)}</Text>
Frist: <Text style={styles.fett}>{formatiereDatum(analyse.frist.datum)}</Text>
{'\n'}
{analyse.frist.aktion}
</Text>
@@ -170,13 +175,13 @@ export function AnalyseScreen({ navigation, route }: Props) {
{analyse.termin && (
<>
<Text style={styles.fristText}>
🕐 Termin: <Text style={styles.fett}>{formatiereDatum(analyse.termin.datum)}</Text>
Termin: <Text style={styles.fett}>{formatiereDatum(analyse.termin.datum)}</Text>
{analyse.termin.uhrzeit ? ` um ${analyse.termin.uhrzeit} Uhr` : ''}
{analyse.termin.ort ? `\n📍 ${analyse.termin.ort}` : ''}
{analyse.termin.ort ? `\nOrt: ${analyse.termin.ort}` : ''}
</Text>
<GrossButton
titel="Termin zum Kalender hinzufügen"
symbol="🗓️"
ikone="kalender"
variante="sekundaer"
onPress={kalenderExport}
/>
@@ -203,7 +208,11 @@ export function AnalyseScreen({ navigation, route }: Props) {
accessibilityRole="checkbox"
accessibilityState={{ checked: !!erledigt[i] }}
>
<Text style={styles.checkBox}>{erledigt[i] ? '☑️' : '⬜'}</Text>
<Ikone
name={erledigt[i] ? 'checkAn' : 'checkAus'}
groesse={26}
farbe={erledigt[i] ? farben.ampelGruen : farben.textTertiaer}
/>
<Text style={[styles.text, { flex: 1 }, erledigt[i] && styles.durchgestrichen]}>
{punkt}
</Text>
@@ -229,13 +238,13 @@ export function AnalyseScreen({ navigation, route }: Props) {
{analyse.antwort_noetig && (
<GrossButton
titel="Antwort erstellen"
symbol="✍️"
ikone="stift"
onPress={() => navigation.navigate('Antwort', { briefId: brief.id })}
/>
)}
<View style={{ height: abstand.m }} />
<GrossButton titel="Brief löschen" symbol="🗑️" variante="gefahr" onPress={loeschen} />
<GrossButton titel="Brief löschen" ikone="muell" variante="gefahr" onPress={loeschen} />
<View style={{ height: abstand.xl }} />
{/* Sprach-Auswahl-Dialog */}
@@ -275,23 +284,19 @@ const styles = StyleSheet.create({
zentriert: { flex: 1, justifyContent: 'center', alignItems: 'center' },
karte: {
backgroundColor: farben.flaeche,
borderRadius: 16,
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
padding: abstand.m,
marginTop: abstand.m,
gap: abstand.s,
...kartenSchatten,
},
// DIE Antwort der App — als "Textmarker"-Karte hervorgehoben (Markenzeichen)
// DIE Antwort der App — dezent im Marken-Tint hervorgehoben
karteWichtig: {
backgroundColor: farben.markerHintergrund,
borderRadius: 16,
backgroundColor: farben.hervorhebung,
borderRadius: 12,
borderWidth: 1,
borderColor: farben.markerRand,
borderColor: farben.hervorhebungRand,
padding: abstand.m,
marginTop: abstand.m,
...kartenSchatten,
},
abschnittTitel: {
fontSize: schrift.gross,
@@ -310,22 +315,19 @@ const styles = StyleSheet.create({
minHeight: 44,
paddingVertical: 4,
},
checkBox: { fontSize: 24 },
durchgestrichen: { textDecorationLine: 'line-through', color: farben.textSekundaer },
begriffBlock: { marginBottom: abstand.s },
begriff: { fontSize: schrift.basis, fontWeight: '700', color: farben.text },
sprachButton: {
marginTop: abstand.m,
minHeight: TOUCH_TARGET,
borderRadius: 16,
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
backgroundColor: farben.flaeche,
justifyContent: 'center',
paddingHorizontal: abstand.m,
...kartenSchatten,
},
sprachButtonText: { fontSize: schrift.basis, color: farben.text, fontWeight: '600' },
sprachZeileInhalt: { flexDirection: 'row', alignItems: 'center', gap: abstand.s },
sprachButtonText: { flex: 1, fontSize: schrift.basis, color: farben.text, fontWeight: '600' },
modalHintergrund: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
@@ -333,7 +335,7 @@ const styles = StyleSheet.create({
padding: abstand.l,
},
modalKarte: {
backgroundColor: farben.hintergrund,
backgroundColor: farben.flaeche,
borderRadius: 14,
padding: abstand.m,
maxHeight: '75%',
+3 -3
View File
@@ -146,7 +146,7 @@ export function AntwortScreen({ route }: Props) {
<GrossButton
titel={text ? 'Neuen Entwurf erstellen' : 'Entwurf erstellen'}
symbol="✨"
ikone="funken"
onPress={erzeugen}
deaktiviert={!typ}
laedt={laedt}
@@ -173,8 +173,8 @@ export function AntwortScreen({ route }: Props) {
accessibilityLabel="Brieftext"
/>
<View style={{ gap: abstand.s }}>
<GrossButton titel="Als PDF exportieren" symbol="📄" onPress={alsPdf} />
<GrossButton titel="Als Text teilen / kopieren" symbol="📤" variante="sekundaer" onPress={alsText} />
<GrossButton titel="Als PDF exportieren" ikone="dokument" onPress={alsPdf} />
<GrossButton titel="Als Text teilen / kopieren" ikone="teilen" variante="sekundaer" onPress={alsText} />
</View>
</>
)}
+1 -1
View File
@@ -260,7 +260,7 @@ export function EinstellungenScreen({ navigation }: Props) {
<View style={styles.trenner} />
<Text style={styles.abschnittTitel}>Daten löschen</Text>
<GrossButton titel="Alle Daten löschen" symbol="🗑️" variante="gefahr" onPress={allesLoeschen} />
<GrossButton titel="Alle Daten löschen" ikone="muell" variante="gefahr" onPress={allesLoeschen} />
<View style={{ height: abstand.xl }} />
</ScrollView>
);
+16 -26
View File
@@ -18,29 +18,30 @@ import { holeConsent } from '../services/storage';
import { berechneAmpel } from '../utils/ampel';
import { formatiereDatum } from '../services/erinnerungen';
import { GrossButton } from '../components/GrossButton';
import { farben, schrift, abstand, kartenSchatten, TOUCH_TARGET } from '../theme';
import { Ikone, IkonenName } from '../components/Ikone';
import { farben, schrift, abstand, TOUCH_TARGET } from '../theme';
type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
/** Quadratische Navigations-Kachel: Symbol oben, Beschriftung darunter.
/** Quadratische Navigations-Kachel: Icon oben, Beschriftung darunter.
* (Löst das Platzproblem nebeneinanderstehender Text-Buttons.) */
function Kachel({
symbol,
ikone,
titel,
onPress,
}: {
symbol: string;
ikone: IkonenName;
titel: string;
onPress: () => void;
}) {
return (
<Pressable
style={({ pressed }) => [styles.kachel, pressed && { opacity: 0.7 }]}
style={({ pressed }) => [styles.kachel, pressed && { opacity: 0.65 }]}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={titel}
>
<Text style={styles.kachelSymbol}>{symbol}</Text>
<Ikone name={ikone} groesse={26} farbe={farben.primaer} />
<Text style={styles.kachelTitel} numberOfLines={1}>
{titel}
</Text>
@@ -105,7 +106,7 @@ export function HomeScreen({ navigation }: Props) {
{ampel.text}
</Text>
</View>
<Text style={styles.pfeil}>›</Text>
<Ikone name="pfeilRechts" groesse={18} farbe={farben.textTertiaer} />
</Pressable>
);
};
@@ -122,11 +123,11 @@ export function HomeScreen({ navigation }: Props) {
contentContainerStyle={styles.inhalt}
ListHeaderComponent={
<View style={styles.kopf}>
<GrossButton titel="Brief scannen" symbol="📷" onPress={scanStarten} />
<GrossButton titel="Brief scannen" ikone="kamera" onPress={scanStarten} />
<View style={styles.reihe}>
<Kachel symbol="📖" titel="Glossar" onPress={() => navigation.navigate('Glossar')} />
<Kachel ikone="buch" titel="Glossar" onPress={() => navigation.navigate('Glossar')} />
<Kachel
symbol="⚙️"
ikone="zahnrad"
titel="Einstellungen"
onPress={() => navigation.navigate('Einstellungen')}
/>
@@ -149,7 +150,7 @@ export function HomeScreen({ navigation }: Props) {
ListEmptyComponent={
briefe.length === 0 ? (
<View style={styles.leerBox}>
<Text style={styles.leerSymbol}>📬</Text>
<Ikone name="brief" groesse={48} farbe={farben.textTertiaer} />
<Text style={styles.leerTitel}>Noch keine Briefe</Text>
<Text style={styles.leer}>
Scannen Sie Ihren ersten Behördenbrief —{'\n'}in einer Minute
@@ -173,16 +174,12 @@ const styles = StyleSheet.create({
flex: 1,
minHeight: TOUCH_TARGET + 22,
backgroundColor: farben.flaeche,
borderRadius: 16,
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: abstand.s,
gap: 4,
...kartenSchatten,
gap: 6,
},
kachelSymbol: { fontSize: 26 },
kachelTitel: { fontSize: schrift.klein + 1, fontWeight: '600', color: farben.primaer },
abschnittsLabel: {
marginTop: abstand.m,
@@ -194,8 +191,6 @@ const styles = StyleSheet.create({
},
suche: {
backgroundColor: farben.flaeche,
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
padding: abstand.s,
fontSize: schrift.basis,
@@ -206,21 +201,16 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
backgroundColor: farben.flaeche,
borderRadius: 14,
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
borderLeftWidth: 6,
padding: abstand.m,
marginBottom: abstand.s,
gap: abstand.s,
...kartenSchatten,
},
kartenTitel: { fontSize: schrift.basis, fontWeight: '700', color: farben.text },
kartenUntertitel: { fontSize: schrift.klein, color: farben.textSekundaer, marginTop: 2 },
kartenAmpelText: { fontSize: schrift.klein, fontWeight: '600', marginTop: 2 },
pfeil: { fontSize: 32, color: farben.textSekundaer },
leerBox: { alignItems: 'center', marginTop: abstand.xl, gap: abstand.xs },
leerSymbol: { fontSize: 52 },
leerBox: { alignItems: 'center', marginTop: abstand.xl, gap: abstand.s },
leerTitel: { fontSize: schrift.gross, fontWeight: '700', color: farben.text },
leer: {
textAlign: 'center',
+3 -3
View File
@@ -182,9 +182,9 @@ export function ScanScreen({ navigation }: Props) {
Fotografieren Sie den Brief gut beleuchtet und von oben. Der ganze Text
muss auf dem Foto sein.
</Text>
<GrossButton titel="Foto aufnehmen" symbol="📷" onPress={fotoAufnehmen} />
<GrossButton titel="Aus Galerie wählen" symbol="🖼️" variante="sekundaer" onPress={ausGalerie} />
<GrossButton titel="PDF hochladen" symbol="📄" variante="sekundaer" onPress={pdfWaehlen} />
<GrossButton titel="Foto aufnehmen" ikone="kamera" onPress={fotoAufnehmen} />
<GrossButton titel="Aus Galerie wählen" ikone="galerie" variante="sekundaer" onPress={ausGalerie} />
<GrossButton titel="PDF hochladen" ikone="dokument" variante="sekundaer" onPress={pdfWaehlen} />
{NUTZT_PROXY && (
<Text style={styles.kontingent}>
{kontingentLeer