Skip to content

Commit 144c8bf

Browse files
committed
feat: Implementação da tela APOD (Astronomy Picture Of the Day)
1 parent 45efe2f commit 144c8bf

File tree

13 files changed

+249
-33
lines changed

13 files changed

+249
-33
lines changed

.env.example

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
SENTRY_ORG=
22
SENTRY_PROJECT=
3-
SENTRY_DSN=
3+
SENTRY_DSN=
4+
5+
NASA_API_KEY=

App.tsx

+19-9
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@ import { View, Text, TouchableOpacity, StyleSheet, Animated } from 'react-native
55
import { createDrawerNavigator } from '@react-navigation/drawer';
66
import { NavigationContainer } from '@react-navigation/native';
77
import { MaterialIcons } from '@expo/vector-icons';
8-
9-
import Constants from 'expo-constants';
8+
import { QueryClient, QueryClientProvider } from 'react-query';
9+
import { envConstants } from '@consts/consts';
1010

1111
import * as Sentry from '@sentry/react-native';
1212

13+
import { ApodScreen } from '@screens/Apod/Apod';
1314
import { HomeScreen } from '@screens/Home/Home';
1415
import { RoversScreen } from '@screens/Rovers/Rovers';
1516
import { GalaxiesScreen } from '@screens/Galaxies/Galaxies';
1617

1718
import { Colors } from '@consts/consts';
18-
19-
// Acessando as variáveis definidas no .env
20-
const sentryDsn = Constants.manifest2?.extra?.expoClient?.extra?.sentryDsn;
19+
import { useGlobalStore } from '@store/store';
20+
import { LoadingSpinner } from '@components/LoadingSpinner/LoadingSpinner';
2121

2222
Sentry.init({
23-
dsn: sentryDsn,
23+
dsn: envConstants.sentryDsn,
2424
debug: true, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production
2525
});
2626

27+
const queryClient = new QueryClient();
2728
const Drawer = createDrawerNavigator();
2829

2930
// Menu lateral
@@ -33,6 +34,9 @@ const CustomDrawerContent = ({ navigation }: any) => {
3334
<TouchableOpacity style={styles.drawerButton} onPress={() => navigation.navigate('Home')}>
3435
<Text style={styles.drawerText}>Home</Text>
3536
</TouchableOpacity>
37+
<TouchableOpacity style={styles.drawerButton} onPress={() => navigation.navigate('Apod')}>
38+
<Text style={styles.drawerText}>Astronomy Picture Of the Day</Text>
39+
</TouchableOpacity>
3640
<TouchableOpacity style={styles.drawerButton} onPress={() => navigation.navigate('Galaxies')}>
3741
<Text style={styles.drawerText}>Galaxies</Text>
3842
</TouchableOpacity>
@@ -68,17 +72,23 @@ const AppNavigator = () => {
6872
return (
6973
<Drawer.Navigator drawerContent={(props) => <CustomDrawerContent {...props} />}>
7074
<Drawer.Screen name="Home" component={HomeScreen} options={screenOptions} />
75+
<Drawer.Screen name="Apod" component={ApodScreen} options={screenOptions} />
7176
<Drawer.Screen name="Galaxies" component={GalaxiesScreen} options={screenOptions} />
7277
<Drawer.Screen name="Rovers" component={RoversScreen} options={screenOptions} />
7378
</Drawer.Navigator>
7479
);
7580
};
7681

7782
function App() {
83+
const { globalLoading } = useGlobalStore();
84+
7885
return (
79-
<NavigationContainer>
80-
<AppNavigator />
81-
</NavigationContainer>
86+
<QueryClientProvider client={queryClient}>
87+
<NavigationContainer>
88+
<AppNavigator />
89+
{globalLoading && <LoadingSpinner />}
90+
</NavigationContainer>
91+
</QueryClientProvider>
8292
);
8393
}
8494

app.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ export default ({ config }) => ({
3939
sentryOrg: process.env.SENTRY_ORG,
4040
sentryProject: process.env.SENTRY_PROJECT,
4141
sentryDsn: process.env.SENTRY_DSN,
42+
nasaApiKey: process.env.NASA_API_KEY,
4243
},
4344
});

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
},
1111
"dependencies": {
1212
"@expo/vector-icons": "expo/vector-icons",
13+
"@react-native-community/datetimepicker": "^8.2.0",
1314
"@react-navigation/drawer": "^6.7.2",
1415
"@react-navigation/native": "^6.1.18",
1516
"@react-navigation/stack": "^6.4.1",
1617
"@sentry/react-native": "~5.24.3",
1718
"babel-plugin-module-resolver": "^5.0.2",
19+
"date-fns": "^4.1.0",
1820
"dotenv": "^16.4.5",
1921
"dotenv-cli": "^7.4.2",
2022
"expo": "~51.0.28",
@@ -27,6 +29,7 @@
2729
"react-native-reanimated": "~3.10.1",
2830
"react-native-safe-area-context": "4.10.5",
2931
"react-native-screens": "3.31.1",
32+
"react-native-video": "^6.6.3",
3033
"react-query": "^3.39.3",
3134
"zustand": "^5.0.0-rc.2"
3235
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { SafeAreaView } from 'react-native-safe-area-context';
2+
3+
export const CustomSafeAreaView = (props: any) => {
4+
return (
5+
<SafeAreaView style={props.style} edges={['left', 'right', 'bottom']}>
6+
{props.children}
7+
</SafeAreaView>
8+
);
9+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Colors } from '@consts/consts';
2+
import { View, ActivityIndicator, StyleSheet, Text, Modal } from 'react-native';
3+
4+
export const LoadingSpinner = () => {
5+
return (
6+
<Modal transparent={true} animationType="none" visible={true}>
7+
<View style={styles.container}>
8+
<Text>
9+
<ActivityIndicator size="large" color={Colors.purple} />
10+
</Text>
11+
</View>
12+
</Modal>
13+
);
14+
};
15+
16+
const styles = StyleSheet.create({
17+
container: {
18+
flex: 1,
19+
justifyContent: 'center',
20+
alignItems: 'center',
21+
backgroundColor: Colors.black,
22+
opacity: 0.8,
23+
},
24+
});

src/components/MyButton/MyButton.tsx

-12
This file was deleted.

src/consts/consts.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
import Constants from 'expo-constants';
2+
13
export const Colors = {
24
white: '#fff',
5+
black: '#000',
36
purple: 'rgb(112, 93, 207)',
47
darkGray: '#333',
58
lightGray: '#f0f0f0',
69
};
10+
11+
// Acessando as variáveis definidas no .env
12+
export const envConstants = {
13+
sentryDsn: Constants.manifest2?.extra?.expoClient?.extra?.sentryDsn,
14+
nasaApiKey: Constants.manifest2?.extra?.expoClient?.extra?.nasaApiKey,
15+
};

src/screens/Apod/Apod.tsx

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import React, { useState } from 'react';
2+
import { View, Text, StyleSheet, Image, ScrollView, Button } from 'react-native';
3+
import { useQuery } from 'react-query';
4+
import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
5+
import { format } from 'date-fns';
6+
7+
import { Colors, envConstants } from '@consts/consts';
8+
9+
import { useGlobalStore } from '@store/store';
10+
import { ApodDataInterface } from 'src/types/types';
11+
12+
import { CustomSafeAreaView } from '@components/CustomSafeAreaView/CustomSafeAreaView';
13+
14+
const fetchAPOD = async (date: string): Promise<ApodDataInterface> => {
15+
const fetchUrl = `https://api.nasa.gov/planetary/apod?api_key=${envConstants.nasaApiKey}&date=${date}`;
16+
console.log('fetchUrl: ', fetchUrl);
17+
18+
const response = await fetch(fetchUrl);
19+
if (!response.ok) {
20+
throw new Error('Network response was not ok');
21+
}
22+
return response.json();
23+
};
24+
25+
export const ApodScreen = () => {
26+
const { setGlobalLoading } = useGlobalStore();
27+
28+
const [selectedDate, setSelectedDate] = useState(new Date());
29+
const [dateString, setDateString] = useState(format(new Date(), 'yyyy-MM-dd'));
30+
const [showDatePicker, setShowDatePicker] = useState(false);
31+
const [isVideo, setIsVideo] = useState(false);
32+
33+
const { data, error, isLoading } = useQuery<ApodDataInterface, Error>(['apod', dateString], () =>
34+
fetchAPOD(dateString),
35+
);
36+
37+
React.useEffect(() => {
38+
if (data) {
39+
console.log({ data });
40+
const verifyVideo = data.url.includes('youtube');
41+
setIsVideo(verifyVideo);
42+
}
43+
}, [data]);
44+
45+
React.useEffect(() => {
46+
console.log('isLoading: ', isLoading);
47+
48+
setGlobalLoading(isLoading);
49+
}, [isLoading]);
50+
51+
const handleDateChange = (event: DateTimePickerEvent, date: Date | undefined) => {
52+
setShowDatePicker(false);
53+
if (date) {
54+
setSelectedDate(date);
55+
setDateString(format(date, 'yyyy-MM-dd'));
56+
}
57+
};
58+
59+
const openDatePicker = () => {
60+
setShowDatePicker(true);
61+
};
62+
63+
return (
64+
<CustomSafeAreaView style={styles.screenContainer}>
65+
<ScrollView style={{ padding: 10 }}>
66+
<View>
67+
<View style={{ justifyContent: 'center', alignItems: 'center', marginBottom: 20 }}>
68+
<Button title="Select Date" onPress={openDatePicker} />
69+
{showDatePicker && (
70+
<DateTimePicker
71+
value={selectedDate}
72+
mode="date"
73+
display="default"
74+
onChange={handleDateChange}
75+
/>
76+
)}
77+
</View>
78+
79+
<View style={{ gap: 10 }}>
80+
<Text style={styles.title}>{data?.title}</Text>
81+
<View>
82+
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>Explanation: </Text>
83+
<Text style={{ fontSize: 16 }}>{data?.explanation}</Text>
84+
</View>
85+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
86+
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>Date: </Text>
87+
<Text style={{ fontSize: 16 }}>{data?.date}</Text>
88+
</View>
89+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
90+
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>Copyright: </Text>
91+
<Text style={{ fontSize: 16 }}>
92+
{data?.copyright ? data.copyright?.trim() : 'No Copyrights'}
93+
</Text>
94+
</View>
95+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
96+
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>Media Type: </Text>
97+
<Text style={{ fontSize: 16 }}>{data?.media_type}</Text>
98+
</View>
99+
</View>
100+
101+
{data?.media_type === 'video' ? (
102+
<Text style={{ fontSize: 16, marginTop: 10 }}>
103+
Video format not yet supported. Please wait for new updates.{' '}
104+
</Text>
105+
) : (
106+
<View
107+
style={{
108+
borderWidth: 3,
109+
borderColor: Colors.purple,
110+
borderRadius: 4,
111+
marginTop: 10,
112+
}}
113+
>
114+
<Image source={{ uri: data?.url }} style={styles.image} />
115+
</View>
116+
)}
117+
</View>
118+
</ScrollView>
119+
</CustomSafeAreaView>
120+
);
121+
};
122+
123+
const styles = StyleSheet.create({
124+
screenContainer: {
125+
flex: 1,
126+
padding: 8,
127+
backgroundColor: Colors.lightGray,
128+
},
129+
title: {
130+
fontSize: 24,
131+
fontWeight: 'bold',
132+
},
133+
image: {
134+
width: '100%',
135+
height: 300,
136+
// marginVertical: 10,
137+
},
138+
video: {
139+
alignSelf: 'center',
140+
width: '100%',
141+
height: 300,
142+
},
143+
});

src/screens/Home/Home.tsx

+4-8
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { Colors } from '@consts/consts';
22
import { Text, View, StyleSheet, Image, ScrollView } from 'react-native';
33
import { SafeAreaView } from 'react-native-safe-area-context';
44

5+
import { CustomSafeAreaView } from '@components/CustomSafeAreaView/CustomSafeAreaView';
6+
57
const GALAXY = require('@assets/images/galaxy.png');
68

79
export const HomeScreen = () => {
810
return (
9-
<SafeAreaView style={styles.screenContainer}>
11+
<CustomSafeAreaView style={styles.screenContainer}>
1012
<ScrollView>
1113
<View
1214
style={{
@@ -25,12 +27,6 @@ export const HomeScreen = () => {
2527
explore a wealth of information and images, such as data on comets, measurements from
2628
Mars, and even real-time imagery of Earth.
2729
</Text>
28-
<Text style={styles.screenText}>
29-
Our goal is to provide a rich and immersive experience, connecting you to a vast array
30-
of cosmic data. Leveraging public APIs from NASA's catalog, the app allows you to
31-
explore a wealth of information and images, such as data on comets, measurements from
32-
Mars, and even real-time imagery of Earth.
33-
</Text>
3430
<Text style={styles.screenText}>
3531
With an intuitive interface and up-to-date information, you'll have the universe at your
3632
fingertips!
@@ -52,7 +48,7 @@ export const HomeScreen = () => {
5248
</View>
5349
</View>
5450
</ScrollView>
55-
</SafeAreaView>
51+
</CustomSafeAreaView>
5652
);
5753
};
5854

src/store/store.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { create } from 'zustand';
22

3-
export const useGlobalStore = create((set) => ({
3+
interface IGlobalStore {
4+
globalLoading: boolean;
5+
6+
setGlobalLoading: (isLoading: boolean) => void;
7+
}
8+
9+
export const useGlobalStore = create<IGlobalStore>((set) => ({
410
// Definindo os estados globais
5-
user: null,
11+
globalLoading: false,
612

713
// Funções para modificar os estados globais
8-
setUser: (user: any) => set({ user: user }),
14+
setGlobalLoading: (isLoading) => set({ globalLoading: isLoading }),
915
}));

src/types/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface ApodDataInterface {
2+
title?: string;
3+
explanation?: string;
4+
media_type?: string;
5+
url: string;
6+
copyright?: string;
7+
date?: string;
8+
}

0 commit comments

Comments
 (0)