anisav-mobile/app/(tabs)/profile.tsx

266 lines
7.1 KiB
TypeScript
Raw Normal View History

2026-07-19 06:05:12 +05:00
import { useRef } from 'react'
import { Animated, Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import Ionicons from '@expo/vector-icons/Ionicons'
import { useAuth } from '../../src/lib/AuthContext'
import { imageUrl } from '../../src/lib/api'
import { colors, radius, spacing } from '../../src/lib/theme'
import Loader from '../../src/components/Loader'
import Footer from '../../src/components/Footer'
interface ProfileRowProps {
icon: keyof typeof Ionicons.glyphMap
label: string
onPress: () => void
danger?: boolean
}
function ProfileRow({ icon, label, onPress, danger }: ProfileRowProps) {
const scale = useRef(new Animated.Value(1)).current
function pressIn() {
Animated.spring(scale, { toValue: 0.97, useNativeDriver: true, speed: 50 }).start()
}
function pressOut() {
Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 50 }).start()
}
return (
<Pressable onPress={onPress} onPressIn={pressIn} onPressOut={pressOut}>
<Animated.View style={[styles.row, { transform: [{ scale }] }]}>
<View style={styles.rowLeft}>
<Ionicons name={icon} size={19} color={danger ? colors.accent : colors.textDim} />
<Text style={[styles.rowLabel, danger && styles.rowLabelDanger]}>{label}</Text>
</View>
{!danger && <Ionicons name="chevron-forward" size={18} color={colors.textFaint} />}
</Animated.View>
</Pressable>
)
}
function LoginButton() {
const router = useRouter()
const scale = useRef(new Animated.Value(1)).current
function pressIn() {
Animated.spring(scale, { toValue: 0.96, useNativeDriver: true, speed: 50 }).start()
}
function pressOut() {
Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 50 }).start()
}
return (
<Pressable
onPress={() => router.push('/login')}
onPressIn={pressIn}
onPressOut={pressOut}
>
<Animated.View style={[styles.loginButton, { transform: [{ scale }] }]}>
<Text style={styles.loginButtonText}>Войти</Text>
</Animated.View>
</Pressable>
)
}
export default function ProfileScreen() {
const insets = useSafeAreaInsets()
const router = useRouter()
const { user, loading, logout } = useAuth()
if (loading) {
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
<Loader label="Загрузка..." />
</View>
)
}
const avatarUri = user
? imageUrl(user.avatar?.optimized?.preview || user.avatar?.preview)
: undefined
return (
<ScrollView
style={[styles.container, { paddingTop: insets.top }]}
contentContainerStyle={{ paddingBottom: insets.bottom + spacing(6) }}
>
<Text style={styles.title}>Профиль</Text>
{user ? (
<View style={styles.userHeader}>
{avatarUri ? (
<Image source={{ uri: avatarUri }} style={styles.avatar} />
) : (
<View style={styles.avatarFallback}>
<Text style={styles.avatarFallbackText}>
{user.nickname?.[0]?.toUpperCase() ?? '?'}
</Text>
</View>
)}
<Text style={styles.nickname}>{user.nickname}</Text>
</View>
) : (
<View style={styles.loggedOutWrap}>
<View style={styles.loggedOutIconCircle}>
<Ionicons name="person" size={30} color={colors.textDim} />
</View>
<Text style={styles.loggedOutTitle}>Вы не вошли в аккаунт</Text>
<Text style={styles.loggedOutText}>
Войдите в аккаунт AniLiberty, чтобы синхронизировать избранное, списки и историю
просмотра между устройствами
</Text>
<LoginButton />
</View>
)}
<View style={styles.section}>
<ProfileRow
icon="star-outline"
label="Понравилось"
onPress={() => router.push('/wishlist')}
/>
{user && (
<>
<ProfileRow
icon="list-outline"
label="Мои списки"
onPress={() => router.push('/account/lists')}
/>
<ProfileRow
icon="time-outline"
label="История"
onPress={() => router.push('/account/history')}
/>
</>
)}
</View>
{user && (
<View style={styles.section}>
<ProfileRow icon="log-out-outline" label="Выйти" onPress={logout} danger />
</View>
)}
<Footer />
</ScrollView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
title: {
color: colors.text,
fontSize: 26,
fontWeight: '800',
paddingHorizontal: spacing(4),
marginTop: spacing(2),
marginBottom: spacing(4),
},
userHeader: {
alignItems: 'center',
paddingHorizontal: spacing(4),
marginBottom: spacing(6),
},
avatar: {
width: 84,
height: 84,
borderRadius: radius.full,
backgroundColor: colors.surfaceRaised,
},
avatarFallback: {
width: 84,
height: 84,
borderRadius: radius.full,
backgroundColor: colors.accent,
alignItems: 'center',
justifyContent: 'center',
},
avatarFallbackText: {
color: colors.text,
fontSize: 32,
fontWeight: '700',
},
nickname: {
color: colors.text,
fontSize: 18,
fontWeight: '700',
marginTop: spacing(3),
},
loggedOutWrap: {
alignItems: 'center',
paddingHorizontal: spacing(6),
marginBottom: spacing(6),
},
loggedOutIconCircle: {
width: 68,
height: 68,
borderRadius: radius.full,
backgroundColor: colors.surfaceRaised,
alignItems: 'center',
justifyContent: 'center',
},
loggedOutTitle: {
color: colors.text,
fontSize: 16,
fontWeight: '700',
marginTop: spacing(4),
textAlign: 'center',
},
loggedOutText: {
color: colors.textFaint,
fontSize: 13,
textAlign: 'center',
marginTop: spacing(2),
lineHeight: 18,
},
loginButton: {
marginTop: spacing(5),
backgroundColor: colors.accent,
borderRadius: radius.full,
paddingHorizontal: spacing(8),
paddingVertical: spacing(3),
},
loginButtonText: {
color: colors.text,
fontSize: 15,
fontWeight: '700',
},
section: {
marginHorizontal: spacing(4),
marginBottom: spacing(4),
backgroundColor: colors.surfaceCard,
borderRadius: radius.lg,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.border,
overflow: 'hidden',
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing(4),
paddingVertical: spacing(4),
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.border,
},
rowLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing(3),
},
rowLabel: {
color: colors.text,
fontSize: 15,
fontWeight: '500',
},
rowLabelDanger: {
color: colors.accent,
},
})