Files
info-target-mobile/screens/announcements/ui/AnnouncementsList.tsx
Samandar Turgunboyev d747c72c8d complated
2026-02-17 10:46:57 +05:00

250 lines
6.7 KiB
TypeScript

import { useTheme } from '@/components/ThemeContext';
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { useVideoPlayer, VideoPlayer, VideoView } from 'expo-video';
import { Play } from 'lucide-react-native';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
Animated,
RefreshControl,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { announcement_api } from '../lib/api';
import AnnouncementCard from './AnnouncementCard';
import EmptyState from './EmptyState';
function VideoCard({ player }: { player: VideoPlayer }) {
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
const subscription = player.addListener('playingChange', (state) => {
setIsPlaying(state.isPlaying);
});
return () => {
subscription.remove();
};
}, [player]);
return (
<View>
<VideoView player={player} style={styles.video} contentFit="contain" />
{!isPlaying && (
<View style={styles.playOverlay}>
<TouchableOpacity
style={styles.playButton}
onPress={() => {
player.play();
setIsPlaying(true);
}}
>
<Play color="white" size={26} fill="black" />
</TouchableOpacity>
</View>
)}
</View>
);
}
export default function DashboardScreen() {
const queryClient = useQueryClient();
const fadeAnim = useRef(new Animated.Value(0)).current;
const { isDark } = useTheme();
const { t, i18n } = useTranslation();
const userLang = i18n.language.startsWith('ru')
? 'ru'
: i18n.language.startsWith('en')
? 'en'
: 'uz';
const [selectedLang, setSelectedLang] = useState<'uz' | 'ru' | 'en'>(userLang);
const theme = {
background: isDark ? '#0f172a' : '#f8fafc',
primary: '#2563eb',
text: isDark ? '#f8fafc' : '#0f172a',
loaderBg: isDark ? '#0f172a' : '#ffffff',
};
// Announcements query
const { data, isLoading, isRefetching, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['announcements_list'],
queryFn: async ({ pageParam = 1 }) => {
const res = await announcement_api.list({
page: pageParam,
page_size: 10,
});
return res.data.data;
},
getNextPageParam: (lastPage) =>
lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
initialPageParam: 1,
});
const allAnnouncements = data?.pages.flatMap((p) => p.results) ?? [];
useEffect(() => {
fadeAnim.setValue(0);
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, [allAnnouncements]);
// Announcement videos
const videos = {
uz: require('@/assets/announcements-video/video_uz.webm'),
ru: require('@/assets/announcements-video/video_ru.webm'),
en: require('@/assets/announcements-video/video_en.webm'),
};
// Government videos: faqat RU mavjud
const govermentVideos: Partial<Record<'uz' | 'ru' | 'en', any>> = {
ru: require('@/assets/goverment/video_ru.webm'),
};
// Update selected language
useEffect(() => {
const lang = i18n.language.startsWith('ru')
? 'ru'
: i18n.language.startsWith('en')
? 'en'
: 'uz';
setSelectedLang(lang);
}, [i18n.language]);
// 🔹 Hooks: conditional emas, har doim chaqiriladi
const player = useVideoPlayer(videos[selectedLang], (player) => {
player.loop = false;
player.volume = 1;
player.muted = false;
});
const govermentVideoSource = govermentVideos[selectedLang] ?? null;
const player2 = useVideoPlayer(govermentVideoSource, (player) => {
if (!govermentVideoSource) return; // no video, do nothing
player.loop = false;
player.volume = 1;
player.muted = false;
});
const onRefresh = () => {
queryClient.refetchQueries({ queryKey: ['announcements_list'] });
};
const loadMore = () => {
if (hasNextPage) fetchNextPage();
};
const videoItems = [
{ id: '1', player },
govermentVideoSource && { id: '2', player: player2 },
].filter(Boolean) as { id: string; player: VideoPlayer }[];
const renderVideoHeader = () => (
<View style={{ marginBottom: 8 }}>
{videoItems.map((item) => (
<View key={item.id} style={styles.videoContainer}>
<VideoCard player={item.player} />
</View>
))}
</View>
);
if (isLoading) {
return (
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
<View style={[styles.loaderBox, { backgroundColor: theme.loaderBg }]}>
<ActivityIndicator size="large" color={theme.primary} />
</View>
</SafeAreaView>
);
}
return (
<View style={[styles.container, { backgroundColor: theme.background }]}>
<Text style={{ color: theme.text, fontSize: 20, marginVertical: 16 }}>
{t("E'lonlar ro'yxati")}
</Text>
<Animated.FlatList
style={{ flex: 1, opacity: fadeAnim }}
data={allAnnouncements}
keyExtractor={(item) => item.id.toString()}
numColumns={2}
columnWrapperStyle={styles.columnWrapper}
renderItem={({ item }) => <AnnouncementCard announcement={item} />}
ListHeaderComponent={renderVideoHeader}
refreshControl={
<RefreshControl
refreshing={isRefetching}
onRefresh={onRefresh}
colors={[theme.primary]}
tintColor={theme.primary}
progressBackgroundColor={theme.background}
/>
}
ListEmptyComponent={<EmptyState onRefresh={onRefresh} isRefreshing={isRefetching} />}
onEndReached={loadMore}
onEndReachedThreshold={0.3}
contentContainerStyle={{ paddingBottom: 80 }}
showsVerticalScrollIndicator={false}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 16,
},
loaderBox: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
columnWrapper: {
justifyContent: 'space-between',
gap: 12,
},
videoContainer: {
width: '100%',
height: 250,
marginBottom: 8,
borderRadius: 12,
overflow: 'hidden',
backgroundColor: '#000',
},
video: {
width: '100%',
height: '100%',
},
playOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
},
playButton: {
backgroundColor: 'white',
borderRadius: 50,
width: 48,
height: 48,
justifyContent: 'center',
alignItems: 'center',
},
});