200 lines
5.8 KiB
TypeScript
200 lines
5.8 KiB
TypeScript
// EServicesScreen.tsx
|
|
import { useTheme } from '@/components/ThemeContext';
|
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
import { Image } from 'expo-image';
|
|
import { ChevronLeft, XIcon } from 'lucide-react-native';
|
|
import React, { useCallback, useRef, useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Dimensions,
|
|
FlatList,
|
|
Modal,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { WebView } from 'react-native-webview';
|
|
import { eservices_api } from '../lib/api';
|
|
|
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
const PAGE_SIZE = 10;
|
|
|
|
export interface GovermentServiceDataRes {
|
|
id: number;
|
|
name: string;
|
|
url: string;
|
|
logo: string;
|
|
}
|
|
|
|
export default function EServicesScreen() {
|
|
const { isDark } = useTheme();
|
|
const [webUrl, setWebUrl] = useState<string | null>(null);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const webviewRef = useRef<WebView>(null); // WebView ref for goBack
|
|
|
|
const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
|
useInfiniteQuery({
|
|
queryKey: ['goverment_service'],
|
|
queryFn: async ({ pageParam = 1 }) => {
|
|
const response = await eservices_api.list({
|
|
page: pageParam,
|
|
page_size: PAGE_SIZE,
|
|
});
|
|
return response.data.data;
|
|
},
|
|
getNextPageParam: (lastPage) =>
|
|
lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
|
|
initialPageParam: 1,
|
|
});
|
|
|
|
const services: GovermentServiceDataRes[] = data?.pages.flatMap((p) => p.results) ?? [];
|
|
|
|
const openWebView = (url: string) => {
|
|
setWebUrl(url);
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const renderItem = useCallback(
|
|
({ item }: { item: GovermentServiceDataRes }) => (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.card,
|
|
{ backgroundColor: isDark ? '#1e293b' : '#f8fafc' },
|
|
isDark ? styles.darkShadow : styles.lightShadow,
|
|
]}
|
|
onPress={() => openWebView(item.url)}
|
|
>
|
|
<Image source={{ uri: item.logo }} style={styles.logo} resizeMode="contain" />
|
|
<Text style={[styles.name, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>{item.name}</Text>
|
|
</TouchableOpacity>
|
|
),
|
|
[isDark]
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
|
<ActivityIndicator size="large" color="#3b82f6" />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
|
<Text style={{ color: 'red' }}>Xatolik yuz berdi</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
|
<FlatList
|
|
data={services}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
renderItem={renderItem}
|
|
numColumns={2}
|
|
columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }}
|
|
contentContainerStyle={{ padding: 16 }}
|
|
onEndReached={() => hasNextPage && fetchNextPage()}
|
|
onEndReachedThreshold={0.4}
|
|
ListFooterComponent={
|
|
isFetchingNextPage ? <ActivityIndicator color="#3b82f6" style={{ margin: 20 }} /> : null
|
|
}
|
|
showsVerticalScrollIndicator={false}
|
|
/>
|
|
|
|
{/* WebView Modal */}
|
|
{/* WebView Modal */}
|
|
<Modal visible={modalVisible} animationType="slide">
|
|
<SafeAreaView
|
|
style={{
|
|
flex: 1,
|
|
backgroundColor: isDark ? '#0f172a' : '#f8fafc', // modal background
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
padding: 12,
|
|
backgroundColor: isDark ? '#1e293b' : '#f8fafc', // header background
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
}}
|
|
>
|
|
{/* Back tugmasi */}
|
|
<TouchableOpacity
|
|
onPress={() => {
|
|
if (webviewRef.current) webviewRef.current.goBack();
|
|
}}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<ChevronLeft color={isDark ? '#f1f5f9' : '#0f172a'} size={24} />
|
|
</TouchableOpacity>
|
|
|
|
{/* Close tugmasi */}
|
|
<TouchableOpacity
|
|
onPress={() => setModalVisible(false)}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<XIcon color={isDark ? '#f1f5f9' : '#0f172a'} size={24} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* WebView */}
|
|
{webUrl && (
|
|
<WebView
|
|
ref={webviewRef}
|
|
source={{ uri: webUrl }}
|
|
style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }} // webview background
|
|
startInLoadingState
|
|
renderLoading={() => (
|
|
<ActivityIndicator color="#3b82f6" size="large" style={{ flex: 1 }} />
|
|
)}
|
|
/>
|
|
)}
|
|
</SafeAreaView>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const CARD_WIDTH = (SCREEN_WIDTH - 48) / 2;
|
|
|
|
const styles = StyleSheet.create({
|
|
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
card: {
|
|
width: CARD_WIDTH,
|
|
borderRadius: 12,
|
|
padding: 12,
|
|
alignItems: 'center',
|
|
},
|
|
logo: {
|
|
width: 60,
|
|
height: 60,
|
|
marginBottom: 8,
|
|
},
|
|
name: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
textAlign: 'center',
|
|
},
|
|
darkShadow: {
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.4,
|
|
shadowRadius: 6,
|
|
elevation: 3,
|
|
},
|
|
lightShadow: {
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
elevation: 2,
|
|
},
|
|
});
|