import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Animated, Easing, FlatList, Image, KeyboardAvoidingView, Platform, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { useApp } from '../../context/AppContext'; import { useColors } from '../../constants/Colors'; import { useSafeAnalytics } from '../../services/analytics'; import { OnboardingProgressService } from '../../services/onboardingProgressService'; import { PreAuthOnboardingService } from '../../services/preAuthOnboardingService'; import { Language } from '../../types'; // Avatar path matching copied file const BOT_AVATAR = require('../../assets/robot_monstera_avatar.jpg'); interface Message { id: string; sender: 'bot' | 'user'; text: string; subtext?: string; timestamp: string; } const getChatCopy = (language: Language) => { if (language === 'de') { return { title: 'Monstera', skip: 'Überspringen', continue: 'Weiter', today: 'Heute, 20:42 Uhr', typing: 'schreibt...', botGreeting: "Hi! Ich bin deine neue Monstera. 🌿 Ich freue mich riesig darauf, mit dir zu wachsen!", botSource: "Als erstes: Wie hast du von GreenLens gehört?", botGoal: "Verstanden! Und was ist dein Hauptziel mit deinen Pflanzen?", botExperience: "Schön! Wie erfahren bist du in der Pflanzenpflege?", botLight: "Damit ich glücklich bleibe und diese großen, schönen Blätter wachsen, muss ich ein bisschen über mein neues Zuhause wissen. Wie viel Licht bekomme ich hier?", botComplete: "Perfekt! Ich bin bereit einzuziehen. Lass uns deinen Pflegeplan erstellen!", sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Freunde oder Familie', search: 'Google oder Suche', other: 'Etwas anderes', }, goals: { identify: 'Pflanzen erkennen', care: 'Pflegepläne', collection: 'Sammlung vergrößern', learn: 'Über Pflanzen lernen', }, experiences: { beginner: 'Anfänger (Ich bin neu)', intermediate: 'Fortgeschritten (Ich habe Pflanzen)', advanced: 'Experte (Ich weiß Bescheid)', }, lights: { bright_indirect: { title: 'Helles, indirektes Licht', sub: 'In der Nähe eines sonnigen Fensters, ohne direkte Strahlen', }, low_light: { title: 'Wenig Licht', sub: 'Weit weg von Fenstern, meistens Schatten', }, direct_sunlight: { title: 'Direktes Sonnenlicht', sub: 'Den ganzen Tag direkt an einem sonnigen Fenster', }, }, }; } if (language === 'es') { return { title: 'Monstera', skip: 'Omitir', continue: 'Continuar', today: 'Hoy, 8:42 PM', typing: 'escribiendo...', botGreeting: "¡Hola! Soy tu nueva Monstera. 🌿 ¡Estoy muy emocionada de crecer contigo!", botSource: "Primero, ¿cómo te enteraste de GreenLens?", botGoal: "¡Entendido! ¿Y cuál es tu objetivo principal con tus plantas?", botExperience: "¡Genial! ¿Qué experiencia tienes en el cuidado de plantas?", botLight: "Para estar feliz y que crezcan esas hojas grandes y hermosas, necesito saber un poco sobre mi nuevo hogar. ¿Cuánta luz recibiré aquí?", botComplete: "¡Perfecto! Estoy lista para mudarme. ¡Creemos tu plan de cuidados!", sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Amigos o familia', search: 'Google o búsqueda', other: 'Otra cosa', }, goals: { identify: 'Identificar plantas', care: 'Horarios de cuidado', collection: 'Crecer mi colección', learn: 'Aprender sobre plantas', }, experiences: { beginner: 'Principiante (Soy nuevo)', intermediate: 'Intermedio (Tengo plantas)', advanced: 'Experto (Sé bastante)', }, lights: { bright_indirect: { title: 'Luz brillante e indirecta', sub: 'Cerca de una ventana soleada, sin sol directo', }, low_light: { title: 'Poca luz', sub: 'Lejos de las ventanas, principalmente sombra', }, direct_sunlight: { title: 'Luz solar directa', sub: 'Directamente en una ventana soleada todo el día', }, }, }; } return { title: 'Monstera', skip: 'Skip', continue: 'Continue', today: 'Today, 8:42 PM', typing: 'typing...', botGreeting: "Hi! I'm your new Monstera. 🌿 I'm so excited to grow with you!", botSource: "First, how did you hear about GreenLens?", botGoal: "Got it! And what is your main goal with your plants?", botExperience: "Nice! How experienced are you with plant care?", botLight: "To stay happy and grow those big, beautiful leaves, I need to know a bit about my new home. How much light do I get here?", botComplete: "Perfect! I'm ready to move in. Let's build your care plan!", sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Friends or family', search: 'Google or search', other: 'Something else', }, goals: { identify: 'Identify plants', care: 'Care schedules', collection: 'Grow collection', learn: 'Learn about plants', }, experiences: { beginner: 'Beginner (I\'m a new plant parent)', intermediate: 'Intermediate (I have some plants)', advanced: 'Advanced (I\'m a plant expert)', }, lights: { bright_indirect: { title: 'Bright, indirect light', sub: 'Near a sunny window, but no harsh rays', }, low_light: { title: 'Low light', sub: 'Far from windows, mostly shadow', }, direct_sunlight: { title: 'Direct sunlight', sub: 'Directly in a sunny window all day', }, }, }; }; const TypingIndicator = ({ colors }: { colors: any }) => { const dot1 = useRef(new Animated.Value(0)).current; const dot2 = useRef(new Animated.Value(0)).current; const dot3 = useRef(new Animated.Value(0)).current; useEffect(() => { const animateDot = (value: Animated.Value, delay: number) => { return Animated.loop( Animated.sequence([ Animated.delay(delay), Animated.timing(value, { toValue: -6, duration: 250, useNativeDriver: true, }), Animated.timing(value, { toValue: 0, duration: 250, useNativeDriver: true, }), Animated.delay(350), ]) ); }; const anim1 = animateDot(dot1, 0); const anim2 = animateDot(dot2, 120); const anim3 = animateDot(dot3, 240); anim1.start(); anim2.start(); anim3.start(); return () => { anim1.stop(); anim2.stop(); anim3.stop(); }; }, [dot1, dot2, dot3]); return ( ); }; export default function OnboardingChatScreen() { const router = useRouter(); const posthog = useSafeAnalytics(); const { session, isDarkMode, colorPalette, language } = useApp(); const colors = useColors(isDarkMode, colorPalette); const copy = getChatCopy(language); const { height } = useWindowDimensions(); const compact = height < 700; const [currentStep, setCurrentStep] = useState(0); const [messages, setMessages] = useState([]); const [isTyping, setIsTyping] = useState(false); const [showOptions, setShowOptions] = useState(false); const [answers, setAnswers] = useState({ acquisitionSource: null as string | null, primaryGoal: null as string | null, experienceLevel: null as string | null, lightLevel: null as string | null, }); const flatListRef = useRef(null); const floatAnim = useRef(new Animated.Value(0)).current; // Float animation loop for the background monstera pot useEffect(() => { const float = () => { Animated.sequence([ Animated.timing(floatAnim, { toValue: -12, duration: 3000, easing: Easing.inOut(Easing.sin), useNativeDriver: true, }), Animated.timing(floatAnim, { toValue: 0, duration: 3000, easing: Easing.inOut(Easing.sin), useNativeDriver: true, }), ]).start(() => float()); }; float(); }, [floatAnim]); // Initial Bot Greeting useEffect(() => { setIsTyping(true); setShowOptions(false); const timer1 = setTimeout(() => { setMessages([ { id: 'greeting', sender: 'bot', text: copy.botGreeting, timestamp: copy.today, }, ]); setIsTyping(false); // Delay before asking first question const timer2 = setTimeout(() => { setIsTyping(true); const timer3 = setTimeout(() => { setMessages((prev) => [ ...prev, { id: 'question-source', sender: 'bot', text: copy.botSource, timestamp: copy.today, }, ]); setIsTyping(false); setShowOptions(true); // Only show options after question is fully posted! }, 800); return () => clearTimeout(timer3); }, 500); return () => clearTimeout(timer2); }, 1200); return () => clearTimeout(timer1); }, []); // Scroll to bottom on messages update useEffect(() => { setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); }, [messages, isTyping]); const selectOption = (id: string, label: string) => { // Add user bubble const userMsg: Message = { id: `user-${currentStep}-${Date.now()}`, sender: 'user', text: label, timestamp: copy.today, }; setMessages((prev) => [...prev, userMsg]); const nextStep = currentStep + 1; setCurrentStep(nextStep); // Save answer const nextAnswers = { ...answers }; if (currentStep === 0) nextAnswers.acquisitionSource = id; else if (currentStep === 1) nextAnswers.primaryGoal = id; else if (currentStep === 2) nextAnswers.experienceLevel = id; else if (currentStep === 3) nextAnswers.lightLevel = id; setAnswers(nextAnswers); // Trigger bot response typing setIsTyping(true); setShowOptions(false); // Hide immediately when user answers const delay = setTimeout(() => { let botText = ''; let botSubtext = undefined; if (nextStep === 1) { botText = copy.botGoal; } else if (nextStep === 2) { botText = copy.botExperience; } else if (nextStep === 3) { botText = copy.botLight; } else if (nextStep === 4) { botText = copy.botComplete; } setMessages((prev) => [ ...prev, { id: `bot-${nextStep}-${Date.now()}`, sender: 'bot', text: botText, subtext: botSubtext, timestamp: copy.today, }, ]); setIsTyping(false); setShowOptions(true); // Re-enable options after bot finishes typing }, 850); return () => clearTimeout(delay); }; const handleBack = () => { if (currentStep === 0) { router.back(); return; } const prevStep = currentStep - 1; setCurrentStep(prevStep); // Revert last answers const nextAnswers = { ...answers }; if (prevStep === 0) nextAnswers.acquisitionSource = null; else if (prevStep === 1) nextAnswers.primaryGoal = null; else if (prevStep === 2) nextAnswers.experienceLevel = null; else if (prevStep === 3) nextAnswers.lightLevel = null; setAnswers(nextAnswers); // Remove user bubble and bot bubble from messages history setMessages((prev) => { const copyMsgs = [...prev]; // Pop bot question bubble and user response bubble copyMsgs.pop(); copyMsgs.pop(); return copyMsgs; }); }; const onSkip = () => { posthog.capture('onboarding_chat_skipped', { step: currentStep }); router.replace('/onboarding/health-check'); }; const onFinish = () => { if (session?.userId) { if (answers.acquisitionSource) OnboardingProgressService.setAcquisitionSource(session.userId, answers.acquisitionSource); if (answers.primaryGoal) OnboardingProgressService.setPrimaryGoal(session.userId, answers.primaryGoal); if (answers.experienceLevel) OnboardingProgressService.setExperienceLevel(session.userId, answers.experienceLevel); } if (answers.acquisitionSource) void PreAuthOnboardingService.setAnswer('acquisitionSource', answers.acquisitionSource); if (answers.primaryGoal) void PreAuthOnboardingService.setAnswer('primaryGoal', answers.primaryGoal); if (answers.experienceLevel) void PreAuthOnboardingService.setAnswer('experienceLevel', answers.experienceLevel); if (answers.lightLevel) void PreAuthOnboardingService.setAnswer('lightLevel', answers.lightLevel); posthog.capture('onboarding_chat_completed', { source: answers.acquisitionSource, goal: answers.primaryGoal, experience: answers.experienceLevel, light: answers.lightLevel, }); router.replace('/onboarding/health-check'); }; // Build options list based on current question step const renderOptions = () => { if (!showOptions || isTyping) return null; if (currentStep === 0) { return ( How did you find GreenLens? {([ { id: 'app_store', label: copy.sources.app_store, icon: 'logo-apple' as const }, { id: 'instagram', label: copy.sources.instagram, icon: 'logo-instagram' as const }, { id: 'tiktok', label: copy.sources.tiktok, icon: 'logo-youtube' as const }, { id: 'friend', label: copy.sources.friend, icon: 'people-outline' as const }, { id: 'search', label: copy.sources.search, icon: 'search-outline' as const }, { id: 'other', label: copy.sources.other, icon: 'sparkles-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } if (currentStep === 1) { return ( What is your main goal? {([ { id: 'identify', label: copy.goals.identify, icon: 'search-outline' as const }, { id: 'care', label: copy.goals.care, icon: 'water-outline' as const }, { id: 'collection', label: copy.goals.collection, icon: 'leaf-outline' as const }, { id: 'learn', label: copy.goals.learn, icon: 'book-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } if (currentStep === 2) { return ( What is your experience level? {([ { id: 'beginner', label: copy.experiences.beginner, icon: 'leaf-outline' as const }, { id: 'intermediate', label: copy.experiences.intermediate, icon: 'sunny-outline' as const }, { id: 'advanced', label: copy.experiences.advanced, icon: 'flask-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } if (currentStep === 3) { return ( How much light do I get here? {([ { id: 'bright_indirect', label: copy.lights.bright_indirect.title, sub: copy.lights.bright_indirect.sub, icon: 'wb-twilight' }, { id: 'low_light', label: copy.lights.low_light.title, sub: copy.lights.low_light.sub, icon: 'brightness-4' }, { id: 'direct_sunlight', label: copy.lights.direct_sunlight.title, sub: copy.lights.direct_sunlight.sub, icon: 'sunny' }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} {opt.sub} ))} ); } if (currentStep === 4) { return ( {copy.continue} ); } return null; }; const renderMessageItem = ({ item }: { item: Message }) => { const isBot = item.sender === 'bot'; return ( {isBot && ( )} {item.text} {item.subtext ? ( {item.subtext} ) : null} ); }; const progressPercent = `${Math.min(currentStep * 25, 100)}%`; return ( {/* Soft stepwise gradient mask to blend bottom image edge with background */} {/* Header Bar */} {copy.skip} {/* Chat History Panel */} item.id} renderItem={renderMessageItem} contentContainerStyle={styles.chatListContent} showsVerticalScrollIndicator={false} ListHeaderComponent={ {copy.today} } ListFooterComponent={ isTyping ? ( ) : null } /> {/* Input/Selection Panel */} {renderOptions()} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#131e14', }, robotBackground: { position: 'absolute', top: 0, left: 0, width: '100%', height: 380, opacity: 0.28, }, maskContainer: { position: 'absolute', top: 260, left: 0, right: 0, height: 120, }, maskLine: { height: 20, width: '100%', }, optionRowLeft: { flexDirection: 'row', alignItems: 'center', }, safe: { flex: 1, justifyContent: 'space-between', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: 'rgba(255,255,255,0.08)', }, headerBack: { width: 38, height: 38, borderRadius: 19, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255,255,255,0.04)', }, progressContainer: { flex: 1, marginHorizontal: 16, }, progressBarTrack: { height: 6, borderRadius: 3, backgroundColor: 'rgba(255,255,255,0.08)', overflow: 'hidden', }, progressBarFill: { height: 6, borderRadius: 3, backgroundColor: '#a5d56e', }, headerSkip: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 14, backgroundColor: 'rgba(255,255,255,0.04)', }, skipText: { color: '#dae6d6', fontSize: 13, fontWeight: '700', }, chatPanel: { flex: 1, paddingHorizontal: 16, }, chatListContent: { paddingVertical: 16, gap: 16, }, timeDivider: { alignSelf: 'center', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 999, paddingHorizontal: 12, paddingVertical: 4, marginBottom: 16, }, timeDividerText: { color: '#8d9382', fontSize: 11, fontWeight: '700', }, messageRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 10, marginVertical: 4, }, messageRowLeft: { alignSelf: 'flex-start', maxWidth: '85%', }, messageRowRight: { alignSelf: 'flex-end', flexDirection: 'row-reverse', maxWidth: '85%', }, chatAvatar: { width: 32, height: 32, borderRadius: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, bubble: { paddingHorizontal: 16, paddingVertical: 12, borderRadius: 18, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 1, }, bubbleLeft: { borderBottomLeftRadius: 3, }, bubbleRight: { borderBottomRightRadius: 3, }, bubbleText: { fontSize: 15, lineHeight: 21, fontWeight: '600', }, bubbleSub: { fontSize: 12, marginTop: 4, color: '#8d9382', }, typingContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, width: 36, height: 18, justifyContent: 'center', }, typingDot: { width: 6, height: 6, borderRadius: 3, }, inputPanel: { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: 'rgba(255,255,255,0.08)', paddingTop: 12, paddingBottom: 24, paddingHorizontal: 16, backgroundColor: '#131e14', }, optionsWrap: { gap: 10, }, stepTitle: { color: '#8d9382', fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 4, textAlign: 'center', }, optionsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, justifyContent: 'center', }, pillBtn: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 14, paddingVertical: 10, borderRadius: 999, borderWidth: 1.5, backgroundColor: '#101e11', }, pillEmoji: { fontSize: 14, }, pillLabel: { fontSize: 13.5, fontWeight: '700', }, optionsVertical: { gap: 8, }, optionRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 14, borderRadius: 14, borderWidth: 1, backgroundColor: '#101e11', }, rowText: { fontSize: 14.5, fontWeight: '700', }, bentoOption: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderRadius: 14, borderWidth: 1, backgroundColor: '#101e11', }, bentoCopy: { flex: 1, gap: 2, marginRight: 12, }, bentoTitle: { fontSize: 15, fontWeight: '700', }, bentoSub: { fontSize: 11.5, fontWeight: '600', lineHeight: 15, }, footerWrap: { paddingVertical: 8, }, continueCta: { height: 56, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, continueCtaText: { fontSize: 16.5, fontWeight: '900', }, });