This commit is contained in:
Timo Knuth
2026-02-27 15:19:24 +01:00
parent b7f8221095
commit 253c3c1c6d
134 changed files with 11188 additions and 1871 deletions

View File

@@ -1,14 +1,43 @@
import {
View, Text, FlatList, TouchableOpacity, RefreshControl, ScrollView, StyleSheet,
View, Text, FlatList, TouchableOpacity, RefreshControl, ScrollView, StyleSheet, Animated,
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useState } from 'react'
import { useState, useEffect, useRef, useCallback } from 'react'
import { useRouter } from 'expo-router'
import { useFocusEffect } from 'expo-router'
import { useNewsList } from '@/hooks/useNews'
import { NewsCard } from '@/components/news/NewsCard'
import { EmptyState } from '@/components/ui/EmptyState'
import { LoadingSpinner } from '@/components/ui/LoadingSpinner'
function SkeletonCard() {
const anim = useRef(new Animated.Value(0.4)).current
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(anim, { toValue: 1, duration: 800, useNativeDriver: true }),
Animated.timing(anim, { toValue: 0.4, duration: 800, useNativeDriver: true }),
])
).start()
}, [])
return (
<Animated.View style={[skeletonStyles.card, { opacity: anim }]}>
<View style={skeletonStyles.badge} />
<View style={skeletonStyles.titleLine} />
<View style={skeletonStyles.titleLineShort} />
<View style={skeletonStyles.metaLine} />
</Animated.View>
)
}
const skeletonStyles = StyleSheet.create({
card: { backgroundColor: '#FFFFFF', borderRadius: 12, padding: 16, marginBottom: 10, marginHorizontal: 16 },
badge: { height: 20, width: 80, borderRadius: 10, backgroundColor: '#E2E8F0', marginBottom: 12 },
titleLine: { height: 16, borderRadius: 8, backgroundColor: '#E2E8F0', width: '85%', marginBottom: 8 },
titleLineShort: { height: 16, borderRadius: 8, backgroundColor: '#E2E8F0', width: '55%', marginBottom: 12 },
metaLine: { height: 12, borderRadius: 6, backgroundColor: '#F1F5F9', width: '40%' },
})
const FILTERS = [
{ value: undefined, label: 'Alle' },
{ value: 'Wichtig', label: 'Wichtig' },
@@ -20,8 +49,17 @@ const FILTERS = [
export default function NewsScreen() {
const router = useRouter()
const [kategorie, setKategorie] = useState<string | undefined>(undefined)
const [showSkeleton, setShowSkeleton] = useState(true)
const { data, isLoading, refetch, isRefetching } = useNewsList(kategorie)
useFocusEffect(
useCallback(() => {
setShowSkeleton(true)
const t = setTimeout(() => setShowSkeleton(false), 800)
return () => clearTimeout(t)
}, [])
)
const unreadCount = data?.filter((n) => !n.isRead).length ?? 0
return (
@@ -61,15 +99,20 @@ export default function NewsScreen() {
<View style={styles.divider} />
{isLoading ? (
<LoadingSpinner />
{showSkeleton ? (
<View style={{ paddingTop: 16 }}>
{[1,2,3,4].map((i) => <SkeletonCard key={i} />)}
</View>
) : (
<FlatList
data={data ?? []}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.list}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
refreshControl={
<RefreshControl refreshing={isRefetching} onRefresh={refetch} tintColor="#003B7E" />
<RefreshControl refreshing={isRefetching} onRefresh={refetch} tintColor="#003B7E" progressViewOffset={50} />
}
renderItem={({ item }) => (
<NewsCard
@@ -78,7 +121,7 @@ export default function NewsScreen() {
/>
)}
ListEmptyComponent={
<EmptyState icon="N" title="Keine News" subtitle="Noch keine Beitraege veroeffentlicht." />
<EmptyState icon="newspaper-outline" title="Keine News" subtitle="Noch keine Beitraege veroeffentlicht." />
}
/>
)}