Working In progress
index.js
import { View, Text, StyleSheet, Image, Pressable } from "react-native";
import React from "react";
import { StatusBar } from "expo-status-bar";
import { hp, wp } from "../helpers/common";
import { LinearGradient } from "expo-linear-gradient";
import Animated, { FadeIn, FadeInDown } from "react-native-reanimated";
import { theme } from "../constants/theme";
import { useRouter } from "expo-router";
const WelcomeScreen = () => {
const router = useRouter();
return (
<View style={styles.container}>
<StatusBar style="light" />
<Image
source={require("../assets/images/wallpaper2.png")}
style={styles.bgImage}
resizeMode="cover"
/>
{/* Linear Gradient */}
<Animated.View entering={FadeInDown.duration(600)} style={{ flex: 1 }}>
<LinearGradient
colors={[
"rgba(255, 255, 255,0)",
"rgba(255, 255, 255,0.5)",
"white",
"white",
]}
style={styles.gradient}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 0.8 }}
/>
{/* content */}
<View style={styles.contentContainer}>
<Animated.Text
entering={FadeInDown.delay(400).springify()}
style={styles.title}
>
VividVistas
</Animated.Text>
<Animated.Text
entering={FadeInDown.delay(500).springify()}
style={styles.punchLine}
>
Expand Your Horizon{" "}
</Animated.Text>
<Animated.View entering={FadeInDown.delay(600).springify()}>
<Pressable
onPress={() => router.push("home")}
style={styles.startButton}
>
<Text style={styles.startText}>Start Explore</Text>
</Pressable>
</Animated.View>
</View>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
bgImage: {
width: wp(100),
height: hp(100),
position: "absolute",
},
gradient: {
width: wp(100),
height: hp(65),
bottom: 0,
position: "absolute",
},
contentContainer: {
flex: 1,
alignItems: "center",
justifyContent: "flex-end",
gap: 14,
},
title: {
fontSize: hp(7),
color: theme.colors.neutral(0.9),
fontWeight: theme.fontWeights.bold,
},
punchLine: {
fontSize: hp(2),
letterSpacing: 1,
marginBottom: 10,
fontWeight: theme.fontWeights.medium,
},
startButton: {
marginBottom: 50,
backgroundColor: theme.colors.neutral(0.9),
padding: 15,
paddingHorizontal: 90,
borderRadius: theme.radius.xl,
borderCurve: "continuous",
},
startText: {
color: theme.colors.white,
fontWeight: theme.fontWeights.medium,
fontSize: hp(3),
letterSpacing: 1,
},
});
export default WelcomeScreen;
The WelcomeScreen
component in React Native is designed to create an engaging entry point for the "VividVistas" wallpaper app. Here's a concise overview of the main elements and functionalities:
Layout and Styling
Container: The top-level view that holds all other components. It's styled to fill the entire screen.
Background Image: A full-screen image (
wallpaper2.png
) set as the background of the welcome screen, positioned absolutely to cover the entire screen.Linear Gradient: An animated
LinearGradient
overlay that starts transparent and becomes opaque towards the bottom, enhancing text visibility over the background image.
Animated Components
The screen uses the
react-native-reanimated
library to animate elements such as the app title, punchline, and the "Start Explore" button with effects likeFadeInDown
.Title: "VividVistas", displayed with a large font and a bold style.
Punchline: "Expand Your Horizon", displayed beneath the title to provide a motivational message.
Start Button: A pressable button animated to appear after the texts. Pressing this button navigates the user to the 'home' screen using
expo-router
.
Navigation
- Uses
expo-router
for routing, where pressing the "Start Explore" button routes the user to a different screen within the app.
Purpose
This welcome screen serves as the initial interaction point for users, combining visually appealing elements with functional components to ensure a smooth and inviting user experience as they start using the app.
layout.js
import { View, Text } from "react-native";
import React from "react";
import { Stack } from "expo-router";
const Layout = () => {
return (
<Stack>
<Stack.Screen
name="index"
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="home/index"
options={{
headerShown: false,
}}
/>
</Stack>
);
};
export default Layout;
export const theme = {
colors: {
white: "#fff",
black: "#000",
grayBG: "#e5e5e5",
//neutral
neutral: (opacity) => `rgba(10,10,10, ${opacity})`,
},
fontWeights: {
medium: "500",
semibold: "600",
bold: "700",
},
radius: {
xs: 10,
sm: 12,
md: 14,
lg: 16,
xl: 18,
},
};
import { Dimensions } from "react-native";
const { width: deviceWidth, height: deviceHeight } = Dimensions.get("window");
export const wp = (percentage) => {
const width = deviceWidth;
return (width * percentage) / 100;
};
export const hp = (percentage) => {
const height = deviceHeight;
return (height * percentage) / 100;
};
The code defines two functions, wp
(width percentage) and hp
(height percentage), which calculate responsive layout dimensions based on the device's screen size in React Native. These functions help in setting dimensions (like width and height) of UI elements as a percentage of the screen's total width and height. For example, wp(50)
gives a width that is 50% of the screen's width, and hp(20)
gives a height that is 20% of the screen's height. This approach ensures that your app's layout adjusts smoothly across different device sizes, making it more responsive and consistent.
Welcome screen is Completed. After clicking on start explore it goes to homescreen
Inside Home Screen
work in progress
Heading , hamburger icon , search bar and categories
component Categories.js
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
import React from "react";
import { data } from "../constants/data";
import { hp, wp } from "../helpers/common";
import { theme } from "../constants/theme";
import Animated, { FadeInRight } from "react-native-reanimated";
export default function Categories({ activeCategory, handleChangeCategory }) {
return (
<FlatList
horizontal
contentContainerStyle={styles.flatlistContainer}
showsHorizontalScrollIndicator={false}
data={data.categories}
keyExtractor={(item) => item}
renderItem={({ item, index }) => (
<CategoryItem
isActive={activeCategory === item}
handleChangeCategory={handleChangeCategory}
title={item}
index={index}
/>
)}
/>
);
}
const CategoryItem = ({ title, index, isActive, handleChangeCategory }) => {
let color = isActive ? theme.colors.white : theme.colors.neutral(0.8);
let backgroundColor = isActive
? theme.colors.neutral(0.8)
: theme.colors.white;
return (
<Animated.View
entering={FadeInRight.delay(index * 200)
.duration(1000)
.springify()
.damping(14)}
>
<Pressable
onPress={() => handleChangeCategory(isActive ? null : title)}
style={[styles.category, { backgroundColor }]}
>
<Text style={[styles.title, { color }]}>{title}</Text>
</Pressable>
</Animated.View>
);
};
const styles = StyleSheet.create({
flatlistContainer: {
paddingHorizontal: wp(4),
gap: 8,
},
category: {
padding: 12,
paddingHorizontal: 15,
borderWidth: 1,
borderColor: theme.colors.grayBG,
// backgroundColor:'white',
borderRadius: theme.radius.lg,
borderCurve: "continuous",
},
title: {
fontSize: hp(1.8),
fontWeight: theme.fontWeights.medium,
},
});
data.js
const categories = [
"backgrounds",
"fashion",
"nature",
"science",
"education",
"feelings",
"health",
"people",
"religion",
"places",
"animals",
"industry",
"computer",
"food",
"sports",
"transportation",
"travel",
"buildings",
"business",
"music",
];
export const data = {
categories,
};
index.js
import {
Pressable,
StyleSheet,
Text,
View,
ScrollView,
TextInput,
} from "react-native";
import React, { useRef, useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Feather, FontAwesome6, Ionicons } from "@expo/vector-icons";
import { theme } from "../../constants/theme";
import { hp, wp } from "../../helpers/common";
import Categories from "../../components/categories";
export default function HomeScreen() {
const { top } = useSafeAreaInsets();
const paddingTop = top > 0 ? top + 10 : 30;
const [search, setSearch] = useState("");
const [activeCategory, setActiveCategory] = useState(null);
const searchInputRef = useRef(null);
const handleChangeCategory = (cat) => {
setActiveCategory(cat);
};
return (
<View style={[styles.container, { paddingTop }]}>
{/* Header */}
<View style={styles.header}>
<Pressable>
<Text style={styles.title}>VividVistas</Text>
</Pressable>
<Pressable>
<FontAwesome6
name="bars-staggered"
size={22}
color={theme.colors.neutral(0.7)}
/>
</Pressable>
</View>
<ScrollView contentContainerStyle={{ gap: 15 }}>
{/* search bar */}
<View style={styles.searchBar}>
<View style={styles.searchIcon}>
<Feather
name="search"
size={24}
color={theme.colors.neutral(0.4)}
/>
</View>
<TextInput
placeholder="Search Photos...."
value={search}
ref={searchInputRef}
onChangeText={(value) => setSearch(value)}
style={styles.searchInput}
/>
{search && (
<Pressable style={styles.closeIcon}>
<Ionicons
name="close"
size={24}
color={theme.colors.neutral(0.6)}
/>
</Pressable>
)}
</View>
{/* categories */}
<View style={styles.categories}>
<Categories
activeCategory={activeCategory}
handleChangeCategory={handleChangeCategory}
/>
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 15,
},
header: {
marginHorizontal: wp(4),
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
title: {
fontSize: hp(4),
fontWeight: theme.fontWeights.semibold,
color: theme.colors.neutral(0.9),
},
searchBar: {
marginHorizontal: wp(4),
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
borderWidth: 1,
borderColor: theme.colors.grayBG,
borderRadius: theme.radius.lg,
padding: 6,
paddingLeft: 10,
borderRadius: theme.radius.lg,
},
searchIcon: {
padding: 8,
},
searchInput: {
flex: 1,
borderRadius: theme.radius.sm,
paddingVertical: 10,
fontSize: hp(1.8),
},
closeIcon: {
padding: 8,
backgroundColor: theme.colors.neutral(0.1),
borderRadius: theme.radius.sm,
},
});