import { useState, useCallback, useEffect, useRef } from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Dimensions, ScrollView, } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, withRepeat, withSequence, FadeIn, FadeInDown, FadeInUp, interpolate, Extrapolate, withDelay, Easing, } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; import * as WebBrowser from 'expo-web-browser'; import * as Linking from 'expo-linking'; import { router } from 'expo-router'; import * as Haptics from 'expo-haptics'; import Svg, { Path, Circle, Defs, RadialGradient, Stop } from 'react-native-svg'; import { Colors, Font } from '../constants/theme'; import { buildOAuthUrl, exchangeCode, saveTokens } from '../services/api'; import { useAuth } from '../store'; import { saveUser } from '../hooks/useAuth'; WebBrowser.maybeCompleteAuthSession(); const { width, height } = Dimensions.get('window'); // Fake album-like gradient cards for the carousel const CARDS = [ ['#1a1a5e', '#3a0ca3'], ['#0d1b2a', '#1b4332'], ['#2d0a3a', '#7b2d8b'], ['#1a0a00', '#7c3f00'], ['#001b33', '#003d73'], ['#1a1a3e', '#4a0080'], ['#0a1a00', '#1a4a00'], ['#2a0010', '#6a0030'], ['#001a2a', '#004a5a'], ]; export default function AuthScreen() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const { setUser } = useAuth(); // Button animation const btnScale = useSharedValue(1); const btnGlow = useSharedValue(0); // Orb animations const orb1X = useSharedValue(0); const orb1Y = useSharedValue(0); const orb2X = useSharedValue(0); const orb2Y = useSharedValue(0); const orb3X = useSharedValue(0); // Title word stagger const titleOpacity = useSharedValue(0); const titleY = useSharedValue(30); useEffect(() => { // Orb 1 - slow drift orb1X.value = withRepeat( withSequence( withTiming(40, { duration: 9000, easing: Easing.inOut(Easing.sin) }), withTiming(-30, { duration: 9000, easing: Easing.inOut(Easing.sin) }), ), -1, true, ); orb1Y.value = withRepeat( withSequence( withTiming(-50, { duration: 7000, easing: Easing.inOut(Easing.sin) }), withTiming(30, { duration: 7000, easing: Easing.inOut(Easing.sin) }), ), -1, true, ); // Orb 2 orb2X.value = withRepeat( withSequence( withTiming(-60, { duration: 11000, easing: Easing.inOut(Easing.sin) }), withTiming(40, { duration: 11000, easing: Easing.inOut(Easing.sin) }), ), -1, true, ); orb2Y.value = withRepeat( withSequence( withTiming(60, { duration: 8000, easing: Easing.inOut(Easing.sin) }), withTiming(-40, { duration: 8000, easing: Easing.inOut(Easing.sin) }), ), -1, true, ); // Orb 3 orb3X.value = withRepeat( withSequence( withTiming(50, { duration: 13000, easing: Easing.inOut(Easing.sin) }), withTiming(-35, { duration: 13000, easing: Easing.inOut(Easing.sin) }), ), -1, true, ); // Title entrance titleOpacity.value = withDelay(300, withTiming(1, { duration: 900 })); titleY.value = withDelay(300, withSpring(0, { damping: 18, stiffness: 80 })); // Button glow pulse btnGlow.value = withDelay(1200, withRepeat( withSequence( withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }), withTiming(0, { duration: 2000, easing: Easing.inOut(Easing.ease) }), ), -1, false, )); }, []); const orb1Style = useAnimatedStyle(() => ({ transform: [{ translateX: orb1X.value }, { translateY: orb1Y.value }], })); const orb2Style = useAnimatedStyle(() => ({ transform: [{ translateX: orb2X.value }, { translateY: orb2Y.value }], })); const orb3Style = useAnimatedStyle(() => ({ transform: [{ translateX: orb3X.value }], })); const titleStyle = useAnimatedStyle(() => ({ opacity: titleOpacity.value, transform: [{ translateY: titleY.value }], })); const btnStyle = useAnimatedStyle(() => ({ transform: [{ scale: btnScale.value }], })); const glowStyle = useAnimatedStyle(() => ({ opacity: interpolate(btnGlow.value, [0, 1], [0.35, 0.75], Extrapolate.CLAMP), transform: [{ scale: interpolate(btnGlow.value, [0, 1], [1, 1.08], Extrapolate.CLAMP) }], })); const handleLogin = useCallback(async () => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); setLoading(true); setError(''); try { const state = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); const url = buildOAuthUrl(state); const result = await WebBrowser.openAuthSessionAsync(url, 'burmalda://auth/callback'); if (result.type !== 'success') { setError('Авторизация отменена'); return; } const parsed = Linking.parse(result.url); const code = parsed.queryParams?.code as string | undefined; if (!code) { setError((parsed.queryParams?.error as string) || 'Ошибка авторизации'); return; } const data = await exchangeCode(code); await saveTokens(data.access_token, data.refresh_token); await saveUser(data.user); setUser(data.user); router.replace('/(tabs)'); } catch (e: any) { setError(e.message || 'Что-то пошло не так'); } finally { setLoading(false); } }, [setUser]); return ( {/* Deep dark base */} {/* Animated color orbs */} {/* Album cards carousel — auto scroll */} {/* Gradient fade over carousel */} {/* Bottom gradient */} {/* Content */} {/* Logo */} {/* Abstract waveform / sound lines — no music note */} Burmalda Твоя музыка. Твой ритм. {/* Login button */} { btnScale.value = withSpring(0.96, { damping: 12 }); }} onPressOut={() => { btnScale.value = withSpring(1, { damping: 12 }); }} onPress={handleLogin} disabled={loading} style={s.btnOuter} > {loading ? ( ) : ( )} {loading ? 'Входим…' : 'Войти через NodeShift'} {!!error && ( {error} )} Продолжая, вы соглашаетесь с условиями NodeShift ); } // ── Auto-scrolling card row ──────────────────────────────────────────────────── function AutoScrollRow({ reverse, speed }: { reverse: boolean; speed: number }) { const scrollRef = useRef(null); const offset = useRef(0); const maxScroll = useRef(CARDS.length * 90); useEffect(() => { const interval = setInterval(() => { offset.current = reverse ? offset.current <= 0 ? maxScroll.current : offset.current - 0.8 : offset.current >= maxScroll.current ? 0 : offset.current + 0.8; scrollRef.current?.scrollTo({ x: offset.current, animated: false }); }, speed / 10); return () => clearInterval(interval); }, []); const doubled = [...CARDS, ...CARDS, ...CARDS]; return ( {doubled.map((colors, i) => ( ))} ); } function GradientCard({ colors }: { colors: [string, string] }) { return ( ); } // ── Feature row item ────────────────────────────────────────────────────────── function FeatureRow({ icon, text, delay }: { icon: string; text: string; delay: number }) { const icons: Record = { wave: ( ), cloud: ( ), heart: ( ), }; return ( {icons[icon]} {text} ); } // ── Styles ──────────────────────────────────────────────────────────────────── const CARD_W = 76; const CARD_H = 76; const s = StyleSheet.create({ root: { flex: 1, backgroundColor: '#070711' }, // Orbs orb: { position: 'absolute', borderRadius: 999 }, orbInner: { flex: 1, borderRadius: 999 }, orb1: { width: 340, height: 340, top: -80, left: -100, opacity: 0.22 }, orb2: { width: 280, height: 280, top: height * 0.25, right: -80, opacity: 0.18 }, orb3: { width: 200, height: 200, bottom: height * 0.3, left: -60, opacity: 0.14 }, // Carousel carouselWrap: { position: 'absolute', top: 0, left: 0, right: 0, height: height * 0.55, gap: 10, paddingTop: 60, overflow: 'hidden' }, cardRow: { flexGrow: 0, paddingHorizontal: 8 }, card: { width: CARD_W, height: CARD_H, borderRadius: 14, marginHorizontal: 5, opacity: 0.7 }, // Content content: { flex: 1, justifyContent: 'flex-end', paddingBottom: 48, paddingHorizontal: 28 }, logoWrap: { alignItems: 'flex-start', marginBottom: 16 }, logoIconWrap:{ alignSelf: 'flex-start' }, logoIcon: { width: 64, height: 64, borderRadius: 18, alignItems: 'center', justifyContent: 'center' }, brand: { fontSize: 48, fontWeight: '800', color: '#fff', letterSpacing: -2, marginBottom: 8, lineHeight: 52 }, tagline: { fontSize: 18, color: 'rgba(255,255,255,0.55)', fontWeight: '500', marginBottom: 32, letterSpacing: -0.3 }, // Features features: { gap: 12, marginBottom: 36 }, featureRow: { flexDirection: 'row', alignItems: 'center', gap: 12 }, featureIcon: { width: 32, height: 32, borderRadius: 10, backgroundColor: 'rgba(67,97,238,0.15)', alignItems: 'center', justifyContent: 'center' }, featureText: { color: 'rgba(255,255,255,0.7)', fontSize: 15, fontWeight: '500' }, // Button btnArea: { gap: 16 }, btnOuter: { borderRadius: 18, overflow: 'hidden', shadowColor: '#7b2ff7', shadowOffset: { width: 0, height: 12 }, shadowOpacity: 0.5, shadowRadius: 24 }, btn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 18, paddingHorizontal: 24 }, btnText: { color: '#fff', fontSize: 16, fontWeight: '700', letterSpacing: -0.3 }, error: { color: '#ff6b6b', fontSize: 13, textAlign: 'center' }, terms: { color: 'rgba(255,255,255,0.28)', fontSize: 12, textAlign: 'center', lineHeight: 18 }, });