| import React, { useState, useRef } from 'react'; |
| import { |
| View, |
| StyleSheet, |
| Dimensions, |
| Animated, |
| TouchableOpacity, |
| } from 'react-native'; |
| import { |
| Text, |
| Button, |
| useTheme, |
| Surface, |
| } from 'react-native-paper'; |
| import { useNavigation } from '@react-navigation/native'; |
| import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
| import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; |
|
|
| |
|
|
| type RootStackParamList = { |
| Onboarding: undefined; |
| GoogleSignIn: undefined; |
| }; |
|
|
| type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'Onboarding'>; |
|
|
| interface SlideData { |
| icon: string; |
| title: string; |
| description: string; |
| } |
|
|
| |
|
|
| const SLIDES: SlideData[] = [ |
| { |
| icon: 'car-multiple', |
| title: 'Share Your Ride,\nSave Together', |
| description: |
| 'Split costs with fellow commuters heading the same way. Save money and reduce your carbon footprint.', |
| }, |
| { |
| icon: 'map-marker-path', |
| title: 'Same Path,\nAuto Match', |
| description: |
| 'Our smart matching algorithm finds riders and passengers on similar routes in real time.', |
| }, |
| { |
| icon: 'shield-check', |
| title: 'Chat, Track,\nArrive Safe', |
| description: |
| 'In-app messaging, live ride tracking, and SOS features keep you connected and safe throughout your journey.', |
| }, |
| ]; |
|
|
| const SCREEN_WIDTH = Dimensions.get('window').width; |
|
|
| |
|
|
| export default function WelcomeScreen() { |
| const theme = useTheme(); |
| const navigation = useNavigation<NavigationProp>(); |
| const [currentSlide, setCurrentSlide] = useState(0); |
| const translateX = useRef(new Animated.Value(0)).current; |
|
|
| const isLastSlide = currentSlide === SLIDES.length - 1; |
|
|
| |
|
|
| const goToSlide = (index: number) => { |
| Animated.timing(translateX, { |
| toValue: -index * SCREEN_WIDTH, |
| duration: 300, |
| useNativeDriver: true, |
| }).start(); |
| setCurrentSlide(index); |
| }; |
|
|
| const goNext = () => { |
| if (isLastSlide) { |
| navigation.navigate('GoogleSignIn'); |
| } else { |
| goToSlide(currentSlide + 1); |
| } |
| }; |
|
|
| const skip = () => { |
| navigation.navigate('GoogleSignIn'); |
| }; |
|
|
| |
|
|
| return ( |
| <Surface style={styles.container}> |
| {/* Slides */} |
| <Animated.View |
| style={[ |
| styles.slidesContainer, |
| { transform: [{ translateX }] }, |
| ]} |
| > |
| {SLIDES.map((slide, index) => ( |
| <View key={index} style={[styles.slide, { width: SCREEN_WIDTH }]}> |
| {/* Icon area */} |
| <View |
| style={[ |
| styles.iconCircle, |
| { backgroundColor: theme.colors.primaryContainer }, |
| ]} |
| > |
| <MaterialCommunityIcons |
| name={slide.icon} |
| size={64} |
| color={theme.colors.onPrimaryContainer} |
| /> |
| </View> |
| |
| {/* Title */} |
| <Text |
| variant="headlineMedium" |
| style={[styles.slideTitle, { color: theme.colors.onSurface }]} |
| > |
| {slide.title} |
| </Text> |
| |
| {/* Description */} |
| <Text |
| variant="bodyMedium" |
| style={[styles.slideDescription, { color: theme.colors.onSurfaceVariant }]} |
| > |
| {slide.description} |
| </Text> |
| </View> |
| ))} |
| </Animated.View> |
| |
| {/* Dot indicators */} |
| <View style={styles.dotsContainer}> |
| {SLIDES.map((_, index) => ( |
| <TouchableOpacity key={index} onPress={() => goToSlide(index)}> |
| <View |
| style={[ |
| styles.dot, |
| { |
| backgroundColor: |
| index === currentSlide |
| ? theme.colors.primary |
| : theme.colors.surfaceVariant, |
| width: index === currentSlide ? 24 : 8, |
| }, |
| ]} |
| /> |
| </TouchableOpacity> |
| ))} |
| </View> |
| |
| {/* Buttons */} |
| <View style={styles.buttonsContainer}> |
| {!isLastSlide && ( |
| <Button |
| mode="text" |
| onPress={skip} |
| textColor={theme.colors.primary} |
| style={styles.skipButton} |
| > |
| Skip |
| </Button> |
| )} |
| |
| <Button |
| mode="contained" |
| onPress={goNext} |
| style={styles.getStartedButton} |
| buttonColor={theme.colors.primary} |
| textColor={theme.colors.onPrimary} |
| > |
| {isLastSlide ? 'Get Started' : 'Next'} |
| </Button> |
| </View> |
| </Surface> |
| ); |
| } |
|
|
| |
|
|
| const styles = StyleSheet.create({ |
| container: { |
| flex: 1, |
| }, |
| slidesContainer: { |
| flex: 1, |
| flexDirection: 'row', |
| }, |
| slide: { |
| flex: 1, |
| justifyContent: 'center', |
| alignItems: 'center', |
| paddingHorizontal: 32, |
| }, |
| iconCircle: { |
| width: 140, |
| height: 140, |
| borderRadius: 70, |
| justifyContent: 'center', |
| alignItems: 'center', |
| marginBottom: 40, |
| elevation: 2, |
| shadowColor: '#000', |
| shadowOffset: { width: 0, height: 2 }, |
| shadowOpacity: 0.1, |
| shadowRadius: 8, |
| }, |
| slideTitle: { |
| textAlign: 'center', |
| fontWeight: '600', |
| marginBottom: 16, |
| lineHeight: 32, |
| }, |
| slideDescription: { |
| textAlign: 'center', |
| lineHeight: 24, |
| maxWidth: 320, |
| }, |
| dotsContainer: { |
| flexDirection: 'row', |
| justifyContent: 'center', |
| alignItems: 'center', |
| gap: 8, |
| marginBottom: 32, |
| }, |
| dot: { |
| height: 8, |
| borderRadius: 4, |
| }, |
| buttonsContainer: { |
| flexDirection: 'row', |
| justifyContent: 'center', |
| alignItems: 'center', |
| paddingHorizontal: 24, |
| paddingBottom: 48, |
| gap: 16, |
| }, |
| skipButton: { |
| flex: 0, |
| }, |
| getStartedButton: { |
| flex: 0, |
| paddingHorizontal: 32, |
| }, |
| }); |
|
|