Layout CODE
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/useColorScheme';
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/[Link]'),
});
if (!loaded) {
// Async font loading only occurs in development.
return null;
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack initialRouteName="index">
<[Link] name="index" options={{ headerShown: false }} />
<[Link] name="(tabs)" options={{ headerShown: false }} />
<[Link] name="+not-found" />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
);
}
Not Found CODE
Import { Link, Stack } from 'expo-router';
import { StyleSheet } from 'react-native';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
export default function NotFoundScreen() {
return (
<>
<[Link] options={{ title: 'Oops!' }} />
<ThemedView style={[Link]}>
<ThemedText type="title">This screen does not exist.</ThemedText>
<Link href="/" style={[Link]}>
<ThemedText type="link">Go to home screen!</ThemedText>
</Link>
</ThemedView>
</>
);
const styles = [Link]({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});
Home CODE
import * as ImagePicker from "expo-image-picker";
import { useRouter } from "expo-router";
import { signOut } from "firebase/auth";
import { useState } from "react";
import { ActivityIndicator, Alert, Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput,
View } from "react-native";
import { uploadToCloudinary } from "../components/cloudinary";
import { getGenerativeRemoveUrl, getGenerativeReplaceUrl } from
"../components/cloudinaryGen";
import { auth } from "../firebaseConfig";
export default function Home() {
const [fromPrompt, setFromPrompt] = useState("hair");
const [toPrompt, setToPrompt] = useState("curly hair with bangs");
const router = useRouter();
const [image, setImage] = useState<string | null>(null);
const [generativeUrl, setGenerativeUrl] = useState<string | null>(null);
const [removeUrl, setRemoveUrl] = useState<string | null>(null);
const [publicId, setPublicId] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
// Predefined hairstyle options
const hairstyleOptions = [
{ label: "Long Hair", value: "long hair", emoji: "💁♀️" },
{ label: "Short Hair", value: "short hair", emoji: "👩🦱" },
{ label: "Curly Hair", value: "curly hair with bangs", emoji: "🌀" },
{ label: "Straight Hair", value: "straight long hair", emoji: "💇♀️" },
{ label: "Bob Cut", value: "bob haircut", emoji: "👩💼" },
{ label: "Pixie Cut", value: "pixie cut short hair", emoji: "🧚♀️" },
{ label: "Afro Hair", value: "afro hairstyle", emoji: "👩🦲" },
{ label: "Braided Hair", value: "braided hairstyle", emoji: "👸" },
{ label: "Wavy Hair", value: "wavy hair", emoji: "🌊" },
{ label: "Ponytail", value: "high ponytail", emoji: "🐴" },
{ label: "Bangs", value: "hair with bangs", emoji: "💫" },
{ label: "Bald", value: "bald head", emoji: "👨🦲" },
];
const handleGenerativeRemove = () => {
if (publicId) {
const url = getGenerativeRemoveUrl(publicId, fromPrompt);
setRemoveUrl(url);
};
const downloadImage = async (imageUri: string, imageName: string) => {
try {
// Open the image URL in the browser for download
const supported = await [Link](imageUri);
if (supported) {
await [Link](imageUri);
[Link]('Download', `Opening ${imageName} in browser for download.`);
} else {
[Link]('Error', 'Cannot open image URL.');
} catch (error) {
[Link]('Error opening image:', error);
[Link]('Error', 'Failed to open image for download.');
}
};
const logout = async () => {
await signOut(auth);
[Link]("/");
};
const pickImage = async () => {
const permission = await [Link]();
if (![Link]) {
alert("Permission required to access photos!");
return;
const result = await [Link]({
mediaTypes: [Link],
allowsEditing: true,
aspect: [4, 4],
quality: 1,
});
if (![Link]) {
const uri = [Link][0].uri;
await uploadImage(uri);
};
const uploadImage = async (uri: string) => {
try {
setUploading(true);
setGenerativeUrl(null);
setRemoveUrl(null);
// Upload to Cloudinary
const uploadRes = await uploadToCloudinary(uri);
setImage(uploadRes.secure_url);
// Save publicId for later use
const publicId = uploadRes.public_id;
setPublicId(publicId);
// Apply generative replace effect with user prompts
const genUrl = getGenerativeReplaceUrl(publicId, fromPrompt, toPrompt);
setGenerativeUrl(genUrl);
} catch (error: any) {
[Link](error);
alert("Upload failed: " + ([Link] || error));
} finally {
setUploading(false);
};
return (
<ScrollView style={[Link]} contentContainerStyle={[Link]}>
{/* Header */}
<View style={[Link]}>
<View style={[Link]}>
<Text style={[Link]}>HairStyle App</Text>
<Text style={[Link]}>Transform your hairstyle with Us</Text>
</View>
<Pressable style={[Link]} onPress={logout}>
<Text style={[Link]}>Logout</Text>
</Pressable>
</View>
{/* Welcome Section */}
<View style={[Link]}>
<Text style={[Link]}>Welcome back!</Text>
<Text style={[Link]}>{[Link]?.email}</Text>
</View>
{/* Input Section */}
<View style={[Link]}>
<Text style={[Link]}>Transformation Settings</Text>
<View style={[Link]}>
<Text style={[Link]}>What to replace:</Text>
<TextInput
value={fromPrompt}
onChangeText={setFromPrompt}
placeholder="e.g. hairstyle, hat, shirt"
placeholderTextColor="#94a3b8"
style={[Link]}
/>
</View>
<View style={[Link]}>
<Text style={[Link]}>Choose Hairstyle:</Text>
<View style={[Link]}>
{[Link]((option) => (
<Pressable
key={[Link]}
style={[
[Link],
toPrompt === [Link] && [Link]
]}
onPress={() => setToPrompt([Link])}
>
<Text style={[Link]}>{[Link]}</Text>
<Text style={[
[Link],
toPrompt === [Link] && [Link]
]}>{[Link]}</Text>
</Pressable>
))}
</View>
</View>
</View>
{/* Upload Section */}
<View style={[Link]}>
<Pressable style={[Link]} onPress={pickImage} disabled={uploading}>
<Text style={[Link]}>
{uploading ? "Processing..." : "📸 Pick & Transform Photo"}
</Text>
</Pressable>
{uploading && (
<View style={[Link]}>
<ActivityIndicator size="large" color="#3b82f6" />
<Text style={[Link]}>Applying AI magic...</Text>
</View>
)}
</View>
{/* Results Section */}
{(image || generativeUrl || removeUrl) && (
<View style={[Link]}>
<Text style={[Link]}>Results</Text>
<View style={[Link]}>
{/* Original Image */}
{image && (
<View style={[Link]}>
<Text style={[Link]}>Original</Text>
<Image source={{ uri: image }} style={[Link]} />
</View>
)}
{/* Generative Replace Image */}
{generativeUrl && (
<View style={[Link]}>
<Text style={[Link]}>AI Transform</Text>
<Image source={{ uri: generativeUrl }} style={[Link]} />
<Pressable
style={[Link]}
onPress={() => downloadImage(generativeUrl, 'AI Transform')}
>
<Text style={[Link]}>💾 Download</Text>
</Pressable>
</View>
)}
{/* Generative Remove Image */}
{removeUrl && (
<View style={[Link]}>
<Text style={[Link]}>AI Remove</Text>
<Image source={{ uri: removeUrl }} style={[Link]} />
<Pressable
style={[Link]}
onPress={() => downloadImage(removeUrl, 'AI Remove')}
>
<Text style={[Link]}>💾 Download</Text>
</Pressable>
</View>
)}
</View>
</View>
)}
</ScrollView>
);
const styles = [Link]({
container: {
flex: 1,
backgroundColor: '#0f172a', // Dark slate background
},
contentContainer: {
paddingBottom: 30,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
paddingHorizontal: 20,
paddingTop: 60,
paddingBottom: 20,
backgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
},
headerContent: {
flex: 1,
},
appTitle: {
fontSize: 32,
fontWeight: 'bold',
color: '#ffffff',
marginBottom: 4,
},
subtitle: {
fontSize: 16,
color: '#e2e8f0',
opacity: 0.9,
},
logoutButton: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
logoutText: {
color: '#ffffff',
fontSize: 14,
fontWeight: '600',
},
welcomeCard: {
backgroundColor: '#1e293b',
marginHorizontal: 20,
marginTop: -10,
marginBottom: 20,
padding: 20,
borderRadius: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
welcomeText: {
fontSize: 20,
fontWeight: '600',
color: '#f1f5f9',
marginBottom: 4,
},
userEmail: {
fontSize: 14,
color: '#94a3b8',
},
inputSection: {
marginHorizontal: 20,
marginBottom: 24,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#f1f5f9',
marginBottom: 16,
},
inputContainer: {
marginBottom: 16,
},
inputLabel: {
fontSize: 14,
fontWeight: '500',
color: '#cbd5e1',
marginBottom: 8,
},
textInput: {
backgroundColor: '#334155',
borderWidth: 1,
borderColor: '#475569',
borderRadius: 12,
padding: 16,
fontSize: 16,
color: '#f1f5f9',
minHeight: 50,
},
uploadSection: {
marginHorizontal: 20,
marginBottom: 24,
},
uploadButton: {
backgroundColor: '#3b82f6',
padding: 18,
borderRadius: 16,
alignItems: 'center',
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
uploadButtonText: {
color: '#ffffff',
fontSize: 18,
fontWeight: '600',
},
loadingContainer: {
alignItems: 'center',
marginTop: 20,
},
loadingText: {
color: '#94a3b8',
fontSize: 16,
marginTop: 12,
},
resultsSection: {
marginHorizontal: 20,
},
imageGrid: {
gap: 16,
},
imageCard: {
backgroundColor: '#1e293b',
borderRadius: 16,
padding: 16,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 6,
elevation: 6,
marginBottom: 16,
},
imageTitle: {
fontSize: 16,
fontWeight: '600',
color: '#f1f5f9',
marginBottom: 12,
},
resultImage: {
width: 280,
height: 280,
borderRadius: 12,
marginBottom: 16,
},
downloadButton: {
backgroundColor: '#10b981',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 12,
minWidth: 140,
alignItems: 'center',
},
downloadButtonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
hairstyleGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 12,
marginTop: 8,
},
hairstyleCard: {
backgroundColor: '#1e293b',
borderRadius: 12,
padding: 12,
alignItems: 'center',
justifyContent: 'center',
minWidth: 85,
minHeight: 85,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
borderWidth: 2,
borderColor: 'transparent',
},
selectedCard: {
backgroundColor: '#3b82f6',
borderColor: '#60a5fa',
shadowColor: '#3b82f6',
shadowOpacity: 0.3,
},
emoji: {
fontSize: 24,
marginBottom: 4,
},
cardLabel: {
fontSize: 11,
fontWeight: '500',
color: '#94a3b8',
textAlign: 'center',
lineHeight: 14,
},
selectedCardLabel: {
color: '#ffffff',
fontWeight: '600',
},
});
INDEX CODE
import React, { useState } from "react";
import { View, TextInput, Text, StyleSheet, TouchableOpacity } from "react-native";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebaseConfig";
import { useRouter } from "expo-router";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleLogin = async () => {
try {
await signInWithEmailAndPassword(auth, email, password);
[Link]("/home"); // 👈 go to home screen
} catch (error: any) {
alert([Link]);
};
return (
<View style={[Link]}>
<Text style={[Link]}>Login</Text>
<TextInput
style={[Link]}
placeholder="Email"
placeholderTextColor="#666"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={[Link]}
placeholder="Password"
placeholderTextColor="#666"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
<TouchableOpacity style={[Link]} onPress={handleLogin} activeOpacity={0.8}>
<Text style={[Link]}>Login</Text>
</TouchableOpacity>
<TouchableOpacity style={[Link]} onPress={() => [Link]("/register")}
activeOpacity={0.8}>
<Text style={[Link]}>Go to Register</Text>
</TouchableOpacity>
</View>
);
const styles = [Link]({
container: { flex: 1, justifyContent: "center", padding: 20, backgroundColor: "#fff" },
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 12,
marginBottom: 12,
borderRadius: 8,
backgroundColor: "#fff",
color: "#000",
},
title: { fontSize: 24, marginBottom: 20, textAlign: "center", color: "#000" },
primaryButton: {
backgroundColor: "#1e90ff",
paddingVertical: 12,
alignItems: "center",
borderRadius: 12,
marginBottom: 12,
},
primaryButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
secondaryButton: {
backgroundColor: "#f1f5f9",
paddingVertical: 12,
alignItems: "center",
borderRadius: 12,
},
secondaryButtonText: {
color: "#111827",
fontSize: 16,
fontWeight: "600",
},
});
LOGIN CODE
import React, { useState } from "react";
import { View, TextInput, Button, Text, StyleSheet } from "react-native";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebaseConfig"; // ✅ use your config
export default function Login({ navigation }: any) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async () => {
try {
await signInWithEmailAndPassword(auth, email, password);
[Link]("home"); // 👈 go to home screen after login
} catch (error: any) {
alert([Link]);
};
return (
<View style={[Link]}>
<Text style={[Link]}>Login</Text>
<TextInput
style={[Link]}
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={[Link]}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
<Button title="Login" onPress={handleLogin} />
<Button
title="Go to Register"
onPress={() => [Link]("register")}
/>
</View>
);
const styles = [Link]({
container: { flex: 1, justifyContent: "center", padding: 20 },
input: { borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 8 },
title: { fontSize: 24, marginBottom: 20, textAlign: "center" },
});
REGISTER CODE
import React, { useState } from "react";
import { View, TextInput, Text, StyleSheet, TouchableOpacity } from "react-native";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebaseConfig";
import { useRouter } from "expo-router";
export default function Register() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleRegister = async () => {
try {
await createUserWithEmailAndPassword(auth, email, password);
[Link]("/home"); // 👈 redirect after register
} catch (error: any) {
[Link]("Register error:", error?.code, error?.message);
alert(`${error?.code || "auth/error"}: ${error?.message || "Unknown error"}`);
};
return (
<View style={[Link]}>
<Text style={[Link]}>Register</Text>
<TextInput
style={[Link]}
placeholder="Email"
placeholderTextColor="#666"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={[Link]}
placeholder="Password"
placeholderTextColor="#666"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
<TouchableOpacity style={[Link]} onPress={handleRegister}
activeOpacity={0.8}>
<Text style={[Link]}>Register</Text>
</TouchableOpacity>
<TouchableOpacity style={[Link]} onPress={() => [Link]()}
activeOpacity={0.8}>
<Text style={[Link]}>Back to Login</Text>
</TouchableOpacity>
</View>
);
const styles = [Link]({
container: { flex: 1, justifyContent: "center", padding: 20, backgroundColor: "#fff" },
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 12,
marginBottom: 12,
borderRadius: 8,
backgroundColor: "#fff",
color: "#000",
},
title: { fontSize: 24, marginBottom: 20, textAlign: "center", color: "#000" },
primaryButton: {
backgroundColor: "#1e90ff",
paddingVertical: 12,
alignItems: "center",
borderRadius: 12,
marginBottom: 12,
},
primaryButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
secondaryButton: {
backgroundColor: "#f1f5f9",
paddingVertical: 12,
alignItems: "center",
borderRadius: 12,
},
secondaryButtonText: {
color: "#111827",
fontSize: 16,
fontWeight: "600",
},
});