mobileapp / src /screens /onboarding /WelcomeScreen.tsx
Antaram Dev Bot
feat: complete ANTARAM.ORG ride-sharing app frontend
5c876be
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';
// ─── Types ─────────────────────────────────────────────────────────────────────
type RootStackParamList = {
Onboarding: undefined;
GoogleSignIn: undefined;
};
type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'Onboarding'>;
interface SlideData {
icon: string;
title: string;
description: string;
}
// ─── Slide Data ────────────────────────────────────────────────────────────────
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;
// ─── WelcomeScreen Component ──────────────────────────────────────────────────
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;
// ── Slide Navigation ──────────────────────────────────────────────────────────
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');
};
// ── Render ────────────────────────────────────────────────────────────────────
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>
);
}
// ─── Styles ────────────────────────────────────────────────────────────────────
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,
},
});