first commit

This commit is contained in:
JasonFraser 2024-01-06 22:25:29 -04:00
commit 94fb17e79f
40 changed files with 32571 additions and 0 deletions

41
.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli
expo-env.d.ts
# @end expo-cli

0
README.md Normal file
View File

39
app.json Normal file
View File

@ -0,0 +1,39 @@
{
"expo": {
"name": "apartment-clone",
"slug": "apartment-clone",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router"
],
"experiments": {
"typedRoutes": true
}
}
}

82
app/(tabs)/_layout.tsx Normal file
View File

@ -0,0 +1,82 @@
//This file serves as the index.tsx file (main)
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Link, Tabs } from 'expo-router';
import { Pressable } from 'react-native';
import { appTheme } from "../../theme";
import Colors from '../../constants/Colors';
// creatign tab bar icons and passing establishing properties to be passed during exectution
// see more icon families at https://icons.expo.fyi/
function TabBarIcon(props: {
name: React.ComponentProps<typeof MaterialCommunityIcons>['name'];
color: string;
}) {
return <MaterialCommunityIcons size={28} style={{ marginBottom: -3 }} {...props} />;
}
// Creating the tab bar at the bottom
export default function TabLayout() {
// const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: appTheme["color-primary-500"],
}}>
<Tabs.Screen
name="search_tab" //defining which tab should be rendered, this tab will call a screen component from the component folder
options={{
title: '',
tabBarLabel: 'Search',
tabBarIcon: ({ color }) => <TabBarIcon name="magnify" color={color} />, //using the tabBarIcon option within tabs and calling the TabBarIcon function created above to pass the established properties to it
// Info circle at the top right
// headerRight: () => (
// <Link href="/modal" asChild>
// <Pressable>
// {({ pressed }) => (
// <FontAwesome
// name="info-circle"
// size={25}
// color={Colors[colorScheme ?? 'light'].text}
// style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
// />
// )}
// </Pressable>
// </Link>
// ),
}}
/>
<Tabs.Screen
name="saved_tab"
options={{
title: '',
tabBarLabel: 'Saved',
tabBarIcon: ({ color }) => <TabBarIcon name="heart-outline" color={color} />,
}}
/>
<Tabs.Screen
name="account_tab"
options={{
title: '',
tabBarLabel: 'Account',
tabBarIcon: ({ color }) => <TabBarIcon name="account-circle-outline" color={color} />,
}}
/>
</Tabs>
);
}

View File

@ -0,0 +1,33 @@
import { StyleSheet } from 'react-native';
import AccountScreenInfo from '../../components/AccountScreenInfo';
import { Text, View } from '../../components/Themed';
export default function AccountScreen() {
return (
<View style={styles.container}>
{/* <Text style={styles.title}>Account Screen</Text> */}
{/* <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" /> */}
<AccountScreenInfo />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

35
app/(tabs)/saved_tab.tsx Normal file
View File

@ -0,0 +1,35 @@
//use rnfes to create a new template
import { StyleSheet } from 'react-native';
import SavedScreenInfo from '../../components/SavedScreenInfo';
import { Text, View } from '../../components/Themed';
export default function SavedScreen() {
return (
<View style={styles.container}>
{/* <Text style={styles.title}>Saved Screen</Text>
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" /> */}
<SavedScreenInfo />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

31
app/(tabs)/search_tab.tsx Normal file
View File

@ -0,0 +1,31 @@
import { StyleSheet } from 'react-native';
import { Text, View } from '../../components/Themed';
import SearchScreenInfo from '../../components/SearchScreenInfo';
export default function SearchScreen() {
return (
<View style={styles.container}>
<SearchScreenInfo />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

46
app/+html.tsx Normal file
View File

@ -0,0 +1,46 @@
import { ScrollViewStyleReset } from 'expo-router/html';
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
{/*
This viewport disables scaling which makes the mobile website act more like a native app.
However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
*/}
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"
/>
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
const responsiveBackground = `
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}`;

40
app/[...missing].tsx Normal file
View File

@ -0,0 +1,40 @@
import { Link, Stack } from 'expo-router';
import { StyleSheet } from 'react-native';
import { Text, View } from '../components/Themed';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text style={styles.title}>This screen doesn't exist.</Text>
<Link href="/" style={styles.link}>
<Text style={styles.linkText}>Go to home screen!</Text>
</Link>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
link: {
marginTop: 15,
paddingVertical: 15,
},
linkText: {
fontSize: 14,
color: '#2e78b7',
},
});

77
app/_layout.tsx Normal file
View File

@ -0,0 +1,77 @@
//This file serves as the app.tsx file (app)
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { SplashScreen, Stack } from 'expo-router';
import { useEffect } from 'react';
import { useColorScheme } from 'react-native';
import * as eva from '@eva-design/eva';
import { ApplicationProvider, Layout, Text } from '@ui-kitten/components';
import { appTheme } from "../theme";
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from 'expo-router';
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: '(tabs)',
};
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded, error] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
...FontAwesome.font,
});
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {
if (error) throw error;
}, [error]);
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
//This application provider comes from ui kitten implementation
<ApplicationProvider {...eva} theme={appTheme}>
<RootLayoutNav />
</ApplicationProvider>
);
}
function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
{/* Creating a stacklayout */}
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
</ThemeProvider>
);
}

10
app/index.tsx Normal file
View File

@ -0,0 +1,10 @@
//This file is needed so that the react native with expo can work
import { Redirect } from "expo-router";
const StartPage = () => {
return <Redirect href="/(tabs)/search_tab" />;
};
export default StartPage;

35
app/modal.tsx Normal file
View File

@ -0,0 +1,35 @@
import { StatusBar } from 'expo-status-bar';
import { Platform, StyleSheet } from 'react-native';
import SearchScreenInfo from '../components/SearchScreenInfo';
import { Text, View } from '../components/Themed';
export default function ModalScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Modal</Text>
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
<SearchScreenInfo path="app/modal.tsx" />
{/* Use a light status bar on iOS to account for the black space above the modal */}
<StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/images/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

10
babel.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
// Required for expo-router
'expo-router/babel',
],
};
};

View File

@ -0,0 +1,115 @@
// import React from 'react';
// import { StyleSheet } from 'react-native';
// import Colors from '../constants/Colors';
// import { ExternalLink } from './ExternalLink';
// import { MonoText } from './StyledText';
// import { Text, View } from './Themed';
// export default function AccountScreenInfo({ path }: { path: string }) {
// return (
// <View>
// <View style={styles.getStartedContainer}>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// Apartment Clone Account Screen:
// </Text>
// <View
// style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
// darkColor="rgba(255,255,255,0.05)"
// lightColor="rgba(0,0,0,0.05)">
// <MonoText>{path}</MonoText>
// </View>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// This is where your code will live.
// </Text>
// </View>
// <View style={styles.helpContainer}>
// <ExternalLink
// style={styles.helpLink}
// href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet">
// <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
// Tap here if your app doesn't automatically update after making changes
// </Text>
// </ExternalLink>
// </View>
// </View>
// );
// }
// const styles = StyleSheet.create({
// getStartedContainer: {
// alignItems: 'center',
// marginHorizontal: 50,
// },
// homeScreenFilename: {
// marginVertical: 7,
// },
// codeHighlightContainer: {
// borderRadius: 3,
// paddingHorizontal: 4,
// },
// getStartedText: {
// fontSize: 17,
// lineHeight: 24,
// textAlign: 'center',
// },
// helpContainer: {
// marginTop: 15,
// marginHorizontal: 20,
// alignItems: 'center',
// },
// helpLink: {
// paddingVertical: 15,
// },
// helpLinkText: {
// textAlign: 'center',
// },
// });
import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
import Screen from './Screen'
const AccountScreenInfo = () => {
return (
<Screen>
<Text style={styles.title}>
AccountScreenInfo component is being displayed here
</Text>
</Screen>
)
}
export default AccountScreenInfo
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'normal',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@ -0,0 +1,187 @@
import {
Animated,
StyleSheet,
LayoutChangeEvent,
View,
TouchableOpacity,
Platform,
FlatList,
} from 'react-native'
import React, { useState } from 'react'
import { HEADERHEIGHT, LISTMARGIN } from '../constants'
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { appTheme } from '../theme';
import { Button, Text } from '@ui-kitten/components';
import Row from './Row';
const AnimatedListHeader = ({scrollAnimation}:{scrollAnimation: Animated.Value}) => {
const [offsetAnimation] = useState(new Animated.Value(0));
const [clampedScroll, setClampedScroll] = useState(
Animated.diffClamp(
Animated.add(
scrollAnimation.interpolate({
inputRange: [0,1],
outputRange: [0,1],
extrapolateLeft: "clamp"
}),
offsetAnimation
),
0,
1
)
)
const navbarTranslate = clampedScroll.interpolate({
inputRange : [0, HEADERHEIGHT],
outputRange: [0, -HEADERHEIGHT],
extrapolate: "clamp",
});
const onLayout = (event: LayoutChangeEvent) => {
let { height } = event.nativeEvent.layout;
setClampedScroll(
Animated.diffClamp(
Animated.add(
scrollAnimation.interpolate({
inputRange: [0,1],
outputRange: [0,1],
extrapolateLeft: "clamp"
}),
offsetAnimation
),
0,
height
)
)
}
const filterButtons = [
{
iconName: "filter-variant",
onPress: () => console.log("filter all"),
},
{
label: "Price",
onPress: () => console.log("price"),
},
{
label: "Move In Date",
onPress: () => console.log("move in date"),
},
{
label: "Pets",
onPress: () => console.log("pets"),
},
];
return (
<Animated.View
style={{
position: "absolute",
top: 0,
right: 0,
left: 0,
zIndex: 1000,
height: HEADERHEIGHT,
backgroundColor: "#fff",
transform: [{ translateY: navbarTranslate }],
}}
onLayout={onLayout}
>
<View style={styles.AnimatedHeaderStyle}>
<TouchableOpacity style={styles.TouchableOpacityStyle} onPress={() => console.log("navigate to the input screen")}>
<Row style={styles.RowStyle}>
<MaterialCommunityIcons name="magnify" color={appTheme["color-primary-500"]} size={28} />
<Text style={styles.Text}>Find a Location</Text>
</Row>
</TouchableOpacity>
<FlatList
style={styles.FlatListStyle}
data={filterButtons}
horizontal
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => {
if (item.iconName) {
return <Button
appearance={'ghost'}
style={[styles.Searchbtn, {width: 48}]}
onPress={item.onPress}
accessoryLeft={
<MaterialCommunityIcons
name={item.iconName as any}
size={20}
color={appTheme["color-primary-500"]}
/>
}
>
</Button>
}
return <Button
appearance={'ghost'}
style={styles.Searchbtn}
onPress={item.onPress}
>
{item.label}
</Button>
}}
keyExtractor={(_, index) => index.toString()}
>
</FlatList>
</View>
</Animated.View>
)
}
export default AnimatedListHeader
const styles = StyleSheet.create({
AnimatedHeaderStyle: {
marginHorizontal: LISTMARGIN
},
TouchableOpacityStyle: {
marginTop: Platform.OS === "ios" ? 50 : 5,
borderWidth: 1,
borderColor: appTheme["search-border-default"],
borderRadius: 30,
padding: 10,
},
RowStyle: {
alignItems: "center"
},
Text: {
marginLeft: 10
},
Searchbtn: {
borderRadius: 30,
borderColor: appTheme["search-border-default"],
marginHorizontal: 3,
},
FlatListStyle: {
marginTop: 10
}
})

36
components/ButtonTest.tsx Normal file
View File

@ -0,0 +1,36 @@
import { StyleSheet, Text, Pressable } from 'react-native'
import React from 'react'
import { appTheme } from "../theme";
export default function ButtonTest(props: any) {
const { onPress, title = 'Save', style } = props;
return (
<Pressable style={style.button} onPress={onPress}>
<Text style={style.text}>
{title}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
paddingHorizontal: 32,
borderRadius: 4,
elevation: 3,
backgroundColor: appTheme["color-primary-500"],
},
text: {
fontSize: 16,
lineHeight: 21,
fontWeight: 'bold',
letterSpacing: 0.25,
color: 'white',
},
})

61
components/Card.tsx Normal file
View File

@ -0,0 +1,61 @@
import {
View,
Dimensions,
StyleSheet,
ViewStyle,
} from 'react-native';
import React from 'react';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text, Button } from "@ui-kitten/components";
import { appTheme } from "../theme";
import { Property } from '../types/property';
import ImageCarousel from './ImageCarousel';
import Row from './Row';
import CardInformation from './CardInformation';
const LISTMARGIN = 10;
const WIDTH = Dimensions.get("screen").width - LISTMARGIN * 2;
//each property listing is shown as a card, using this component
const Card = ({ property, style,}:{property: Property; style?: ViewStyle;}) => {
return (
<View style={{ marginVertical: 5 }}>
<ImageCarousel images={property.images}/>
{/* Everything under the image is wrapped in this view */}
<CardInformation property={property}/>
</View>
)
}
export default Card
const styles = StyleSheet.create({
button: {
width: "49%",
},
defaultMarginTop: {
marginTop: 5
},
rowJustification: {
justifyContent: "space-between"
},
informationContainer: {
paddingVertical: 10,
paddingHorizontal: 5,
borderColor: "#d3d3d3",
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
borderWidth: 1,
}
})

View File

@ -0,0 +1,132 @@
import {
View,
Dimensions,
StyleSheet,
} from 'react-native';
import React from 'react';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text, Button } from "@ui-kitten/components";
import { appTheme } from "../theme";
import { Property } from '../types/property';
import Row from './Row';
const LISTMARGIN = 10;
const WIDTH = Dimensions.get("screen").width - LISTMARGIN * 2;
const CardInformation = ({ property}:{property: Property}) => {
return (
<View
style={styles.informationContainer}>
{/* Property text data - Row component imported */}
<Row
style={styles.rowJustification}>
<Text category={"h6"}>
${property.rentLow.toLocaleString()} -{" "}
{property.rentHigh.toLocaleString()}
</Text>
<MaterialCommunityIcons
name="heart-outline"
color={appTheme["color-primary-500"]}
size={24}/>
</Row>
<Text category={"p1"}>
{property.bedroomLow.toLocaleString()} -{" "}
{property.bedroomHigh.toLocaleString()} Beds
</Text>
<Text category={"p1"} style={styles.defaultMarginTop}>
{property.name}
</Text>
<Text category={"p1"}>
{property.street}
</Text>
<Text category={"p1"}>
{property.city}, {property.state}, {property.zip}
</Text>
<Text category={"p1"} style={styles.defaultMarginTop}>
{property.tags.map((tag, index) =>
index === property.tags.length - 1 ? tag : tag + ", "
//looping (mapping) through the tags and comparing the index (i) or position of the loop to the number of propertys in the array. If its less than the length minus 1, we need to add a comma and a space after the word.
// ? means if true
// : means else
// expression after else could be simplified with `${tag}, `
)}
</Text>
{/* Button data - Row component imported */}
<Row
style={{
marginTop: 5,
justifyContent: "space-between"
}}>
<Button
appearance={"ghost"}
style={[
styles.button,
{ borderColor: appTheme["color-primary-500"]},
]}
size="medium"
onPress={() => console.log("email the property manager")}>
Email
</Button>
<Button
style={[
styles.button,
{ borderColor: appTheme["color-primary-500"]},
]}
size="medium"
onPress={() => console.log("call the property manager")}>
Call
</Button>
{/* Calling the buttontest component and passing it props */}
{/* <ButtonTest
onPress={() => console.log("testing the new button")}
title='Test'
style={{
}}
/> */}
</Row>
</View>
)
}
export default CardInformation
const styles = StyleSheet.create({
button: {
width: "49%",
},
defaultMarginTop: {
marginTop: 5
},
rowJustification: {
justifyContent: "space-between"
},
informationContainer: {
paddingVertical: 10,
paddingHorizontal: 5,
borderColor: "#d3d3d3",
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
borderWidth: 1,
}
})

View File

@ -0,0 +1,28 @@
import { Link } from 'expo-router';
import * as WebBrowser from 'expo-web-browser';
import React from 'react';
import { Platform } from 'react-native';
export function ExternalLink(
props: Omit<React.ComponentProps<typeof Link>, 'href'> & { href: string }
) {
return (
<Link
hrefAttrs={{
// On web, launch the link in a new tab.
target: '_blank',
}}
{...props}
// @ts-expect-error: External URLs are not typed.
href={props.href}
onPress={(e) => {
if (Platform.OS !== 'web') {
// Prevent the default behavior of linking to the default browser on native.
e.preventDefault();
// Open the link in an in-app browser.
WebBrowser.openBrowserAsync(props.href as string);
}
}}
/>
);
}

View File

@ -0,0 +1,113 @@
import { StyleSheet, FlatList, Pressable, Image } from 'react-native'
import React from 'react'
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { WIDTH } from '../constants';
import { useState, useRef } from 'react';
//we will be taking in an array of images as the argument
const ImageCarousel = ({ images }:{ images: string[] }) => {
const flatListRef = useRef<FlatList | null>(null)
const viewConfig = { viewAreaCoveragePercentThreshold: 95 };
const [activeIndex, setActiveIndex] = useState(0)
const onViewRef = useRef(({changed}: {changed: any}) => {
if (changed[0].isViewable) {
setActiveIndex(changed[0].index);
}
})
//handle the scrolling of images when the chevrons are pressed
const handlePressLeft = () => {
if (activeIndex === 0)
return flatListRef.current?.scrollToIndex({
animated: false,
index: images.length - 1,
});
flatListRef.current?.scrollToIndex({
index: activeIndex - 1,
});
};
const handlePressRight = () => {
if (activeIndex === images.length - 1)
return flatListRef.current?.scrollToIndex({
animated: false,
index: 0,
});
flatListRef.current?.scrollToIndex({
index: activeIndex + 1,
});
};
return (
<>
{/* Flatlist to scrolll through a single property image */}
<FlatList
style={{
flexGrow: 0
}}
ref={(ref) => (flatListRef.current = ref)}
data={images}
horizontal
showsHorizontalScrollIndicator={false}
snapToAlignment='center'
pagingEnabled
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfig}
renderItem={({ item }) => (
<Image
source={{ uri: item }}
style={ styles.image }
/>
)}
keyExtractor={(item) => item}
/>
{/* Chevrons */}
<Pressable
style={[
styles.chevron,
{left: 5}
]}
onPress={handlePressLeft}
>
<MaterialCommunityIcons
name="chevron-left"
color="white"
size={45}
/>
</Pressable>
<Pressable
style={[
styles.chevron,
{right: 5}
]}
onPress={handlePressRight}
>
<MaterialCommunityIcons
name="chevron-right"
color="white"
size={45}
/>
</Pressable>
</>
)
}
export default ImageCarousel
const styles = StyleSheet.create({
image: {
height: 225,
width: WIDTH,
borderTopLeftRadius: 5,
borderTopRightRadius: 5
},
chevron: {
position: "absolute",
top: 95
}
})

23
components/Row.tsx Normal file
View File

@ -0,0 +1,23 @@
import { StyleSheet, View, ViewStyle } from 'react-native'
import React from 'react'
const Row = ({children, style}:{children: any, style: ViewStyle}) => {
return (
<View style={[styles.container, style]}>
{children}
</View>
)
}
export default Row
const styles = StyleSheet.create({
container: {
flexDirection: "row",
}
})

View File

@ -0,0 +1,114 @@
// import React from 'react';
// import { StyleSheet } from 'react-native';
// import Colors from '../constants/Colors';
// import { ExternalLink } from './ExternalLink';
// import { MonoText } from './StyledText';
// import { Text, View } from './Themed';
// export default function SavedScreenInfo({ path }: { path: string }) {
// return (
// <View>
// <View style={styles.getStartedContainer}>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// Apartment Clone Saved Screen:
// </Text>
// <View
// style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
// darkColor="rgba(255,255,255,0.05)"
// lightColor="rgba(0,0,0,0.05)">
// <MonoText>{path}</MonoText>
// </View>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// This is where your code will live.
// </Text>
// </View>
// <View style={styles.helpContainer}>
// <ExternalLink
// style={styles.helpLink}
// href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet">
// <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
// Tap here if your app doesn't automatically update after making changes
// </Text>
// </ExternalLink>
// </View>
// </View>
// );
// }
// const styles = StyleSheet.create({
// getStartedContainer: {
// alignItems: 'center',
// marginHorizontal: 50,
// },
// homeScreenFilename: {
// marginVertical: 7,
// },
// codeHighlightContainer: {
// borderRadius: 3,
// paddingHorizontal: 4,
// },
// getStartedText: {
// fontSize: 17,
// lineHeight: 24,
// textAlign: 'center',
// },
// helpContainer: {
// marginTop: 15,
// marginHorizontal: 20,
// alignItems: 'center',
// },
// helpLink: {
// paddingVertical: 15,
// },
// helpLinkText: {
// textAlign: 'center',
// },
// });
import { View, Text, StyleSheet } from 'react-native'
import React from 'react'
import Screen from './Screen'
const SavedScreenInfo = () => {
return (
<Screen>
<Text style={styles.title}>
SavedScreenInfo component is being displayed here
</Text>
</Screen>
)
}
export default SavedScreenInfo
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'normal',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

44
components/Screen.tsx Normal file
View File

@ -0,0 +1,44 @@
// To address the issue of text being above the staus bar
import {
SafeAreaView,
StyleSheet,
ViewStyle,
Platform,
StatusBar } from 'react-native'
import React from 'react'
const Screen = ({
children, //taking children as props
style
}:{
children: any; //returning children as any props
style?: ViewStyle;
}) => {
return (
<SafeAreaView
style={[styles.container, style]} //passing multiple styles to an array. We are using our own styles at bottom but adding this props in case we want to pass in specific styles
>
<StatusBar barStyle={"dark-content"}/>
{children}
</SafeAreaView>
);
};
export default Screen
const styles = StyleSheet.create({
container: {
flex: 1,
//if the operating system is android, we are going to give it a padding top, if not, set the height to zero.
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0,
},
});

View File

@ -0,0 +1,150 @@
import {
Animated,
} from 'react-native';
import Screen from './Screen';
import { HEADERHEIGHT, LISTMARGIN } from '../constants';
import Card from './Card';
import React from 'react';
import { useState } from 'react'
import AnimatedListHeader from './AnimatedListHeader';
const SearchScreenInfo = () => {
const properties = [{
id: 1,
images: [
"https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"https://images.pexels.com/photos/8031889/pexels-photo-8031889.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
],
rentLow: 3750,
rentHigh: 31054,
bedroomLow: 1,
bedroomHigh: 5,
name: "The Hamilton",
street: "555 NE 34th St",
city: "Miami",
state: "Florida",
zip: 33137,
tags: ["Parking"],
},
{
id: 2,
images: [
"https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"https://images.pexels.com/photos/8031889/pexels-photo-8031889.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
],
rentLow: 3750,
rentHigh: 31054,
bedroomLow: 1,
bedroomHigh: 5,
name: "The Hamilton",
street: "555 NE 34th St",
city: "Miami",
state: "Florida",
zip: 33137,
tags: ["Parking"],
},
{
id: 3,
images: [
"https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"https://images.pexels.com/photos/8031889/pexels-photo-8031889.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
],
rentLow: 3750,
rentHigh: 31054,
bedroomLow: 1,
bedroomHigh: 5,
name: "The Hamilton",
street: "555 NE 34th St",
city: "Miami",
state: "Florida",
zip: 33137,
tags: ["Parking"],
},
{
id: 4,
images: [
"https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"https://images.pexels.com/photos/8031889/pexels-photo-8031889.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
],
rentLow: 3750,
rentHigh: 31054,
bedroomLow: 1,
bedroomHigh: 5,
name: "The Hamilton",
street: "555 NE 34th St",
city: "Miami",
state: "Florida",
zip: 33137,
tags: ["Parking"],
},
{
id: 5,
images: [
"https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"https://images.pexels.com/photos/8031889/pexels-photo-8031889.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
],
rentLow: 3750,
rentHigh: 31054,
bedroomLow: 1,
bedroomHigh: 5,
name: "The Hamilton",
street: "555 NE 34th St",
city: "Miami",
state: "Florida",
zip: 33137,
tags: ["Parking"],
},
];
const [scrollAnimation] = useState(new Animated.Value(0));
return (
<Screen>
<AnimatedListHeader scrollAnimation={scrollAnimation} />
{/* Main FlatList to handle scrolling to view the properties listing */}
<Animated.FlatList
//Code to animate the header
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: scrollAnimation
},
},
},
],
{useNativeDriver: true}
)}
contentContainerStyle={{paddingTop: HEADERHEIGHT - 20}}
bounces={false}
scrollEventThrottle={16}
data={properties}
keyExtractor={(item) => item.id.toString()}
showsVerticalScrollIndicator={false}
style={{ marginHorizontal: LISTMARGIN }}
renderItem={({item}) => (
<Card property={item}/>
)}
/>
</Screen>
)
}
export default SearchScreenInfo

View File

@ -0,0 +1,5 @@
import { Text, TextProps } from './Themed';
export function MonoText(props: TextProps) {
return <Text {...props} style={[props.style, { fontFamily: 'SpaceMono' }]} />;
}

44
components/Themed.tsx Normal file
View File

@ -0,0 +1,44 @@
/**
* Learn more about Light and Dark modes:
* https://docs.expo.io/guides/color-schemes/
*/
import { Text as DefaultText, useColorScheme, View as DefaultView } from 'react-native';
import Colors from '../constants/Colors';
type ThemeProps = {
lightColor?: string;
darkColor?: string;
};
export type TextProps = ThemeProps & DefaultText['props'];
export type ViewProps = ThemeProps & DefaultView['props'];
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
const theme = useColorScheme() ?? 'light';
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
export function Text(props: TextProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
return <DefaultText style={[{ color }, style]} {...otherProps} />;
}
export function View(props: ViewProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
}

View File

@ -0,0 +1,10 @@
import * as React from 'react';
import renderer from 'react-test-renderer';
import { MonoText } from '../StyledText';
it(`renders correctly`, () => {
const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
expect(tree).toMatchSnapshot();
});

14
constants.ts Normal file
View File

@ -0,0 +1,14 @@
import { Dimensions, Platform, StatusBar } from "react-native";
export const LISTMARGIN = 10;
export const WIDTH = Dimensions.get("screen").width - LISTMARGIN * 2;
const baseHeight = 160;
const isoNotch = 40;
const isoHeight = baseHeight + isoNotch;
let androidHeight = baseHeight;
let androidNotch = 0;
if (StatusBar.currentHeight) androidNotch = StatusBar.currentHeight;
androidHeight += androidNotch;
export const HEADERHEIGHT = Platform.OS === "ios" ? isoHeight : androidHeight;

19
constants/Colors.ts Normal file
View File

@ -0,0 +1,19 @@
const tintColorLight = '#2f95dc';
const tintColorDark = '#fff';
export default {
light: {
text: '#000',
background: '#fff',
tint: tintColorLight,
tabIconDefault: '#ccc',
tabIconSelected: tintColorLight,
},
dark: {
text: '#fff',
background: '#000',
tint: tintColorDark,
tabIconDefault: '#ccc',
tabIconSelected: tintColorDark,
},
};

10
metro.config.js Normal file
View File

@ -0,0 +1,10 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
// [Web-only]: Enables CSS support in Metro.
isCSSEnabled: true,
});
module.exports = config;

30857
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "apartment-clone",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest --watchAll"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"@eva-design/eva": "^2.2.0",
"@expo/vector-icons": "^13.0.0",
"@react-navigation/native": "^6.0.2",
"@ui-kitten/components": "^5.3.1",
"expo": "~49.0.15",
"expo-font": "~11.4.0",
"expo-linking": "~5.0.2",
"expo-router": "^2.0.0",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0",
"expo-system-ui": "~2.4.0",
"expo-web-browser": "~12.3.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.6",
"react-native-gesture-handler": "~2.12.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-web": "~0.19.6"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.14",
"jest": "^29.2.1",
"jest-expo": "~49.0.0",
"react-test-renderer": "18.2.0",
"typescript": "^5.1.3"
},
"overrides": {
"react-refresh": "~0.14.0"
},
"resolutions": {
"react-refresh": "~0.14.0"
},
"private": true
}

52
theme.ts Normal file
View File

@ -0,0 +1,52 @@
import { light} from '@eva-design/eva';
export const appTheme = {
...light,
"color-primary-100": "#EFFBDB",
"color-primary-200": "#DDF8B9",
"color-primary-300": "#BEEA91",
"color-primary-400": "#9DD56F",
"color-primary-500": "#72B944",
"color-primary-600": "#569F31",
"color-primary-700": "#3D8522",
"color-primary-800": "#286B15",
"color-primary-900": "#18580D",
"color-success-100": "#CEFDD5",
"color-success-200": "#9DFBB5",
"color-success-300": "#6CF59C",
"color-success-400": "#46EC91",
"color-success-500": "#0FE082",
"color-success-600": "#0AC081",
"color-success-700": "#07A17A",
"color-success-800": "#04816F",
"color-success-900": "#026B66",
"color-info-100": "#CBF7FD",
"color-info-200": "#98E9FB",
"color-info-300": "#64D2F5",
"color-info-400": "#3EB6EC",
"color-info-500": "#048FE0",
"color-info-600": "#026FC0",
"color-info-700": "#0253A1",
"color-info-800": "#013A81",
"color-info-900": "#00296B",
"color-warning-100": "#FEFDD0",
"color-warning-200": "#FDFBA1",
"color-warning-300": "#FAF672",
"color-warning-400": "#F5F04F",
"color-warning-500": "#EFE817",
"color-warning-600": "#CDC610",
"color-warning-700": "#ACA50B",
"color-warning-800": "#8A8507",
"color-warning-900": "#726D04",
"color-danger-100": "#FFEAD6",
"color-danger-200": "#FFCFAE",
"color-danger-300": "#FFAE85",
"color-danger-400": "#FF8E67",
"color-danger-500": "#FF5A35",
"color-danger-600": "#DB3A26",
"color-danger-700": "#B71F1A",
"color-danger-800": "#931016",
"color-danger-900": "#7A0A17",
"search-border-default": "#d3d3d3",
};

12
tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
]
}

14
types/property.ts Normal file
View File

@ -0,0 +1,14 @@
export type Property = {
id: number;
images: string[];
rentLow: number;
rentHigh: number;
bedroomLow: number;
bedroomHigh: number;
name: string;
street: string;
city: string;
state: string;
zip: number;
tags: string[];
}