250 lines
6.7 KiB
TypeScript
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',
|
|
},
|
|
});
|