Compare commits
10 commits
6d485b374f
...
84d6bf8dcd
Author | SHA1 | Date | |
---|---|---|---|
84d6bf8dcd | |||
9124dc99ed | |||
e998cb829e | |||
ea96109874 | |||
bda2c41772 | |||
0fdb862a30 | |||
e4e1c7fda2 | |||
39854f65ef | |||
f58b86c204 | |||
a179edeee5 |
|
@ -1,50 +1,9 @@
|
||||||
# Welcome to your Expo app 👋
|
# Donut Shop Application with React Native
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
This project is a donut shop mobile application, where users can search for the list of donuts of your choice, and add to you favorite list. Users can also place the order and get a receipt through this mobile application.
|
||||||
|
|
||||||
## Get started
|
## To start the project
|
||||||
|
* Download [Expo Go](https://expo.dev/go) on your device to launch the project.
|
||||||
1. Install dependencies
|
* To start the project, run this command in your terminal `npx expo start --tunnel`
|
||||||
|
* Scan the QR code that shows in your terminal.
|
||||||
```bash
|
* For more information about Tunneling, please read [Expo](https://docs.expo.dev/more/expo-cli/#tunneling).
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the app
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
|
||||||
|
|
||||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
||||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
||||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
||||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
||||||
|
|
||||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
|
||||||
|
|
||||||
## Get a fresh project
|
|
||||||
|
|
||||||
When you're ready, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run reset-project
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
|
||||||
|
|
||||||
## Learn more
|
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
||||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
||||||
|
|
||||||
## Join the community
|
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|
|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
59
donutshop/app/components/HomeCardItem.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { StyleSheet, Text, View, Pressable, Image} from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const HomeCardItem = () => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{padding: 5}}>
|
||||||
|
<View style={{shadowColor: '#B4B4B8', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.5, shadowRadius: 1,}}>
|
||||||
|
<Pressable>
|
||||||
|
<View style={{borderColor: '#EEEEEE', borderWidth: 1, borderRadius: 5}}>
|
||||||
|
<Image source={require('../../assets/images/menu/coming_soon.png')} style={{width: '100%', height: 200, borderTopLeftRadius: 5, borderTopRightRadius: 5}}/>
|
||||||
|
<View style={{padding: 10}}>
|
||||||
|
<Text style={{fontSize: 16, fontWeight: 600, letterSpacing: 0.5, marginBottom: 10}}>Pokémon Donuts<Text style={{fontSize: 12, color: '#DA7297'}}>(Coming soon)</Text></Text>
|
||||||
|
<Text>The limited-edition Pokémon Donuts is coming soon!</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{marginTop: 10, shadowColor: '#B4B4B8', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.5, shadowRadius: 1,}}>
|
||||||
|
<Pressable onPress={() => navigation.navigate('menu')}>
|
||||||
|
<View style={{borderColor: '#EEEEEE', borderWidth: 1, borderRadius: 5}}>
|
||||||
|
<Image source={require('../../assets/images/menu/new_special.jpg')} style={{width: '100%', height: 200, borderTopLeftRadius: 5, borderTopRightRadius: 5}}/>
|
||||||
|
<View style={{padding: 10}}>
|
||||||
|
<Text style={{fontSize: 16, fontWeight: 600, letterSpacing: 0.5, marginBottom: 10}}>Mothers Day Box for your mama</Text>
|
||||||
|
<Text>Lets show our appreciation for our moms by treating them with the special Mothers Day Box!</Text>
|
||||||
|
<Pressable onPress={() => navigation.navigate('menu')} style={{marginTop: 10, padding: 10, backgroundColor: '#DA7297', width: 100, borderRadius: 5, alignItems: 'center'}}>
|
||||||
|
<Text style={{color: 'white', fontWeight: 500}}>Order Now</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{marginTop: 10, shadowColor: '#B4B4B8', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.5, shadowRadius: 1,}}>
|
||||||
|
<Pressable onPress={() => navigation.navigate('menu')}>
|
||||||
|
<View style={{borderColor: '#EEEEEE', borderWidth: 1, borderRadius: 5}}>
|
||||||
|
<Image source={require('../../assets/images/menu/new_drink.jpg')} style={{width: '100%', height: 200, borderTopLeftRadius: 5, borderTopRightRadius: 5}}/>
|
||||||
|
<View style={{padding: 10}}>
|
||||||
|
<Text style={{fontSize: 16, fontWeight: 600, letterSpacing: 0.5, marginBottom: 10}}>Colorful Smoothies are here</Text>
|
||||||
|
<Text>The colors are not due to artificial colorings, but rather from natural fruits and vegetables</Text>
|
||||||
|
<Pressable onPress={() => navigation.navigate('menu')} style={{marginTop: 10, padding: 10, backgroundColor: '#DA7297', width: 100, borderRadius: 5, alignItems: 'center'}}>
|
||||||
|
<Text style={{color: 'white', fontWeight: 500}}>Order Now</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeCardItem
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
58
donutshop/app/components/OrderReceiptCard.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
|
import React from 'react';
|
||||||
|
import OrderReceiptItem from '../components/OrderReceiptItem';
|
||||||
|
|
||||||
|
|
||||||
|
interface OrderHistoryCardProps {
|
||||||
|
navigationHandler: any;
|
||||||
|
CartList: any;
|
||||||
|
CartListPrice: string;
|
||||||
|
OrderDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderReceiptCard: React.FC<OrderHistoryCardProps> = ({
|
||||||
|
navigationHandler,
|
||||||
|
CartList,
|
||||||
|
CartListPrice,
|
||||||
|
OrderDate,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{borderColor: 'gray', borderWidth:1, marginBottom: 10}}>
|
||||||
|
<View style={{justifyContent: 'flex-end', padding: 10, backgroundColor: '#EEEEEE'}}>
|
||||||
|
<Text style={{fontWeight: 600}}>{OrderDate}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{gap: 10}}>
|
||||||
|
<View style={{borderColor: 'black', borderWidth: 0.5, borderStyle: 'dashed'}}/>
|
||||||
|
{CartList.map((data: any, index: any) => (
|
||||||
|
<Pressable key={index.toString() + data.id} onPress={() => {
|
||||||
|
navigationHandler({
|
||||||
|
index: data.index,
|
||||||
|
id: data.id,
|
||||||
|
type: data.type,
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<OrderReceiptItem
|
||||||
|
type={data.type}
|
||||||
|
name={data.name}
|
||||||
|
donutname={data.donutname}
|
||||||
|
image_item={data.image_item}
|
||||||
|
prices={data.prices}
|
||||||
|
ItemPrice={data.ItemPrice}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View style={{borderColor: 'black', borderWidth: 0.5, borderStyle: 'dashed'}}/>
|
||||||
|
<View style={{flexDirection: 'row', justifyContent: 'space-between', gap: 20, alignItems: 'center', padding: 10}}>
|
||||||
|
<Text style={{fontWeight: 600}}>Total Amount</Text>
|
||||||
|
<Text style={{fontWeight: 600}}>$ {CartListPrice}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderReceiptCard
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
53
donutshop/app/components/OrderReceiptItem.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { Image, ImageProps, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
interface OrderReceiptItemProps{
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
donutname: string;
|
||||||
|
image_item: ImageProps;
|
||||||
|
prices: any;
|
||||||
|
ItemPrice: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const OrderReceiptItem: React.FC<OrderReceiptItemProps> = ({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
donutname,
|
||||||
|
image_item,
|
||||||
|
prices,
|
||||||
|
ItemPrice,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={{paddingHorizontal: 10, marginBottom: 10}}>
|
||||||
|
<View style={{flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'}}>
|
||||||
|
<View style={{flexDirection: 'row', gap: 10, alignItems: 'center'}}>
|
||||||
|
<Image source={image_item} style={{width: 50, height: 50}} />
|
||||||
|
{prices.map((item: any, index: any) => (
|
||||||
|
<View key={index.toString()}>
|
||||||
|
<Text style={{width: 200}}>{name || donutname} <Text style={{fontWeight: 600}}>{item.size}</Text></Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<Text>${ItemPrice}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{prices.map((data: any, index: any) => (
|
||||||
|
<View key={index.toString()} style={{flex: 1, alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between', marginTop: 10}}>
|
||||||
|
<View style={{flex:1, flexDirection: 'row', gap: 10}}>
|
||||||
|
<Text>${data.price}</Text>
|
||||||
|
<Text>X {data.quantity}</Text>
|
||||||
|
</View>
|
||||||
|
<Text>${(data.quantity * data.price).toFixed(2).toString()}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderReceiptItem
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
|
@ -10,7 +10,7 @@ import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
const Detail = ({navigation, route}: any) => {
|
const Detail = ({navigation, route}: any) => {
|
||||||
const ItemOfIndex = useStore((state: any) =>
|
const ItemOfIndex = useStore((state: any) =>
|
||||||
route.params.type == 'Donut' ? state.AllDonutList2 : state.AllDrinkList2,
|
route.params.type == 'Donut' ? state.AllDonutMenu : state.AllDrinkMenu,
|
||||||
)[route.params.index];
|
)[route.params.index];
|
||||||
|
|
||||||
const addToFavoriteList = useStore((state: any) => state.addToFavoriteList);
|
const addToFavoriteList = useStore((state: any) => state.addToFavoriteList);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import PaymentFooter from "../components/PaymentFooter";
|
||||||
|
|
||||||
const DetailDrink = ({navigation, route}: any) => {
|
const DetailDrink = ({navigation, route}: any) => {
|
||||||
const ItemOfIndex = useStore((state: any) =>
|
const ItemOfIndex = useStore((state: any) =>
|
||||||
route.params.type == 'Donut' ? state.AllDonutList2 : state.AllDrinkList2,
|
route.params.type == 'Donut' ? state.AllDonutMenu : state.AllDrinkMenu,
|
||||||
)[route.params.index];
|
)[route.params.index];
|
||||||
|
|
||||||
const addToFavoriteList = useStore((state: any) => state.addToFavoriteList);
|
const addToFavoriteList = useStore((state: any) => state.addToFavoriteList);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { View, Text, ScrollView, StyleSheet } from 'react-native'
|
import { View, Text, ScrollView, StyleSheet, Image, Pressable } from 'react-native'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import HomeHeader from '../components/HomeHeader'
|
import HomeHeader from '../components/HomeHeader'
|
||||||
|
import HomeCardItem from '../components/HomeCardItem'
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
@ -10,6 +11,7 @@ const Home = () => {
|
||||||
<StatusBar style="dark"/>
|
<StatusBar style="dark"/>
|
||||||
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.ScrollViewFlex}>
|
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.ScrollViewFlex}>
|
||||||
<HomeHeader />
|
<HomeHeader />
|
||||||
|
<HomeCardItem />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { View, Text, ScrollView, StyleSheet, Pressable, Dimensions, ToastAndroid} from 'react-native'
|
import { View, Text, ScrollView, StyleSheet, Pressable, Dimensions} from 'react-native'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useStore } from '../store/store';
|
import { useStore } from '../store/store';
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||||
|
@ -32,8 +32,8 @@ const getDonutList = (category: string, data: any) => {
|
||||||
|
|
||||||
|
|
||||||
const Menu = ({navigation}: any) => {
|
const Menu = ({navigation}: any) => {
|
||||||
const DonutList = useStore((state: any) => state.AllDonutList2);
|
const DonutList = useStore((state: any) => state.AllDonutMenu);
|
||||||
const DrinkList = useStore((state: any) => state.AllDrinkList2);
|
const DrinkList = useStore((state: any) => state.AllDrinkMenu);
|
||||||
|
|
||||||
const [categories, setCategories] = useState(getCategoriesFromData(DonutList));
|
const [categories, setCategories] = useState(getCategoriesFromData(DonutList));
|
||||||
const [searchMenu, setSearchMenu] = useState('');
|
const [searchMenu, setSearchMenu] = useState('');
|
||||||
|
|
|
@ -1,10 +1,46 @@
|
||||||
import { View, Text } from 'react-native'
|
import { View, Text, ScrollView, Image } from 'react-native';
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
|
import { useStore } from '../store/store';
|
||||||
|
import OrderReceiptCard from '../components/OrderReceiptCard';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const OrderReceipt = ({navigation}: any) => {
|
||||||
|
const ReceiptList = useStore((state: any) => state.ReceiptList);
|
||||||
|
const navigationHandler = ({index, id, type}: any) => {
|
||||||
|
navigation.push('detail', {
|
||||||
|
index,
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const OrderReceipt = () => {
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={{backgroundColor: 'white', flex: 1}}>
|
||||||
<Text>OrderHistory</Text>
|
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{flexGrow: 1, marginTop: 20, paddingHorizontal: 20, paddingVertical: 10}}>
|
||||||
|
<View style={{flex: 1, justifyContent: 'space-between'}}>
|
||||||
|
<Text style={{fontSize: 30, fontWeight: 500}}>Receipt</Text>
|
||||||
|
{ReceiptList.length == 0 ? (
|
||||||
|
<View>
|
||||||
|
<Image source={require('../../assets/images/misc/receipt.png')} style={{ width: 200, height: 200, resizeMode: "contain"}} />
|
||||||
|
</View>
|
||||||
|
):(
|
||||||
|
<View style={{marginTop: 20}}>
|
||||||
|
{ReceiptList.map((data: any, index: any) => (
|
||||||
|
<OrderReceiptCard
|
||||||
|
key={index.toString()}
|
||||||
|
navigationHandler={navigationHandler}
|
||||||
|
CartList={data.CartList}
|
||||||
|
CartListPrice={data.CartListPrice}
|
||||||
|
OrderDate={data.OrderDate}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||||
import PaymentOption from "../components/PaymentOption";
|
import PaymentOption from "../components/PaymentOption";
|
||||||
import PaymentFooter from "../components/PaymentFooter";
|
import PaymentFooter from "../components/PaymentFooter";
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import { useStore } from '../store/store';
|
||||||
|
|
||||||
|
|
||||||
const PaymentOptionList = [
|
const PaymentOptionList = [
|
||||||
|
@ -29,10 +30,34 @@ const PaymentOptionList = [
|
||||||
|
|
||||||
|
|
||||||
const Payment = ({navigation, route}: any) => {
|
const Payment = ({navigation, route}: any) => {
|
||||||
|
const calculateCartPrice = useStore((state: any) => state.calculateCartPrice);
|
||||||
|
const addToReceiptList = useStore(((state: any) => state.addToReceiptList));
|
||||||
|
|
||||||
const [paymentMethod, setPaymentMenthod] = useState('Credit card');
|
const [paymentMethod, setPaymentMenthod] = useState('Credit card');
|
||||||
|
const [showApprove, setShowApprove] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const buttonPressHandler = () => {
|
||||||
|
setShowApprove(true);
|
||||||
|
addToReceiptList();
|
||||||
|
calculateCartPrice();
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowApprove(false);
|
||||||
|
navigation.navigate('receipt')
|
||||||
|
}, 3000)
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex:1, backgroundColor: 'white'}}>
|
<View style={{flex:1, backgroundColor: 'white'}}>
|
||||||
|
{showApprove ? (
|
||||||
|
<View style={{flex: 1, position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, zIndex: 1000, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Image source={require('../../assets/images/misc/check.png')} style={{width: 100, height: 100}} />
|
||||||
|
<Text style={{marginTop: 20, marginBottom: 10, fontSize: 26, color: '#88D66C'}}>Approved!</Text>
|
||||||
|
<Text style={{fontSize: 20, color: '#88D66C'}}>Thank you for your order!</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{flexGrow: 1}}>
|
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{flexGrow: 1}}>
|
||||||
<View style={{marginTop: 20, paddingHorizontal: 20, paddingVertical: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between'}}>
|
<View style={{marginTop: 20, paddingHorizontal: 20, paddingVertical: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between'}}>
|
||||||
<Pressable onPress={() => {navigation.pop()}}>
|
<Pressable onPress={() => {navigation.pop()}}>
|
||||||
|
@ -71,7 +96,7 @@ const Payment = ({navigation, route}: any) => {
|
||||||
<PaymentFooter
|
<PaymentFooter
|
||||||
buttonTitle={`Pay with ${paymentMethod}`}
|
buttonTitle={`Pay with ${paymentMethod}`}
|
||||||
price={{price: route.params.amount, currency: '$'}}
|
price={{price: route.params.amount, currency: '$'}}
|
||||||
buttonPressHandler={() => {}}
|
buttonPressHandler={buttonPressHandler}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,8 +8,8 @@ import DrinkData from "../data/DrinkData";
|
||||||
export const useStore = create(
|
export const useStore = create(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
AllDonutList1: DonutData,
|
AllDonutMenu: DonutData,
|
||||||
AllDrinkList: DrinkData,
|
AllDrinkMenu: DrinkData,
|
||||||
CartPrice: 0,
|
CartPrice: 0,
|
||||||
FavoriteList: [],
|
FavoriteList: [],
|
||||||
CartList: [],
|
CartList: [],
|
||||||
|
@ -68,25 +68,25 @@ export const useStore = create(
|
||||||
set(
|
set(
|
||||||
produce(state => {
|
produce(state => {
|
||||||
if(type == 'Donut'){
|
if(type == 'Donut'){
|
||||||
for(let i = 0; i < state.AllDonutList1.length; i++){
|
for(let i = 0; i < state.AllDonutMenu.length; i++){
|
||||||
if(state.AllDonutList1[i].id == id){
|
if(state.AllDonutMenu[i].id == id){
|
||||||
if(state.AllDonutList1[i].favourite == false){
|
if(state.AllDonutMenu[i].favourite == false){
|
||||||
state.AllDonutList1[i].favourite = true;
|
state.AllDonutMenu[i].favourite = true;
|
||||||
state.FavoriteList.unshift(state.AllDonutList1[i]);
|
state.FavoriteList.unshift(state.AllDonutMenu[i]);
|
||||||
} else{
|
} else{
|
||||||
state.AllDonutList1[i].favourite = false;
|
state.AllDonutMenu[i].favourite = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(type == 'Drink'){
|
} else if(type == 'Drink'){
|
||||||
for(let i = 0; i< state.AllDrinkList.length; i++){
|
for(let i = 0; i < state.AllDrinkMenu.length; i++){
|
||||||
if(state.AllDrinkList[i].id == id){
|
if(state.AllDrinkMenu[i].id == id){
|
||||||
if(state.AllDrinkList[i].favourite == false){
|
if(state.AllDrinkMenu[i].favourite == false){
|
||||||
state.AllDrinkList[i].favourite = true;
|
state.AllDrinkMenu[i].favourite = true;
|
||||||
state.FavoriteList.unshift(state.AllDrinkList[i]);
|
state.FavoriteList.unshift(state.AllDrinkMenu[i]);
|
||||||
} else{
|
} else{
|
||||||
state.AllDrinkList[i].favourite = false;
|
state.AllDrinkMenu[i].favourite = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -98,23 +98,23 @@ export const useStore = create(
|
||||||
set(
|
set(
|
||||||
produce(state => {
|
produce(state => {
|
||||||
if(type = 'Donut'){
|
if(type = 'Donut'){
|
||||||
for(let i = 0; i < state.AllDonutList1.length; i++){
|
for(let i = 0; i < state.AllDonutMenu.length; i++){
|
||||||
if(state.AllDonutList1[i].id == id){
|
if(state.AllDonutMenu[i].id == id){
|
||||||
if(state.AllDonutList1[i].favourite == true){
|
if(state.AllDonutMenu[i].favourite == true){
|
||||||
state.AllDonutList1[i].favourite = false;
|
state.AllDonutMenu[i].favourite = false;
|
||||||
} else{
|
} else{
|
||||||
state.AllDonutList1[i].favourite = true;
|
state.AllDonutMenu[i].favourite = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(type = 'Drink'){
|
} else if(type = 'Drink'){
|
||||||
for(let i = 0; i < state.AllDrinkList.length; i++){
|
for(let i = 0; i < state.AllDrinkMenu.length; i++){
|
||||||
if(state.AllDrinkList[i].id == id){
|
if(state.AllDrinkMenu[i].id == id){
|
||||||
if(state.AllDrinkList[i].favourite == true){
|
if(state.AllDrinkMenu[i].favourite == true){
|
||||||
state.AllDrinkList[i].favourite = false;
|
state.AllDrinkMenu[i].favourite = false;
|
||||||
} else{
|
} else{
|
||||||
state.AllDrinkList[i].favourite = true;
|
state.AllDrinkMenu[i].favourite = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,72 @@ export const useStore = create(
|
||||||
state.FavoriteList.splice(spliceIndex, 1);
|
state.FavoriteList.splice(spliceIndex, 1);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
addCartItemQuantity:(id: string, size: string) =>
|
||||||
|
set(
|
||||||
|
produce(state => {
|
||||||
|
for(let i = 0; i < state.CartList.length; i++){
|
||||||
|
if(state.CartList[i].id == id){
|
||||||
|
for (let j = 0; j < state.CartList[i].prices.length; j++) {
|
||||||
|
if (state.CartList[i].prices[j].size == size) {
|
||||||
|
state.CartList[i].prices[j].quantity++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
removeCartItemQuantity:(id: string, size: string) =>
|
||||||
|
set(
|
||||||
|
produce(state => {
|
||||||
|
for (let i = 0; i < state.CartList.length; i++) {
|
||||||
|
if(state.CartList[i].id == id){
|
||||||
|
for (let j = 0; j < state.CartList[i].prices.length; j++) {
|
||||||
|
if (state.CartList[i].prices[j].size == size) {
|
||||||
|
if(state.CartList[i].prices.length > 1){
|
||||||
|
if (state.CartList[i].prices[j].quantity > 1) {
|
||||||
|
state.CartList[i].prices[j].quantity--;
|
||||||
|
} else{
|
||||||
|
state.CartList[i].prices.splice(j, 1);
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
if (state.CartList[i].prices[j].quantity > 1) {
|
||||||
|
state.CartList[i].prices[j].quantity--;
|
||||||
|
} else {
|
||||||
|
state.CartList.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
addToReceiptList:() =>
|
||||||
|
set(
|
||||||
|
produce(state => {
|
||||||
|
let temp = state.CartList.reduce(
|
||||||
|
(accumulator: number, currentValue: any) =>
|
||||||
|
accumulator + parseFloat(currentValue.ItemPrice),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if(state.ReceiptList.length > 0){
|
||||||
|
state.ReceiptList.unshift({
|
||||||
|
OrderDate: new Date().toDateString() + ' ' + new Date().toLocaleTimeString(),
|
||||||
|
CartList: state.CartList,
|
||||||
|
CartListPrice: temp.toFixed(2).toString(),
|
||||||
|
})
|
||||||
|
} else{
|
||||||
|
state.ReceiptList.push({
|
||||||
|
OrderDate: new Date().toDateString() + ' ' + new Date().toLocaleTimeString(),
|
||||||
|
CartList: state.CartList,
|
||||||
|
CartListPrice: temp.toFixed(2).toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state.CartList = [];
|
||||||
|
}),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'donutshop',
|
name: 'donutshop',
|
||||||
|
|
23
ev_station/.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
node_modules/
|
||||||
|
.expo/
|
||||||
|
dist/
|
||||||
|
npm-debug.*
|
||||||
|
*.jks
|
||||||
|
*.p8
|
||||||
|
*.p12
|
||||||
|
*.key
|
||||||
|
*.mobileprovision
|
||||||
|
*.orig.*
|
||||||
|
web-build/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||||
|
# The following patterns were generated by expo-cli
|
||||||
|
|
||||||
|
expo-env.d.ts
|
||||||
|
# @end expo-cli
|
||||||
|
|
||||||
|
.env
|
||||||
|
app/misc/FirebaseConfig.js
|
9
ev_station/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# EV Charging Station Finder with React Native
|
||||||
|
|
||||||
|
EV Charging Station Finder is mobile application where users can search for the nearest EV charging station, save their list, and start their route to the picked EV station.
|
||||||
|
|
||||||
|
## To start the project
|
||||||
|
* Download [Expo Go](https://expo.dev/go) on your device to launch the project.
|
||||||
|
* To start the project, run this command in your terminal `npx expo start --tunnel`
|
||||||
|
* Scan the QR code that shows in your terminal.
|
||||||
|
* For more information about Tunneling, please read [Expo](https://docs.expo.dev/more/expo-cli/#tunneling).
|
42
ev_station/app.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"name": "ev_station",
|
||||||
|
"slug": "ev_station",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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-location",
|
||||||
|
{
|
||||||
|
"locationAlwaysAndWhenInUsePermission": "Allow ev_station to use your location."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expo-router"
|
||||||
|
],
|
||||||
|
"experiments": {
|
||||||
|
"typedRoutes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
ev_station/app/Navigation/HomeNavigator.jsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { View, Text, StyleSheet } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
|
||||||
|
import HomeScreen from "../screen/HomeScreen"
|
||||||
|
import SaveScreen from "../screen/SaveScreen"
|
||||||
|
import {Entypo, MaterialIcons, AntDesign} from '@expo/vector-icons';
|
||||||
|
|
||||||
|
|
||||||
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
|
const HomeNavigator = () => {
|
||||||
|
return (
|
||||||
|
<Tab.Navigator screenOptions={{headerShown: false, tabBarHideOnKeyboard: true}}>
|
||||||
|
<Tab.Screen name='home' component={HomeScreen}
|
||||||
|
options={{
|
||||||
|
tabBarLabel: "Home",
|
||||||
|
tabBarActiveTintColor: '#379777',
|
||||||
|
tabBarInactiveTintColor: 'gray',
|
||||||
|
tabBarIcon:({focused}) =>
|
||||||
|
focused? (
|
||||||
|
<Entypo name="home" size={26} color="#379777" style={{marginTop: 5, marginBottom: 5}}/>
|
||||||
|
) : (
|
||||||
|
<Entypo name="home" size={26} color="gray" style={{marginTop: 5, marginBottom: 5}}/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen name='save' component={SaveScreen}
|
||||||
|
options={{
|
||||||
|
tabBarLabel: "Save",
|
||||||
|
tabBarActiveTintColor: '#379777',
|
||||||
|
tabBarInactiveTintColor: 'gray',
|
||||||
|
tabBarIcon:({focused}) =>
|
||||||
|
focused? (
|
||||||
|
<MaterialIcons name="data-saver-on" size={26} color="#379777" style={{marginTop: 5, marginBottom: 5}}/>
|
||||||
|
) : (
|
||||||
|
<MaterialIcons name="data-saver-on" size={26} color="gray" style={{marginTop: 5, marginBottom: 5}}/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab.Navigator>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeNavigator
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
3
ev_station/app/context/MakerContext.jsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export const MarkerContext = createContext(null);
|
3
ev_station/app/context/UserLocationContext.jsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export const UserLocationContext = createContext(null);
|
76
ev_station/app/index.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { StatusBar } from "expo-status-bar";
|
||||||
|
import { View, Text } from "react-native";
|
||||||
|
import Login from "./screen/loginScreen/Login";
|
||||||
|
import { ClerkProvider, SignedIn, SignedOut } from '@clerk/clerk-expo';
|
||||||
|
import *as SecureStore from "expo-secure-store";
|
||||||
|
import * as Location from 'expo-location';
|
||||||
|
import { NavigationContainer } from "@react-navigation/native";
|
||||||
|
import HomeNavigator from "./Navigation/HomeNavigator";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { UserLocationContext } from "./context/UserLocationContext";
|
||||||
|
|
||||||
|
|
||||||
|
const tokenCache = {
|
||||||
|
async getToken(key){
|
||||||
|
try {
|
||||||
|
return SecureStore.getItemAsync(key);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveToken(key, value){
|
||||||
|
try {
|
||||||
|
return SecureStore.setItemAsync(key, value);
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const PUBLIC_KEY = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||||
|
|
||||||
|
//use current location
|
||||||
|
const [location, setLocation] = useState(null);
|
||||||
|
const [errorMsg, setErrorMsg] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
let { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
if (status !== 'granted') {
|
||||||
|
setErrorMsg('Permission to access location was denied');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let location = await Location.getCurrentPositionAsync({});
|
||||||
|
setLocation(location.coords);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let text = 'Waiting..';
|
||||||
|
if (errorMsg) {
|
||||||
|
text = errorMsg;
|
||||||
|
} else if (location) {
|
||||||
|
text = JSON.stringify(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClerkProvider tokenCache={tokenCache} publishableKey={PUBLIC_KEY}>
|
||||||
|
<UserLocationContext.Provider value={{location, setLocation}}>
|
||||||
|
<View style={{flex: 1, backgroundColor: 'white'}}>
|
||||||
|
<StatusBar style="auto"/>
|
||||||
|
<SignedIn>
|
||||||
|
<NavigationContainer independent={true}>
|
||||||
|
<HomeNavigator />
|
||||||
|
</NavigationContainer>
|
||||||
|
</SignedIn>
|
||||||
|
<SignedOut>
|
||||||
|
<Login/>
|
||||||
|
</SignedOut>
|
||||||
|
</View>
|
||||||
|
</UserLocationContext.Provider>
|
||||||
|
</ClerkProvider>
|
||||||
|
);
|
||||||
|
}
|
26
ev_station/app/misc/GlobalApi.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const BASE_URL = 'https://places.googleapis.com/v1/places:searchNearby';
|
||||||
|
const API_KEY = process.env.EXPO_PUBLIC_GOOGLE_PLACE_API_KEY;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers:{
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Goog-Api-Key': API_KEY,
|
||||||
|
'X-Goog-FieldMask': [
|
||||||
|
'places.displayName',
|
||||||
|
'places.formattedAddress',
|
||||||
|
'places.location',
|
||||||
|
'places.evChargeOptions',
|
||||||
|
'places.photos',
|
||||||
|
'places.id'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NewNearByPlace = (data) => axios.post(BASE_URL, data, config).catch(function (error) {
|
||||||
|
console.log(error.toJSON());
|
||||||
|
});
|
||||||
|
;
|
||||||
|
|
||||||
|
export default{NewNearByPlace, API_KEY}
|
19
ev_station/app/screen/HomeHeader.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import { useUser } from '@clerk/clerk-expo';
|
||||||
|
|
||||||
|
const HomeHeader = () => {
|
||||||
|
const {user} = useUser();
|
||||||
|
return (
|
||||||
|
<View style={{backgroundColor: 'white', paddingHorizontal: 20, paddingVertical: 10}}>
|
||||||
|
<View style={{flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'}}>
|
||||||
|
<Image source={require('../../assets/images/ev_images/EV.png')} style={{width: 120, height: 60, objectFit: 'contain'}}/>
|
||||||
|
<Image source={{uri: user?.imageUrl}} style={{width: 40, height: 40, borderRadius: '50%'}}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeHeader
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
62
ev_station/app/screen/HomeScreen.jsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import MapViewScreen from "../screen/mapView/MapViewScreen";
|
||||||
|
import HomeHeader from './HomeHeader';
|
||||||
|
import HomeSearch from './HomeSearch';
|
||||||
|
import { UserLocationContext } from '../context/UserLocationContext';
|
||||||
|
import GlobalApi from '../misc/GlobalApi';
|
||||||
|
import EVListView from '../screen/googleList/EVListView';
|
||||||
|
import { MarkerContext } from '../context/MakerContext';
|
||||||
|
|
||||||
|
|
||||||
|
const HomeScreen = () => {
|
||||||
|
const {location, setLocation} = useContext(UserLocationContext);
|
||||||
|
const [placeList, setPlaceList] = useState([]);
|
||||||
|
|
||||||
|
const [selectedMarker, setSelectedMarker] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
location && GetNearByPlace();
|
||||||
|
},[location])
|
||||||
|
|
||||||
|
const GetNearByPlace = () => {
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
"includedTypes": ["electric_vehicle_charging_station"],
|
||||||
|
"maxResultCount": 10,
|
||||||
|
"locationRestriction": {
|
||||||
|
"circle": {
|
||||||
|
"center": {
|
||||||
|
"latitude": location?.latitude,
|
||||||
|
"longitude": location?.longitude
|
||||||
|
},
|
||||||
|
"radius": 5000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalApi.NewNearByPlace(data).then(res => {
|
||||||
|
console.log(JSON.stringify(res.data));
|
||||||
|
setPlaceList(res.data?.places);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarkerContext.Provider value={{selectedMarker, setSelectedMarker}}>
|
||||||
|
<View style={{flex: 1, backgroundColor: 'white'}}>
|
||||||
|
<View style={{position: 'absolute', zIndex: 10, width: '100%'}}>
|
||||||
|
<HomeHeader />
|
||||||
|
<HomeSearch searchedLocation={(location) => setLocation({latitude: location.lat, longitude: location.lng})}/>
|
||||||
|
</View>
|
||||||
|
{placeList && <MapViewScreen placeList={placeList}/>}
|
||||||
|
<View style={{position: 'absolute', bottom: 0, zIndex: 20, width: '100%'}}>
|
||||||
|
{placeList && <EVListView placeList={placeList}/>}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</MarkerContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeScreen
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
34
ev_station/app/screen/HomeSearch.jsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
|
||||||
|
|
||||||
|
|
||||||
|
const mapApiKey = process.env.EXPO_PUBLIC_GOOGLE_PLACE_API_KEY;
|
||||||
|
|
||||||
|
const HomeSearch = ({searchedLocation}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{marginVertical: 10, marginHorizontal: 10, borderRadius: 5}}>
|
||||||
|
<GooglePlacesAutocomplete
|
||||||
|
placeholder='Search EV charging station'
|
||||||
|
textInputProps={{
|
||||||
|
placeholderTextColor: '#45474B'
|
||||||
|
}}
|
||||||
|
enablePoweredByContainer={false}
|
||||||
|
fetchDetails={true}
|
||||||
|
onPress={(data, details = null) => {
|
||||||
|
searchedLocation(details?.geometry?.location)
|
||||||
|
}}
|
||||||
|
query={{
|
||||||
|
key: mapApiKey,
|
||||||
|
language: 'en',
|
||||||
|
}}
|
||||||
|
onFail={error => console.error(error)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeSearch
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
58
ev_station/app/screen/SaveScreen.jsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { ActivityIndicator, FlatList, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { collection, query, where, getDocs, getFirestore } from "firebase/firestore";
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useUser } from '@clerk/clerk-expo';
|
||||||
|
import { app } from '../misc/FirebaseConfig';
|
||||||
|
import ItemList from './googleList/ItemList';
|
||||||
|
|
||||||
|
const SaveScreen = () => {
|
||||||
|
const {user} = useUser();
|
||||||
|
const [saveList, setSaveList] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
db = getFirestore(app);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
user && getSaveList();
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
const getSaveList = async() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setSaveList([])
|
||||||
|
const q = query(collection(db, "ev-save"), where("email", "==", user?.primaryEmailAddress?.emailAddress));
|
||||||
|
|
||||||
|
const querySnapshot = await getDocs(q);
|
||||||
|
querySnapshot.forEach((doc) => {
|
||||||
|
// doc.data() is never undefined for query doc snapshots
|
||||||
|
console.log(doc.id, " => ", doc.data());
|
||||||
|
setSaveList(saveList => [...saveList, doc.data()]);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{flex: 1, backgroundColor: 'white'}}>
|
||||||
|
{!saveList?
|
||||||
|
<View style={{flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%'}}>
|
||||||
|
<ActivityIndicator size={'large'} color={'#379777'}/>
|
||||||
|
<Text style={{marginTop: 10}}>Loading</Text>
|
||||||
|
|
||||||
|
</View> : null
|
||||||
|
}
|
||||||
|
<FlatList
|
||||||
|
onRefresh={() => getSaveList()}
|
||||||
|
refreshing={isLoading}
|
||||||
|
data={saveList}
|
||||||
|
renderItem={({item, index}) => (
|
||||||
|
<View key={index} style={{marginVertical: 5, marginHorizontal: 5, borderColor: 'gray', borderWidth: 1}}>
|
||||||
|
<ItemList place={item.place} isSave={true} markedSave={() => getSaveList()}/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SaveScreen
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
71
ev_station/app/screen/googleList/EVListView.jsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { Dimensions, FlatList, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from 'react'
|
||||||
|
import ItemList from './ItemList'
|
||||||
|
import { MarkerContext } from '@/app/context/MakerContext';
|
||||||
|
import { collection, query, where, getDocs, getFirestore } from "firebase/firestore";
|
||||||
|
import { app } from '@/app/misc/FirebaseConfig';
|
||||||
|
import { useUser } from '@clerk/clerk-expo';
|
||||||
|
|
||||||
|
const EVListView = ({placeList}) => {
|
||||||
|
const {user} = useUser();
|
||||||
|
const [saveList, setSaveList] = useState([]);
|
||||||
|
const flatListRef = useRef(null);
|
||||||
|
const {selectedMarker, setSelectedMarker} = useContext(MarkerContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectedMarker && scrollToIndex(selectedMarker);
|
||||||
|
}, [selectedMarker])
|
||||||
|
|
||||||
|
const scrollToIndex = (index) => {
|
||||||
|
flatListRef.current?.scrollToIndex({animated: true, index})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getItemLayout = (_,index) => ({
|
||||||
|
length: Dimensions.get('window').width,
|
||||||
|
offset: Dimensions.get('window').width*index,
|
||||||
|
index
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const db = getFirestore(app);
|
||||||
|
useEffect(() => {
|
||||||
|
user && getSaveList();
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
const getSaveList = async() => {
|
||||||
|
setSaveList([])
|
||||||
|
const q = query(collection(db, "ev-save"), where("email", "==", user?.primaryEmailAddress?.emailAddress));
|
||||||
|
|
||||||
|
const querySnapshot = await getDocs(q);
|
||||||
|
querySnapshot.forEach((doc) => {
|
||||||
|
// doc.data() is never undefined for query doc snapshots
|
||||||
|
console.log(doc.id, " => ", doc.data());
|
||||||
|
setSaveList(saveList => [...saveList, doc.data()]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSave = (place) => {
|
||||||
|
const result = saveList.find(item => item.place.id == place.id);
|
||||||
|
return result?true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<FlatList horizontal={true}
|
||||||
|
getItemLayout={getItemLayout}
|
||||||
|
data={placeList}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
ref={flatListRef}
|
||||||
|
renderItem={({item, index}) => (
|
||||||
|
<View key={index}>
|
||||||
|
<ItemList place={item} isSave={isSave(item)} markedSave={() => getSaveList()}/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EVListView
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
66
ev_station/app/screen/googleList/ItemList.jsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { Dimensions, Image, Linking, Platform, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import GlobalApi from '@/app/misc/GlobalApi'
|
||||||
|
import AntDesign from '@expo/vector-icons/AntDesign';
|
||||||
|
import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
|
||||||
|
import { getFirestore } from "firebase/firestore";
|
||||||
|
import { app } from "../../misc/FirebaseConfig";
|
||||||
|
import { doc, setDoc, deleteDoc} from "firebase/firestore";
|
||||||
|
import { useUser } from '@clerk/clerk-expo';
|
||||||
|
|
||||||
|
const ItemList = ({place, isSave, markedSave}) => {
|
||||||
|
const placePhoto_Base_Url = 'https://places.googleapis.com/v1/'
|
||||||
|
const {user} = useUser();
|
||||||
|
const db = getFirestore(app);
|
||||||
|
const onSave = async(place) => {
|
||||||
|
await setDoc(doc(db, 'ev-save', (place.id).toString()), {
|
||||||
|
place: place,
|
||||||
|
email: user?.primaryEmailAddress?.emailAddress
|
||||||
|
});
|
||||||
|
markedSave();
|
||||||
|
};
|
||||||
|
const onRemoveSave = async(placeId) => {
|
||||||
|
await deleteDoc(doc(db, 'ev-save', placeId.toString()));
|
||||||
|
markedSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStartClick = () => {
|
||||||
|
const url = Platform.select({
|
||||||
|
ios: "maps:"+place?.location?.latitude+","+place?.location?.longitude+"?q="+place?.formattedAddress,
|
||||||
|
});
|
||||||
|
Linking.openURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{width: Dimensions.get('screen').width *0.92, height: 280, backgroundColor: 'white', marginLeft: 10, marginVertical: 5 ,borderRadius: 5, flexDirection: 'column', justifyContent: 'space-between'}}>
|
||||||
|
{!isSave?
|
||||||
|
<Pressable onPress={() => onSave(place)}
|
||||||
|
style={{position: 'absolute', zIndex: 20, right: 0, margin: 10, width: 40, height: 40, backgroundColor: '#379777', alignItems: 'center', justifyContent: 'center', borderRadius: '50%'}}>
|
||||||
|
<AntDesign name="pluscircleo" size={30} color="white" />
|
||||||
|
</Pressable> :
|
||||||
|
<Pressable onPress={() => onRemoveSave(place.id)}
|
||||||
|
style={{position: 'absolute', zIndex: 20, right: 0, margin: 10, width: 40, height: 40, backgroundColor: '#379777', alignItems: 'center', justifyContent: 'center', borderRadius: '50%'}}>
|
||||||
|
<MaterialCommunityIcons name="map-marker-star" size={30} color="white" />
|
||||||
|
</Pressable>
|
||||||
|
}
|
||||||
|
<Image source={place?.photos?{uri:placePhoto_Base_Url+place.photos[0].name+"/media?key="+GlobalApi.API_KEY+"&maxHeightPx=500&maxWidthPx=500"}
|
||||||
|
: require('../../../assets/images/ev_images/EV.png')} style={{width: '100%', height: 120, objectFit: 'cover', borderTopLeftRadius: 5, borderTopRightRadius: 5}}/>
|
||||||
|
<View style={{padding: 10, flexDirection: 'column', justifyContent: 'space-between'}}>
|
||||||
|
<View>
|
||||||
|
<Text style={{fontSize: 20, fontWeight: 600, color: '#379777'}}>{place.displayName.text}</Text>
|
||||||
|
<Text style={{marginTop: 5, color: '#45474B', fontWeight: 600, fontSize: 15}}>Total chargers: {place?.evChargeOptions?.connectorCount}</Text>
|
||||||
|
<Text style={{marginVertical: 5, color: '#45474B', fontSize: 15}}>{place?.formattedAddress}</Text>
|
||||||
|
</View>
|
||||||
|
<Pressable onPress={() => onStartClick()}
|
||||||
|
style={{backgroundColor: '#379777', alignItems: 'center', justifyContent: 'center', padding: 5, marginTop: 10, borderRadius: 5}}>
|
||||||
|
<Text style={{color: 'white', fontSize: 18, fontWeight: 500}}>Start</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ItemList
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
42
ev_station/app/screen/loginScreen/Login.jsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React from 'react';
|
||||||
|
import *as WebBrowser from "expo-web-browser";
|
||||||
|
import { useOAuth } from '@clerk/clerk-expo';
|
||||||
|
import { useWarmUpBrowser } from '../../../hooks/warmUpBrowser'
|
||||||
|
|
||||||
|
|
||||||
|
WebBrowser.maybeCompleteAuthSession();
|
||||||
|
const Login = () => {
|
||||||
|
useWarmUpBrowser();
|
||||||
|
const {startOAuthFlow} = useOAuth({strategy: "oauth_google"})
|
||||||
|
|
||||||
|
const loginHandler = async() => {
|
||||||
|
try {
|
||||||
|
const {createdSessionId, signIn, signUp, setActive} = await startOAuthFlow();
|
||||||
|
if(createdSessionId){
|
||||||
|
setActive({session: createdSessionId});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("oAuth error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{flex:1, justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Image source={require('../../../assets/images/ev_images/EV.png')}/>
|
||||||
|
<View style={{padding: 20, marginVertical: 10}}>
|
||||||
|
<Text style={{fontSize: 24, textAlign: 'center', paddingHorizontal: 5, fontWeight: 600, color: '#45474B'}}>Find reliable EV charging stations near you just one click</Text>
|
||||||
|
<Pressable onPress={loginHandler} style={{marginVertical: 50}}>
|
||||||
|
<View style={{justifyContent: 'center', alignItems: 'center', padding: 10, backgroundColor: '#379777', marginHorizontal: 100, borderRadius: 5}}>
|
||||||
|
<Text style={{fontSize: 20, fontWeight: 500, color: 'white'}}>Start</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
38
ev_station/app/screen/mapView/MapViewScreen.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { Image, StyleSheet, Text, View } from 'react-native';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import MapView, { Marker, PROVIDER_DEFAULT} from 'react-native-maps';
|
||||||
|
import { UserLocationContext } from '@/app/context/UserLocationContext';
|
||||||
|
import MarkerList from '../mapView/MarkerList';
|
||||||
|
|
||||||
|
|
||||||
|
const MapViewScreen = ({placeList}) => {
|
||||||
|
const {location, setLocation} = useContext(UserLocationContext)
|
||||||
|
|
||||||
|
|
||||||
|
return location?.latitude &&(
|
||||||
|
<View>
|
||||||
|
<MapView style={{width: '100%', height: '100%'}}
|
||||||
|
provider={PROVIDER_DEFAULT}
|
||||||
|
region={{
|
||||||
|
latitude: location?.latitude,
|
||||||
|
longitude: location?.longitude,
|
||||||
|
latitudeDelta: 0.2,
|
||||||
|
longitudeDelta: 0.2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{location? <Marker coordinate={{latitude: location?.latitude, longitude: location?.longitude}}>
|
||||||
|
<Image source={require('../../../assets/images/ev_images/car.png')} style={{width: 50, height: 50}}/>
|
||||||
|
</Marker> : null}
|
||||||
|
|
||||||
|
{placeList && placeList.map((item, index) => (
|
||||||
|
<MarkerList key={index} place={item} index={index}/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</MapView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapViewScreen
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
22
ev_station/app/screen/mapView/MarkerList.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { Marker } from 'react-native-maps'
|
||||||
|
import { MarkerContext } from '@/app/context/MakerContext'
|
||||||
|
|
||||||
|
const MarkerList = ({index, place}) => {
|
||||||
|
const {selectedMarker, setSelectedMarker} = useContext(MarkerContext);
|
||||||
|
|
||||||
|
return place && (
|
||||||
|
<Marker onPress={() => setSelectedMarker(index)}
|
||||||
|
coordinate={{
|
||||||
|
latitude: place.location?.latitude,
|
||||||
|
longitude: place.location?.longitude
|
||||||
|
}}>
|
||||||
|
<Image source={require('../../../assets/images/ev_images/charging.png')} style={{width: 50, height: 50}}/>
|
||||||
|
</Marker>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MarkerList
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({})
|
BIN
ev_station/assets/fonts/SpaceMono-Regular.ttf
Executable file
BIN
ev_station/assets/images/adaptive-icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
ev_station/assets/images/ev_images/EV.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
ev_station/assets/images/ev_images/car.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
ev_station/assets/images/ev_images/charging-station.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
ev_station/assets/images/ev_images/charging.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
ev_station/assets/images/favicon.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
ev_station/assets/images/icon.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
ev_station/assets/images/partial-react-logo.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
ev_station/assets/images/react-logo.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
ev_station/assets/images/react-logo@2x.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
ev_station/assets/images/react-logo@3x.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
ev_station/assets/images/splash.png
Normal file
After Width: | Height: | Size: 46 KiB |
6
ev_station/babel.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = function (api) {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
presets: ['babel-preset-expo'],
|
||||||
|
};
|
||||||
|
};
|
11
ev_station/hooks/warmUpBrowser.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import *as WebBrowser from "expo-web-browser";
|
||||||
|
|
||||||
|
export const useWarmUpBrowser = () => {
|
||||||
|
React.useEffect(() => {
|
||||||
|
void WebBrowser.warmUpAsync();
|
||||||
|
return() => {
|
||||||
|
void WebBrowser.coolDownAsync();
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
};
|
19557
ev_station/package-lock.json
generated
Normal file
57
ev_station/package.json
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"name": "ev_station",
|
||||||
|
"main": "expo-router/entry",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"start": "expo start",
|
||||||
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
|
"android": "expo start --android",
|
||||||
|
"ios": "expo start --ios",
|
||||||
|
"web": "expo start --web",
|
||||||
|
"test": "jest --watchAll",
|
||||||
|
"lint": "expo lint"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-expo"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@clerk/clerk-expo": "^2.0.0",
|
||||||
|
"@expo/vector-icons": "^14.0.2",
|
||||||
|
"@react-navigation/native": "^6.1.18",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"expo": "~51.0.22",
|
||||||
|
"expo-constants": "~16.0.2",
|
||||||
|
"expo-font": "~12.0.9",
|
||||||
|
"expo-linking": "~6.3.1",
|
||||||
|
"expo-location": "~17.0.1",
|
||||||
|
"expo-router": "~3.5.18",
|
||||||
|
"expo-secure-store": "^13.0.2",
|
||||||
|
"expo-splash-screen": "~0.27.5",
|
||||||
|
"expo-status-bar": "~1.12.1",
|
||||||
|
"expo-system-ui": "~3.0.7",
|
||||||
|
"expo-web-browser": "~13.0.3",
|
||||||
|
"firebase": "^10.12.4",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-native": "0.74.3",
|
||||||
|
"react-native-gesture-handler": "~2.16.1",
|
||||||
|
"react-native-google-places-autocomplete": "^2.5.6",
|
||||||
|
"react-native-maps": "1.14.0",
|
||||||
|
"react-native-places-input": "^1.1.7",
|
||||||
|
"react-native-reanimated": "~3.10.1",
|
||||||
|
"react-native-safe-area-context": "4.10.5",
|
||||||
|
"react-native-screens": "3.31.1",
|
||||||
|
"react-native-web": "~0.19.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.20.0",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
|
"@types/react": "~18.2.45",
|
||||||
|
"@types/react-test-renderer": "^18.0.7",
|
||||||
|
"jest": "^29.2.1",
|
||||||
|
"jest-expo": "~51.0.3",
|
||||||
|
"react-test-renderer": "18.2.0",
|
||||||
|
"typescript": "~5.3.3"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
73
ev_station/scripts/reset-project.js
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This script is used to reset the project to a blank state.
|
||||||
|
* It moves the /app directory to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.
|
||||||
|
* You can remove the `reset-project` script from package.json and safely delete this file after running it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = process.cwd();
|
||||||
|
const oldDirPath = path.join(root, 'app');
|
||||||
|
const newDirPath = path.join(root, 'app-example');
|
||||||
|
const newAppDirPath = path.join(root, 'app');
|
||||||
|
|
||||||
|
const indexContent = `import { Text, View } from "react-native";
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>Edit app/index.tsx to edit this screen.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const layoutContent = `import { Stack } from "expo-router";
|
||||||
|
|
||||||
|
export default function RootLayout() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen name="index" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.rename(oldDirPath, newDirPath, (error) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error(`Error renaming directory: ${error}`);
|
||||||
|
}
|
||||||
|
console.log('/app moved to /app-example.');
|
||||||
|
|
||||||
|
fs.mkdir(newAppDirPath, { recursive: true }, (error) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error(`Error creating new app directory: ${error}`);
|
||||||
|
}
|
||||||
|
console.log('New /app directory created.');
|
||||||
|
|
||||||
|
const indexPath = path.join(newAppDirPath, 'index.tsx');
|
||||||
|
fs.writeFile(indexPath, indexContent, (error) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error(`Error creating index.tsx: ${error}`);
|
||||||
|
}
|
||||||
|
console.log('app/index.tsx created.');
|
||||||
|
|
||||||
|
const layoutPath = path.join(newAppDirPath, '_layout.tsx');
|
||||||
|
fs.writeFile(layoutPath, layoutContent, (error) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error(`Error creating _layout.tsx: ${error}`);
|
||||||
|
}
|
||||||
|
console.log('app/_layout.tsx created.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
ev_station/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "expo/tsconfig.base",
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".expo/types/**/*.ts",
|
||||||
|
"expo-env.d.ts"
|
||||||
|
, "app/index.js" ]
|
||||||
|
}
|
|
@ -1,50 +1,9 @@
|
||||||
# Welcome to your Expo app 👋
|
# News application with React Native
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
This project is a news mobile application that fetches data from [News API](https://newsapi.org/), where users will be able to share and read the latest news from different sources.
|
||||||
|
|
||||||
## Get started
|
## To start the project
|
||||||
|
* Download [Expo Go](https://expo.dev/go) on your device to launch the project.
|
||||||
1. Install dependencies
|
* To start the project, run this command in your terminal `npx expo start --tunnel`
|
||||||
|
* Scan the QR code that shows in your terminal.
|
||||||
```bash
|
* For more information about Tunneling, please read [Expo](https://docs.expo.dev/more/expo-cli/#tunneling).
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the app
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
|
||||||
|
|
||||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
||||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
||||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
||||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
||||||
|
|
||||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
|
||||||
|
|
||||||
## Get a fresh project
|
|
||||||
|
|
||||||
When you're ready, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run reset-project
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
|
||||||
|
|
||||||
## Learn more
|
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
||||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
||||||
|
|
||||||
## Join the community
|
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|
|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
||||||
|
|
|
@ -6,14 +6,13 @@ import HomeNavigator from './Navigation/HomeNavigator'
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<NavigationContainer independent={true}>
|
||||||
<NavigationContainer independent={true}>
|
<KeyboardAvoidingView>
|
||||||
<KeyboardAvoidingView>
|
<View style={{height: '100%', width: '100%'}}>
|
||||||
<View style={{height: '100%', width: '100%'}}>
|
<HomeNavigator/>
|
||||||
<HomeNavigator/>
|
</View>
|
||||||
</View>
|
</KeyboardAvoidingView>
|
||||||
</KeyboardAvoidingView>
|
</NavigationContainer>
|
||||||
</NavigationContainer>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|