├── src ├── components │ ├── Loading │ │ ├── index.tsx │ │ └── Loading.tsx │ └── CountryItem │ │ ├── index.tsx │ │ ├── CountryItem.tsx │ │ └── styles.tsx ├── screens │ ├── DataScreen │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── DataScreen.tsx │ ├── ActiveScreen │ │ ├── styles.tsx │ │ ├── index.tsx │ │ └── ActiveScreen.tsx │ ├── PlotsScreen │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── PlotsScreen.tsx │ ├── ConfirmedScreen │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── ConfirmedScreen.tsx │ └── TotalsScreen │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── TotalsScreen.tsx ├── styles │ ├── index.tsx │ └── global.tsx ├── models │ ├── ApiData.tsx │ ├── change.tsx │ ├── PieData.tsx │ └── CountryInfo.tsx └── utils │ ├── api.tsx │ └── helpers.tsx ├── assets ├── icon.png └── splash.png ├── images ├── screenshot_1.png ├── screenshot_2.png ├── screenshot_3.png └── screenshot_4.png ├── babel.config.js ├── .expo-shared └── assets.json ├── .gitignore ├── tsconfig.json ├── README.md ├── app.json ├── package.json └── App.tsx /src/components/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/screens/DataScreen/index.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/screens/ActiveScreen/styles.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/assets/icon.png -------------------------------------------------------------------------------- /src/styles/index.tsx: -------------------------------------------------------------------------------- 1 | import * as gloabl from './global'; 2 | export default gloabl; -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/assets/splash.png -------------------------------------------------------------------------------- /images/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/images/screenshot_1.png -------------------------------------------------------------------------------- /images/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/images/screenshot_2.png -------------------------------------------------------------------------------- /images/screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/images/screenshot_3.png -------------------------------------------------------------------------------- /images/screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedaElmar/CoronaLive/master/images/screenshot_4.png -------------------------------------------------------------------------------- /src/screens/PlotsScreen/index.tsx: -------------------------------------------------------------------------------- 1 | import PlotsScreen from './PlotsScreen'; 2 | export default PlotsScreen; -------------------------------------------------------------------------------- /src/components/CountryItem/index.tsx: -------------------------------------------------------------------------------- 1 | import CountryItem from './CountryItem'; 2 | export default CountryItem; -------------------------------------------------------------------------------- /src/screens/ActiveScreen/index.tsx: -------------------------------------------------------------------------------- 1 | import ActiveScreen from './ActiveScreen'; 2 | export default ActiveScreen; -------------------------------------------------------------------------------- /src/screens/ConfirmedScreen/index.tsx: -------------------------------------------------------------------------------- 1 | import ConfirmedScreen from './ConfirmedScreen'; 2 | export default ConfirmedScreen; -------------------------------------------------------------------------------- /src/screens/TotalsScreen/index.tsx: -------------------------------------------------------------------------------- 1 | import TotalsScreen from './TotalsScreen'; 2 | export default TotalsScreen; 3 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /src/models/ApiData.tsx: -------------------------------------------------------------------------------- 1 | import { CountryInfo } from "./CountryInfo"; 2 | 3 | export class ApiData { 4 | 5 | countries_stat : Array; 6 | statistic_taken_at: string; 7 | 8 | constructor() { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/models/change.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @description represent a new data 3 | * @interface 4 | * 5 | */ 6 | 7 | export interface Change { 8 | 9 | newConfirmed: number; 10 | newRecovered: number; 11 | newDeaths: number; 12 | growthPercent: number; 13 | 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoronaLive 2 | 3 | A React Native application to track the COVID-19 around the world. 4 | 5 | --- 6 | 7 | ### Screenshots 8 | 9 |

10 | 11 | 12 | 13 | 14 |

-------------------------------------------------------------------------------- /src/models/PieData.tsx: -------------------------------------------------------------------------------- 1 | export default class PieData { 2 | 3 | name: string; 4 | data: number; 5 | color: string; 6 | legendFontColor: string; 7 | legendFontSize: number; 8 | 9 | constructor(name: string, data: number, color: string) { 10 | this.name = name; 11 | this.data = data; 12 | this.color = color; 13 | this.legendFontColor = '#f7f1e3'; 14 | this.legendFontSize = 15; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, StatusBar, ActivityIndicator } from 'react-native'; 3 | import global from '../../styles'; 4 | 5 | const Loading = () => 6 | 7 | 8 | 9 | 10 | 11 | export default Loading; -------------------------------------------------------------------------------- /src/screens/PlotsScreen/styles.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { StyleSheet } from 'react-native'; 3 | 4 | 5 | const styles = StyleSheet.create({ 6 | container: { 7 | justifyContent: 'center' 8 | }, 9 | text: { 10 | paddingVertical: 15, 11 | textAlign: "center", 12 | fontSize: 24, 13 | color: "#bdc3c7", 14 | fontFamily: "Roboto", 15 | fontWeight: "bold", 16 | }, 17 | 18 | stackecChart: { 19 | 20 | } 21 | }); 22 | 23 | export default styles; -------------------------------------------------------------------------------- /src/screens/DataScreen/styles.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | 4 | const styles = StyleSheet.create({ 5 | container: { 6 | justifyContent: 'center' 7 | }, 8 | text: { 9 | paddingVertical: 20, 10 | textAlign: "center", 11 | fontSize: 24, 12 | color: "#bdc3c7", 13 | fontFamily: "Roboto", 14 | fontWeight: "bold", 15 | }, 16 | 17 | stackecChart: { 18 | borderRadius: 5, 19 | } 20 | }); 21 | 22 | export default styles; -------------------------------------------------------------------------------- /src/models/CountryInfo.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * cases: Confirmed per country 3 | * 4 | * deaths: deaths per country 5 | * 6 | * total_recovered: recoverd per country 7 | */ 8 | 9 | 10 | export class CountryInfo { 11 | 12 | active_cases?: string; // active nows 13 | cases?: string; // confirmed 14 | country_name?: string; 15 | deaths?: string; 16 | key?: string; 17 | new_cases?: string; // new cases 18 | new_deaths?: string; 19 | region?: string; 20 | serious_critical?: string; 21 | total_cases_per_1m_population?: string; 22 | total_recovered?: string; // recovered 23 | 24 | constructor() { 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/utils/api.tsx: -------------------------------------------------------------------------------- 1 | const API = 'coronavirus-monitor.p.rapidapi.com' 2 | const KEY = 'ad52b86c33mshca1e9a04685f047p18c453jsn382f736f82be' 3 | 4 | export const casesByCountry = async () => { 5 | try { 6 | const res = await fetch(`https://coronavirus-monitor.p.rapidapi.com/coronavirus/cases_by_country.php 7 | `, { 8 | headers: { 9 | 'x-rapidapi-host': API, 10 | 'x-rapidapi-key': KEY, 11 | Accpet: 'application/json', 12 | 'Content-Type': 'application/json' 13 | }, 14 | }); 15 | const resJson = await res.json(); 16 | return resJson; 17 | } 18 | catch (err) { 19 | return console.error(err); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/CountryItem/CountryItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View } from 'react-native'; 3 | import styles from './styles'; 4 | 5 | const CountryItem = ({ name, cases, deaths, recovered, seriousColor }) => { 6 | 7 | return ( 8 | 9 | {name} 10 | {cases} 11 | {recovered} 12 | {deaths} 13 | 14 | 15 | ) 16 | 17 | } 18 | export default CountryItem; -------------------------------------------------------------------------------- /src/styles/global.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export const BG_COLOR = '#343336'; 4 | 5 | export const BAR_COLOR = '#4e4d52'; 6 | 7 | export const TEXT_COLOR = '#e5dbda'; 8 | 9 | export const HEADER_TEXT_COLOR = '#fff'; 10 | 11 | export const MUTED_COLOR = '#8e8786'; 12 | 13 | export const LINK_COLOR = '#19ccba'; 14 | 15 | export const ACCENT_COLORS = ['#d31d65', '#751c53', '#c248c0', '#7d6e8b', '#bbc6f7']; 16 | 17 | 18 | export const COMMON_STYLES = StyleSheet.create({ 19 | 20 | container: { 21 | flex: 1, 22 | flexDirection: 'column', 23 | justifyContent: 'center', 24 | }, 25 | 26 | text: { 27 | color: TEXT_COLOR, 28 | fontFamily: 'Helvetica Neue' 29 | } 30 | }); 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/screens/ConfirmedScreen/styles.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | 5 | header: { 6 | flexDirection: 'row', 7 | paddingVertical: 25, 8 | borderBottomColor: 'white', 9 | borderBottomWidth: 2, 10 | width: "100%" 11 | }, 12 | 13 | 14 | labelText: { 15 | flex: 1, 16 | textAlign: "center", 17 | alignItems: "center", 18 | alignSelf: "center", 19 | fontSize: 16, 20 | 21 | fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto', 22 | width: "100%" 23 | }, 24 | 25 | confirmedText: { 26 | color: '#ecf0f1', 27 | }, 28 | 29 | countryText: { 30 | color: '#1e90ff', 31 | }, 32 | 33 | deathsText: { 34 | color: '#7f8c8d' 35 | 36 | }, 37 | 38 | recovredText: { 39 | color: '#27ae60' 40 | }, 41 | 42 | }); 43 | 44 | export default styles; -------------------------------------------------------------------------------- /src/utils/helpers.tsx: -------------------------------------------------------------------------------- 1 | import { CountryInfo } from "../models/CountryInfo"; 2 | 3 | // helper: add comma to number 4 | export const numberWithComma = (x: number): string => 5 | x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 6 | 7 | //helper: string convert to number 8 | export const toNumber = (str: string): number => 9 | parseInt(str.replace(/,/g, '')); 10 | 11 | export const getTopOf = (arr: Array, info: string, top: number): Array => 12 | arr.sort((a, b) => parseInt(b[info].replace(/,/g, '')) - parseInt(a[info].replace(/,/g, ''))).slice(0, top); 13 | 14 | // helper: get list of countries names 15 | /* private getNames(arry: CountryInfo[]): string[] { 16 | let names: string[]; 17 | arry.forEach((c) => names.push(c.country_name)) 18 | return names; 19 | } */ 20 | 21 | // helper: get the total 22 | export const totalOf = (arr: Array, str: string): number => 23 | arr.map((c) => parseInt(c[str].replace(/,/g, ''))).reduce((pValue, cValue) => pValue + cValue, 0); 24 | 25 | -------------------------------------------------------------------------------- /src/screens/TotalsScreen/styles.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | 5 | numberText: { 6 | fontSize: 44, 7 | fontWeight: 'bold', 8 | textAlign: 'center', 9 | fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto' 10 | }, 11 | labelText: { 12 | fontSize: 25, 13 | textAlign: 'center', 14 | fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto' 15 | }, 16 | 17 | viewBorder: { 18 | paddingBottom: 10, 19 | }, 20 | 21 | confirmedText: { 22 | color: '#ecf0f1', 23 | 24 | }, 25 | 26 | confirmedNumber: { 27 | color: 'rgb(244, 19, 19)', 28 | 29 | }, 30 | 31 | deathsText: { 32 | color: '#7f8c8d' 33 | 34 | }, 35 | 36 | deathsNumber: { 37 | color: '#bdc3c7', 38 | 39 | }, 40 | recovredText: { 41 | color: '#27ae60' 42 | }, 43 | 44 | recoveredNumber: { 45 | color: '#2ecc71', 46 | } 47 | 48 | 49 | }); 50 | 51 | 52 | export default styles; 53 | -------------------------------------------------------------------------------- /src/components/CountryItem/styles.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | 5 | 6 | countryItem: { 7 | flex: 1, 8 | flexDirection: 'row', 9 | paddingVertical: 20, 10 | borderBottomColor: 'gray', 11 | borderBottomWidth: 1, 12 | width: "100%" 13 | 14 | }, 15 | 16 | labelText: { 17 | flex: 1, 18 | textAlign: "center", 19 | alignItems: "center", 20 | alignSelf: "center", 21 | fontSize: 18, 22 | 23 | fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto', 24 | width: "100%" 25 | }, 26 | 27 | 28 | countryName: { 29 | color: '#70a1ff', 30 | }, 31 | 32 | 33 | recoveredNumber: { 34 | color: '#2ecc71', 35 | }, 36 | 37 | 38 | deathsNumber: { 39 | color: '#bdc3c7', 40 | 41 | }, 42 | 43 | confirmedNumber: { 44 | color: 'rgb(244, 19, 19)', 45 | 46 | }, 47 | 48 | confirmedText: { 49 | color: '#ecf0f1', 50 | 51 | }, 52 | 53 | 54 | 55 | }); 56 | 57 | export default styles; -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "CoronaLive", 4 | "android": { 5 | "config": { 6 | "googleMobileAdsAppId": "ca-app-pub-8120812323524890~7173838622" 7 | }, 8 | "package": "com.ahmnouira.CoronaLive", 9 | "versionCode": 1 10 | }, 11 | "backgroundColor": "#343336", 12 | "slug": "CoronaLive", 13 | "privacy": "unlisted", 14 | "sdkVersion": "36.0.0", 15 | "platforms": [ 16 | "ios", 17 | "android", 18 | "web" 19 | ], 20 | "description": "CoronaLive App allows you to have fast, rapid overview of the COVID-19 Cases-Recoverd-Deaths arround the world", 21 | "version": "1.0.0", 22 | "orientation": "default", 23 | "icon": "./assets/icon.png", 24 | "splash": { 25 | "image": "./assets/splash.png", 26 | "resizeMode": "cover", 27 | "backgroundColor": "#001635" 28 | }, 29 | "updates": { 30 | "fallbackToCacheTimeout": 0 31 | }, 32 | "assetBundlePatterns": [ 33 | "**/*" 34 | ], 35 | "ios": { 36 | "bundleIdentifier": "com.ahmnouira.CoronaLive", 37 | "buildNumber": "1.0.0", 38 | "supportsTablet": true 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@react-native-community/masked-view": "0.1.5", 12 | "@react-navigation/bottom-tabs": "^5.2.2", 13 | "@react-navigation/native": "^5.1.1", 14 | "expo": "~36.0.0", 15 | "react": "~16.9.0", 16 | "react-dom": "~16.9.0", 17 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz", 18 | "react-native-chart-kit": "^5.2.0", 19 | "react-native-gesture-handler": "~1.5.0", 20 | "react-native-reanimated": "~1.4.0", 21 | "react-native-safe-area-context": "0.6.0", 22 | "react-native-screens": "2.0.0-alpha.12", 23 | "react-native-svg": "^9.13.3", 24 | "react-native-web": "~0.11.7", 25 | "react-native-webview": "^7.4.3" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.0.0", 29 | "@types/react": "~16.9.0", 30 | "@types/react-native": "~0.60.23", 31 | "babel-preset-expo": "~8.0.0", 32 | "typescript": "~3.6.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavigationContainer } from '@react-navigation/native'; 3 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; 4 | import PlotsScreen from './src/screens/PlotsScreen'; 5 | import TotalsScreen from './src/screens/TotalsScreen'; 6 | import ActiveScreen from './src/screens/ActiveScreen'; 7 | import ConfirmedScreen from './src/screens/ConfirmedScreen'; 8 | import DataScreen from './src/screens/DataScreen/DataScreen'; 9 | import { Ionicons } from '@expo/vector-icons'; 10 | import global from './src/styles'; 11 | 12 | const Tab = createBottomTabNavigator(); 13 | 14 | export default function App() { 15 | 16 | return ( 17 | 18 | ({ 25 | tabBarIcon: ({ focused, color, size }) => { 26 | let iconName: string; 27 | 28 | switch (route.name) { 29 | case 'Totals': 30 | iconName = 'ios-people'; 31 | break; 32 | case 'Stats': 33 | iconName = 'ios-paper'; 34 | break; 35 | case 'Plots': 36 | iconName = 'ios-pulse'; 37 | break; 38 | case 'Analytics': 39 | iconName = 'ios-stats'; 40 | break; 41 | case 'News': 42 | iconName = 'ios-medkit'; 43 | } 44 | return 45 | }, 46 | 47 | })}> 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/screens/TotalsScreen/TotalsScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, StatusBar } from 'react-native'; 3 | import styles from './styles'; 4 | import { casesByCountry } from '../../utils/api'; 5 | import { numberWithComma, totalOf } from '../../utils/helpers'; 6 | import { ApiData } from '../../models/ApiData'; 7 | import Loading from '../../components/Loading/Loading'; 8 | import global from '../../styles'; 9 | 10 | export default function TotalsScreen(): JSX.Element { 11 | 12 | const [totalConfirmed, setTotalConfirmed] = React.useState(''); 13 | const [totalDeaths, setTotalDeaths] = React.useState(''); 14 | const [totalRecovred, setTotalRecovred] = React.useState(''); 15 | const [isLoading, setIsLoading] = React.useState(true); 16 | 17 | React.useEffect(() => { 18 | 19 | casesByCountry().then((res: ApiData) => { 20 | 21 | const { statistic_taken_at, countries_stat } = res; 22 | 23 | setTotalConfirmed(numberWithComma(totalOf(countries_stat, 'cases'))); 24 | setTotalDeaths(numberWithComma(totalOf(countries_stat, 'deaths'))); 25 | setTotalRecovred(numberWithComma(totalOf(countries_stat, 'total_recovered'))); 26 | setIsLoading(false); 27 | 28 | }).catch(err => console.error(err)); 29 | }); 30 | 31 | if (isLoading) { 32 | return ( 33 | 34 | ); 35 | } 36 | return ( 37 | 38 | 39 | 40 | 41 | Total Confirmed 42 | {totalConfirmed} 43 | 44 | 45 | 46 | Total Deaths 47 | {totalDeaths} 48 | 49 | 50 | Total Recovered 51 | {totalRecovred} 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/screens/ConfirmedScreen/ConfirmedScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, Button, ActivityIndicator, StatusBar, ScrollView, SafeAreaView } from 'react-native'; 3 | import global from '../../styles'; 4 | import styles from './styles'; 5 | import { casesByCountry } from '../../utils/api'; 6 | import { CountryInfo } from '../../models/CountryInfo'; 7 | import CountryItem from '../../components/CountryItem'; 8 | import { ApiData } from '../../models/ApiData'; 9 | import Loading from '../../components/Loading/Loading'; 10 | 11 | export default function ConfirmedScreen() { 12 | 13 | const [lastUpdated, setLastUpdated] = React.useState(''); 14 | const [isLoading, setIsLoading] = React.useState(true); 15 | const [info, setInfo] = React.useState(Array()); 16 | 17 | React.useEffect(() => { 18 | 19 | casesByCountry().then((res: ApiData) => { 20 | let { statistic_taken_at, countries_stat } = res; 21 | setIsLoading(false); 22 | setInfo(_addKeys(countries_stat)); 23 | setLastUpdated(statistic_taken_at); 24 | }); 25 | }); 26 | 27 | 28 | const _addKeys = (arrCountryInfo: CountryInfo[]) => 29 | arrCountryInfo.map(countryInfo => Object.assign(countryInfo, { key: countryInfo.country_name })) 30 | 31 | 32 | // filter by names alphabitic 33 | const _filterByCountryName = (): void => { 34 | 35 | let sorted: CountryInfo[] = info.sort((a, b) => { 36 | if (a.country_name < b.country_name) return -1; 37 | if (a.country_name > b.country_name) return 1; 38 | return 0; 39 | }); 40 | setInfo(_addKeys(sorted)); 41 | } 42 | 43 | const _renderCountries = () => info.map((c: CountryInfo) => 44 | 45 | ) 46 | 47 | const _getData = (filter?: string): void => { 48 | 49 | casesByCountry().then((res: ApiData) => { 50 | 51 | let { statistic_taken_at, countries_stat } = res; 52 | 53 | if (filter === 'total_recovered' || filter === 'deaths') { 54 | countries_stat = countries_stat.sort((a, b) => parseInt(b[filter].replace(/,/g, '')) - parseInt(a[filter].replace(/,/g, ''))); 55 | } 56 | setIsLoading(false); 57 | setInfo(_addKeys(countries_stat)); 58 | setLastUpdated(statistic_taken_at); 59 | }); 60 | 61 | } 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | _filterByCountryName()}>Country 69 | _getData()}>Confirmed 70 | _getData('total_recovered')}> Recovered 71 | _getData('deaths')}>Deaths 72 | 73 | 74 | {isLoading && ( 75 | 76 | )} 77 | 78 | {!isLoading && ( 79 | 80 | {_renderCountries()} 81 | 82 | )} 83 | 84 | ) 85 | 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/screens/ActiveScreen/ActiveScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, SafeAreaView, StatusBar, ActivityIndicator, ScrollView } from 'react-native'; 3 | import global from '../../styles'; 4 | import { CountryInfo } from '../../models/CountryInfo'; 5 | import { casesByCountry } from '../../utils/api'; 6 | import { ApiData } from '../../models/ApiData'; 7 | import CountryItem from '../../components/CountryItem'; 8 | 9 | // because the same styling here 10 | import styles from '../ConfirmedScreen/styles'; 11 | 12 | class ActiveScreen extends React.Component { 13 | 14 | 15 | constructor(props: any) { 16 | 17 | super(props); 18 | 19 | this.state = { 20 | info: Array(), 21 | listOfCountries: Array(), 22 | lastUpdate: '', 23 | isLoading: true 24 | } 25 | } 26 | 27 | componentDidMount() { 28 | // init 29 | this._getData(); 30 | 31 | } 32 | 33 | 34 | _getData(filter: string = 'new_cases'): void { 35 | 36 | casesByCountry().then((res: ApiData) => { 37 | const { statistic_taken_at, countries_stat } = res; 38 | 39 | let sorted: CountryInfo[] = countries_stat.sort((a, b) => parseInt(b[filter].replace(/,/g, '')) - parseInt(a[filter].replace(/,/g, ''))); 40 | this.setState({ isLoading: false, info: this._addKeys(sorted), lastUpdate: statistic_taken_at }); 41 | }); 42 | 43 | } 44 | 45 | 46 | 47 | 48 | 49 | _filterByCountryName = () => { 50 | let list: Array = this.state['info']; 51 | 52 | let sorted = list.sort((a, b) => { 53 | if (a.country_name < b.country_name) return -1; 54 | if (a.country_name > b.country_name) return 1; 55 | return 0; 56 | }); 57 | this.setState({ info: this._addKeys(sorted) }); 58 | } 59 | 60 | _addKeys = (arrCountryInfo: CountryInfo[]) => 61 | arrCountryInfo.map(countryInfo => Object.assign(countryInfo, { key: countryInfo.country_name })) 62 | 63 | 64 | // pass the new data instead 65 | _renderCountries = () => this.state['info'].map((c: CountryInfo) => 66 | 67 | ) 68 | 69 | render() { 70 | 71 | return ( 72 | 73 | 74 | 75 | 76 | this._filterByCountryName()}>Country 77 | this._getData()}>Cases 78 | this._getData('serious_critical')}>Serious 79 | this._getData('new_deaths')}>Deaths 80 | 81 | 82 | {this.state['isLoading'] && ( 83 | 84 | 85 | 86 | )} 87 | 88 | {!this.state['isLoading'] && ( 89 | 90 | {/* */} 91 | {this._renderCountries()} 92 | 93 | )} 94 | 95 | ) 96 | 97 | } 98 | } 99 | 100 | 101 | 102 | export default ActiveScreen; -------------------------------------------------------------------------------- /src/screens/PlotsScreen/PlotsScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { View, Text, StatusBar, Dimensions } from 'react-native'; 3 | import styles from './styles'; 4 | import { BarChart, PieChart, ChartConfig, ChartData } from "react-native-chart-kit"; 5 | import gloabl from '../../styles'; 6 | import { casesByCountry } from '../../utils/api'; 7 | import { getTopOf, toNumber, totalOf } from '../../utils/helpers'; 8 | import { ApiData } from '../../models/ApiData'; 9 | import Loading from '../../components/Loading/Loading'; 10 | import PieData from '../../models/PieData'; 11 | 12 | // pieConfig and barConfig are the same 'maybe shared' 13 | const CHAT_CONFIG: ChartConfig = { 14 | backgroundGradientFrom: "#1E2923", 15 | backgroundGradientFromOpacity: 0, 16 | backgroundGradientTo: "#08130D", 17 | backgroundGradientToOpacity: 0.7, 18 | color: (opacity = 1) => `rgba(254, 245, 48, ${opacity})`, 19 | labelColor: (opacity = 0.8) => `rgba(255, 255, 255, ${opacity})`, 20 | strokeWidth: 5, // optional, default 3 21 | barPercentage: 1, 22 | fillShadowGradientOpacity: 0.7, 23 | decimalPlaces: null, 24 | } 25 | // shared 26 | const WIDTH: number = Dimensions.get('window').width; 27 | const HEIGHT: number = Math.round(Dimensions.get('window').height) / 2 - 98; 28 | 29 | 30 | const chartData: ChartData = { 31 | labels: Array(), 32 | datasets: [ 33 | { 34 | data: Array() 35 | } 36 | ] 37 | } 38 | 39 | export default function PlotsScreen() { 40 | 41 | const [isLoading, setIsLoading] = useState(true); 42 | const [barData, setBarData] = useState(chartData); 43 | const [pieData, setPieData] = useState(Array()); 44 | 45 | useEffect(() => { 46 | _getTotats(); 47 | }); 48 | 49 | const _getTotats = () => { 50 | 51 | casesByCountry().then((res: ApiData) => { 52 | 53 | const { statistic_taken_at, countries_stat } = res; 54 | 55 | // console.log('topDeaths', topCountries); 56 | 57 | const barData: ChartData = { 58 | labels: [...getTopOf(countries_stat, 'deaths', 7).map(obj => obj.country_name)], 59 | datasets: [ 60 | { 61 | data: [...getTopOf(countries_stat, 'deaths', 7).map(obj => toNumber(obj.deaths))] 62 | } 63 | ], 64 | }; 65 | 66 | // console.log('barData', barData); 67 | 68 | let totaleWorldCases = totalOf(countries_stat, 'cases'); 69 | let top5Cases = getTopOf(countries_stat, 'cases', 5); 70 | 71 | 72 | 73 | for (let i = 0; i < top5Cases.length; i++) { 74 | totaleWorldCases -= toNumber(top5Cases[i].cases); 75 | } 76 | 77 | console.log(top5Cases); 78 | console.log(totaleWorldCases); 79 | 80 | const pieData: Array = [ 81 | { 82 | name: top5Cases[0].country_name, 83 | data: toNumber(top5Cases[0].cases), 84 | color: "rgba(255, 0, 0, 0.7)", 85 | legendFontColor: "#f7f1e3", 86 | legendFontSize: 15 87 | }, 88 | { 89 | name: top5Cases[1].country_name, 90 | data: toNumber(top5Cases[1].cases), 91 | color: "rgba(255, 115, 0, 0.7)", 92 | legendFontColor: "#f7f1e3", 93 | legendFontSize: 15 94 | }, 95 | { 96 | name: top5Cases[2].country_name, 97 | data: toNumber(top5Cases[2].cases), 98 | color: "rgba(255, 236, 0, 0.7)", 99 | legendFontColor: "#f7f1e3", 100 | legendFontSize: 15 101 | }, 102 | { 103 | name: top5Cases[3].country_name, 104 | data: toNumber(top5Cases[3].cases), 105 | color: "rgba(82, 215, 38, 0.7)", 106 | legendFontColor: "#f7f1e3", 107 | legendFontSize: 15 108 | }, 109 | { 110 | name: top5Cases[4].country_name, 111 | data: toNumber(top5Cases[4].cases), 112 | color: "rgba(124, 221, 221, 0.7)", 113 | legendFontColor: "#f7f1e3", 114 | legendFontSize: 15 115 | }, 116 | // compare to other 117 | { 118 | name: "Others", 119 | data: totaleWorldCases, 120 | color: "rgba(0, 126, 214, 0.7)", 121 | legendFontColor: "#f7f1e3", 122 | legendFontSize: 15 123 | } 124 | ]; 125 | setIsLoading(false); 126 | setBarData(barData); 127 | setPieData(pieData); 128 | }); 129 | } 130 | if (isLoading) { 131 | return ( 132 | 133 | ) 134 | } 135 | return ( 136 | 137 | 138 | Top Deaths 139 | 151 | Corona Around The World 152 | 153 | 164 | 165 | ) 166 | } 167 | -------------------------------------------------------------------------------- /src/screens/DataScreen/DataScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View, Dimensions, StatusBar, ActivityIndicator } from 'react-native'; 3 | import { 4 | ProgressChart, ProgressChartProps, ProgressChartData, StackedBarChart, StackedBarChartData, ChartConfig 5 | 6 | } from "react-native-chart-kit"; 7 | import gloabl from '../../styles'; 8 | import styles from './styles'; 9 | import { casesByCountry } from '../../utils/api'; 10 | import { ApiData } from '../../models/ApiData'; 11 | import { CountryInfo } from '../../models/CountryInfo'; 12 | 13 | 14 | const progressConfig: ChartConfig = { 15 | 16 | // the first color in the linear of chat's background 17 | backgroundGradientFrom: "#1E2923", 18 | backgroundGradientFromOpacity: 0, 19 | backgroundGradientTo: "#08130D", 20 | backgroundGradientToOpacity: 0.5, 21 | 22 | // color for 'light green' 23 | color: (opacity = 1) => `rgba(26, 255, 146, ${opacity})`, 24 | // color for lables 'white' 25 | labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 26 | 27 | strokeWidth: 30, // optional, default 3 28 | barPercentage: 1, 29 | barRadius: 20, 30 | decimalPlaces: 2, 31 | 32 | propsForBackgroundLines: { 33 | 34 | }, 35 | 36 | style: { 37 | borderRadius: 16, 38 | alignSelf: "center", 39 | }, 40 | 41 | 42 | } 43 | 44 | 45 | 46 | const width: number = Dimensions.get('window').width; 47 | const height: number = Math.round(Dimensions.get('window').height) / 2 - 98; 48 | 49 | const stackedConfig: ChartConfig = { 50 | // the first color in the linear of chat's background 51 | backgroundGradientFrom: "#1E2923", 52 | backgroundGradientFromOpacity: 0, 53 | backgroundGradientTo: "#08130D", 54 | backgroundGradientToOpacity: 0.5, 55 | 56 | // color for 'light green' 57 | color: (opacity = 1) => `rgba(26, 255, 146, ${opacity})`, 58 | // color for lables 'white' 59 | labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 60 | 61 | strokeWidth: 3, // optional, default 3 62 | barPercentage: 1, 63 | barRadius: 3, 64 | propsForBackgroundLines: { 65 | 66 | }, 67 | 68 | style: { 69 | borderRadius: 16, 70 | alignSelf: "center", 71 | }, 72 | 73 | 74 | } 75 | 76 | 77 | class DataScreen extends React.Component { 78 | 79 | constructor(props) { 80 | super(props); 81 | this.state = { 82 | progData: {}, 83 | stacktedData: {}, 84 | isLoading: true 85 | } 86 | } 87 | 88 | 89 | componentDidMount() { 90 | this._getTotats(); 91 | } 92 | 93 | 94 | _getTotats() { 95 | 96 | casesByCountry().then((res: ApiData) => { 97 | 98 | const { statistic_taken_at, countries_stat } = res; 99 | 100 | 101 | 102 | /** progress **/ 103 | // get total of each cases/deaths/recovered 104 | let totalConfirmed = countries_stat.map((c) => parseInt(c.cases.replace(/,/g, ''))).reduce((pValue, cValue) => pValue + cValue, 0); 105 | let totalDeaths = countries_stat.map((c) => parseInt(c.deaths.replace(/,/g, ''))).reduce((pValue, cValue) => pValue + cValue, 0); 106 | let totalRecoverd = countries_stat.map((c) => parseInt(c.total_recovered.replace(/,/g, ''))).reduce((pValue, cValue) => pValue + cValue, 0); 107 | 108 | // this is represent 100% 109 | let total = totalConfirmed + totalDeaths + totalRecoverd; 110 | 111 | 112 | 113 | let confirmedPercent = totalConfirmed / total; 114 | let deathsPercent = totalDeaths / total; 115 | let recoverdPersent = totalRecoverd / total; 116 | 117 | 118 | let progressChartData: ProgressChartData = { 119 | labels: ["Deaths", "Recovered", "Cases"], 120 | data: [deathsPercent, recoverdPersent, confirmedPercent] 121 | } 122 | 123 | /* 124 | console.log(total); 125 | console.log('confirmedPersenr', confirmedPercent) 126 | console.log('deathsPersent', deathsPercent); 127 | console.log('recoveredPesernt', recoverdPersent); 128 | */ 129 | 130 | /*** end progress */ 131 | 132 | 133 | // get the 3 top countries have most !!! 134 | let topCountries: CountryInfo[] = countries_stat.sort((a, b) => parseInt(b.active_cases.replace(/,/g, '')) - parseInt(a.active_cases.replace(/,/g, ''))).slice(0, 3); 135 | 136 | // console.log(topCountries); 137 | 138 | let c1 = topCountries[0]; 139 | let c2 = topCountries[1]; 140 | let c3 = topCountries[2]; 141 | 142 | let stackedBarChartData: StackedBarChartData = { 143 | labels: [c1.country_name, c2.country_name, c3.country_name], 144 | legend: ["Cases", "Serious", "Deaths"], 145 | data: [ 146 | [...this.getInfos(c1)], [...this.getInfos(c2)], [...this.getInfos(c2)] 147 | ], 148 | barColors: ["#70a1ff", "rgba(248,104,104, 0.8)", "#bdc3c7"] 149 | } 150 | 151 | this.setState({ 152 | isLoading: false, 153 | progData: progressChartData, 154 | stacktedData: stackedBarChartData 155 | }); 156 | }); 157 | 158 | } 159 | 160 | private getInfos(c: CountryInfo): number[] { 161 | let deaths = parseInt(c.new_deaths.replace(/,/g, '')); 162 | let serious = parseInt(c.serious_critical.replace(/,/g, '')); 163 | let cases = parseInt(c.new_cases.replace(/,/, '')); 164 | return [cases, serious, deaths]; 165 | } 166 | 167 | 168 | render() { 169 | 170 | if (this.state['isLoading']) { 171 | return ( 172 | 173 | 174 | 175 | 176 | ) 177 | } 178 | 179 | return ( 180 | 181 | 182 | Cases-Recovered-Deaths 183 | 184 | Lastest Stats 185 | 195 | 196 | ); 197 | } 198 | } 199 | export default DataScreen; --------------------------------------------------------------------------------