├── .expo-shared
└── assets.json
├── .gitignore
├── App.js
├── README.md
├── api
└── pexels.js
├── app.json
├── assets
├── adaptive-icon.png
├── favicon.png
├── icon.png
├── pexels.jpg
└── splash.png
├── babel.config.js
├── components
├── CardImage.js
└── ImageList.js
├── package-lock.json
├── package.json
└── views
├── HomeScreen.js
└── ImageScreen.js
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 |
12 | # macOS
13 | .DS_Store
14 |
15 |
16 | # environment varaibles
17 | .env
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { NavigationContainer } from "@react-navigation/native";
3 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
4 | import { StatusBar } from "expo-status-bar";
5 | import { Image, Text, StyleSheet } from "react-native";
6 | import pexelsLogo from "./assets/pexels.jpg";
7 |
8 | import HomeScreen from "./views/HomeScreen";
9 | import ImageScreen from "./views/ImageScreen";
10 |
11 | const Stack = createNativeStackNavigator();
12 |
13 | const App = () => {
14 | const [openSearch, setOpenSearch] = useState(false);
15 |
16 | return (
17 |
18 |
19 | ,
23 | headerRight: () => (
24 | setOpenSearch(!openSearch)}
27 | >
28 | {openSearch ? "Close" : "Search"}
29 |
30 | ),
31 | title: "Pexels App",
32 | headerTintColor: "#fff",
33 | headerTitleStyle: {
34 | fontWeight: "bold",
35 | },
36 | headerStyle: {
37 | backgroundColor: "#0D0D0D",
38 | },
39 | }}
40 | >
41 | {(props) => }
42 |
43 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | const styles = StyleSheet.create({
64 | logo: {
65 | width: 37,
66 | height: 37,
67 | marginEnd: 5,
68 | borderRadius: 5,
69 | },
70 | });
71 |
72 | export default App;
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Image Gallery Pexels API
2 |
3 |
--------------------------------------------------------------------------------
/api/pexels.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export const getImages = async (searchTerm = "technology") =>
4 | await axios.get(`https://api.pexels.com/v1/search?query=${searchTerm}`, {
5 | headers: {
6 | Authorization: process.env.API_KEY,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-pexels-api",
4 | "slug": "react-native-pexels-api",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "splash": {
9 | "image": "./assets/splash.png",
10 | "resizeMode": "contain",
11 | "backgroundColor": "#ffffff"
12 | },
13 | "updates": {
14 | "fallbackToCacheTimeout": 0
15 | },
16 | "assetBundlePatterns": [
17 | "**/*"
18 | ],
19 | "ios": {
20 | "supportsTablet": true
21 | },
22 | "android": {
23 | "adaptiveIcon": {
24 | "foregroundImage": "./assets/adaptive-icon.png",
25 | "backgroundColor": "#FFFFFF"
26 | }
27 | },
28 | "web": {
29 | "favicon": "./assets/favicon.png"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/react-native-pexels-api/aca3af9c37fd07fc5b46b9070f79950cde1ad18a/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/react-native-pexels-api/aca3af9c37fd07fc5b46b9070f79950cde1ad18a/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/react-native-pexels-api/aca3af9c37fd07fc5b46b9070f79950cde1ad18a/assets/icon.png
--------------------------------------------------------------------------------
/assets/pexels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/react-native-pexels-api/aca3af9c37fd07fc5b46b9070f79950cde1ad18a/assets/pexels.jpg
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/react-native-pexels-api/aca3af9c37fd07fc5b46b9070f79950cde1ad18a/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ["babel-preset-expo"],
5 | plugins: ["inline-dotenv"],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/components/CardImage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, TouchableOpacity, Image } from "react-native";
3 | import { useNavigation } from "@react-navigation/native";
4 |
5 | const CardImage = ({ image }) => {
6 | const navigation = useNavigation();
7 |
8 | return (
9 | navigation.navigate("ImageScreen", { image })}
11 | style={styles.cardImage}
12 | >
13 |
21 |
22 | );
23 | };
24 |
25 | const styles = StyleSheet.create({
26 | cardImage: {
27 | display: "flex",
28 | width: "49.5%",
29 | margin: 4,
30 | justifyContent: "space-between",
31 | backgroundColor: "#2C292C",
32 | borderWidth: 0,
33 | borderRadius: 5,
34 | },
35 | });
36 |
37 | export default CardImage;
38 |
--------------------------------------------------------------------------------
/components/ImageList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Text, StyleSheet, FlatList } from "react-native";
3 | import CardImage from "../components/CardImage";
4 |
5 | const ImageList = ({ photos }) => {
6 | const renderItem = ({ item, index }) => ;
7 |
8 | if (photos.length === 0) return Loading;
9 |
10 | return (
11 |
17 | );
18 | };
19 |
20 | const styles = StyleSheet.create({
21 | container: {},
22 | });
23 |
24 | export default ImageList;
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "@react-navigation/native": "^6.0.2",
12 | "@react-navigation/native-stack": "^6.1.0",
13 | "axios": "^0.21.1",
14 | "babel-plugin-inline-dotenv": "^1.6.0",
15 | "expo": "~42.0.1",
16 | "expo-file-system": "~11.1.3",
17 | "expo-media-library": "~12.1.2",
18 | "expo-status-bar": "~1.0.4",
19 | "expo-web-browser": "~9.2.0",
20 | "react": "16.13.1",
21 | "react-dom": "16.13.1",
22 | "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz",
23 | "react-native-elements": "^3.4.2",
24 | "react-native-safe-area-context": "3.2.0",
25 | "react-native-screens": "~3.4.0",
26 | "react-native-vector-icons": "^8.1.0",
27 | "react-native-web": "~0.13.12"
28 | },
29 | "devDependencies": {
30 | "@babel/core": "^7.9.0"
31 | },
32 | "private": true
33 | }
34 |
--------------------------------------------------------------------------------
/views/HomeScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { StyleSheet, View, Text } from "react-native";
3 | import ImageList from "../components/ImageList";
4 | import { Input, Button } from "react-native-elements";
5 |
6 | import { getImages } from "../api/pexels";
7 |
8 | export default function Home({ openSearch }) {
9 | const [searchTerm, setSearchTerm] = useState("");
10 | const [photos, setPhotos] = useState([]);
11 | const [totalResults, setTotalResults] = useState(0);
12 |
13 | const loadImages = async (searchTerm) => {
14 | const res = await getImages(searchTerm);
15 | setPhotos(res.data.photos);
16 | setTotalResults(res.data.total_results);
17 | };
18 |
19 | useEffect(() => {
20 | loadImages();
21 | }, []);
22 |
23 | const handleSearch = async () => await loadImages(searchTerm);
24 |
25 | return (
26 | <>
27 | {/* Searching Section */}
28 | {openSearch && (
29 |
30 | setSearchTerm(value)}
35 | inputContainerStyle={styles.searchInput}
36 | leftIconContainerStyle={styles.searchLeftIcon}
37 | />
38 |
44 | )}
45 |
46 | {/* Main Container */}
47 |
48 | {/* Total Result Text */}
49 | {totalResults > 0 && (
50 | {totalResults} Resultados
51 | )}
52 |
53 | {/* Container List */}
54 |
55 |
56 | >
57 | );
58 | }
59 |
60 | Home.defaultProps = {
61 | openSearch: false,
62 | };
63 |
64 | const styles = StyleSheet.create({
65 | container: {
66 | flex: 1,
67 | backgroundColor: "#0D0D0D",
68 | alignItems: "center",
69 | justifyContent: "center",
70 | },
71 | totalResulText: { color: "#D0D0D0", textAlign: "right", width: "100%" },
72 | searchSection: {
73 | backgroundColor: "#0D0D0D",
74 | width: "100%",
75 | paddingRight: 80,
76 | paddingLeft: 10,
77 | flex: 1 / 5,
78 | flexDirection: "row",
79 | alignItems: "center",
80 | },
81 | searchInput: {
82 | backgroundColor: "#2C292C",
83 | borderBottomWidth: 0,
84 | paddingHorizontal: 4,
85 | },
86 | input: {
87 | color: "#fff",
88 | },
89 | searchLeftIcon: {
90 | paddingStart: 10,
91 | marginRight: 7,
92 | },
93 | buttonSearch: { backgroundColor: "#229783", marginBottom: 27 },
94 | });
95 |
--------------------------------------------------------------------------------
/views/ImageScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Avatar, Button } from "react-native-elements";
3 | import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
4 | import * as WebBrowser from "expo-web-browser";
5 | import { getImages } from "../api/pexels";
6 | import ImageList from "../components/ImageList";
7 |
8 | import * as FileSystem from "expo-file-system";
9 | import * as MediaLibrary from "expo-media-library";
10 |
11 | const ImageScreen = ({ route }) => {
12 | const { image } = route.params;
13 | const [images, setImages] = useState([]);
14 |
15 | const loadImages = async () => {
16 | const res = await getImages();
17 | setImages(res.data.photos);
18 | };
19 |
20 | useEffect(() => {
21 | loadImages();
22 | }, []);
23 |
24 | const handlePress = async () =>
25 | await WebBrowser.openBrowserAsync(image.photographer_url);
26 |
27 | const downloadFile = async () => {
28 | let fileUri = FileSystem.documentDirectory + image.id + ".jpeg";
29 |
30 | try {
31 | const { uri } = await FileSystem.downloadAsync(
32 | image.src.portrait,
33 | fileUri
34 | );
35 | saveFile(uri);
36 | } catch (error) {
37 | console.error(error);
38 | }
39 | };
40 |
41 | const saveFile = async (fileUri) => {
42 | const { status } = await MediaLibrary.requestPermissionsAsync();
43 | if (status === "granted") {
44 | const asset = await MediaLibrary.createAssetAsync(fileUri);
45 | await MediaLibrary.createAlbumAsync("Download", asset, false);
46 | }
47 | };
48 |
49 | const handleDownload = async () => {
50 | downloadFile();
51 | };
52 |
53 | return (
54 |
55 |
62 |
72 |
73 | string[0])
78 | .join("")
79 | .toUpperCase()}
80 | containerStyle={{ backgroundColor: image.avg_color }}
81 | />
82 |
83 | {image.photographer}
84 |
85 |
86 |
92 |
93 |
94 |
95 |
96 |
97 | );
98 | };
99 |
100 | const styles = StyleSheet.create({
101 | headerPhotographer: {
102 | backgroundColor: "#0D0D0D",
103 | flex: 1,
104 | flexDirection: "column",
105 | justifyContent: "flex-start",
106 | alignItems: "flex-start",
107 | padding: 10,
108 | },
109 | textPhotographer: {
110 | fontSize: 18,
111 | marginStart: 5,
112 | color: "#7f8c8d",
113 | fontWeight: "bold",
114 | },
115 | cardImageText: {
116 | color: "#fff",
117 | },
118 | });
119 |
120 | export default ImageScreen;
121 |
--------------------------------------------------------------------------------