Archived
1
0

feat: реализован профиль пользователя с публичными сейвами

This commit is contained in:
Vlad0sEnIgma345
2025-11-27 09:11:24 +03:00
parent 7fd19f9a09
commit bc972febc5

View File

@ -0,0 +1,184 @@
import React, { useEffect, useState } from 'react';
import {
FlatList,
TouchableOpacity,
Image,
RefreshControl,
Alert,
ActivityIndicator,
} from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { useVideoPlayer, VideoView } from 'expo-video';
import { Text, View } from '@/components/Themed';
import { savesApi } from '@/lib/api';
import type { SaveListItem } from '@shared-types';
import { useColorScheme } from '@/components/useColorScheme';
import Colors from '@/constants/Colors';
import { File, ArrowLeft, Image as ImageIcon } from 'lucide-react-native';
export default function UserProfileScreen() {
const { slug } = useLocalSearchParams<{ slug: string }>();
const [saves, setSaves] = useState<SaveListItem[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const router = useRouter();
const colorScheme = useColorScheme();
const colors = Colors[colorScheme ?? 'light'];
const loadSaves = async () => {
if (!slug) return;
try {
const data = await savesApi.getPublicSavesByUser(slug);
setSaves(data);
} catch (error: any) {
Alert.alert('Ошибка', error.message || 'Не удалось загрузить сейвы');
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
if (slug) {
loadSaves();
}
}, [slug]);
const handleRefresh = () => {
setRefreshing(true);
loadSaves();
};
const handleSavePress = (save: SaveListItem) => {
router.push(`/save/${save.id}`);
};
// Компонент для элемента списка
const SaveItem = React.memo(({ item }: { item: SaveListItem }) => {
if (!item) return null;
const itemColorScheme = useColorScheme();
const itemColors = Colors[itemColorScheme ?? 'light'];
const videoPlayer = useVideoPlayer(
item.type === 'video' ? item.url : null,
(player) => {
player.muted = true; // Без звука в списке
player.loop = true; // Зацикливание для превью
}
);
return (
<TouchableOpacity
className="border rounded-xl mb-3 overflow-hidden"
style={{ borderColor: itemColors.tabIconDefault }}
onPress={() => handleSavePress(item)}
>
<View className="flex-row p-3">
<View className="w-24 h-24 rounded-lg overflow-hidden mr-3">
{item.type === 'image' || item.type === 'gif' ? (
<Image
source={{ uri: item.url }}
className="w-full h-full"
resizeMode="cover"
/>
) : item.type === 'video' ? (
<VideoView
player={videoPlayer}
className="w-full h-full bg-black"
contentFit="cover"
nativeControls={false}
fullscreenOptions={{ enable: false }}
/>
) : (
<View className="w-full h-full justify-center items-center" style={{ backgroundColor: itemColors.tabIconDefault }}>
<File size={32} color={itemColors.text} />
</View>
)}
</View>
<View className="flex-1">
<Text className="text-base font-semibold mb-1" numberOfLines={1}>
{item.name || 'Без названия'}
</Text>
{item.description && (
<Text
className="text-sm mb-2"
style={{ color: itemColors.tabIconDefault }}
numberOfLines={2}
>
{item.description}
</Text>
)}
{item.tags && item.tags.length > 0 && (
<View className="flex-row flex-wrap gap-1.5 mb-2 items-center">
{item.tags.slice(0, 3).map((tag, idx) => (
<View
key={idx}
className="px-2 py-1 rounded"
style={{ backgroundColor: itemColors.tint + '20' }}
>
<Text className="text-xs" style={{ color: itemColors.tint }}>
{tag}
</Text>
</View>
))}
{item.tags.length > 3 && (
<Text className="text-xs" style={{ color: itemColors.tabIconDefault }}>
+{item.tags.length - 3}
</Text>
)}
</View>
)}
<Text className="text-xs" style={{ color: itemColors.tabIconDefault }}>
{new Date(item.createdAt).toLocaleDateString('ru-RU')}
</Text>
</View>
</View>
</TouchableOpacity>
);
});
if (loading) {
return (
<View className="flex-1 justify-center items-center">
<ActivityIndicator size="large" color={colors.tint} />
</View>
);
}
return (
<View className="flex-1">
<View className="flex-row justify-between items-center p-4 pt-16">
<TouchableOpacity
onPress={() => router.back()}
className="p-2"
>
<ArrowLeft size={24} color={colors.text} />
</TouchableOpacity>
<Text className="text-3xl font-bold flex-1 text-center">Публичные сейвы</Text>
<View className="w-10" />
</View>
{saves.length === 0 ? (
<View className="flex-1 justify-center items-center p-10">
<ImageIcon size={64} color={colors.tabIconDefault} />
<Text className="text-lg mt-4 mb-2 text-center font-semibold">Нет публичных сейвов</Text>
<Text className="text-sm text-center" style={{ color: colors.tabIconDefault }}>
У этого пользователя пока нет публичных сейвов
</Text>
</View>
) : (
<FlatList
data={saves}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <SaveItem item={item} />}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
contentContainerStyle={{ padding: 16 }}
/>
)}
</View>
);
}