496 lines
14 KiB
TypeScript
496 lines
14 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useRouter } from 'expo-router';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useSafeAnalytics } from '../../services/analytics';
|
|
import { useColors } from '../../constants/Colors';
|
|
import { useApp } from '../../context/AppContext';
|
|
import { Language } from '../../types';
|
|
|
|
type ColorsType = ReturnType<typeof useColors>;
|
|
|
|
const getSlidesCopy = (language: Language) => {
|
|
if (language === 'de') {
|
|
return {
|
|
slides: [
|
|
{
|
|
title: 'Scanne jede Pflanze',
|
|
body: 'Richte die Kamera auf eine Pflanze und GreenLens erkennt sie in Sekunden.',
|
|
},
|
|
{
|
|
title: 'Health Check & Pflegeplan',
|
|
body: 'GreenLens erkennt Probleme früh und erstellt deinen Rettungsplan.',
|
|
},
|
|
{
|
|
title: 'Nie mehr Gießen vergessen',
|
|
body: 'Smarte Erinnerungen und deine Pflanzen-Bibliothek halten alles im Blick.',
|
|
},
|
|
],
|
|
resultChip: 'Monstera · 98%',
|
|
healthCheckLabel: 'Health Check',
|
|
overwateringDetected: 'Überwässerung erkannt',
|
|
rescuePlanReady: '7-Tage-Rettungsplan bereit',
|
|
waterReminder: 'Monstera gießen — heute',
|
|
fertilizeReminder: 'Basilikum düngen — in 3 Tagen',
|
|
continueLabel: 'Weiter',
|
|
};
|
|
}
|
|
if (language === 'es') {
|
|
return {
|
|
slides: [
|
|
{
|
|
title: 'Escanea cualquier planta',
|
|
body: 'Apunta la cámara a una planta y GreenLens la identifica en segundos.',
|
|
},
|
|
{
|
|
title: 'Chequeo de salud y plan de cuidados',
|
|
body: 'GreenLens detecta problemas a tiempo y crea tu plan de rescate.',
|
|
},
|
|
{
|
|
title: 'No olvides regar nunca más',
|
|
body: 'Recordatorios inteligentes y tu biblioteca de plantas lo mantienen todo al día.',
|
|
},
|
|
],
|
|
resultChip: 'Monstera · 98%',
|
|
healthCheckLabel: 'Chequeo de salud',
|
|
overwateringDetected: 'Exceso de riego detectado',
|
|
rescuePlanReady: 'Plan de rescate de 7 días listo',
|
|
waterReminder: 'Regar Monstera — hoy',
|
|
fertilizeReminder: 'Abonar albahaca — en 3 días',
|
|
continueLabel: 'Continuar',
|
|
};
|
|
}
|
|
return {
|
|
slides: [
|
|
{
|
|
title: 'Scan Any Plant',
|
|
body: 'Point your camera at a plant and GreenLens identifies it in seconds.',
|
|
},
|
|
{
|
|
title: 'Health Check & Care Plan',
|
|
body: 'GreenLens spots problems early and builds a rescue plan for you.',
|
|
},
|
|
{
|
|
title: 'Never Forget Watering',
|
|
body: 'Smart reminders and your personal plant library keep everything on track.',
|
|
},
|
|
],
|
|
resultChip: 'Monstera · 98%',
|
|
healthCheckLabel: 'Health Check',
|
|
overwateringDetected: 'Overwatering detected',
|
|
rescuePlanReady: '7-day rescue plan ready',
|
|
waterReminder: 'Water Monstera — today',
|
|
fertilizeReminder: 'Fertilize Basil — in 3 days',
|
|
continueLabel: 'Continue',
|
|
};
|
|
};
|
|
|
|
function ScanFrameOverlay({ resultChip, colors }: { resultChip: string; colors: ColorsType }) {
|
|
return (
|
|
<>
|
|
<View style={styles.scanFrameWrap} pointerEvents="none">
|
|
<View style={[styles.cornerTL, { borderColor: colors.primary }]} />
|
|
<View style={[styles.cornerTR, { borderColor: colors.primary }]} />
|
|
<View style={[styles.cornerBL, { borderColor: colors.primary }]} />
|
|
<View style={[styles.cornerBR, { borderColor: colors.primary }]} />
|
|
</View>
|
|
<View style={styles.resultChip}>
|
|
<Ionicons name="leaf" size={15} color={colors.primary} />
|
|
<Text style={styles.resultChipText}>{resultChip}</Text>
|
|
</View>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function HealthCardOverlay({
|
|
label,
|
|
overwateringDetected,
|
|
rescuePlanReady,
|
|
}: {
|
|
label: string;
|
|
overwateringDetected: string;
|
|
rescuePlanReady: string;
|
|
}) {
|
|
return (
|
|
<View style={styles.healthCard}>
|
|
<View style={styles.healthCardHeader}>
|
|
<View style={styles.healthCardIcon}>
|
|
<Ionicons name="medkit" size={16} color="#C62828" />
|
|
</View>
|
|
<Text style={styles.healthCardTitle}>{label}</Text>
|
|
</View>
|
|
<View style={[styles.healthRow, styles.healthRowWarning]}>
|
|
<Ionicons name="warning" size={15} color="#C62828" />
|
|
<Text style={styles.healthRowWarningText}>{overwateringDetected}</Text>
|
|
</View>
|
|
<View style={[styles.healthRow, styles.healthRowSuccess]}>
|
|
<Ionicons name="checkmark-circle" size={15} color="#2e7d32" />
|
|
<Text style={styles.healthRowSuccessText}>{rescuePlanReady}</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function ReminderChipsOverlay({ waterReminder, fertilizeReminder }: { waterReminder: string; fertilizeReminder: string }) {
|
|
const [waterLabel, waterMeta] = splitReminder(waterReminder);
|
|
const [fertilizeLabel, fertilizeMeta] = splitReminder(fertilizeReminder);
|
|
return (
|
|
<>
|
|
<View style={[styles.reminderChip, styles.reminderChipTop]}>
|
|
<View style={[styles.reminderIcon, { backgroundColor: '#dff2e6' }]}>
|
|
<Ionicons name="water" size={16} color="#2e7d32" />
|
|
</View>
|
|
<View>
|
|
<Text style={styles.reminderLabel}>{waterLabel}</Text>
|
|
<Text style={styles.reminderMeta}>{waterMeta}</Text>
|
|
</View>
|
|
</View>
|
|
<View style={[styles.reminderChip, styles.reminderChipBottom]}>
|
|
<View style={[styles.reminderIcon, { backgroundColor: '#e3f3c8' }]}>
|
|
<Ionicons name="leaf" size={16} color="#558b2f" />
|
|
</View>
|
|
<View>
|
|
<Text style={styles.reminderLabel}>{fertilizeLabel}</Text>
|
|
<Text style={styles.reminderMeta}>{fertilizeMeta}</Text>
|
|
</View>
|
|
</View>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Splits "Water Monstera — today" into ["Water Monstera", "today"] for a two-line chip.
|
|
function splitReminder(text: string): [string, string] {
|
|
const parts = text.split('—').map((part) => part.trim());
|
|
if (parts.length === 2) return [parts[0], parts[1]];
|
|
return [text, ''];
|
|
}
|
|
|
|
export default function OnboardingSlidesScreen() {
|
|
const router = useRouter();
|
|
const { language, isDarkMode, colorPalette } = useApp();
|
|
const colors = useColors(isDarkMode, colorPalette);
|
|
const posthog = useSafeAnalytics();
|
|
const [page, setPage] = useState(0);
|
|
const copy = getSlidesCopy(language);
|
|
const slide = copy.slides[page];
|
|
|
|
useEffect(() => {
|
|
posthog.capture('onboarding_slide_viewed', { index: page });
|
|
}, [page, posthog]);
|
|
|
|
const next = () => {
|
|
if (page < copy.slides.length - 1) {
|
|
setPage(page + 1);
|
|
} else {
|
|
router.replace('/onboarding/source');
|
|
}
|
|
};
|
|
|
|
const back = () => {
|
|
if (page > 0) {
|
|
setPage(page - 1);
|
|
} else {
|
|
router.back();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: isDarkMode ? '#0a110b' : '#f8fbef' }]}>
|
|
<View style={styles.imageArea}>
|
|
<Image
|
|
source={
|
|
page === 0
|
|
? require('../../assets/paywall_scan_background.png')
|
|
: page === 1
|
|
? require('../../assets/onboarding_health_scan_mockup.png')
|
|
: require('../../assets/welcome_botanical_header.png')
|
|
}
|
|
style={styles.image}
|
|
resizeMode="cover"
|
|
/>
|
|
<SafeAreaView style={styles.imageSafeArea} edges={['top']}>
|
|
<TouchableOpacity onPress={back} style={styles.backBtn} activeOpacity={0.85}>
|
|
<Ionicons name="arrow-back" size={20} color="#1f2520" />
|
|
</TouchableOpacity>
|
|
</SafeAreaView>
|
|
{page === 0 && <ScanFrameOverlay resultChip={copy.resultChip} colors={colors} />}
|
|
{page === 1 && (
|
|
<HealthCardOverlay
|
|
label={copy.healthCheckLabel}
|
|
overwateringDetected={copy.overwateringDetected}
|
|
rescuePlanReady={copy.rescuePlanReady}
|
|
/>
|
|
)}
|
|
{page === 2 && (
|
|
<ReminderChipsOverlay waterReminder={copy.waterReminder} fertilizeReminder={copy.fertilizeReminder} />
|
|
)}
|
|
</View>
|
|
<View style={[styles.sheet, { backgroundColor: colors.surface }]}>
|
|
<Text style={[styles.title, { color: colors.text }]}>{slide.title}</Text>
|
|
<Text style={[styles.body, { color: colors.textSecondary }]}>{slide.body}</Text>
|
|
<View style={styles.dots}>
|
|
{copy.slides.map((_, index) => (
|
|
<View
|
|
key={index}
|
|
style={[
|
|
styles.dot,
|
|
index === page
|
|
? [styles.dotActive, { backgroundColor: colors.primary }]
|
|
: { backgroundColor: colors.border },
|
|
]}
|
|
/>
|
|
))}
|
|
</View>
|
|
<TouchableOpacity
|
|
style={[styles.cta, { backgroundColor: colors.primary }]}
|
|
onPress={next}
|
|
activeOpacity={0.86}
|
|
>
|
|
<Text style={[styles.ctaText, { color: colors.onPrimary }]}>{copy.continueLabel}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
imageArea: {
|
|
height: '58%',
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
},
|
|
image: {
|
|
width: '100%',
|
|
height: '100%',
|
|
position: 'absolute',
|
|
},
|
|
imageSafeArea: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
},
|
|
backBtn: {
|
|
marginLeft: 16,
|
|
marginTop: 8,
|
|
width: 38,
|
|
height: 38,
|
|
borderRadius: 19,
|
|
backgroundColor: 'rgba(255,255,255,0.85)',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
// Scan frame overlay (slide 1)
|
|
scanFrameWrap: {
|
|
position: 'absolute',
|
|
top: '22%',
|
|
left: '20%',
|
|
right: '20%',
|
|
bottom: '26%',
|
|
},
|
|
cornerTL: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: 30,
|
|
height: 30,
|
|
borderTopWidth: 4,
|
|
borderLeftWidth: 4,
|
|
borderTopLeftRadius: 8,
|
|
},
|
|
cornerTR: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
right: 0,
|
|
width: 30,
|
|
height: 30,
|
|
borderTopWidth: 4,
|
|
borderRightWidth: 4,
|
|
borderTopRightRadius: 8,
|
|
},
|
|
cornerBL: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
width: 30,
|
|
height: 30,
|
|
borderBottomWidth: 4,
|
|
borderLeftWidth: 4,
|
|
borderBottomLeftRadius: 8,
|
|
},
|
|
cornerBR: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
right: 0,
|
|
width: 30,
|
|
height: 30,
|
|
borderBottomWidth: 4,
|
|
borderRightWidth: 4,
|
|
borderBottomRightRadius: 8,
|
|
},
|
|
resultChip: {
|
|
position: 'absolute',
|
|
bottom: '10%',
|
|
left: 20,
|
|
right: 20,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
backgroundColor: 'rgba(255,255,255,0.94)',
|
|
borderRadius: 999,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
},
|
|
resultChipText: {
|
|
fontSize: 15,
|
|
fontWeight: '700',
|
|
color: '#1f2520',
|
|
},
|
|
// Health card overlay (slide 2)
|
|
healthCard: {
|
|
position: 'absolute',
|
|
bottom: 16,
|
|
left: 16,
|
|
right: 16,
|
|
backgroundColor: 'rgba(255,255,255,0.96)',
|
|
borderRadius: 18,
|
|
padding: 14,
|
|
gap: 8,
|
|
},
|
|
healthCardHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
marginBottom: 2,
|
|
},
|
|
healthCardIcon: {
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: 14,
|
|
backgroundColor: '#fdeaea',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
healthCardTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '800',
|
|
color: '#1f2520',
|
|
},
|
|
healthRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
borderRadius: 10,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 8,
|
|
},
|
|
healthRowWarning: {
|
|
backgroundColor: '#fdeaea',
|
|
},
|
|
healthRowWarningText: {
|
|
fontSize: 13.5,
|
|
fontWeight: '700',
|
|
color: '#C62828',
|
|
},
|
|
healthRowSuccess: {
|
|
backgroundColor: '#e8f3e3',
|
|
},
|
|
healthRowSuccessText: {
|
|
fontSize: 13.5,
|
|
fontWeight: '700',
|
|
color: '#2e7d32',
|
|
},
|
|
// Reminder chips overlay (slide 3)
|
|
reminderChip: {
|
|
position: 'absolute',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
backgroundColor: 'rgba(255,255,255,0.96)',
|
|
borderRadius: 999,
|
|
paddingVertical: 8,
|
|
paddingRight: 18,
|
|
paddingLeft: 8,
|
|
},
|
|
reminderChipTop: {
|
|
top: '24%',
|
|
right: 20,
|
|
},
|
|
reminderChipBottom: {
|
|
top: '42%',
|
|
left: 20,
|
|
},
|
|
reminderIcon: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 16,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
reminderLabel: {
|
|
fontSize: 14,
|
|
fontWeight: '800',
|
|
color: '#1f2520',
|
|
},
|
|
reminderMeta: {
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
color: '#5a8a3d',
|
|
},
|
|
// Bottom sheet
|
|
sheet: {
|
|
flex: 1,
|
|
borderTopLeftRadius: 28,
|
|
borderTopRightRadius: 28,
|
|
marginTop: -24,
|
|
paddingHorizontal: 24,
|
|
paddingTop: 32,
|
|
alignItems: 'center',
|
|
},
|
|
title: {
|
|
fontSize: 30,
|
|
fontWeight: '900',
|
|
textAlign: 'center',
|
|
marginBottom: 10,
|
|
},
|
|
body: {
|
|
fontSize: 15.5,
|
|
lineHeight: 22,
|
|
textAlign: 'center',
|
|
maxWidth: 320,
|
|
marginBottom: 20,
|
|
},
|
|
dots: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
marginBottom: 'auto',
|
|
},
|
|
dot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
},
|
|
dotActive: {
|
|
width: 26,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
},
|
|
cta: {
|
|
alignSelf: 'stretch',
|
|
height: 58,
|
|
borderRadius: 14,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginBottom: 24,
|
|
},
|
|
ctaText: {
|
|
fontSize: 17,
|
|
fontWeight: '800',
|
|
},
|
|
});
|