├── assets
├── icon.png
├── favicon.png
├── splash.png
└── adaptive-icon.png
├── babel.config.js
├── examples
├── 12-pokedex
│ ├── assets
│ │ ├── pokeball.png
│ │ ├── type-bug.png
│ │ ├── type-dark.png
│ │ ├── type-fairy.png
│ │ ├── type-fire.png
│ │ ├── type-ghost.png
│ │ ├── type-grass.png
│ │ ├── type-ice.png
│ │ ├── type-rock.png
│ │ ├── type-steel.png
│ │ ├── type-water.png
│ │ ├── move-active.png
│ │ ├── type-dragon.png
│ │ ├── type-flying.png
│ │ ├── type-ground.png
│ │ ├── type-normal.png
│ │ ├── type-poison.png
│ │ ├── type-psychic.png
│ │ ├── pokemon-active.png
│ │ ├── type-electric.png
│ │ └── type-fighting.png
│ ├── components
│ │ ├── PokemonType.js
│ │ ├── MainHeader.js
│ │ └── PokemonStatus.js
│ ├── constants.js
│ ├── Pokedex.js
│ └── screens
│ │ ├── MoveDetail.js
│ │ ├── MoveList.js
│ │ ├── PokemonList.js
│ │ └── PokemonDetail.js
├── 3-the-light
│ ├── assets
│ │ ├── bulb-off.jpg
│ │ ├── bulb-on.jpg
│ │ ├── traffic-light.png
│ │ ├── traffic-light-green.png
│ │ ├── traffic-light-red.png
│ │ └── traffic-light-yellow.png
│ ├── 1.TheLight.js
│ └── 2.TrafficLight.js
├── 5-instagram-feed
│ ├── assets
│ │ ├── img1.jpg
│ │ ├── img2.jpg
│ │ ├── img3.jpg
│ │ ├── img4.jpg
│ │ ├── img5.jpg
│ │ ├── avatar.png
│ │ ├── avatar1.jpg
│ │ ├── avatar2.jpg
│ │ ├── avatar3.jpg
│ │ └── avatar4.jpg
│ ├── components
│ │ ├── Stories.js
│ │ ├── Header.js
│ │ ├── Story.js
│ │ └── Article.js
│ ├── data.js
│ └── InstagramFeed.js
├── 2-login-screen
│ ├── assets
│ │ └── facebook-banner.jpg
│ ├── 1.MomoLogin.js
│ └── 2.FacebookLogin.js
├── 10-music-player
│ ├── assets
│ │ ├── slider-thumb-ios.png
│ │ └── slider-thumb-android.png
│ ├── style.js
│ ├── util.js
│ ├── MusicPlayer.style.js
│ ├── listSong.js
│ ├── PlayerModal.style.js
│ ├── PlayerModal.js
│ └── MusicPlayer.js
├── 9-bmi-calculator
│ ├── assets
│ │ └── slider-thumb-image.png
│ ├── const.js
│ ├── components
│ │ ├── Button.js
│ │ ├── GenderSelection.js
│ │ ├── HeightSelection.js
│ │ ├── ResultModal.js
│ │ └── UnitSelection.js
│ ├── style.js
│ └── BMICalculator.js
├── 11-news
│ ├── apis.js
│ ├── Article.js
│ └── WorldwideNews.js
├── 4-register-form
│ ├── validation.js
│ ├── FormField.js
│ ├── styles.js
│ └── RegisterForm.js
├── 1-hello-world
│ ├── 1.HelloWorld1.js
│ └── 2.HelloWorld2.js
├── 8-stopwatch
│ ├── util.js
│ ├── Result.js
│ ├── Control.js
│ └── StopWatch.js
├── 6-rock-paper-scissors
│ ├── Actions.js
│ ├── DisplayResult.js
│ └── RockPaperScissors.js
└── 7-scan-qr-code
│ ├── ScannerView.js
│ └── ScanQrCode.js
├── .gitignore
├── .eslintrc.js
├── app.json
├── package.json
├── App.js
├── example-list.js
├── README.md
└── LICENSE
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/pokeball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/pokeball.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-bug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-bug.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-dark.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-fairy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-fairy.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-fire.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-ghost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-ghost.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-grass.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-ice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-ice.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-rock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-rock.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-steel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-steel.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-water.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-water.png
--------------------------------------------------------------------------------
/examples/3-the-light/assets/bulb-off.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/bulb-off.jpg
--------------------------------------------------------------------------------
/examples/3-the-light/assets/bulb-on.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/bulb-on.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/img1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/img1.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/img2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/img2.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/img3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/img3.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/img4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/img4.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/img5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/img5.jpg
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/move-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/move-active.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-dragon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-dragon.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-flying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-flying.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-ground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-ground.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-normal.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-poison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-poison.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-psychic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-psychic.png
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/avatar.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/pokemon-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/pokemon-active.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-electric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-electric.png
--------------------------------------------------------------------------------
/examples/12-pokedex/assets/type-fighting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/12-pokedex/assets/type-fighting.png
--------------------------------------------------------------------------------
/examples/3-the-light/assets/traffic-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/traffic-light.png
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/avatar1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/avatar1.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/avatar2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/avatar2.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/avatar3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/avatar3.jpg
--------------------------------------------------------------------------------
/examples/5-instagram-feed/assets/avatar4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/5-instagram-feed/assets/avatar4.jpg
--------------------------------------------------------------------------------
/examples/2-login-screen/assets/facebook-banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/2-login-screen/assets/facebook-banner.jpg
--------------------------------------------------------------------------------
/examples/3-the-light/assets/traffic-light-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/traffic-light-green.png
--------------------------------------------------------------------------------
/examples/3-the-light/assets/traffic-light-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/traffic-light-red.png
--------------------------------------------------------------------------------
/examples/10-music-player/assets/slider-thumb-ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/10-music-player/assets/slider-thumb-ios.png
--------------------------------------------------------------------------------
/examples/3-the-light/assets/traffic-light-yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/3-the-light/assets/traffic-light-yellow.png
--------------------------------------------------------------------------------
/examples/10-music-player/assets/slider-thumb-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/10-music-player/assets/slider-thumb-android.png
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/assets/slider-thumb-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinhuy/react-native-expo-examples/HEAD/examples/9-bmi-calculator/assets/slider-thumb-image.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .expo
3 | expo-env.d.ts
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 | dist
13 |
14 | # macOS
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/examples/10-music-player/style.js:
--------------------------------------------------------------------------------
1 | export const PRIMARY_COLOR = "#f39220";
2 | export const PRIMARY_TEXT_COLOR = "#686868";
3 | export const SECONDARY_TEXT_COLOR = "#9a9a9a";
4 |
5 | export const ROW = {
6 | flexDirection: "row",
7 | justifyContent: "space-between",
8 | };
9 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://docs.expo.dev/guides/using-eslint/
2 | module.exports = {
3 | extends: 'expo',
4 | globals: {
5 | setTimeout: 'readonly',
6 | clearTimeout: 'readonly',
7 | setInterval: 'readonly',
8 | clearInterval: 'readonly',
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/const.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_VALUE = {
2 | gender: "male",
3 | height: 150,
4 | weight: 50,
5 | age: 20,
6 | };
7 | export const MIN_WEIGHT = 10;
8 | export const MAX_WEIGHT = 150;
9 | export const MIN_AGE = 1;
10 | export const MAX_AGE = 150;
11 |
--------------------------------------------------------------------------------
/examples/10-music-player/util.js:
--------------------------------------------------------------------------------
1 | const padToTwo = (number) => (number <= 9 ? `0${number}` : number);
2 |
3 | export const displayTime = (milliSeconds) => {
4 | let minutes = 0;
5 | let seconds = Math.round(milliSeconds / 1000);
6 | let remainSeconds = seconds % 60;
7 | minutes = (seconds - remainSeconds) / 60;
8 | return `${padToTwo(minutes)}:${padToTwo(remainSeconds)}`;
9 | };
10 |
--------------------------------------------------------------------------------
/examples/11-news/apis.js:
--------------------------------------------------------------------------------
1 | // Register API Key here for more requests & APIs: https://newsapi.org
2 | const API_KEY = "ce52845d6e754123b3ecf9f68006b846";
3 |
4 | export async function getNews(page = 1, pageSize = 10) {
5 | try {
6 | const response = await fetch(
7 | `https://newsapi.org/v2/top-headlines?language=en&page=${page}&pageSize=${pageSize}&apiKey=${API_KEY}`
8 | );
9 | const jsonData = await response.json();
10 | return jsonData.articles || [];
11 | } catch {
12 | return [];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/12-pokedex/components/PokemonType.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ActivityIndicator } from "react-native";
3 | import { Image } from "react-native-elements";
4 | import { PokemonTypeIcon } from "../constants";
5 |
6 | export default function PokemonType({ type }) {
7 | return (
8 | }
13 | />
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/4-register-form/validation.js:
--------------------------------------------------------------------------------
1 | import * as Yup from "yup";
2 |
3 | // https://github.com/jquense/yup
4 | export const validationSchema = Yup.object().shape({
5 | firstName: Yup.string().required("First Name is required"),
6 | lastName: Yup.string().required("Last Name is required"),
7 | email: Yup.string()
8 | .email("Enter a valid email")
9 | .required("Please enter a registered email"),
10 | password: Yup.string()
11 | .required("Please enter a password")
12 | .min(6, "Password must have at least 6 characters"),
13 | confirmPassword: Yup.string()
14 | .required("Please confirm password")
15 | .oneOf([Yup.ref("password")], "Password & Confirm Password does not match"),
16 | });
17 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-expo-examples",
4 | "slug": "react-native-expo-examples",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "ios": {
15 | "supportsTablet": true
16 | },
17 | "android": {
18 | "adaptiveIcon": {
19 | "foregroundImage": "./assets/adaptive-icon.png",
20 | "backgroundColor": "#ffffff"
21 | }
22 | },
23 | "web": {
24 | "favicon": "./assets/favicon.png"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/1-hello-world/1.HelloWorld1.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Text, View } from "react-native";
3 |
4 | export default function HelloWorld1() {
5 | return (
6 | // https://reactnative.dev/docs/view
7 |
15 | {/* https://reactnative.dev/docs/text */}
16 |
24 | Hello world!
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/components/Stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ScrollView } from "react-native";
3 | import { profile, stories } from "../data";
4 | import Story from "./Story";
5 |
6 | export default function Stories() {
7 | return (
8 | // https://reactnative.dev/docs/scrollview
9 |
10 |
11 |
12 | {stories.map((story) => (
13 |
19 | ))}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/examples/8-stopwatch/util.js:
--------------------------------------------------------------------------------
1 | const padToTwo = (number) => (number <= 9 ? `0${number}` : number);
2 |
3 | export const displayTime = (centiseconds) => {
4 | let minutes = 0;
5 | let seconds = 0;
6 |
7 | if (centiseconds < 0) {
8 | centiseconds = 0;
9 | }
10 |
11 | if (centiseconds < 100) {
12 | return `00:00,${padToTwo(centiseconds)}`;
13 | }
14 |
15 | let remainCentiseconds = centiseconds % 100;
16 | seconds = (centiseconds - remainCentiseconds) / 100;
17 |
18 | if (seconds < 60) {
19 | return `00:${padToTwo(seconds)},${padToTwo(remainCentiseconds)}`;
20 | }
21 |
22 | let remainSeconds = seconds % 60;
23 | minutes = (seconds - remainSeconds) / 60;
24 |
25 | return `${padToTwo(minutes)}:${padToTwo(remainSeconds)}:${padToTwo(
26 | remainCentiseconds
27 | )}`;
28 | };
29 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Platform, StyleSheet, Text, TouchableOpacity } from "react-native";
3 | import { CENTER, TEXT } from "../style";
4 |
5 | function Button({ title, onPress }) {
6 | return (
7 |
8 | {title}
9 |
10 | );
11 | }
12 |
13 | const styles = StyleSheet.create({
14 | button: {
15 | ...CENTER,
16 | height: 60,
17 | borderRadius: 5,
18 | backgroundColor: "#e83d66",
19 | marginTop: 15,
20 | marginBottom: Platform.OS === "ios" ? 0 : 10,
21 | },
22 | buttonText: {
23 | ...TEXT,
24 | fontSize: 24,
25 | fontWeight: "bold",
26 | },
27 | });
28 |
29 | export default React.memo(Button);
30 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/components/Header.js:
--------------------------------------------------------------------------------
1 | import { Feather } from "@expo/vector-icons";
2 | import React from "react";
3 | import { Image, StyleSheet, TouchableOpacity } from "react-native";
4 |
5 | const INSTAGRAM_LOGO =
6 | "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Instagram_logo.svg/1200px-Instagram_logo.svg.png";
7 |
8 | export default function Header() {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | const styles = StyleSheet.create({
25 | logo: {
26 | flex: 1,
27 | height: 30,
28 | resizeMode: "contain",
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/style.js:
--------------------------------------------------------------------------------
1 | import { Platform } from "react-native";
2 |
3 | export const BG_COLOR = "#323344";
4 | export const HIGHLIGHT_BG_COLOR = "#24263b";
5 |
6 | export const TEXT = {
7 | color: "#fff",
8 | textAlign: "center",
9 | };
10 |
11 | export const TEXT_LABEL = {
12 | fontSize: 15,
13 | textAlign: "center",
14 | color: "#848694",
15 | };
16 |
17 | export const TEXT_VALUE = {
18 | ...TEXT,
19 | fontFamily: Platform.OS === "ios" ? "Helvetica Neue" : null,
20 | fontSize: 35,
21 | lineHeight: 55,
22 | fontWeight: "bold",
23 | };
24 |
25 | export const ROW = {
26 | flex: 1,
27 | flexDirection: "row",
28 | justifyContent: "space-between",
29 | };
30 |
31 | export const CENTER = {
32 | justifyContent: "center",
33 | alignItems: "center",
34 | };
35 |
36 | export const BOX = {
37 | flex: 1,
38 | backgroundColor: BG_COLOR,
39 | padding: 15,
40 | borderRadius: 10,
41 | };
42 |
--------------------------------------------------------------------------------
/examples/4-register-form/FormField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Text, TextInput, View } from "react-native";
3 | import { styles } from "./styles";
4 |
5 | export default function FormField({
6 | field,
7 | label,
8 | secureTextEntry,
9 | autoCapitalize,
10 | values,
11 | touched,
12 | errors,
13 | handleChange,
14 | handleBlur,
15 | }) {
16 | return (
17 |
18 | {label}
19 |
20 |
28 |
29 | {touched[field] && errors[field] ? (
30 |
31 | {errors[field]}
32 |
33 | ) : null}
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/examples/12-pokedex/components/MainHeader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Header, Icon } from "react-native-elements";
3 | import { BackgroundColor } from "../constants";
4 |
5 | export default function MainHeader({ navigation, isMain, title }) {
6 | if (isMain) {
7 | return (
8 |
14 | );
15 | } else {
16 | return (
17 | {
28 | navigation.goBack();
29 | }}
30 | />
31 | }
32 | centerComponent={{ text: title, style: { color: "#fff" } }}
33 | />
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/8-stopwatch/Result.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ScrollView, StyleSheet, Text, View } from "react-native";
3 | import { displayTime } from "./util";
4 |
5 | function Result({ results }) {
6 | // console.log(results);
7 | return (
8 |
9 |
10 |
11 | {results.map((item, index) => (
12 |
13 |
14 | Step {results.length - index}
15 |
16 |
17 | {displayTime(item)}
18 |
19 | ))}
20 |
21 | );
22 | }
23 |
24 | const styles = StyleSheet.create({
25 | resultItem: {
26 | flexDirection: "row",
27 | justifyContent: "space-between",
28 | alignItems: "center",
29 | borderBottomWidth: 1,
30 | borderColor: "#313131",
31 | height: 50,
32 | paddingHorizontal: 15,
33 | },
34 | resultItemText: { color: "#fff" },
35 | });
36 |
37 | // https://reactjs.org/docs/react-api.html#reactmemo
38 | export default React.memo(Result);
39 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/components/GenderSelection.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import React from "react";
3 | import { StyleSheet, Text, TouchableOpacity } from "react-native";
4 | import {
5 | BG_COLOR,
6 | BOX,
7 | CENTER,
8 | HIGHLIGHT_BG_COLOR,
9 | TEXT_LABEL,
10 | } from "../style";
11 |
12 | function GenderSelection({ label, iconName, iconColor, isActive, setActive }) {
13 | return (
14 |
23 |
24 | {label}
25 |
26 | );
27 | }
28 |
29 | const styles = StyleSheet.create({
30 | box: {
31 | ...CENTER,
32 | ...BOX,
33 | marginHorizontal: 10,
34 | },
35 | label: {
36 | ...TEXT_LABEL,
37 | marginTop: 10,
38 | },
39 | });
40 |
41 | // https://reactjs.org/docs/react-api.html#reactmemo
42 | export default React.memo(GenderSelection);
43 |
--------------------------------------------------------------------------------
/examples/10-music-player/MusicPlayer.style.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StyleSheet } from "react-native";
3 | import {
4 | PRIMARY_COLOR,
5 | PRIMARY_TEXT_COLOR,
6 | ROW,
7 | SECONDARY_TEXT_COLOR,
8 | } from "./style";
9 |
10 | export const styles = StyleSheet.create({
11 | container: {
12 | flex: 1,
13 | paddingTop: Constants.statusBarHeight,
14 | },
15 | header: {
16 | padding: 15,
17 | marginBottom: 10,
18 | },
19 | headerTitle: {
20 | fontWeight: "bold",
21 | fontSize: 24,
22 | color: PRIMARY_COLOR,
23 | },
24 | listItem: {
25 | flexDirection: "row",
26 | margin: 15,
27 | },
28 | coverImage: {
29 | width: 60,
30 | height: 60,
31 | borderRadius: 6,
32 | marginRight: 15,
33 | },
34 | songName: {
35 | fontWeight: "bold",
36 | fontSize: 16,
37 | lineHeight: 24,
38 | color: PRIMARY_TEXT_COLOR,
39 | },
40 | songInfo: {
41 | ...ROW,
42 | flex: 1,
43 | marginTop: 10,
44 | },
45 | singerName: {
46 | fontSize: 14,
47 | color: SECONDARY_TEXT_COLOR,
48 | },
49 | songDuration: {
50 | fontSize: 14,
51 | color: SECONDARY_TEXT_COLOR,
52 | },
53 | });
54 |
--------------------------------------------------------------------------------
/examples/12-pokedex/components/PokemonStatus.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, Text, View } from "react-native";
3 | import ProgressBar from "react-native-progress/Bar";
4 |
5 | export default function PokemonStatus({ title, value, progress }) {
6 | return (
7 |
8 | {title}
9 | {value}
10 |
11 | {/* https://github.com/oblador/react-native-progress */}
12 |
21 |
22 |
23 | );
24 | }
25 |
26 | const styles = StyleSheet.create({
27 | pokemonStatus: {
28 | flexDirection: "row",
29 | alignItems: "center",
30 | marginBottom: 10,
31 | },
32 | pokemonStatusName: {
33 | color: "#1a87d9",
34 | fontWeight: "bold",
35 | flex: 1 / 7,
36 | },
37 | pokemonStatusPoint: {
38 | paddingHorizontal: 10,
39 | flex: 1 / 7,
40 | },
41 | pokemonStatusBar: {
42 | flex: 5 / 7,
43 | },
44 | });
45 |
--------------------------------------------------------------------------------
/examples/12-pokedex/constants.js:
--------------------------------------------------------------------------------
1 | const FullPokemonsAPI =
2 | "https://gamepress.gg/sites/default/files/aggregatedjson/pokemon-data-full-en-PoGO.json";
3 | const FullMovesAPI =
4 | "https://gamepress.gg/sites/default/files/aggregatedjson/move-data-full-PoGO.json";
5 |
6 | const PokemonTypeIcon = {
7 | bug: require("./assets/type-bug.png"),
8 | dark: require("./assets/type-dark.png"),
9 | dragon: require("./assets/type-dragon.png"),
10 | electric: require("./assets/type-electric.png"),
11 | fairy: require("./assets/type-fairy.png"),
12 | fighting: require("./assets/type-fighting.png"),
13 | fire: require("./assets/type-fire.png"),
14 | flying: require("./assets/type-flying.png"),
15 | ghost: require("./assets/type-ghost.png"),
16 | grass: require("./assets/type-grass.png"),
17 | ground: require("./assets/type-ground.png"),
18 | ice: require("./assets/type-ice.png"),
19 | normal: require("./assets/type-normal.png"),
20 | poison: require("./assets/type-poison.png"),
21 | psychic: require("./assets/type-psychic.png"),
22 | rock: require("./assets/type-rock.png"),
23 | steel: require("./assets/type-steel.png"),
24 | water: require("./assets/type-water.png"),
25 | default: require("./assets/type-ice.png"),
26 | };
27 |
28 | const BackgroundColor = "#559EDF";
29 |
30 | export { FullPokemonsAPI, FullMovesAPI, PokemonTypeIcon, BackgroundColor };
31 |
--------------------------------------------------------------------------------
/examples/1-hello-world/2.HelloWorld2.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import React from "react";
3 | import { SafeAreaView, StyleSheet, Text } from "react-native";
4 |
5 | export default function HelloWorld2() {
6 | return (
7 | // https://reactnative.dev/docs/safeareaview
8 |
9 |
10 | Hello World
11 |
12 |
13 |
14 | Hello World
15 |
16 |
17 |
18 | Hello World
19 |
20 |
21 | );
22 | }
23 |
24 | // https://reactnative.dev/docs/stylesheet
25 | const styles = StyleSheet.create({
26 | container: {
27 | // SafeAreaView on Android devices
28 | paddingTop: Constants.statusBarHeight,
29 | },
30 | heading: {
31 | marginTop: 50,
32 | textAlign: "center",
33 | fontSize: 30,
34 | fontWeight: "bold",
35 | textTransform: "uppercase",
36 | },
37 | highlightText1: {
38 | color: "#e74c3c",
39 | },
40 | highlightText2: {
41 | fontStyle: "italic",
42 | textDecorationLine: "underline",
43 | color: "#2980b9",
44 | },
45 | highlightText3: {
46 | color: "#fff",
47 | backgroundColor: "#59595d",
48 | },
49 | });
50 |
--------------------------------------------------------------------------------
/examples/10-music-player/listSong.js:
--------------------------------------------------------------------------------
1 | export const PLAY_LIST = [
2 | {
3 | sourceUri:
4 | "https://github.com/robinhuy/robinhuy/blob/master/musics/hoa-hai-duong.mp3?raw=true",
5 | name: "Hoa Hải Đường",
6 | coverImage:
7 | "https://user-images.githubusercontent.com/12640832/100078185-d4224880-2e75-11eb-8cc0-e09b3dce7c7a.jpg",
8 | singer: "Jack",
9 | duration: 229632,
10 | },
11 | {
12 | sourceUri:
13 | "https://github.com/robinhuy/robinhuy/blob/master/musics/ai-mang-co-don-di.mp3?raw=true",
14 | name: "Ai Mang Cô Đơn Đi",
15 | coverImage:
16 | "https://user-images.githubusercontent.com/12640832/100081249-637d2b00-2e79-11eb-924b-0b4cdd4e1f9a.jpg",
17 | singer: "K-ICM, APJ",
18 | duration: 221231,
19 | },
20 | {
21 | sourceUri:
22 | "https://github.com/robinhuy/robinhuy/blob/master/musics/bong-hoa-dep-nhat.mp3?raw=true",
23 | name: "Bông Hoa Đẹp Nhất",
24 | coverImage:
25 | "https://user-images.githubusercontent.com/12640832/100080805-d76b0380-2e78-11eb-9aad-648e5cd1749e.jpg",
26 | singer: "Quân A.P",
27 | duration: 315312,
28 | },
29 | {
30 | sourceUri:
31 | "https://github.com/robinhuy/robinhuy/blob/master/musics/thien-dang.mp3?raw=true",
32 | name: "Thiên Đàng",
33 | coverImage:
34 | "https://user-images.githubusercontent.com/12640832/100080888-f10c4b00-2e78-11eb-931c-74272c77cad4.jpg",
35 | singer: "Wowy, JoliPoli",
36 | duration: 232200,
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/examples/10-music-player/PlayerModal.style.js:
--------------------------------------------------------------------------------
1 | import { Platform, StyleSheet } from "react-native";
2 | import { PRIMARY_TEXT_COLOR, ROW, SECONDARY_TEXT_COLOR } from "./style";
3 |
4 | export const styles = StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | backgroundColor: "#fff",
8 | },
9 | content: {
10 | padding: 20,
11 | },
12 | closeButton: {
13 | alignItems: "flex-end",
14 | height: 40,
15 | paddingHorizontal: 15,
16 | },
17 | coverImage: {
18 | width: 200,
19 | height: 200,
20 | borderRadius: 20,
21 | alignSelf: "center",
22 | },
23 | songName: {
24 | fontWeight: "bold",
25 | fontSize: 20,
26 | color: PRIMARY_TEXT_COLOR,
27 | marginTop: 20,
28 | alignSelf: "center",
29 | },
30 | singerName: {
31 | fontSize: 16,
32 | color: SECONDARY_TEXT_COLOR,
33 | marginTop: 5,
34 | fontStyle: "italic",
35 | alignSelf: "center",
36 | },
37 | progress: {
38 | margin: 30,
39 | },
40 | time: {
41 | ...ROW,
42 | marginHorizontal: Platform.OS === "android" ? 15 : 0,
43 | },
44 | timeText: {
45 | color: "#757575",
46 | },
47 | slider: {
48 | height: 30,
49 | },
50 | controls: {
51 | flexDirection: "row",
52 | justifyContent: "center",
53 | alignItems: "center",
54 | marginHorizontal: 30,
55 | },
56 | primaryControlIcon: {
57 | fontSize: 50,
58 | color: "#f27637",
59 | marginHorizontal: 25,
60 | },
61 | secondaryControlIcon: {
62 | fontSize: 25,
63 | color: PRIMARY_TEXT_COLOR,
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/examples/4-register-form/styles.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { Platform, StyleSheet } from "react-native";
3 |
4 | const HEADER_BACKGROUND = "#3498db";
5 | const CONTENT_BACKGROUND = "#f9f9f9";
6 |
7 | export const styles = StyleSheet.create({
8 | topSafeArea: {
9 | backgroundColor: HEADER_BACKGROUND,
10 | },
11 | container: {
12 | flex: 1,
13 | paddingTop: Constants.statusBarHeight,
14 | backgroundColor:
15 | Platform.OS === "ios" ? CONTENT_BACKGROUND : HEADER_BACKGROUND,
16 | },
17 | header: {
18 | height: 60,
19 | justifyContent: "center",
20 | alignItems: "center",
21 | backgroundColor: HEADER_BACKGROUND,
22 | },
23 | headerText: {
24 | color: "#fff",
25 | fontSize: 18,
26 | },
27 | content: {
28 | padding: 20,
29 | backgroundColor: CONTENT_BACKGROUND,
30 | },
31 | formGroup: {
32 | marginBottom: 10,
33 | },
34 | label: {
35 | color: "#7d7e79",
36 | fontSize: 16,
37 | lineHeight: 30,
38 | },
39 | input: {
40 | height: 50,
41 | paddingHorizontal: 20,
42 | borderRadius: 5,
43 | borderWidth: 2,
44 | borderColor: "#e3e3e3",
45 | backgroundColor: "#fff",
46 | },
47 | errorContainer: {
48 | marginVertical: 5,
49 | },
50 | errorText: {
51 | color: "#ff7675",
52 | },
53 | button: {
54 | marginTop: 20,
55 | backgroundColor: "#2980b9",
56 | padding: 15,
57 | borderRadius: 15,
58 | },
59 | buttonText: {
60 | color: "#fff",
61 | fontWeight: "bold",
62 | fontSize: 18,
63 | textAlign: "center",
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/examples/6-rock-paper-scissors/Actions.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import React from "react";
3 | import { StyleSheet, TouchableOpacity, View } from "react-native";
4 |
5 | export default function Actions({ play, canPlay }) {
6 | return (
7 |
8 | play(1)}
12 | >
13 |
14 |
15 |
16 | play(2)}
20 | >
21 |
22 |
23 |
24 | play(3)}
28 | >
29 |
35 |
36 |
37 | );
38 | }
39 |
40 | const styles = StyleSheet.create({
41 | actions: {
42 | height: 100,
43 | flexDirection: "row",
44 | justifyContent: "space-around",
45 | alignItems: "center",
46 | },
47 | actionButton: {
48 | width: 64,
49 | height: 64,
50 | justifyContent: "center",
51 | alignItems: "center",
52 | backgroundColor: "#f9d835",
53 | borderRadius: 32,
54 | },
55 | });
56 |
--------------------------------------------------------------------------------
/examples/8-stopwatch/Control.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
3 |
4 | function Control({ isRunning, handleLeftButtonPress, handleRightButtonPress }) {
5 | return (
6 | <>
7 |
14 |
15 |
16 | {isRunning ? "Step" : "Reset"}
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 | {isRunning ? "Stop" : "Start"}
31 |
32 |
33 |
34 | >
35 | );
36 | }
37 |
38 | const CENTER = {
39 | justifyContent: "center",
40 | alignItems: "center",
41 | };
42 |
43 | const styles = StyleSheet.create({
44 | controlButtonBorder: {
45 | ...CENTER,
46 | width: 70,
47 | height: 70,
48 | borderRadius: 70,
49 | },
50 | controlButton: {
51 | ...CENTER,
52 | width: 65,
53 | height: 65,
54 | borderRadius: 65,
55 | borderColor: "#000",
56 | borderWidth: 1,
57 | },
58 | });
59 |
60 | export default React.memo(Control);
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-expo-examples",
3 | "version": "1.0.0",
4 | "author": "Robin Huy",
5 | "license": "Apache-2.0",
6 | "main": "expo/AppEntry.js",
7 | "scripts": {
8 | "start": "expo start",
9 | "android": "expo start --android",
10 | "ios": "expo start --ios",
11 | "web": "expo start --web"
12 | },
13 | "dependencies": {
14 | "@react-native-community/masked-view": "^0.1.11",
15 | "@react-native-community/slider": "4.5.2",
16 | "@react-navigation/bottom-tabs": "^6.5.0",
17 | "@react-navigation/native": "^6.1.0",
18 | "@react-navigation/stack": "^6.3.8",
19 | "date-fns": "^2.29.3",
20 | "expo": "^51.0.24",
21 | "expo-av": "^14.0.6",
22 | "expo-barcode-scanner": "~13.0.1",
23 | "expo-router": "^3.5.20",
24 | "expo-status-bar": "~1.12.1",
25 | "expo-updates": "^0.25.21",
26 | "formik": "^2.2.9",
27 | "lodash": "^4.17.21",
28 | "react": "18.2.0",
29 | "react-dom": "18.2.0",
30 | "react-native": "0.74.3",
31 | "react-native-animatable": "^1.3.3",
32 | "react-native-elements": "^3.4.2",
33 | "react-native-gesture-handler": "~2.16.1",
34 | "react-native-keyboard-aware-scroll-view": "^0.9.5",
35 | "react-native-modal": "^13.0.1",
36 | "react-native-progress": "^5.0.0",
37 | "react-native-reanimated": "~3.10.1",
38 | "react-native-safe-area-context": "4.10.5",
39 | "react-native-screens": "3.31.1",
40 | "react-native-web": "~0.19.6",
41 | "typescript": "~5.3.3",
42 | "yup": "^0.32.11"
43 | },
44 | "devDependencies": {
45 | "@babel/core": "^7.24.7",
46 | "babel-preset-expo": "^11.0.7",
47 | "eslint": "^8.57.0",
48 | "eslint-config-expo": "^7.0.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/components/HeightSelection.js:
--------------------------------------------------------------------------------
1 | import Slider from "@react-native-community/slider";
2 | import React from "react";
3 | import { Platform, StyleSheet, Text, View } from "react-native";
4 | import sliderThumbImage from "../assets/slider-thumb-image.png";
5 | import { BOX, TEXT_LABEL, TEXT_VALUE } from "../style";
6 |
7 | const MIN_HEIGHT = 50;
8 | const MAX_HEIGHT = 250;
9 |
10 | function HeightSelection({ style, height, setHeight }) {
11 | return (
12 |
13 |
14 | HEIGHT
15 |
16 |
17 | {height}
18 | cm
19 |
20 |
21 | {/* https://github.com/react-native-community/react-native-slider */}
22 | setHeight(Math.round(value))}
30 | value={height}
31 | />
32 |
33 |
34 | );
35 | }
36 |
37 | const styles = StyleSheet.create({
38 | heightSelection: {
39 | ...BOX,
40 | marginVertical: 10,
41 | },
42 | label: {
43 | ...TEXT_LABEL,
44 | },
45 | value: {
46 | ...TEXT_VALUE,
47 | },
48 | unit: {
49 | fontSize: 16,
50 | },
51 | slider: {
52 | width: "100%",
53 | height: 40,
54 | transform: Platform.OS === "android" ? [{ scale: 1.1 }] : [],
55 | },
56 | });
57 |
58 | export default React.memo(HeightSelection);
59 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/data.js:
--------------------------------------------------------------------------------
1 | export const profile = {
2 | avatar: require("./assets/avatar.png"),
3 | };
4 |
5 | export const stories = [
6 | {
7 | id: 1,
8 | avatar: require("./assets/avatar1.jpg"),
9 | name: "5.min.craft",
10 | isSeen: false,
11 | },
12 | {
13 | id: 2,
14 | avatar: require("./assets/avatar2.jpg"),
15 | name: "teammonsterbox",
16 | isSeen: false,
17 | },
18 | {
19 | id: 3,
20 | avatar: require("./assets/avatar3.jpg"),
21 | name: "thangflycomicsoffical",
22 | isSeen: true,
23 | },
24 | {
25 | id: 4,
26 | avatar: require("./assets/avatar4.jpg"),
27 | name: "other__perspectives",
28 | isSeen: true,
29 | },
30 | ];
31 |
32 | export const articles = [
33 | {
34 | id: 1,
35 | avatar: require("./assets/avatar1.jpg"),
36 | name: "5.min.craft",
37 | image: require("./assets/img1.jpg"),
38 | likeCount: "23",
39 | date: "1 hour ago",
40 | },
41 | {
42 | id: 2,
43 | avatar: require("./assets/avatar1.jpg"),
44 | name: "5.min.craft",
45 | image: require("./assets/img2.jpg"),
46 | likeCount: "46",
47 | date: "3 hours ago",
48 | },
49 | {
50 | id: 3,
51 | avatar: require("./assets/avatar3.jpg"),
52 | name: "thangflycomicsoffical",
53 | image: require("./assets/img3.jpg"),
54 | likeCount: "72",
55 | date: "8 hours ago",
56 | },
57 | {
58 | id: 4,
59 | avatar: require("./assets/avatar2.jpg"),
60 | name: "teammonsterbox",
61 | image: require("./assets/img4.jpg"),
62 | likeCount: "19",
63 | date: "1 day ago",
64 | },
65 | {
66 | id: 5,
67 | avatar: require("./assets/avatar2.jpg"),
68 | name: "teammonsterbox",
69 | image: require("./assets/img5.jpg"),
70 | likeCount: "12",
71 | date: "2 days ago",
72 | },
73 | ];
74 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/InstagramFeed.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StatusBar } from "expo-status-bar";
3 | import React from "react";
4 | import { FlatList, SafeAreaView, StyleSheet, View } from "react-native";
5 | import Article from "./components/Article";
6 | import Header from "./components/Header";
7 | import Stories from "./components/Stories";
8 | import { articles } from "./data";
9 |
10 | export default function Instagram() {
11 | const renderItem = ({ item }) => ;
12 | const renderHeader = () => (
13 |
14 |
15 |
16 | );
17 | const keyExtractor = (item) => item.id.toString();
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {/* https://reactnative.dev/docs/flatlist */}
28 |
35 |
36 | );
37 | }
38 |
39 | const BORDER_BOTTOM = {
40 | borderBottomWidth: 1,
41 | borderBottomColor: "#dbdbdb",
42 | };
43 |
44 | const styles = StyleSheet.create({
45 | container: {
46 | flex: 1,
47 | paddingTop: Constants.statusBarHeight,
48 | },
49 | header: {
50 | ...BORDER_BOTTOM,
51 | flexDirection: "row",
52 | justifyContent: "space-between",
53 | alignItems: "center",
54 | paddingHorizontal: 16,
55 | height: 44,
56 | },
57 | stories: {
58 | ...BORDER_BOTTOM,
59 | height: 104,
60 | paddingVertical: 10,
61 | paddingLeft: 8,
62 | backgroundColor: "#fafafa",
63 | },
64 | });
65 |
--------------------------------------------------------------------------------
/examples/6-rock-paper-scissors/DisplayResult.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import React from "react";
3 | import { StyleSheet, Text, View } from "react-native";
4 |
5 | const ICONS = ["hand-rock", "hand-paper", "hand-scissors"];
6 |
7 | export default function DisplayResult({ userChoice, computerChoice }) {
8 | return (
9 | <>
10 |
11 |
18 | You
19 |
20 |
21 |
22 |
31 | Computer
32 |
33 | >
34 | );
35 | }
36 |
37 | const styles = StyleSheet.create({
38 | column: {
39 | flex: 1,
40 | justifyContent: "center",
41 | alignItems: "center",
42 | },
43 | playerName: {
44 | color: "#373737",
45 | fontSize: 16,
46 | marginTop: 16,
47 | },
48 | leftIcon: {
49 | transform: [{ rotateZ: "80deg" }],
50 | },
51 | scissorsLeftIcon: {
52 | transform: [{ rotateZ: "180deg" }, { rotateX: "180deg" }],
53 | },
54 | rightIcon: {
55 | transform: [{ rotateZ: "-80deg" }, { rotateY: "180deg" }],
56 | },
57 | scissorsRightIcon: {
58 | transform: [
59 | { rotateZ: "180deg" },
60 | { rotateY: "180deg" },
61 | { rotateX: "180deg" },
62 | ],
63 | },
64 | });
65 |
--------------------------------------------------------------------------------
/examples/11-news/Article.js:
--------------------------------------------------------------------------------
1 | import formatDistanceToNow from "date-fns/formatDistanceToNow";
2 | import React from "react";
3 | import {
4 | Alert,
5 | Image,
6 | Linking,
7 | StyleSheet,
8 | Text,
9 | TouchableOpacity,
10 | View,
11 | } from "react-native";
12 |
13 | export default function Article({ item }) {
14 | const openLink = () => {
15 | const url = item.url;
16 |
17 | // https://reactnative.dev/docs/linking
18 | Linking.canOpenURL(url).then((supported) => {
19 | if (supported) {
20 | Linking.openURL(url);
21 | } else {
22 | Alert.alert("Broken Link!");
23 | }
24 | });
25 | };
26 |
27 | const publishedFromNow = formatDistanceToNow(new Date(item.publishedAt));
28 |
29 | return (
30 |
31 | {/* Caching image for better performance: https://github.com/DylanVann/react-native-fast-image */}
32 |
33 |
34 |
35 |
36 |
37 | {item.title}
38 |
39 |
40 |
41 | {publishedFromNow}
42 |
43 |
44 | );
45 | }
46 |
47 | const styles = StyleSheet.create({
48 | article: {
49 | flexDirection: "row",
50 | paddingVertical: 15,
51 | },
52 | articleImage: {
53 | width: 150,
54 | height: 85,
55 | resizeMode: "contain",
56 | marginRight: 15,
57 | },
58 | articleTitle: {
59 | fontSize: 18,
60 | fontWeight: "bold",
61 | marginBottom: 10,
62 | },
63 | articleDescription: {
64 | fontSize: 16,
65 | marginBottom: 10,
66 | },
67 | articlePublishedAt: {
68 | fontSize: 14,
69 | },
70 | });
71 |
--------------------------------------------------------------------------------
/examples/7-scan-qr-code/ScannerView.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, View } from "react-native";
3 | import * as Animatable from "react-native-animatable";
4 |
5 | export default function ScannerView({ scanned }) {
6 | return (
7 | // https://github.com/oblador/react-native-animatable
8 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | const BORDER = {
22 | position: "absolute",
23 | borderColor: "#fff",
24 | width: 55,
25 | height: 55,
26 | };
27 | const BORDER_WIDTH = 10;
28 | const BORDER_RADIUS = 35;
29 |
30 | const styles = StyleSheet.create({
31 | scannerView: {
32 | width: 220,
33 | height: 220,
34 | },
35 | borderTopLeft: {
36 | ...BORDER,
37 | top: 0,
38 | left: 0,
39 | borderTopLeftRadius: BORDER_RADIUS,
40 | borderTopWidth: BORDER_WIDTH,
41 | borderLeftWidth: BORDER_WIDTH,
42 | },
43 | borderTopRight: {
44 | ...BORDER,
45 | top: 0,
46 | right: 0,
47 | borderTopRightRadius: BORDER_RADIUS,
48 | borderTopWidth: BORDER_WIDTH,
49 | borderRightWidth: BORDER_WIDTH,
50 | },
51 | borderBottomLeft: {
52 | ...BORDER,
53 | bottom: 0,
54 | left: 0,
55 | borderBottomLeftRadius: BORDER_RADIUS,
56 | borderBottomWidth: BORDER_WIDTH,
57 | borderLeftWidth: BORDER_WIDTH,
58 | },
59 | borderBottomRight: {
60 | ...BORDER,
61 | bottom: 0,
62 | right: 0,
63 | borderBottomRightRadius: BORDER_RADIUS,
64 | borderBottomWidth: BORDER_WIDTH,
65 | borderRightWidth: BORDER_WIDTH,
66 | },
67 | });
68 |
--------------------------------------------------------------------------------
/examples/3-the-light/1.TheLight.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StatusBar } from "expo-status-bar";
3 | import React, { useState } from "react";
4 | import {
5 | Dimensions,
6 | Image,
7 | Platform,
8 | SafeAreaView,
9 | StyleSheet,
10 | Switch,
11 | } from "react-native";
12 | import bulbOff from "./assets/bulb-off.jpg";
13 | import bulbOn from "./assets/bulb-on.jpg";
14 |
15 | export default function TheLight() {
16 | // https://reactjs.org/docs/hooks-reference.html#usestate
17 | const [isEnabled, setIsEnabled] = useState(false);
18 | const toggleSwitch = () => setIsEnabled((previousState) => !previousState);
19 |
20 | return (
21 |
22 |
23 |
24 |
29 |
30 | {/* https://reactnative.dev/docs/switch */}
31 |
38 |
39 | );
40 | }
41 |
42 | // https://reactnative.dev/docs/dimensions
43 | const screenHeight = Dimensions.get("window").height;
44 |
45 | const styles = StyleSheet.create({
46 | container: {
47 | flex: 1,
48 | backgroundColor: "black",
49 | paddingTop: Constants.statusBarHeight,
50 | },
51 | image: {
52 | maxWidth: "100%",
53 | maxHeight: screenHeight - Constants.statusBarHeight - 150,
54 | resizeMode: "contain",
55 | marginBottom: 20,
56 | },
57 | switch: {
58 | alignSelf: "center",
59 | // https://reactnative.dev/docs/transforms
60 | // https://reactnative.dev/docs/platform-specific-code
61 | transform: Platform.OS === "android" ? [{ scale: 1.5 }] : [],
62 | },
63 | });
64 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/components/Story.js:
--------------------------------------------------------------------------------
1 | import { Feather } from "@expo/vector-icons";
2 | import React from "react";
3 | import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native";
4 |
5 | export default function Story({ avatar, name, isCreateStory = false, isSeen }) {
6 | return (
7 |
8 |
9 |
21 |
22 |
23 | {isCreateStory && (
24 |
25 |
26 |
27 | )}
28 |
29 |
30 |
31 | {name}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | const styles = StyleSheet.create({
39 | user: {
40 | width: 80,
41 | paddingHorizontal: 4,
42 | },
43 | avatarBorder: {
44 | width: 56,
45 | height: 56,
46 | borderRadius: 28,
47 | borderWidth: 1,
48 | margin: 4,
49 | marginBottom: 8,
50 | justifyContent: "center",
51 | alignItems: "center",
52 | },
53 | avatar: {
54 | width: 50,
55 | height: 50,
56 | borderRadius: 25,
57 | },
58 | plusIcon: {
59 | position: "absolute",
60 | right: 0,
61 | bottom: 0,
62 | width: 18,
63 | height: 18,
64 | borderRadius: 10,
65 | overflow: "hidden",
66 | justifyContent: "center",
67 | alignItems: "center",
68 | backgroundColor: "#3e95f6",
69 | },
70 | name: {
71 | textAlign: "center",
72 | fontSize: 10,
73 | lineHeight: 14,
74 | color: "#262626",
75 | maxWidth: 64,
76 | },
77 | });
78 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | StyleSheet,
4 | SafeAreaView,
5 | ScrollView,
6 | View,
7 | BackHandler,
8 | } from "react-native";
9 | import { Text, ListItem } from "react-native-elements";
10 | import Constants from "expo-constants";
11 | import { EXAMPLE_LIST } from "./example-list";
12 |
13 | export default function App() {
14 | const [exampleIndex, setExampleIndex] = useState(null);
15 |
16 | // Handle when user press Hardware Back Button
17 | useEffect(() => {
18 | const backAction = () => {
19 | // Go back to Example List
20 | if (exampleIndex !== null) {
21 | setExampleIndex(null);
22 | }
23 | // Exit app if user currently in Example List
24 | else {
25 | BackHandler.exitApp();
26 | }
27 |
28 | return true;
29 | };
30 |
31 | // https://reactnative.dev/docs/backhandler
32 | const backHandler = BackHandler.addEventListener(
33 | "hardwareBackPress",
34 | backAction
35 | );
36 |
37 | return () => backHandler.remove();
38 | }, [exampleIndex]);
39 |
40 | if (exampleIndex !== null) return EXAMPLE_LIST[exampleIndex].component;
41 |
42 | return (
43 |
44 |
45 | React Native Expo Examples
46 |
47 |
48 |
49 | {EXAMPLE_LIST.map((l, i) => (
50 | setExampleIndex(i)}>
51 |
52 | Level {l.level}
53 |
54 |
55 |
56 | {l.name}
57 |
58 |
59 | ))}
60 |
61 |
62 | );
63 | }
64 |
65 | const styles = StyleSheet.create({
66 | container: {
67 | flex: 1,
68 | paddingTop: Constants.statusBarHeight,
69 | },
70 | heading: {
71 | textAlign: "center",
72 | padding: 12,
73 | },
74 | title: {
75 | fontWeight: "bold",
76 | },
77 | });
78 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/components/ResultModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal, SafeAreaView, StyleSheet, Text, View } from "react-native";
3 | import { BOX, TEXT } from "../style";
4 | import Button from "./Button";
5 |
6 | const GOOD_STATUS_COLOR = "#7ac79d";
7 | const BAD_STATUS_COLOR = "#f5ac40";
8 |
9 | function ResultModal({
10 | modalVisible,
11 | bmiPoint,
12 | bmiStatus,
13 | bmiInterpretation,
14 | onModalConfirm,
15 | }) {
16 | return (
17 | // https://reactnative.dev/docs/modal
18 |
19 |
20 |
21 | Your Result
22 |
23 |
24 |
35 | {bmiStatus}
36 |
37 |
38 | {bmiPoint}
39 |
40 |
41 | {bmiInterpretation}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 | }
51 |
52 | const styles = StyleSheet.create({
53 | container: {
54 | flex: 1,
55 | backgroundColor: "#1d2236",
56 | },
57 | content: {
58 | flex: 1,
59 | padding: 15,
60 | },
61 | headerText: {
62 | fontSize: 40,
63 | fontWeight: "bold",
64 | color: "#fff",
65 | marginBottom: 10,
66 | },
67 | contentBox: {
68 | ...BOX,
69 | justifyContent: "space-evenly",
70 | marginVertical: 15,
71 | },
72 | bmiStatusText: {
73 | ...TEXT,
74 | fontSize: 24,
75 | fontWeight: "bold",
76 | },
77 | bmiPointText: {
78 | ...TEXT,
79 | fontSize: 70,
80 | fontWeight: "bold",
81 | },
82 | bmiInterpretationText: {
83 | ...TEXT,
84 | fontSize: 18,
85 | lineHeight: 30,
86 | fontWeight: "500",
87 | },
88 | });
89 |
90 | export default React.memo(ResultModal);
91 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/components/UnitSelection.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import React, { useRef } from "react";
3 | import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
4 | import { BOX, CENTER, TEXT_LABEL, TEXT_VALUE } from "../style";
5 |
6 | function UnitSelection({ label, value, minValue, maxValue, setValue }) {
7 | const timer = useRef(null);
8 |
9 | const decreaseValue = () => {
10 | setValue((value) => {
11 | if (value > minValue) {
12 | return value - 1;
13 | }
14 |
15 | return minValue;
16 | });
17 | };
18 |
19 | const increaseValue = () => {
20 | setValue((value) => {
21 | if (value < maxValue) {
22 | return value + 1;
23 | }
24 |
25 | return maxValue;
26 | });
27 | };
28 |
29 | function fastChangeValue(isIncrease) {
30 | const interval = setInterval(() => {
31 | if (isIncrease) {
32 | increaseValue();
33 | } else {
34 | decreaseValue();
35 | }
36 | }, 50);
37 |
38 | timer.current = interval
39 | }
40 |
41 | return (
42 |
43 | {label}
44 |
45 | {value}
46 |
47 |
48 | fastChangeValue(false)}
52 | onPressOut={() => clearInterval(timer.current)}
53 | >
54 |
55 |
56 |
57 | fastChangeValue(true)}
61 | onPressOut={() => clearInterval(timer.current)}
62 | >
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
70 | const styles = StyleSheet.create({
71 | box: {
72 | ...CENTER,
73 | ...BOX,
74 | marginHorizontal: 10,
75 | },
76 | label: {
77 | ...TEXT_LABEL,
78 | },
79 | value: {
80 | ...TEXT_VALUE,
81 | },
82 | buttonGroup: {
83 | flexDirection: "row",
84 | width: "100%",
85 | justifyContent: "space-around",
86 | },
87 | button: {
88 | ...CENTER,
89 | width: 40,
90 | height: 40,
91 | borderRadius: 20,
92 | backgroundColor: "#5e606e",
93 | },
94 | });
95 |
96 | export default React.memo(UnitSelection);
97 |
--------------------------------------------------------------------------------
/examples/8-stopwatch/StopWatch.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StatusBar } from "expo-status-bar";
3 | import React, { useCallback, useRef, useState } from "react";
4 | import { Platform, SafeAreaView, StyleSheet, Text, View } from "react-native";
5 |
6 | import Control from "./Control";
7 | import Result from "./Result";
8 | import { displayTime } from "./util";
9 |
10 | export default function StopWatch() {
11 | const [time, setTime] = useState(0);
12 | const [isRunning, setRunning] = useState(false);
13 | const [results, setResults] = useState([]);
14 | const timer = useRef(null);
15 |
16 | // https://reactjs.org/docs/hooks-reference.html#usecallback
17 | const handleLeftButtonPress = useCallback(() => {
18 | if (isRunning) {
19 | setResults((previousResults) => [time, ...previousResults]);
20 | } else {
21 | setResults([]);
22 | setTime(0);
23 | }
24 | }, [isRunning, time]);
25 |
26 | const handleRightButtonPress = useCallback(() => {
27 | if (!isRunning) {
28 | const interval = setInterval(() => {
29 | setTime((previousTime) => previousTime + 1);
30 | }, 10);
31 |
32 | timer.current = interval;
33 | } else {
34 | clearInterval(timer.current);
35 | }
36 |
37 | setRunning((previousState) => !previousState);
38 | }, [isRunning]);
39 |
40 | return (
41 |
42 |
43 |
44 |
45 | {displayTime(time)}
46 |
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | const styles = StyleSheet.create({
64 | container: {
65 | flex: 1,
66 | backgroundColor: "black",
67 | paddingTop: Constants.statusBarHeight,
68 | },
69 | display: {
70 | flex: 3 / 5,
71 | justifyContent: "center",
72 | alignItems: "center",
73 | },
74 | displayText: {
75 | color: "#fff",
76 | fontSize: 70,
77 | fontWeight: "200",
78 | fontFamily: Platform.OS === "ios" ? "Helvetica Neue" : null,
79 | },
80 | control: {
81 | height: 70,
82 | flexDirection: "row",
83 | justifyContent: "space-around",
84 | },
85 | result: { flex: 2 / 5 },
86 | });
87 |
--------------------------------------------------------------------------------
/example-list.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import HelloWorld1 from "./examples/1-hello-world/1.HelloWorld1";
3 | import HelloWorld2 from "./examples/1-hello-world/2.HelloWorld2";
4 | import MomoLogin from "./examples/2-login-screen/1.MomoLogin";
5 | import FacebookLogin from "./examples/2-login-screen/2.FacebookLogin";
6 | import TheLight from "./examples/3-the-light/1.TheLight";
7 | import TrafficLight from "./examples/3-the-light/2.TrafficLight";
8 | import RegisterForm from "./examples/4-register-form/RegisterForm";
9 | import InstagramFeed from "./examples/5-instagram-feed/InstagramFeed";
10 | import RockPaperScissors from "./examples/6-rock-paper-scissors/RockPaperScissors";
11 | import ScanQrCode from "./examples/7-scan-qr-code/ScanQrCode";
12 | import StopWatch from "./examples/8-stopwatch/StopWatch";
13 | import BMICalculator from "./examples/9-bmi-calculator/BMICalculator";
14 | import MusicPlayer from "./examples/10-music-player/MusicPlayer";
15 | import WorldwideNews from "./examples/11-news/WorldwideNews";
16 | import Pokedex from "./examples/12-pokedex/Pokedex";
17 |
18 | export const EXAMPLE_LIST = [
19 | {
20 | name: "Hello World 1",
21 | level: 1,
22 | component: ,
23 | },
24 | {
25 | name: "Hello World 2",
26 | level: 1,
27 | component: ,
28 | },
29 | {
30 | name: "Momo Login Screen",
31 | level: 2,
32 | component: ,
33 | },
34 | {
35 | name: "Facebook Login Screen",
36 | level: 2,
37 | component: ,
38 | },
39 | {
40 | name: "The Light",
41 | level: 3,
42 | component: ,
43 | },
44 | {
45 | name: "Traffic Light",
46 | level: 3,
47 | component: ,
48 | },
49 | {
50 | name: "Register Form",
51 | level: 4,
52 | component: ,
53 | },
54 | {
55 | name: "Instagram Feed",
56 | level: 5,
57 | component: ,
58 | },
59 | {
60 | name: "Rock Paper Scissors",
61 | level: 6,
62 | component: ,
63 | },
64 | {
65 | name: "Scan QR Code",
66 | level: 7,
67 | component: ,
68 | },
69 | {
70 | name: "Stop Watch",
71 | level: 8,
72 | component: ,
73 | },
74 | {
75 | name: "BMI Calculator",
76 | level: 9,
77 | component: ,
78 | },
79 | {
80 | name: "Music Player",
81 | level: 10,
82 | component: ,
83 | },
84 | {
85 | name: "Worldwide News",
86 | level: 11,
87 | component: ,
88 | },
89 | {
90 | name: "Pokedex",
91 | level: 12,
92 | component: ,
93 | },
94 | ];
95 |
--------------------------------------------------------------------------------
/examples/12-pokedex/Pokedex.js:
--------------------------------------------------------------------------------
1 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
2 | import { NavigationContainer } from "@react-navigation/native";
3 | import { createStackNavigator } from "@react-navigation/stack";
4 | import { StatusBar } from "expo-status-bar";
5 | import React from "react";
6 | import { Image, View } from "react-native";
7 |
8 | import MoveDetail from "./screens/MoveDetail";
9 | import MoveList from "./screens/MoveList";
10 | import PokemonDetail from "./screens/PokemonDetail";
11 | import PokemonList from "./screens/PokemonList";
12 |
13 | import moveTabIcon from "./assets/move-active.png";
14 | import pokemonTabIcon from "./assets/pokemon-active.png";
15 |
16 | // https://reactnavigation.org/docs/stack-navigator/
17 | const PokemonStack = createStackNavigator();
18 | const MoveStack = createStackNavigator();
19 | const stackScreenOptions = {
20 | headerShown: false,
21 | gestureEnabled: true,
22 | };
23 |
24 | function PokemonStackScreen() {
25 | return (
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | function MoveStackScreen() {
34 | return (
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | // https://reactnavigation.org/docs/bottom-tab-navigator/
43 | const Tab = createBottomTabNavigator();
44 | const ActiveColor = "#000000";
45 | const InActiveColor = "#00000077";
46 | const tabScreenOptions = ({ route }) => ({
47 |
48 | headerShown: false,
49 | tabBarActiveTintColor: ActiveColor,
50 | tabBarInactiveTintColor: InActiveColor,
51 | tabBarIcon: ({ color, size }) => {
52 | return (
53 |
54 |
62 |
63 | );
64 | },
65 | });
66 |
67 | export default function Pokedex() {
68 | return (
69 | <>
70 |
71 |
72 |
73 |
76 |
77 |
78 |
79 |
80 | >
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/examples/3-the-light/2.TrafficLight.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StatusBar } from "expo-status-bar";
3 | import React, { useState } from "react";
4 | import {
5 | Dimensions,
6 | Image,
7 | SafeAreaView,
8 | StyleSheet,
9 | Text,
10 | TouchableHighlight,
11 | View,
12 | } from "react-native";
13 | import trafficLightGreen from "./assets/traffic-light-green.png";
14 | import trafficLightRed from "./assets/traffic-light-red.png";
15 | import trafficLightYellow from "./assets/traffic-light-yellow.png";
16 | import trafficLight from "./assets/traffic-light.png";
17 |
18 | export default function TrafficLight() {
19 | const [color, setColor] = useState("");
20 |
21 | let imageSource = trafficLight;
22 | if (color === "red") {
23 | imageSource = trafficLightRed;
24 | } else if (color === "yellow") {
25 | imageSource = trafficLightYellow;
26 | } else if (color === "green") {
27 | imageSource = trafficLightGreen;
28 | }
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 | {/* https://reactnative.dev/docs/touchablehighlight */}
38 | setColor("red")}
42 | >
43 | Red
44 |
45 |
46 | setColor("yellow")}
50 | >
51 | Yellow
52 |
53 |
54 | setColor("green")}
58 | >
59 | Green
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | const screenHeight = Dimensions.get("window").height;
67 |
68 | const styles = StyleSheet.create({
69 | container: {
70 | flex: 1,
71 | backgroundColor: "black",
72 | paddingTop: Constants.statusBarHeight,
73 | },
74 | image: {
75 | maxWidth: "100%",
76 | maxHeight: screenHeight - Constants.statusBarHeight - 150,
77 | resizeMode: "contain",
78 | marginBottom: 20,
79 | },
80 | buttonGroup: {
81 | flexDirection: "row",
82 | justifyContent: "space-evenly",
83 | },
84 | button: {
85 | width: 80,
86 | paddingVertical: 8,
87 | borderRadius: 4,
88 | justifyContent: "center",
89 | alignItems: "center",
90 | },
91 | buttonText: {
92 | color: "#fff",
93 | },
94 | });
95 |
--------------------------------------------------------------------------------
/examples/7-scan-qr-code/ScanQrCode.js:
--------------------------------------------------------------------------------
1 | import { BarCodeScanner } from "expo-barcode-scanner";
2 | import Constants from "expo-constants";
3 | import React, { useState } from "react";
4 | import {
5 | SafeAreaView,
6 | StyleSheet,
7 | Text,
8 | TouchableOpacity,
9 | View,
10 | } from "react-native";
11 | import ScannerView from "./ScannerView";
12 |
13 | export default function ScanQrCode() {
14 | const [scanned, setScanned] = useState(null);
15 | const [hasPermission, setHasPermission] = useState(null);
16 |
17 | const startScan = () => {
18 | setScanned(false);
19 |
20 | // Request camera permission
21 | if (!hasPermission) {
22 | (async () => {
23 | const { status } = await BarCodeScanner.requestPermissionsAsync();
24 | setHasPermission(status === "granted");
25 | })();
26 | }
27 | };
28 |
29 | const handleBarCodeScanned = ({ data }) => {
30 | setScanned(true);
31 | alert(`Scanned QR code with data: "${data}"`);
32 | };
33 |
34 | return (
35 |
36 | {!scanned && hasPermission && (
37 |
38 | {/* https://docs.expo.io/versions/latest/sdk/bar-code-scanner/ */}
39 |
44 |
45 |
46 | Find QR Code to scan
47 |
48 |
49 | )}
50 |
51 |
52 | {scanned !== null && hasPermission === null && (
53 | Requesting for camera permission
54 | )}
55 |
56 | {scanned !== null && hasPermission === false && (
57 | No access to camera
58 | )}
59 |
60 | {scanned === false && hasPermission && (
61 |
62 | )}
63 |
64 | {(scanned !== false) && (
65 |
66 |
67 | {scanned === null ? "Scan now" : "Scan again"}
68 |
69 |
70 | )}
71 |
72 |
73 | );
74 | }
75 |
76 | const styles = StyleSheet.create({
77 | container: {
78 | flex: 1,
79 | paddingTop: Constants.statusBarHeight,
80 | backgroundColor: "#000",
81 | },
82 | content: {
83 | ...StyleSheet.absoluteFillObject,
84 | justifyContent: "center",
85 | alignItems: "center",
86 | },
87 | helpTextWrapper: {
88 | position: "absolute",
89 | bottom: 0,
90 | left: 0,
91 | right: 0,
92 | padding: 15,
93 | alignItems: "center",
94 | backgroundColor: "rgba(0,0,0,0.6)",
95 | },
96 | helpText: {
97 | color: "#fff",
98 | },
99 | button: {
100 | paddingVertical: 15,
101 | paddingHorizontal: 30,
102 | borderRadius: 10,
103 | backgroundColor: "#303940",
104 | },
105 | buttonText: {
106 | color: "#fff",
107 | },
108 | });
109 |
--------------------------------------------------------------------------------
/examples/12-pokedex/screens/MoveDetail.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ScrollView, StyleSheet, Text, View } from "react-native";
3 | import { Image } from "react-native-elements";
4 | import MainHeader from "../components/MainHeader";
5 |
6 | import { BackgroundColor, PokemonTypeIcon } from "../constants";
7 |
8 | export default function MoveDetail({ navigation, route }) {
9 | const { move = {} } = route.params;
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | {move.title}
18 |
19 |
20 |
26 | {move.move_type.toUpperCase()}
27 |
28 |
29 |
30 |
31 | This move is belong to {move.move_category}.{"\n"}
32 | Dodge window is {move.dodge_window} and {"\n"}
33 | Damage window is {move.damage_window}.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Power
42 | {move.power}
43 |
44 |
45 |
46 | Cooldown
47 | {move.cooldown}
48 |
49 |
50 |
51 | Energy
52 | {move.energy_gain}
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | const styles = StyleSheet.create({
62 | container: {
63 | backgroundColor: BackgroundColor,
64 | flex: 1,
65 | },
66 | content: {
67 | flex: 1,
68 | backgroundColor: "#fff",
69 | borderRadius: 50,
70 | padding: 20,
71 | minHeight: 480,
72 | marginVertical: 30,
73 | marginHorizontal: 15,
74 | },
75 | moveName: {
76 | marginTop: 25,
77 | alignSelf: "center",
78 | fontSize: 40,
79 | color: "#4f4f4f",
80 | },
81 | moveType: {
82 | alignItems: "center",
83 | },
84 | description: {
85 | color: "#4f4f4f",
86 | textAlign: "center",
87 | lineHeight: 22,
88 | marginTop: 15,
89 | marginBottom: 35,
90 | },
91 | moveCompare: {
92 | flexDirection: "row",
93 | },
94 | movePart: {
95 | flex: 1,
96 | alignItems: "center",
97 | borderRightColor: "#f0f0f0",
98 | borderRightWidth: 1,
99 | },
100 | borderRightNone: {
101 | borderRightWidth: 0,
102 | },
103 | movePartTitle: {
104 | marginTop: 10,
105 | marginBottom: 10,
106 | fontSize: 20,
107 | color: "#1a87d9",
108 | fontWeight: "bold",
109 | },
110 | hr: {
111 | height: 1,
112 | backgroundColor: "#f0f0f0",
113 | },
114 | });
115 |
--------------------------------------------------------------------------------
/examples/5-instagram-feed/components/Article.js:
--------------------------------------------------------------------------------
1 | import { Feather } from "@expo/vector-icons";
2 | import React from "react";
3 | import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native";
4 |
5 | export default function Article({ item }) {
6 | const articleImage = Image.resolveAssetSource(item.image);
7 | const articleImageRatio = articleImage.width / articleImage.height;
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {item.name}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {item.likeCount} likes
58 | {item.date}
59 |
60 |
61 | );
62 | }
63 |
64 | const ROW = { flexDirection: "row", alignItems: "center" };
65 |
66 | const styles = StyleSheet.create({
67 | article: {
68 | marginBottom: 15,
69 | },
70 | header: {
71 | ...ROW,
72 | justifyContent: "space-between",
73 | height: 60,
74 | paddingHorizontal: 16,
75 | },
76 | user: {
77 | ...ROW,
78 | },
79 | avatar: {
80 | width: 32,
81 | height: 32,
82 | borderRadius: 16,
83 | },
84 | name: {
85 | textAlign: "center",
86 | fontSize: 12,
87 | lineHeight: 14,
88 | color: "#262626",
89 | marginLeft: 12,
90 | fontWeight: "bold",
91 | },
92 | image: {
93 | width: "100%",
94 | height: null,
95 | resizeMode: "contain",
96 | },
97 | action: {
98 | ...ROW,
99 | justifyContent: "space-between",
100 | marginTop: 4,
101 | paddingHorizontal: 8,
102 | },
103 | actionLeft: {
104 | ...ROW,
105 | },
106 | actionButton: {
107 | padding: 8,
108 | },
109 | info: {
110 | paddingHorizontal: 16,
111 | },
112 | likeCount: {
113 | color: "#262626",
114 | fontWeight: "bold",
115 | marginBottom: 8,
116 | },
117 | date: {
118 | color: "#8e8e8e",
119 | fontSize: 10,
120 | marginBottom: 5,
121 | },
122 | });
123 |
--------------------------------------------------------------------------------
/examples/12-pokedex/screens/MoveList.js:
--------------------------------------------------------------------------------
1 | import { uniqBy } from "lodash";
2 | import React, { useCallback, useEffect, useState } from "react";
3 | import {
4 | ActivityIndicator,
5 | Alert,
6 | FlatList,
7 | Image,
8 | StyleSheet,
9 | View,
10 | } from "react-native";
11 | import { ListItem, SearchBar } from "react-native-elements";
12 |
13 | import MainHeader from "../components/MainHeader";
14 | import { FullMovesAPI, PokemonTypeIcon } from "../constants";
15 |
16 | import { debounce } from "lodash";
17 |
18 | export default function MoveList({ navigation }) {
19 | const [displayMoves, setDisplayMoves] = useState([]);
20 | const [moves, setMoves] = useState([]);
21 | const [isLoading, setLoading] = useState(true);
22 | const [keyword, setKeyword] = useState("");
23 |
24 | const renderItem = ({ item, index }) => {
25 | return (
26 | {
29 | navigation.navigate("MoveDetail", {
30 | move: moves[index],
31 | });
32 | }}
33 | >
34 |
35 | {item.title}
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | const searchMove = useCallback(
46 | debounce((keyword) => {
47 | if (keyword === "") {
48 | setDisplayMoves(moves);
49 | } else {
50 | const filteredMoves = moves.filter((move) => {
51 | return move.title.toLowerCase().includes(keyword.toLowerCase());
52 | });
53 | setDisplayMoves(filteredMoves);
54 | }
55 | }, 1000),
56 | [moves]
57 | );
58 |
59 | const inputSearchMove = (keyword) => {
60 | setKeyword(keyword);
61 | searchMove(keyword);
62 | };
63 |
64 | useEffect(() => {
65 | const fetchData = async (url) => {
66 | try {
67 | const response = await fetch(url);
68 | const responseJson = await response.json();
69 | const removeDuplicated = uniqBy(responseJson, "nid");
70 |
71 | setMoves(removeDuplicated);
72 | setDisplayMoves(removeDuplicated);
73 | setKeyword("");
74 | setLoading(false);
75 | } catch {
76 | Alert.alert("Cannot connect to Server!");
77 | }
78 | };
79 |
80 | fetchData(FullMovesAPI);
81 | }, []);
82 |
83 | return (
84 |
85 |
86 |
87 |
96 |
97 | {!isLoading ? (
98 | item.nid}
102 | initialNumToRender={10}
103 | />
104 | ) : (
105 |
106 | )}
107 |
108 | );
109 | }
110 |
111 | const styles = StyleSheet.create({
112 | listItem: {
113 | borderBottomWidth: 1,
114 | borderBottomColor: "#ddd",
115 | },
116 | });
117 |
--------------------------------------------------------------------------------
/examples/2-login-screen/1.MomoLogin.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import Constants from "expo-constants";
3 | import { StatusBar } from "expo-status-bar";
4 | import React from "react";
5 | import {
6 | SafeAreaView,
7 | StyleSheet,
8 | Text,
9 | TextInput,
10 | TouchableOpacity,
11 | View,
12 | } from "react-native";
13 |
14 | export default function MomoLogin() {
15 | return (
16 |
17 | {/* https://docs.expo.io/versions/latest/sdk/status-bar */}
18 |
19 |
20 |
21 |
22 | Xin chào!
23 | DANG QUANG HUY
24 | 0123456789
25 |
26 |
27 |
28 | {/* https://docs.expo.io/guides/icons */}
29 |
30 |
31 | {/* https://reactnative.dev/docs/textinput */}
32 |
40 |
41 | {/* https://reactnative.dev/docs/touchableopacity */}
42 |
43 | ĐĂNG NHẬP
44 |
45 |
46 |
47 |
48 |
49 | QUÊN MẬT KHẨU
50 |
51 |
52 |
53 | THOÁT TÀI KHOẢN
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | const TEXT = {
62 | color: "#fff",
63 | textAlign: "center",
64 | };
65 |
66 | const styles = StyleSheet.create({
67 | container: {
68 | flex: 1,
69 | backgroundColor: "#b0006d",
70 | paddingTop: Constants.statusBarHeight,
71 | },
72 | content: {
73 | paddingHorizontal: 30,
74 | },
75 | textWrapper: {
76 | marginTop: 60,
77 | marginBottom: 30,
78 | },
79 | hiText: {
80 | ...TEXT,
81 | fontSize: 20,
82 | lineHeight: 50,
83 | fontWeight: "bold",
84 | },
85 | userText: {
86 | ...TEXT,
87 | fontSize: 15,
88 | lineHeight: 30,
89 | },
90 | form: {
91 | marginBottom: 30,
92 | },
93 | iconLock: {
94 | color: "#929292",
95 | position: "absolute",
96 | fontSize: 16,
97 | top: 22,
98 | left: 22,
99 | zIndex: 10,
100 | },
101 | inputPassword: {
102 | height: 60,
103 | borderRadius: 30,
104 | paddingHorizontal: 30,
105 | fontSize: 20,
106 | color: "#929292",
107 | backgroundColor: "#fff",
108 | textAlign: "center",
109 | textAlignVertical: "center",
110 | },
111 | buttonLogin: {
112 | height: 50,
113 | borderRadius: 25,
114 | backgroundColor: "#8d015a",
115 | justifyContent: "center",
116 | marginTop: 15,
117 | },
118 | buttonLoginText: {
119 | ...TEXT,
120 | },
121 | action: {
122 | flexDirection: "row",
123 | justifyContent: "space-between",
124 | },
125 | });
126 |
--------------------------------------------------------------------------------
/examples/6-rock-paper-scissors/RockPaperScissors.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import React, { useRef, useState } from "react";
3 | import { Animated, SafeAreaView, StyleSheet, Text, View } from "react-native";
4 | import Actions from "./Actions";
5 | import DisplayResult from "./DisplayResult";
6 |
7 | export default function RockPaperScissors() {
8 | const [userChoice, setUserChoice] = useState(0);
9 | const [computerChoice, setComputerChoice] = useState(0);
10 | const [result, setResult] = useState("");
11 | const [canPlay, setPlay] = useState(true);
12 |
13 | // https://reactjs.org/docs/hooks-reference.html#useref
14 | const fadeAnimation = useRef(new Animated.Value(1)).current;
15 |
16 | function play(choice) {
17 | // 1 = rock, 2 = paper, 3 = scissors
18 | const randomComputerChoice = Math.floor(Math.random() * 3) + 1;
19 | let resultString = "";
20 |
21 | if (choice === 1) {
22 | resultString = randomComputerChoice === 3 ? "WIN" : "LOSE";
23 | } else if (choice === 2) {
24 | resultString = randomComputerChoice === 1 ? "WIN" : "LOSE";
25 | } else {
26 | resultString = randomComputerChoice === 2 ? "WIN" : "LOSE";
27 | }
28 |
29 | if (choice === randomComputerChoice) {
30 | resultString = "DRAW";
31 | }
32 |
33 | setUserChoice(choice);
34 | setComputerChoice(randomComputerChoice);
35 |
36 | // Wait animation hide old result
37 | setTimeout(() => {
38 | setResult(resultString);
39 | }, 300);
40 |
41 | // Animation hide old result and show new result
42 | // https://reactnative.dev/docs/animations
43 | Animated.sequence([
44 | Animated.timing(fadeAnimation, {
45 | toValue: 0,
46 | duration: 300,
47 | useNativeDriver: true,
48 | }),
49 | Animated.timing(fadeAnimation, {
50 | toValue: 1,
51 | duration: 300,
52 | useNativeDriver: true,
53 | }),
54 | ]).start();
55 |
56 | // Disable action when animation running
57 | setPlay(false);
58 | setTimeout(() => {
59 | setPlay(true);
60 | }, 600);
61 | }
62 |
63 | return (
64 |
65 |
66 |
67 |
70 | {result}
71 |
72 |
73 |
74 |
75 | {!result ? (
76 | Let's Play!
77 | ) : (
78 |
82 | )}
83 |
84 |
85 |
86 |
87 |
88 | );
89 | }
90 |
91 | const styles = StyleSheet.create({
92 | container: {
93 | flex: 1,
94 | paddingTop: Constants.statusBarHeight,
95 | },
96 | content: {
97 | flex: 1,
98 | padding: 15,
99 | },
100 | result: {
101 | height: 100,
102 | justifyContent: "flex-end",
103 | alignItems: "center",
104 | },
105 | resultText: {
106 | fontSize: 48,
107 | fontWeight: "bold",
108 | },
109 | screen: {
110 | flex: 1,
111 | flexDirection: "row",
112 | },
113 | readyText: {
114 | marginTop: -48,
115 | alignSelf: "center",
116 | textAlign: "center",
117 | width: "100%",
118 | fontSize: 48,
119 | fontWeight: "bold",
120 | },
121 | });
122 |
--------------------------------------------------------------------------------
/examples/11-news/WorldwideNews.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { uniqBy } from "lodash";
3 | import React, { useEffect, useRef, useState } from "react";
4 | import {
5 | ActivityIndicator,
6 | FlatList,
7 | SafeAreaView,
8 | StyleSheet,
9 | Text,
10 | View,
11 | } from "react-native";
12 |
13 | import { getNews } from "./apis";
14 | import Article from "./Article";
15 |
16 | const PAGE_SIZE = 20;
17 | const PRIMARY_COLOR = "#e74c3c";
18 |
19 | export default function WorldwideNews() {
20 | const [isLoading, setLoading] = useState(true);
21 | const [articles, setArticles] = useState([]);
22 | const [page, setPage] = useState(1);
23 | const [refreshing, setRefreshing] = useState(false);
24 | const hasMoreData = useRef(true);
25 |
26 | useEffect(() => {
27 | const fetchData = async () => {
28 | if (!hasMoreData.current) return;
29 |
30 | const newArticles = await getNews(page, PAGE_SIZE);
31 |
32 | if (newArticles.length < PAGE_SIZE) {
33 | hasMoreData.current = false;
34 | }
35 |
36 | setArticles((articles) => {
37 | // Combine and filter article has no image
38 | const allArticles = articles.concat(
39 | newArticles.filter((article) => article.urlToImage)
40 | );
41 |
42 | // Remove duplicate articles
43 | // https://lodash.com/docs/4.17.15#uniqBy
44 | return uniqBy(allArticles, "url");
45 | });
46 | setLoading(false);
47 | setRefreshing(false);
48 | };
49 |
50 | fetchData();
51 | }, [page]);
52 |
53 | const refreshData = () => {
54 | setPage(1);
55 | setRefreshing(true);
56 | setArticles([]);
57 | hasMoreData.current = true;
58 | };
59 |
60 | const renderArticle = ({ item }) => ;
61 | const renderDivider = () => ;
62 | const renderFooter = () => (
63 |
64 | {hasMoreData.current && }
65 |
66 | );
67 | const keyExtractor = (item) => item.url;
68 |
69 | return (
70 |
71 |
72 | Worldwide News
73 |
74 | {isLoading ? (
75 |
76 | {/* https://reactnative.dev/docs/activityindicator */}
77 |
78 |
79 | ) : (
80 | // Optimizing FlatList: https://reactnative.dev/docs/optimizing-flatlist-configuration
81 | setPage((page) => page + 1)}
90 | onEndReachedThreshold={1}
91 | onRefresh={refreshData}
92 | refreshing={refreshing}
93 | />
94 | )}
95 |
96 |
97 | );
98 | }
99 |
100 | const styles = StyleSheet.create({
101 | container: {
102 | flex: 1,
103 | paddingTop: Constants.statusBarHeight,
104 | },
105 | content: {
106 | flex: 1,
107 | padding: 15,
108 | },
109 | center: {
110 | flex: 1,
111 | justifyContent: "center",
112 | alignItems: "center",
113 | },
114 | headlines: {
115 | fontSize: 32,
116 | fontWeight: "bold",
117 | lineHeight: 50,
118 | color: PRIMARY_COLOR,
119 | },
120 | articleSeparator: {
121 | borderBottomWidth: 1,
122 | borderBottomColor: "#ed7669",
123 | },
124 | });
125 |
--------------------------------------------------------------------------------
/examples/2-login-screen/2.FacebookLogin.js:
--------------------------------------------------------------------------------
1 | import { StatusBar } from "expo-status-bar";
2 | import React from "react";
3 | import {
4 | Image,
5 | SafeAreaView,
6 | StyleSheet,
7 | Text,
8 | TextInput,
9 | TouchableOpacity,
10 | View,
11 | } from "react-native";
12 | import FacebookBannerImage from "./assets/facebook-banner.jpg";
13 |
14 | export default function FacebookLogin() {
15 | return (
16 | <>
17 |
18 |
19 | {/* https://reactnative.dev/docs/image */}
20 |
21 |
22 |
23 |
24 |
29 |
30 |
36 |
37 |
38 | Đăng nhập
39 |
40 |
41 |
42 | Quên mật khẩu?
43 |
44 |
45 |
46 | Quay lại
47 |
48 |
49 |
50 |
51 |
52 |
53 | HOẶC
54 |
55 |
56 |
57 |
58 |
59 | Tạo tài khoản mới
60 |
61 |
62 |
63 |
64 | >
65 | );
66 | }
67 |
68 | const styles = StyleSheet.create({
69 | banner: {
70 | resizeMode: "contain",
71 | width: "100%",
72 | height: null,
73 | aspectRatio: 750 / 460, // Image ratio
74 | },
75 | container: {
76 | flex: 1,
77 | justifyContent: "space-between",
78 | },
79 | content: {
80 | padding: 22,
81 | },
82 | input: {
83 | borderWidth: 1,
84 | borderColor: "#cdcdcf",
85 | color: "#333333",
86 | fontSize: 16,
87 | height: 44,
88 | paddingHorizontal: 15,
89 | },
90 | inputUsername: {
91 | borderBottomWidth: 0,
92 | borderTopLeftRadius: 3,
93 | borderTopRightRadius: 3,
94 | },
95 | inputPassword: {
96 | borderBottomLeftRadius: 3,
97 | borderBottomRightRadius: 3,
98 | },
99 | button: {
100 | height: 42,
101 | borderRadius: 6,
102 | backgroundColor: "#1977f3",
103 | justifyContent: "center",
104 | marginVertical: 15,
105 | },
106 | buttonText: {
107 | color: "#b4cafb",
108 | textAlign: "center",
109 | fontSize: 16,
110 | },
111 | link: {
112 | paddingVertical: 8,
113 | },
114 | linkText: {
115 | color: "#1c6ede",
116 | textAlign: "center",
117 | fontSize: 16,
118 | fontWeight: "500",
119 | },
120 | footer: {
121 | alignItems: "center",
122 | padding: 22,
123 | paddingBottom: 0,
124 | },
125 | divider: {
126 | flexDirection: "row",
127 | alignItems: "center",
128 | width: "70%",
129 | marginBottom: 10,
130 | },
131 | dividerLine: {
132 | flex: 1,
133 | borderBottomWidth: 1,
134 | borderColor: "#cbccd0",
135 | },
136 | dividerText: {
137 | width: 50,
138 | textAlign: "center",
139 | },
140 | buttonRegister: {
141 | width: "100%",
142 | backgroundColor: "#e7f3ff",
143 | },
144 | buttonRegisterText: {
145 | color: "#1077f7",
146 | },
147 | });
148 |
--------------------------------------------------------------------------------
/examples/12-pokedex/screens/PokemonList.js:
--------------------------------------------------------------------------------
1 | import { debounce } from 'lodash';
2 | import React, { useCallback, useEffect, useState } from "react";
3 | import {
4 | ActivityIndicator,
5 | Alert,
6 | FlatList,
7 | StyleSheet,
8 | View,
9 | } from "react-native";
10 | import { Avatar, ListItem, SearchBar } from "react-native-elements";
11 |
12 | import pokeballIcon from "../assets/pokeball.png";
13 | import MainHeader from "../components/MainHeader";
14 | import PokemonType from "../components/PokemonType";
15 | import { FullPokemonsAPI } from "../constants";
16 |
17 | export default function PokemonList({ navigation }) {
18 | const [displayPokemons, setDisplayPokemons] = useState([]);
19 | const [pokemons, setPokemons] = useState([]);
20 | const [isLoading, setLoading] = useState(true);
21 | const [keyword, setKeyword] = useState("");
22 |
23 | const renderItem = ({ item, index }) => {
24 | const pokemonTypes = item.field_pokemon_type.split(", ");
25 | const PokemonTypeElement = pokemonTypes.map((type, index) => {
26 | return (
27 |
28 |
29 |
30 | );
31 | });
32 |
33 | return (
34 | {
37 | navigation.navigate("PokemonDetail", {
38 | pokemon: displayPokemons[index],
39 | });
40 | }}
41 | >
42 |
46 |
47 |
48 | {item.title_1}
49 |
50 |
51 | #
52 | {item.number.length <= 3
53 | ? ("00" + item.number).slice(-3)
54 | : item.number}
55 |
56 |
57 |
58 | {PokemonTypeElement}
59 |
60 | );
61 | };
62 |
63 | const searchPokemon = useCallback(
64 | debounce((keyword) => {
65 | if (keyword === "") {
66 | setDisplayPokemons(pokemons);
67 | } else {
68 | const filteredPokemons = pokemons.filter((pokemon) => {
69 | return pokemon.title_1.toLowerCase().includes(keyword.toLowerCase());
70 | });
71 | setDisplayPokemons(filteredPokemons);
72 | }
73 | }, 1000),
74 | [pokemons]
75 | );
76 |
77 | const inputSearchPokemon = (keyword) => {
78 | setKeyword(keyword);
79 | searchPokemon(keyword);
80 | };
81 |
82 | useEffect(() => {
83 | const fetchData = async (url) => {
84 | try {
85 | const response = await fetch(url);
86 | const responseJson = await response.json();
87 |
88 | setPokemons(responseJson);
89 | setDisplayPokemons(responseJson);
90 | setKeyword("");
91 | setLoading(false);
92 | } catch (error) {
93 | Alert.alert("Cannot connect to Server!");
94 | }
95 | };
96 |
97 | fetchData(FullPokemonsAPI);
98 | }, []);
99 |
100 | return (
101 |
102 |
103 |
104 |
113 |
114 | {!isLoading ? (
115 | item.nid}
119 | initialNumToRender={10}
120 | />
121 | ) : (
122 |
123 | )}
124 |
125 | );
126 | }
127 |
128 | const styles = StyleSheet.create({
129 | listItemSubtitle: { marginTop: 10, color: "#939393" },
130 | });
131 |
--------------------------------------------------------------------------------
/examples/10-music-player/PlayerModal.js:
--------------------------------------------------------------------------------
1 | import { FontAwesome5 } from "@expo/vector-icons";
2 | import Slider from "@react-native-community/slider";
3 | import React from "react";
4 | import {
5 | Image,
6 | Platform,
7 | SafeAreaView,
8 | Text,
9 | TouchableOpacity,
10 | View,
11 | } from "react-native";
12 | import Modal from "react-native-modal";
13 |
14 | import sliderThumbImageAndroid from "./assets/slider-thumb-android.png";
15 | import sliderThumbImageIOS from "./assets/slider-thumb-ios.png";
16 | import { styles } from "./PlayerModal.style";
17 | import { PRIMARY_COLOR } from "./style";
18 | import { displayTime } from "./util";
19 |
20 | export default function PlayerModal({
21 | isModalVisible,
22 | closeModal,
23 | playingSong,
24 | isPlaying,
25 | isBuffering,
26 | currentSongIndex,
27 | currentPosition,
28 | setcurrentPosition,
29 | setRewinding,
30 | updatePosition,
31 | pauseOrResumeSong,
32 | changeSong,
33 | }) {
34 | const opacity = isBuffering ? 0.5 : 1;
35 |
36 | return (
37 | // https://github.com/react-native-modal/react-native-modal
38 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 | {playingSong.name}
56 |
57 | {playingSong.singer}
58 |
59 |
60 |
61 |
62 | {displayTime(currentPosition)}
63 |
64 |
65 |
66 | {displayTime(playingSong.duration)}
67 |
68 |
69 |
70 | setRewinding(true)}
84 | onSlidingComplete={updatePosition}
85 | value={currentPosition}
86 | disabled={isBuffering}
87 | />
88 |
89 |
90 |
91 | changeSong(currentSongIndex - 1)}
93 | disabled={isBuffering}
94 | >
95 |
99 |
100 |
101 |
105 |
109 |
110 |
111 | changeSong(currentSongIndex + 1)}
113 | disabled={isBuffering}
114 | >
115 |
119 |
120 |
121 |
122 |
123 |
124 | );
125 | }
126 |
--------------------------------------------------------------------------------
/examples/12-pokedex/screens/PokemonDetail.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | ActivityIndicator,
4 | Image,
5 | ScrollView,
6 | StyleSheet,
7 | Text,
8 | View,
9 | } from "react-native";
10 |
11 | import MainHeader from "../components/MainHeader";
12 | import PokemonStatus from "../components/PokemonStatus";
13 | import PokemonType from "../components/PokemonType";
14 | import { BackgroundColor } from "../constants";
15 |
16 | export default function PokemonDetail({ navigation, route }) {
17 | const [staProgress, setStaProgress] = useState(0);
18 | const [atkProgress, setAtkProgress] = useState(0);
19 | const [defProgress, setDefProgress] = useState(0);
20 | const [cpProgress, setCpProgress] = useState(0);
21 |
22 | const maxSTA = 400;
23 | const maxATK = 400;
24 | const maxDEF = 400;
25 | const maxCP = 4000;
26 |
27 | const { pokemon = {} } = route.params;
28 | const pokemonTypes = pokemon.field_pokemon_type.split(", ");
29 |
30 | const PokemonTypeElement = pokemonTypes.map((type, index) => {
31 | return (
32 |
33 |
34 | {type.toUpperCase()}
35 |
36 | );
37 | });
38 |
39 | useEffect(() => {
40 | const timeOut = setTimeout(() => {
41 | setStaProgress(+pokemon.sta / maxSTA);
42 | setAtkProgress(+pokemon.atk / maxATK);
43 | setDefProgress(+pokemon.def / maxDEF);
44 | setCpProgress(+pokemon.cp / maxCP);
45 | }, 800);
46 |
47 | // https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup
48 | return () => {
49 | clearTimeout(timeOut);
50 | };
51 | }, []);
52 |
53 | return (
54 |
55 |
56 |
57 |
58 |
59 | }
63 | source={{ uri: pokemon.uri }}
64 | />
65 |
66 | {pokemon.title_1}
67 |
68 |
69 | {PokemonTypeElement}
70 |
71 |
72 |
73 |
74 | {pokemon.title_1} is a pokemon {pokemon.field_pokemon_generation}.
75 | {"\n"}
76 | Capture Rate: {pokemon.catch_rate}, Flee Rate:{" "}
77 | {pokemon.field_flee_rate}
78 |
79 |
80 |
81 |
82 |
87 |
88 |
93 |
94 |
99 |
100 |
105 |
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | const styles = StyleSheet.create({
113 | container: {
114 | backgroundColor: BackgroundColor,
115 | flex: 1,
116 | },
117 | content: {
118 | flex: 1,
119 | backgroundColor: "#fff",
120 | borderRadius: 50,
121 | padding: 20,
122 | minHeight: 480,
123 | marginTop: 120,
124 | marginBottom: 30,
125 | marginHorizontal: 15,
126 | },
127 | avatar: {
128 | position: "absolute",
129 | width: 200,
130 | height: 200,
131 | alignSelf: "center",
132 | top: -120,
133 | resizeMode: "contain",
134 | },
135 | pokemonName: {
136 | marginTop: 90,
137 | alignSelf: "center",
138 | fontSize: 30,
139 | color: "#4f4f4f",
140 | },
141 | description: {
142 | color: "#4f4f4f",
143 | textAlign: "center",
144 | lineHeight: 22,
145 | marginTop: 15,
146 | marginBottom: 35,
147 | },
148 | pokemonType: {
149 | alignItems: "center",
150 | paddingLeft: 10,
151 | paddingRight: 10,
152 | },
153 | });
154 |
--------------------------------------------------------------------------------
/examples/4-register-form/RegisterForm.js:
--------------------------------------------------------------------------------
1 | import { StatusBar } from "expo-status-bar";
2 | import { Formik } from "formik";
3 | import React from "react";
4 | import {
5 | Alert,
6 | SafeAreaView,
7 | Text,
8 | TouchableOpacity,
9 | View,
10 | } from "react-native";
11 | import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
12 |
13 | import FormField from "./FormField";
14 | import { styles } from "./styles";
15 | import { validationSchema } from "./validation";
16 |
17 | export default function RegisterForm() {
18 | function onSubmitHandler(values) {
19 | console.log(values)
20 | // https://reactnative.dev/docs/alert
21 | Alert.alert(
22 | "Register Successfully!",
23 | "Form data: " + JSON.stringify(values)
24 | );
25 | }
26 |
27 | function isFormValid(isValid, touched) {
28 | return isValid && Object.keys(touched).length !== 0;
29 | }
30 |
31 | return (
32 | <>
33 |
34 |
35 |
36 |
37 |
38 |
39 | Register
40 |
41 |
42 | {/* https://github.com/APSL/react-native-keyboard-aware-scroll-view */}
43 |
49 | {/* https://formik.org/docs/overview */}
50 |
61 | {({
62 | handleChange,
63 | handleBlur,
64 | handleSubmit,
65 | values,
66 | errors,
67 | touched,
68 | isValid,
69 | }) => (
70 | <>
71 |
81 |
82 |
92 |
93 |
102 |
103 |
113 |
114 |
124 |
125 |
128 |
136 | SUBMIT
137 |
138 |
139 | >
140 | )}
141 |
142 |
143 |
144 | >
145 | );
146 | }
147 |
--------------------------------------------------------------------------------
/examples/9-bmi-calculator/BMICalculator.js:
--------------------------------------------------------------------------------
1 | import Constants from "expo-constants";
2 | import { StatusBar } from "expo-status-bar";
3 | import React, { useState } from "react";
4 | import { SafeAreaView, StyleSheet, Text, View } from "react-native";
5 | import Button from "./components/Button";
6 | import GenderSelection from "./components/GenderSelection";
7 | import HeightSelection from "./components/HeightSelection";
8 | import ResultModal from "./components/ResultModal";
9 | import UnitSelection from "./components/UnitSelection";
10 | import {
11 | DEFAULT_VALUE,
12 | MAX_AGE,
13 | MAX_WEIGHT,
14 | MIN_AGE,
15 | MIN_WEIGHT,
16 | } from "./const";
17 | import { CENTER, ROW, TEXT } from "./style";
18 |
19 | export default function BMICalculator() {
20 | const [gender, setGender] = useState(DEFAULT_VALUE.gender);
21 | const [height, setHeight] = useState(DEFAULT_VALUE.height);
22 | const [weight, setWeight] = useState(DEFAULT_VALUE.weight);
23 | const [age, setAge] = useState(DEFAULT_VALUE.age);
24 | const [bmiPoint, setBmiPoint] = useState(0);
25 | const [bmiStatus, setBmiStatus] = useState("NORMAL");
26 | const [bmiInterpretation, setBmiInterpretation] = useState("");
27 | const [modalVisible, setModalVisible] = useState(false);
28 |
29 | const calculate = () => {
30 | const point = weight / (height / 100) ** 2;
31 |
32 | if (point < 18.5) {
33 | setBmiStatus("UNDERWEIGHT");
34 | setBmiInterpretation(
35 | "You have a higher than normal body weight.\nTry to exercise more."
36 | );
37 | } else if (point < 25) {
38 | setBmiStatus("NORMAL");
39 | setBmiInterpretation("You have a normal body weight.\nGood job!");
40 | } else {
41 | setBmiStatus("OVERWEIGHT");
42 | setBmiInterpretation(
43 | "You have a higher than normal body weight.\nTry to exercise more."
44 | );
45 | }
46 |
47 | setBmiPoint(point.toFixed(2));
48 | setModalVisible(true);
49 | };
50 |
51 | const reset = () => {
52 | setGender(DEFAULT_VALUE.gender);
53 | setHeight(DEFAULT_VALUE.height);
54 | setWeight(DEFAULT_VALUE.weight);
55 | setAge(DEFAULT_VALUE.age);
56 | setModalVisible(false);
57 | };
58 |
59 | return (
60 |
61 |
62 |
63 |
64 | BMI CALCULATOR
65 |
66 |
67 |
68 |
69 |
70 | setGender("male")}
76 | />
77 | setGender("female")}
83 | />
84 |
85 |
86 |
87 |
92 |
93 |
94 |
95 |
102 |
109 |
110 |
111 |
112 |
113 |
114 |
121 |
122 |
123 | );
124 | }
125 |
126 | const styles = StyleSheet.create({
127 | container: {
128 | flex: 1,
129 | backgroundColor: "#1d2236",
130 | paddingTop: Constants.statusBarHeight,
131 | },
132 | content: {
133 | flex: 1,
134 | paddingHorizontal: 15,
135 | paddingVertical: 10,
136 | },
137 | header: {
138 | ...CENTER,
139 | height: 70,
140 | borderBottomWidth: 5,
141 | borderBottomColor: "#16192e",
142 | },
143 | headerText: {
144 | ...TEXT,
145 | fontSize: 24,
146 | fontWeight: "500",
147 | },
148 | section: {
149 | flex: 1 / 3,
150 | marginVertical: 5,
151 | },
152 | genderSelection: {
153 | ...ROW,
154 | marginHorizontal: -10,
155 | },
156 | weightAndAgeSelection: {
157 | ...ROW,
158 | marginHorizontal: -10,
159 | },
160 | });
161 |
--------------------------------------------------------------------------------
/examples/10-music-player/MusicPlayer.js:
--------------------------------------------------------------------------------
1 | import { Audio } from "expo-av";
2 | import React, { useEffect, useState } from "react";
3 | import {
4 | FlatList,
5 | Image,
6 | SafeAreaView,
7 | Text,
8 | TouchableOpacity,
9 | View,
10 | } from "react-native";
11 |
12 | import { styles } from "./MusicPlayer.style";
13 | import PlayerModal from "./PlayerModal";
14 | import { PLAY_LIST } from "./listSong";
15 | import { displayTime } from "./util";
16 |
17 | export default function MusicPlayer() {
18 | const [isModalVisible, setModalVisible] = useState(false);
19 | const [playingSong, setPlayingSong] = useState({});
20 | const [isBuffering, setBuffering] = useState(false);
21 | const [isPlaying, setPlaying] = useState(false);
22 | const [isRewinding, setRewinding] = useState(false);
23 | const [currentPosition, setcurrentPosition] = useState(0);
24 | const [currentSongIndex, setCurrentSongIndex] = useState(0);
25 | const [playbackObject, setPlaybackObject] = useState(null);
26 |
27 | const renderHeader = () => (
28 |
29 | List Song
30 |
31 | );
32 |
33 | const renderItem = ({ item, index }) => (
34 | playSong(item, index)}
37 | >
38 |
39 |
40 |
41 | {item.name}
42 |
43 |
44 | {item.singer}
45 | {displayTime(item.duration)}
46 |
47 |
48 |
49 | );
50 |
51 | const keyExtractor = (item) => item.sourceUri;
52 |
53 | const playSong = async (song, index) => {
54 | setModalVisible(true);
55 | setBuffering(true);
56 | setPlaying(false);
57 | setcurrentPosition(0);
58 | setCurrentSongIndex(index);
59 | setPlayingSong(song);
60 |
61 | try {
62 | // Unload playback when change sound
63 | if (playbackObject !== null) {
64 | await playbackObject.unloadAsync();
65 | }
66 |
67 | // Play new sound
68 | const { sound } = await Audio.Sound.createAsync(
69 | { uri: song.sourceUri },
70 | { shouldPlay: true }
71 | );
72 | sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
73 | setPlaybackObject(sound);
74 | } catch {
75 | alert("Can't play this song!");
76 | }
77 | };
78 |
79 | const onPlaybackStatusUpdate = ({
80 | isLoaded,
81 | isBuffering,
82 | isPlaying,
83 | error,
84 | }) => {
85 | if (!isLoaded) {
86 | if (error) {
87 | alert(`Encountered a fatal error during playback: ${error}`);
88 | }
89 | } else {
90 | setBuffering(isBuffering);
91 | setPlaying(isPlaying);
92 | }
93 | };
94 |
95 | const updatePosition = async (position) => {
96 | try {
97 | await playbackObject.setPositionAsync(position);
98 | setcurrentPosition(position);
99 | setRewinding(false);
100 | } catch (err) {
101 | console.log(err)
102 | }
103 | };
104 |
105 | const pauseOrResumeSong = async () => {
106 | if (isPlaying) {
107 | setPlaying(false);
108 | playbackObject.pauseAsync();
109 | } else {
110 | if (currentPosition === playingSong.duration) {
111 | setcurrentPosition(0);
112 | await playbackObject.replayAsync();
113 | } else {
114 | await playbackObject.playAsync();
115 | }
116 | }
117 | };
118 |
119 | const changeSong = (index) => {
120 | if (index < 0) index = PLAY_LIST.length - 1;
121 | else if (index === PLAY_LIST.length) index = 0;
122 |
123 | playSong(PLAY_LIST[index], index);
124 | };
125 |
126 | const stopPlaySong = () => {
127 | setModalVisible(false);
128 | setPlaying(false);
129 | playbackObject.unloadAsync();
130 | };
131 |
132 | // https://reactjs.org/docs/hooks-effect.html
133 | useEffect(() => {
134 | // Run time slider
135 | if (isPlaying && !isBuffering) {
136 | const interval = setInterval(async () => {
137 | const {
138 | positionMillis,
139 | durationMillis,
140 | } = await playbackObject.getStatusAsync();
141 |
142 | // Don't update position when user rewinding
143 | if (!isRewinding) setcurrentPosition(positionMillis || 0);
144 |
145 | // Stop sound if positionMillis equals durationMillis or less than 1 second
146 | if (positionMillis >= durationMillis - 900) {
147 | await playbackObject.setPositionAsync(durationMillis);
148 | setcurrentPosition(durationMillis);
149 | setPlaying(false);
150 | clearInterval(interval);
151 | }
152 | }, 1000);
153 |
154 | return () => clearInterval(interval);
155 | }
156 | }, [isPlaying, isBuffering, isRewinding]);
157 |
158 | return (
159 |
160 |
167 |
168 |
182 |
183 | );
184 | }
185 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Expo examples
2 |
3 | Learn React Native (Expo SDK 51) by easy-to-difficult examples.
4 |
5 | _For more advance examples, see [React Native TypeScript examples](https://github.com/robinhuy/react-native-typescript-examples)_
6 |
7 | ## Run project in development
8 |
9 | - Setting up the development environment: https://reactnative.dev/docs/environment-setup.
10 |
11 | - Install dependencies: `yarn` (or `npm install`).
12 |
13 | - To start the development server, run the following command: `yarn start` (or `npm start`).
14 |
15 | After running the command above, you will see a QR code in your terminal. Scan this QR code to open the app on your device.
16 |
17 | If you're using an Android Emulator or iOS Simulator, you can press `a` or `i` respectively to open the app.
18 |
19 | ## Preview
20 |
21 | Scan the following QR code with an **Android device** to open it in Expo Go or a development build.
22 |
23 |
24 |
25 | Scan the following QR code with an **iOS device** to open it in Expo Go or a development build.
26 |
27 |
28 |
29 | ### 1. Hello World
30 |
31 | Learn how to use: [StyleSheet](https://reactnative.dev/docs/stylesheet), [Text](https://reactnative.dev/docs/text), [View](https://reactnative.dev/docs/view), [SafeAreaView](https://reactnative.dev/docs/safeareaview).
32 |
33 |
34 |
35 | ### 2. Login Screen
36 |
37 | Learn how to use: [StatusBar](https://reactnative.dev/docs/statusbar), [Expo Icons](https://docs.expo.dev/guides/icons/), [Images](https://reactnative.dev/docs/images), [TextInput](https://reactnative.dev/docs/textinput), [TouchableOpacity](https://reactnative.dev/docs/touchableopacity).
38 |
39 |
40 |
41 | ### 3. The Light
42 |
43 | Learn how to use: [Transform](https://reactnative.dev/docs/transforms), [Platform](https://reactnative.dev/docs/platform), [Dimensions](https://reactnative.dev/docs/dimensions), [Switch](https://reactnative.dev/docs/switch), [TouchableHighlight](https://reactnative.dev/docs/touchablehighlight), [React Hook useState](https://react.dev/reference/react/useState).
44 |
45 |
46 |
47 | ### 4. Register Form
48 |
49 | Learn how to use: [React Native Keyboard Aware Scroll View](https://github.com/APSL/react-native-keyboard-aware-scroll-view), [Formik](https://formik.org/), [Yup](https://github.com/jquense/yup), [Alert](https://reactnative.dev/docs/alert), **Separate Style**, **Separate Components**.
50 |
51 |
52 |
53 | ### 5. Instagram Feed
54 |
55 | Learn how to use: [ScrollView](https://reactnative.dev/docs/scrollview), [FlatList](https://reactnative.dev/docs/flatlist).
56 |
57 |
58 |
59 | ### 6. Rock Paper Scissors
60 |
61 | Learn how to use: [Animations](https://reactnative.dev/docs/animations), [React Hook useRef](https://react.dev/reference/react/useRef).
62 |
63 |
64 |
65 | ### 7. Scan QR Code
66 |
67 | Learn how to use: [React Native Animatable](https://github.com/oblador/react-native-animatable), [Expo Barcode Scanner](https://docs.expo.dev/versions/latest/sdk/bar-code-scanner/).
68 |
69 |
70 |
71 | ### 8. Stop Watch
72 |
73 | Learn how to use: **Timer**, [React Memo](https://react.dev/reference/react/memo), [React Hook useCallback](https://react.dev/reference/react/useCallback).
74 |
75 |
76 |
77 | ### 9. BMI Calculator
78 |
79 | Learn how to use: [React Native Slider](https://github.com/callstack/react-native-slider), [Modal](https://reactnative.dev/docs/modal).
80 |
81 |
82 |
83 | ### 10. Music Player
84 |
85 | Learn how to use: [Expo AV](https://docs.expo.dev/versions/latest/sdk/av/), [React Native Modal](https://github.com/react-native-modal/react-native-modal), [React Hook useEffect](https://react.dev/reference/react/useEffect).
86 |
87 |
88 |
89 | ### 11. Worldwide News
90 |
91 | Learn how to use: **Connect JSON API**, [ActivityIndicator](https://reactnative.dev/docs/activityindicator), [Linking](https://reactnative.dev/docs/linking), [Lodash](https://lodash.com/).
92 |
93 |
94 |
95 | ### 12. Pokedex
96 |
97 | Learn how to use: [React Native Elements](https://reactnativeelements.com/), [React Navigation](https://reactnavigation.org/), [React Native Progress](https://github.com/oblador/react-native-progress), **React Hook useEffect with Cleanup**.
98 |
99 |
100 |
101 | ---
102 |
103 | ## License
104 |
105 | This project is licensed under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details.
106 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------