complated
This commit is contained in:
@@ -1,31 +1,86 @@
|
||||
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, View } from 'react-native';
|
||||
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 { AnnouncementListBodyRes } from '../lib/type';
|
||||
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 [announcements, setAnnouncements] = useState<AnnouncementListBodyRes[]>([]);
|
||||
const queryClient = useQueryClient();
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
const { isDark } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
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 });
|
||||
const res = await announcement_api.list({
|
||||
page: pageParam,
|
||||
page_size: 10,
|
||||
});
|
||||
return res.data.data;
|
||||
},
|
||||
getNextPageParam: (lastPage) =>
|
||||
@@ -36,8 +91,6 @@ export default function DashboardScreen() {
|
||||
const allAnnouncements = data?.pages.flatMap((p) => p.results) ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
setAnnouncements(allAnnouncements);
|
||||
|
||||
fadeAnim.setValue(0);
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
@@ -46,6 +99,44 @@ export default function DashboardScreen() {
|
||||
}).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'] });
|
||||
};
|
||||
@@ -54,6 +145,21 @@ export default function DashboardScreen() {
|
||||
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 }]}>
|
||||
@@ -66,33 +172,33 @@ export default function DashboardScreen() {
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<Text style={{ color: 'white', fontSize: 20, marginBottom: 10 }}>
|
||||
<Text style={{ color: theme.text, fontSize: 20, marginVertical: 16 }}>
|
||||
{t("E'lonlar ro'yxati")}
|
||||
</Text>
|
||||
{announcements.length > 0 ? (
|
||||
<Animated.FlatList
|
||||
style={{ flex: 1 }}
|
||||
data={announcements}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
numColumns={2}
|
||||
columnWrapperStyle={styles.columnWrapper}
|
||||
renderItem={({ item }) => <AnnouncementCard announcement={item} />}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefetching}
|
||||
onRefresh={onRefresh}
|
||||
colors={[theme.primary]}
|
||||
tintColor={theme.primary}
|
||||
progressBackgroundColor={theme.background}
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0.3}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState onRefresh={onRefresh} isRefreshing={isRefetching} />
|
||||
)}
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -101,7 +207,6 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 20,
|
||||
},
|
||||
loaderBox: {
|
||||
flex: 1,
|
||||
@@ -112,4 +217,33 @@ const styles = StyleSheet.create({
|
||||
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',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user