import React, { useRef, useEffect } from 'react'; import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform, Animated, Easing, useWindowDimensions } from 'react-native'; import { useTranslation } from 'react-i18next'; import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme'; const TRANSITION_OFFSET = 24; const TRANSITION_DURATION = 280; interface OnboardingLayoutProps { children: React.ReactNode; title?: string; currentStep: number; totalSteps: number; onSkip: () => void; onBack?: () => void; showBackButton?: boolean; /** 用户名字,仅在名字步骤之后的第一个问题(currentStep === 1)且非空时显示招呼语 */ userName?: string; } export function OnboardingLayout({ children, title, currentStep, totalSteps, onSkip, onBack, showBackButton = false, userName = '', }: OnboardingLayoutProps) { const { t } = useTranslation(); const { width, height } = useWindowDimensions(); const isTablet = Platform.OS === 'ios' && Math.min(width, height) >= 768; const contentMaxWidth = isTablet ? 620 : undefined; const showGreeting = currentStep === 1 && userName.trim().length > 0; const displayName = userName.trim(); const prevStepRef = useRef(currentStep); const isFirstRenderRef = useRef(true); const translateX = useRef(new Animated.Value(0)).current; const opacity = useRef(new Animated.Value(1)).current; useEffect(() => { if (isFirstRenderRef.current) { isFirstRenderRef.current = false; prevStepRef.current = currentStep; return; } if (prevStepRef.current === currentStep) return; const direction = currentStep > prevStepRef.current ? 'forward' : 'back'; prevStepRef.current = currentStep; const startX = direction === 'forward' ? TRANSITION_OFFSET : -TRANSITION_OFFSET; translateX.setValue(startX); opacity.setValue(0.72); Animated.parallel([ Animated.timing(translateX, { toValue: 0, duration: TRANSITION_DURATION, useNativeDriver: true, easing: Easing.out(Easing.cubic), }), Animated.timing(opacity, { toValue: 1, duration: TRANSITION_DURATION, useNativeDriver: true, easing: Easing.out(Easing.cubic), }), ]).start(); }, [currentStep, translateX, opacity]); return ( {/* Header: Back & Skip */} {showBackButton && onBack && ( )} {t('onboarding.skipAll')} {/* Title & Progress Row(名字步骤后第一步且名字非空时显示招呼语 + 问题) */} {showGreeting && ( {t('onboardingSurvey.greeting', { name: displayName })} )} {title} ({currentStep}/{totalSteps}) {/* Content:step 切换时滑动 + 淡入 */} {children} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: OnboardingColors.background, }, safeArea: { flex: 1, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, height: 44, }, headerLeft: { width: 44, height: 44, justifyContent: 'center', }, iconButton: { padding: 8, }, backIcon: { width: 19, height: 19, resizeMode: 'contain', }, skipButton: { flexDirection: 'row', alignItems: 'center', padding: 8, }, skipText: { fontSize: 13, color: OnboardingColors.textMuted, fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', marginRight: 4, }, skipIcon: { width: 10, height: 4, resizeMode: 'contain', }, titleRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-end', paddingHorizontal: 20, marginTop: 20, marginBottom: 8, }, titleBlock: { flex: 1, justifyContent: 'flex-end', }, greetingText: { fontSize: 22, color: OnboardingColors.questionTitle, fontFamily: OnboardingFont.question, marginBottom: 4, }, questionTitle: { fontSize: 22, color: OnboardingColors.questionTitle, fontFamily: OnboardingFont.question, }, progressText: { fontSize: 18, color: OnboardingColors.textProgress, fontFamily: OnboardingFont.question, marginLeft: 10, }, content: { flex: 1, paddingHorizontal: 20, }, });