Greenlens/app/onboarding.tsx

255 lines
7.0 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
Dimensions,
Image,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import { useApp } from '../context/AppContext';
import { useColors } from '../constants/Colors';
import { ThemeBackdrop } from '../components/ThemeBackdrop';
const { height: SCREEN_H, width: SCREEN_W } = Dimensions.get('window');
export default function OnboardingScreen() {
const { isDarkMode, colorPalette, t } = useApp();
const colors = useColors(isDarkMode, colorPalette);
const FEATURES = [
{ icon: 'camera-outline' as const, label: t.onboardingFeatureScan },
{ icon: 'notifications-outline' as const, label: t.onboardingFeatureReminder },
{ icon: 'book-outline' as const, label: t.onboardingFeatureLexicon },
];
// Entrance animations
const logoAnim = useRef(new Animated.Value(0)).current;
const logoScale = useRef(new Animated.Value(0.85)).current;
const featuresAnim = useRef(new Animated.Value(0)).current;
const buttonsAnim = useRef(new Animated.Value(0)).current;
const featureAnims = useRef(FEATURES.map(() => new Animated.Value(0))).current;
useEffect(() => {
Animated.sequence([
Animated.parallel([
Animated.timing(logoAnim, { toValue: 1, duration: 700, useNativeDriver: true }),
Animated.spring(logoScale, { toValue: 1, tension: 50, friction: 8, useNativeDriver: true }),
]),
Animated.stagger(100, featureAnims.map(anim =>
Animated.timing(anim, { toValue: 1, duration: 400, useNativeDriver: true })
)),
Animated.timing(buttonsAnim, { toValue: 1, duration: 400, useNativeDriver: true }),
]).start();
}, []);
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<ThemeBackdrop colors={colors} />
{/* Logo-Bereich */}
<Animated.View
style={[
styles.heroSection,
{ opacity: logoAnim, transform: [{ scale: logoScale }] },
]}
>
<View style={[styles.iconContainer, { shadowColor: colors.primary }]}>
<Image
source={require('../assets/icon.png')}
style={styles.appIcon}
resizeMode="cover"
/>
</View>
<Text style={[styles.appName, { color: colors.text }]}>GreenLens</Text>
<Text style={[styles.tagline, { color: colors.textSecondary }]}>
{t.onboardingTagline}
</Text>
</Animated.View>
{/* Feature-Liste */}
<View style={styles.featuresSection}>
{FEATURES.map((feat, i) => (
<Animated.View
key={feat.label}
style={[
styles.featureRow,
{
backgroundColor: colors.surface + '88', // Semi-transparent for backdrop effect
borderColor: colors.border,
opacity: featureAnims[i],
transform: [{
translateY: featureAnims[i].interpolate({
inputRange: [0, 1],
outputRange: [20, 0],
}),
}],
},
]}
>
<View style={[styles.featureIcon, { backgroundColor: colors.primary + '15' }]}>
<Ionicons name={feat.icon} size={22} color={colors.primary} />
</View>
<Text style={[styles.featureText, { color: colors.text }]}>{feat.label}</Text>
</Animated.View>
))}
</View>
{/* Buttons */}
<Animated.View style={[styles.buttonsSection, { opacity: buttonsAnim }]}>
<TouchableOpacity
style={[styles.primaryBtn, { backgroundColor: colors.primary }]}
onPress={() => router.push('/scanner')}
activeOpacity={0.85}
>
<Ionicons name="scan" size={20} color={colors.onPrimary} />
<Text style={[styles.primaryBtnText, { color: colors.onPrimary }]}>
{t.onboardingScanBtn}
</Text>
</TouchableOpacity>
<View style={styles.authActions}>
<TouchableOpacity
style={[styles.secondaryBtn, { borderColor: colors.primary, backgroundColor: colors.surface }]}
onPress={() => router.push('/auth/signup')}
activeOpacity={0.82}
>
<Text style={[styles.secondaryBtnText, { color: colors.primary }]}>
{t.onboardingRegister}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.secondaryBtn, { borderColor: colors.borderStrong, backgroundColor: colors.surface }]}
onPress={() => router.push('/auth/login')}
activeOpacity={0.82}
>
<Text style={[styles.secondaryBtnText, { color: colors.text }]}>
{t.onboardingLogin}
</Text>
</TouchableOpacity>
</View>
<Text style={[styles.disclaimer, { color: colors.textMuted }]}>
{t.onboardingDisclaimer}
</Text>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 32,
paddingTop: SCREEN_H * 0.12,
paddingBottom: 40,
},
heroSection: {
alignItems: 'center',
marginBottom: 40,
},
iconContainer: {
width: 120,
height: 120,
borderRadius: 28,
backgroundColor: '#fff',
elevation: 8,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 12,
marginBottom: 24,
overflow: 'hidden',
},
appIcon: {
width: '100%',
height: '100%',
},
appName: {
fontSize: 40,
fontWeight: '900',
letterSpacing: -1.5,
marginBottom: 4,
},
tagline: {
fontSize: 17,
fontWeight: '500',
opacity: 0.8,
},
featuresSection: {
gap: 12,
flex: 1,
justifyContent: 'center',
},
featureRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 16,
paddingHorizontal: 20,
paddingVertical: 16,
borderRadius: 20,
borderWidth: 1,
},
featureIcon: {
width: 44,
height: 44,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
},
featureText: {
flex: 1,
fontSize: 15,
fontWeight: '600',
letterSpacing: 0.1,
},
buttonsSection: {
gap: 16,
marginTop: 20,
},
primaryBtn: {
height: 58,
borderRadius: 20,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 12,
elevation: 4,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
primaryBtnText: {
fontSize: 17,
fontWeight: '700',
},
authActions: {
flexDirection: 'row',
gap: 12,
},
secondaryBtn: {
flex: 1,
height: 54,
borderRadius: 20,
borderWidth: 1.5,
justifyContent: 'center',
alignItems: 'center',
},
secondaryBtnText: {
fontSize: 15,
fontWeight: '600',
},
disclaimer: {
fontSize: 12,
textAlign: 'center',
opacity: 0.6,
marginTop: 8,
},
});