chahinebrini 14452b2a46 refactor(native): Pressable → TouchableOpacity sweep (style-fn swallows Android styles)
Alle <Pressable style={({pressed}) => ({...})}> ersetzt — style-Funktion
droppt auf Android (New Arch) intermittierend width/height, führt zu 0×0
unsichtbaren Elementen. TouchableOpacity mit activeOpacity ist stabil.

Außerdem übrige Pressables (plain style) aus components/ und app/
migriert sowie zwei überschüssige </View>-Tags in chat.tsx + RoomCard.tsx
entfernt die TS-Fehler verursacht haben.

64 Dateien, typecheck sauber.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:43:10 +02:00

154 lines
5.1 KiB
TypeScript

import { useEffect } from 'react';
import { View, Text, FlatList, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { HeroShieldCheck } from '../../components/HeroShieldCheck';
import { useTranslation } from 'react-i18next';
import { EmptyState } from '../../components/EmptyState';
import { useNotificationStore, type AppNotification } from '../../stores/notifications';
import { useColors } from '../../lib/theme';
export default function NotificationsScreen() {
const router = useRouter();
const { t } = useTranslation();
const colors = useColors();
const items = useNotificationStore((s) => s.items);
const loaded = useNotificationStore((s) => s.loaded);
const load = useNotificationStore((s) => s.load);
const markRead = useNotificationStore((s) => s.markRead);
const remove = useNotificationStore((s) => s.remove);
useEffect(() => {
load();
const tm = setTimeout(() => {
markRead();
}, 400);
return () => clearTimeout(tm);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 20, paddingTop: 12, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border }}>
<TouchableOpacity
onPress={() => router.back()}
activeOpacity={0.7}
style={{ width: 36, height: 36, borderRadius: 18, backgroundColor: colors.surfaceElevated, borderWidth: 1, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }}
>
<Ionicons name="arrow-back" size={18} color={colors.textMuted} />
</TouchableOpacity>
<Text
style={{ color: colors.text, fontSize: 18, flex: 1, fontFamily: 'Nunito_700Bold' }}
>
{t('notifications.title')}
</Text>
</View>
{items.length === 0 ? (
<EmptyState
icon="notifications-off-outline"
title={t('notifications.empty_title')}
subtitle={t('notifications.empty_subtitle')}
/>
) : (
<FlatList
data={items}
keyExtractor={(n) => n.id}
contentContainerStyle={{ paddingVertical: 8 }}
refreshControl={
<RefreshControl
refreshing={!loaded}
onRefresh={load}
tintColor="#007AFF"
/>
}
renderItem={({ item }) => (
<NotificationRow
notif={item}
onPress={() => {
if (item.postId) {
router.push(`/?postId=${item.postId}` as never);
}
}}
onDelete={() => remove(item.id)}
/>
)}
/>
)}
</SafeAreaView>
);
}
function NotificationRow({
notif,
onPress,
onDelete,
}: {
notif: AppNotification;
onPress: () => void;
onDelete: () => void;
}) {
const colors = useColors();
const isUnread = !notif.readAt;
return (
<TouchableOpacity
onPress={onPress}
activeOpacity={0.7}
>
<View
style={{
flexDirection: 'row',
alignItems: 'flex-start',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.border,
backgroundColor: isUnread ? colors.surface : colors.bg,
}}
>
{/* Pure-Icon — KEIN bg-Circle (User-Wunsch: kein extra Rand). */}
<View style={{ width: 36, alignItems: 'center', justifyContent: 'center', marginRight: 12 }}>
{notif.type === 'domain_accepted' ? (
<HeroShieldCheck size={22} color="#16a34a" />
) : (
<Ionicons name={iconForType(notif.type)} size={22} color="#d97706" />
)}
</View>
<View style={{ flex: 1, minWidth: 0, marginRight: 8 }}>
<Text
style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: colors.text }}
numberOfLines={1}
>
{notif.actorName}
</Text>
{notif.preview && (
<Text
style={{
fontSize: 12,
fontFamily: 'Nunito_400Regular',
color: colors.textMuted,
marginTop: 2,
}}
numberOfLines={2}
>
{notif.preview}
</Text>
)}
</View>
<TouchableOpacity onPress={onDelete} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="close" size={16} color="#a3a3a3" />
</TouchableOpacity>
</View>
</TouchableOpacity>
);
}
function iconForType(type: string): React.ComponentProps<typeof Ionicons>['name'] {
if (type.includes('like')) return 'heart';
if (type.includes('comment')) return 'chatbubble';
if (type.includes('follow')) return 'person-add';
if (type.includes('domain')) return 'shield-checkmark';
return 'notifications';
}