From f8b19188e0ff7b16944a9a7f3bd55afa91033849 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 16 Nov 2018 00:56:03 +0100 Subject: [PATCH 01/41] Move About to Screen --- App/{components/Footer => Screens}/About/About.js | 0 App/{components/Footer => Screens}/About/index.js | 0 App/components/Footer/Footer.js | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename App/{components/Footer => Screens}/About/About.js (100%) rename App/{components/Footer => Screens}/About/index.js (100%) diff --git a/App/components/Footer/About/About.js b/App/Screens/About/About.js similarity index 100% rename from App/components/Footer/About/About.js rename to App/Screens/About/About.js diff --git a/App/components/Footer/About/index.js b/App/Screens/About/index.js similarity index 100% rename from App/components/Footer/About/index.js rename to App/Screens/About/index.js diff --git a/App/components/Footer/Footer.js b/App/components/Footer/Footer.js index ffa17c81..755e7403 100644 --- a/App/components/Footer/Footer.js +++ b/App/components/Footer/Footer.js @@ -4,7 +4,7 @@ import React, { Component } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { About } from './About'; +import { About } from '../../Screens/About'; import * as theme from '../../utils/theme'; export class Footer extends Component { @@ -20,7 +20,7 @@ export class Footer extends Component { handleAboutShow = () => this.setState({ isAboutVisible: true }); - render () { + render() { const { style, text } = this.props; const { isAboutVisible } = this.state; return ( From 43a619c4b6cba7a83a06f9d77f524e6aaf122a6e Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 16 Nov 2018 01:52:56 +0100 Subject: [PATCH 02/41] First batch of changes --- App/Screens/About/About.js | 10 +-- .../MapScreen.js => Details/Details.js} | 24 ++---- App/Screens/Details/Header/Header.js | 82 +++++++++++++++++++ .../{MapScreen => Details/Header}/index.js | 2 +- App/Screens/Details/index.js | 4 + App/Screens/Screens.js | 41 +++++----- .../CurrentLocation/CurrentLocation.js | 77 +++++++++++++++++ App/components/CurrentLocation/index.js | 4 + App/components/Header/Header.js | 58 ++----------- App/utils/dataSources/aqicn.js | 8 +- App/utils/theme.js | 6 +- 11 files changed, 215 insertions(+), 101 deletions(-) rename App/Screens/{MapScreen/MapScreen.js => Details/Details.js} (88%) create mode 100644 App/Screens/Details/Header/Header.js rename App/Screens/{MapScreen => Details/Header}/index.js (79%) create mode 100644 App/Screens/Details/index.js create mode 100644 App/components/CurrentLocation/CurrentLocation.js create mode 100644 App/components/CurrentLocation/index.js diff --git a/App/Screens/About/About.js b/App/Screens/About/About.js index 064a79a8..30490683 100644 --- a/App/Screens/About/About.js +++ b/App/Screens/About/About.js @@ -14,9 +14,9 @@ import { View } from 'react-native'; -import cigarette from '../../../../assets/images/cigarette.png'; -import * as theme from '../../../utils/theme'; -import { BackButton } from '../../BackButton'; +import cigarette from '../../../assets/images/cigarette.png'; +import * as theme from '../../utils/theme'; +import { BackButton } from '../../components/BackButton'; export class About extends Component { handleOpenAmaury = () => Linking.openURL('https://twitter.com/amaurymartiny'); @@ -33,11 +33,11 @@ export class About extends Component { handleOpenMarcelo = () => Linking.openURL('https://www.behance.net/marceloscoelho'); - render () { + render() { const { onRequestClose, ...rest } = this.props; return ( - + { return ( @@ -32,17 +32,11 @@ export class MapScreen extends Component { showMap: false }; - showMapTimeout = null; - - componentWillMount () { + componentWillMount() { // Show map after 200ms for smoother screen transition setTimeout(() => this.setState({ showMap: true }), 500); } - componentWillUnmount () { - clearTimeout(this.showMapTimeout); - } - handleMapReady = () => { this.stationMarker && this.stationMarker.showCallout && @@ -53,7 +47,7 @@ export class MapScreen extends Component { this.stationMarker = ref; }; - render () { + render() { const { screenProps: { api, currentLocation, onChangeLocationClick } } = this.props; @@ -75,10 +69,10 @@ export class MapScreen extends Component { {showMap && ( @@ -99,14 +93,14 @@ export class MapScreen extends Component { coordinate={station} image={stationIcon} ref={this.handleStationRef} - title='Air Quality Station' + title="Air Quality Station" description={truncate(station.description, 40)} /> )} diff --git a/App/Screens/Details/Header/Header.js b/App/Screens/Details/Header/Header.js new file mode 100644 index 00000000..91d093c7 --- /dev/null +++ b/App/Screens/Details/Header/Header.js @@ -0,0 +1,82 @@ +// Copyright (c) 2018, Amaury Martiny and the Shoot! I Smoke contributors +// SPDX-License-Identifier: GPL-3.0 + +import React, { Component } from 'react'; +import axios from 'axios'; +import { Constants } from 'expo'; +import haversine from 'haversine'; +import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +import { BackButton } from '../../../components/BackButton'; +import changeLocation from '../../../../assets/images/changeLocation.png'; +import { CurrentLocation } from '../../../components/CurrentLocation'; +import { getCorrectLatLng } from '../../../utils/getCorrectLatLng'; +import * as theme from '../../../utils/theme'; + +export class Header extends Component { + static defaultProps = { + showChangeLocation: false + }; + + render() { + const { + api, + currentLocation, + elevated, + onBackClick, + onChangeLocationClick, + onClick, + showBackButton, + showChangeLocation, + style + } = this.props; + + return ( + + + + + + + + + + + {lastUpdated && ( + + Latest Update: {lastUpdated.getDay()} {lastUpdated.getHours()}: + {lastUpdated.getMinutes()} + + )} + + + + ); + } +} + +const styles = StyleSheet.create({ + backButton: { + marginBottom: 22 + }, + changeLocation: { + marginRight: 5 + }, + container: { + ...theme.elevatedLevel1, + ...theme.withPadding, + paddingBottom: 15, + paddingTop: theme.defaultSpacing + }, + content: { + flexDirection: 'row' + }, + subtitle: { + ...theme.text, + marginTop: 11 + }, + title: { + ...theme.title, + fontSize: 15 + } +}); diff --git a/App/Screens/MapScreen/index.js b/App/Screens/Details/Header/index.js similarity index 79% rename from App/Screens/MapScreen/index.js rename to App/Screens/Details/Header/index.js index 678ea73b..31203ad4 100644 --- a/App/Screens/MapScreen/index.js +++ b/App/Screens/Details/Header/index.js @@ -1,4 +1,4 @@ // Copyright (c) 2018, Amaury Martiny and the Shoot! I Smoke contributors // SPDX-License-Identifier: GPL-3.0 -export * from './MapScreen'; +export * from './Header'; diff --git a/App/Screens/Details/index.js b/App/Screens/Details/index.js new file mode 100644 index 00000000..03afb2f5 --- /dev/null +++ b/App/Screens/Details/index.js @@ -0,0 +1,4 @@ +// Copyright (c) 2018, Amaury Martiny and the Shoot! I Smoke contributors +// SPDX-License-Identifier: GPL-3.0 + +export * from './Details'; diff --git a/App/Screens/Screens.js b/App/Screens/Screens.js index 3dcf493d..8e36bc7a 100644 --- a/App/Screens/Screens.js +++ b/App/Screens/Screens.js @@ -8,10 +8,10 @@ import retry from 'async-retry'; import { createStackNavigator } from 'react-navigation'; import * as dataSources from '../utils/dataSources'; +import { Details } from './Details'; import { ErrorScreen } from './ErrorScreen'; import { Home } from './Home'; import { Loading } from './Loading'; -import { MapScreen } from './MapScreen'; import { pm25ToCigarettes } from '../utils/pm25ToCigarettes'; import { Search } from './Search'; import smokeVideo from '../../assets/video/smoke.mp4'; @@ -26,7 +26,7 @@ const RootStack = createStackNavigator( screen: Home }, Map: { - screen: MapScreen + screen: Details } }, { @@ -55,25 +55,26 @@ const RootStack = createStackNavigator( export class Screens extends Component { state = { api: null, - currentLocation: null, // Initialized to GPS, but can be changed by user + currentLocation: null, // Current selected location of the user, initialized to GPS, but can be changed by user error: null, // Error here or in children component tree - gps: null, - isSearchVisible: false, - showVideo: true + gps: null, // GPS location of the user + isSearchVisible: false, // Search modal on or off + showVideo: true // Showing video or not }; - componentWillMount () { + componentWillMount() { this.fetchData(); } - componentDidCatch (error) { + componentDidCatch(error) { this.setState({ error }); } - async fetchData () { + async fetchData() { const { currentLocation } = this.state; try { - let currentPosition = currentLocation; // The current { latitude, longitude } the user chose + // The current { latitude, longitude } the user chose + let currentPosition = currentLocation; this.setState({ api: null, error: null }); @@ -85,14 +86,14 @@ export class Screens extends Component { throw new Error('Permission to access location was denied.'); } - const { coords } = await Location.getCurrentPositionAsync({}); - currentPosition = coords; - + // const { coords } = await Location.getCurrentPositionAsync({}); // Uncomment to get random location - // coords = { - // latitude: Math.random() * 90, - // longitude: Math.random() * 90 - // }; + coords = { + latitude: Math.random() * 90, + longitude: Math.random() * 90 + }; + + currentPosition = coords; this.setState({ currentLocation: coords, @@ -114,7 +115,7 @@ export class Screens extends Component { ); this.setState({ api }); } catch (error) { - console.error(error); + // TODO Add to sentry this.setState({ error }); } } @@ -145,7 +146,7 @@ export class Screens extends Component { } }; - render () { + render() { const { gps, isSearchVisible } = this.state; return ( @@ -186,7 +187,7 @@ export class Screens extends Component { {showVideo && ( ); @@ -96,6 +88,9 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between' }, + currentLocation: { + maxWidth: '80%' + }, subtitle: { ...theme.text, marginTop: 11 diff --git a/App/components/Header/index.js b/App/Screens/Home/Header/index.js similarity index 100% rename from App/components/Header/index.js rename to App/Screens/Home/Header/index.js diff --git a/App/Screens/Home/Home.js b/App/Screens/Home/Home.js index 4577cc4c..278a47a2 100644 --- a/App/Screens/Home/Home.js +++ b/App/Screens/Home/Home.js @@ -13,7 +13,7 @@ import { import { Cigarettes } from './Cigarettes'; import { Footer } from '../../components/Footer'; -import { Header } from '../../components/Header'; +import { Header } from './Header'; import { pm25ToCigarettes } from '../../utils/pm25ToCigarettes'; import * as theme from '../../utils/theme'; @@ -24,7 +24,6 @@ export class Home extends Component {
props.navigation.navigate('Map')} // TODO Possible not to create a new function every time? - showChangeLocation /> ); } From e0dc841cbb9eed1c40afafb0c02ae5f22539c75c Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 16 Nov 2018 17:32:47 +0100 Subject: [PATCH 06/41] Use raw everywhere --- App/Screens/Details/Header/Header.js | 8 ++------ App/Screens/Home/Cigarettes/Cigarettes.js | 20 ++++++++++---------- App/Screens/Home/Home.js | 14 +++++++------- App/Screens/Screens.js | 4 ++-- App/utils/dataSources/aqicn.js | 4 ++++ App/utils/dataSources/windWaqi.js | 9 +++++++-- App/utils/pm25ToCigarettes.js | 2 +- 7 files changed, 33 insertions(+), 28 deletions(-) diff --git a/App/Screens/Details/Header/Header.js b/App/Screens/Details/Header/Header.js index 24a1d566..84cf9acf 100644 --- a/App/Screens/Details/Header/Header.js +++ b/App/Screens/Details/Header/Header.js @@ -2,15 +2,11 @@ // SPDX-License-Identifier: GPL-3.0 import React, { Component } from 'react'; -import axios from 'axios'; -import { Constants } from 'expo'; -import haversine from 'haversine'; -import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Image, StyleSheet, Text, View } from 'react-native'; import { BackButton } from '../../../components/BackButton'; import changeLocation from '../../../../assets/images/changeLocation.png'; import { CurrentLocation } from '../../../components/CurrentLocation'; -import { getCorrectLatLng } from '../../../utils/getCorrectLatLng'; import * as theme from '../../../utils/theme'; /** @@ -77,7 +73,7 @@ export class Header extends Component { this.renderInfo('PM2.5 AQI:', pm25.v, styles.pollutantItem)} {o3 && this.renderInfo('O3 AQI:', o3.v, styles.pollutantItem)} {pm10 && - this.renderInfo('PM2.5 AQI:', pm10.v, styles.pollutantItem)} + this.renderInfo('PM10 AQI:', pm10.v, styles.pollutantItem)} {no2 && this.renderInfo('NO2 AQI:', no2.v, styles.pollutantItem)} diff --git a/App/Screens/Home/Cigarettes/Cigarettes.js b/App/Screens/Home/Cigarettes/Cigarettes.js index ac4e8270..7cbf109b 100644 --- a/App/Screens/Home/Cigarettes/Cigarettes.js +++ b/App/Screens/Home/Cigarettes/Cigarettes.js @@ -15,10 +15,10 @@ export class Cigarettes extends Component { return 'small'; }; - render () { - const { pm25, style } = this.props; + render() { + const { rawPm25, style } = this.props; const cigarettes = - Math.round(Math.min(pm25ToCigarettes(pm25), 63) * 10) / 10; // We don't show more than 63 + Math.round(Math.min(pm25ToCigarettes(rawPm25), 63) * 10) / 10; // We don't show more than 63 // const cigarettes = 0.9; // Can change values here for testing const count = Math.floor(cigarettes); @@ -32,13 +32,13 @@ export class Cigarettes extends Component { {cigarettes > 1 && count >= 1 ? Array.from(Array(count)).map((_, i) => ( - - - - )) + + + + )) : null} {cigarettes === 1 || decimal > 0 ? ( - + {this.renderText()} { const { screenProps: { - api: { pm25 } + api: { rawPm25 } } } = this.props; - const cigarettes = pm25ToCigarettes(pm25); + const cigarettes = pm25ToCigarettes(rawPm25); if (cigarettes <= 1) return 'Oh'; if (cigarettes < 5) return 'Sh*t'; @@ -90,11 +90,11 @@ export class Home extends Component { renderText = () => { const { screenProps: { - api: { pm25 } + api: { rawPm25 } } } = this.props; // Round to 1 decimal - const cigarettes = Math.round(pm25ToCigarettes(pm25) * 10) / 10; + const cigarettes = Math.round(pm25ToCigarettes(rawPm25) * 10) / 10; return ( diff --git a/App/Screens/Screens.js b/App/Screens/Screens.js index 6fb69dd6..2cb55cd2 100644 --- a/App/Screens/Screens.js +++ b/App/Screens/Screens.js @@ -126,9 +126,9 @@ export class Screens extends Component { getVideoStyle = () => { const { - api: { pm25 } + api: { rawPm25 } } = this.state; - const cigarettes = pm25ToCigarettes(pm25); + const cigarettes = pm25ToCigarettes(rawPm25); if (cigarettes <= 1) return { opacity: 0.2 }; if (cigarettes < 5) return { opacity: 0.5 }; diff --git a/App/utils/dataSources/aqicn.js b/App/utils/dataSources/aqicn.js index 700379b7..399ef371 100644 --- a/App/utils/dataSources/aqicn.js +++ b/App/utils/dataSources/aqicn.js @@ -84,5 +84,9 @@ export const aqicn = async ({ latitude, longitude }) => { throw new Error('PM2.5 not defined in response.'); } + // TODO Find the real raw value + // https://github.com/amaurymartiny/shoot-i-smoke/issues/46 + response.data.rawPm25 = response.data.iaqi.pm25.v; + return response.data; }; diff --git a/App/utils/dataSources/windWaqi.js b/App/utils/dataSources/windWaqi.js index 4a07511e..2ddb9cd7 100644 --- a/App/utils/dataSources/windWaqi.js +++ b/App/utils/dataSources/windWaqi.js @@ -42,8 +42,13 @@ export const windWaqi = async ({ latitude, longitude }) => { } return { - pm25: data.v, - city: { geo: [+data.geo[0], +data.geo[1]], name: data.nlo } + city: { geo: [+data.geo[0], +data.geo[1]], name: data.nlo }, + iaqi: { + pm25: { + v: data.v + } + }, + rawPm25: data.v // TODO Get the real raw pm25 value https://github.com/amaurymartiny/shoot-i-smoke/issues/46 }; } else { throw new Error(response); diff --git a/App/utils/pm25ToCigarettes.js b/App/utils/pm25ToCigarettes.js index 338dbe55..24f58e66 100644 --- a/App/utils/pm25ToCigarettes.js +++ b/App/utils/pm25ToCigarettes.js @@ -8,4 +8,4 @@ * @see http://berkeleyearth.org/air-pollution-and-cigarette-equivalence/ * @param {Float} api - The api object returned by the WAQI api. */ -export const pm25ToCigarettes = pm25 => pm25 / 22; +export const pm25ToCigarettes = rawPm25 => rawPm25 / 22; From 5f469833147b5ec2c82cdc60877cf3d162b1dc3a Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 16 Nov 2018 22:11:17 +0100 Subject: [PATCH 07/41] Add MST --- App/App.js | 19 +++++++--- App/Screens/Home/Header/Header.js | 21 ++++------- App/Screens/Home/Home.js | 53 +++++++++++++-------------- App/Screens/Screens.js | 59 ++++++++++++++++++------------- App/stores/api.js | 42 ++++++++++++++++++++++ App/stores/index.js | 37 +++++++++++++++++++ App/stores/location.js | 30 ++++++++++++++++ App/utils/dataSources/aqicn.js | 14 +++++--- App/utils/dataSources/windWaqi.js | 10 ++++-- package.json | 3 ++ yarn.lock | 32 ++++++++++++++++- 11 files changed, 240 insertions(+), 80 deletions(-) create mode 100644 App/stores/api.js create mode 100644 App/stores/index.js create mode 100644 App/stores/location.js diff --git a/App/App.js b/App/App.js index fb4e5087..f0b177db 100644 --- a/App/App.js +++ b/App/App.js @@ -3,16 +3,21 @@ import React, { Component } from 'react'; import { Font } from 'expo'; +import { Provider } from 'mobx-react'; -import { Screens } from './Screens'; +import { RootStore } from './stores'; import { Background as LoadingBackground } from './Screens/Loading/Background'; +import { Screens } from './Screens'; + +// Set up global MST stores +const stores = RootStore.create({ api: undefined, location: {} }); export class App extends Component { state = { fontLoaded: false }; - async componentDidMount () { + async componentDidMount() { // Using custom fonts with Expo // https://docs.expo.io/versions/latest/guides/using-custom-fonts await Font.loadAsync({ @@ -23,9 +28,15 @@ export class App extends Component { this.setState({ fontLoaded: true }); } - render () { + render() { const { fontLoaded } = this.state; - return fontLoaded ? : ; + return fontLoaded ? ( + + + + ) : ( + + ); } } diff --git a/App/Screens/Home/Header/Header.js b/App/Screens/Home/Header/Header.js index 9c4404ed..2062b459 100644 --- a/App/Screens/Home/Header/Header.js +++ b/App/Screens/Home/Header/Header.js @@ -2,36 +2,27 @@ // SPDX-License-Identifier: GPL-3.0 import React, { Component } from 'react'; -import haversine from 'haversine'; +import { inject, observer } from 'mobx-react'; import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { BackButton } from '../../../components/BackButton'; import changeLocation from '../../../../assets/images/changeLocation.png'; import { CurrentLocation } from '../../../components/CurrentLocation'; -import { getCorrectLatLng } from '../../../utils/getCorrectLatLng'; import * as theme from '../../../utils/theme'; +@inject('stores') +@observer export class Header extends Component { render() { const { - api, - currentLocation, elevated, onBackClick, onChangeLocationClick, onClick, showBackButton, + stores: { api, location, distanceToStation }, style } = this.props; - const distance = Math.round( - haversine( - currentLocation, - getCorrectLatLng(currentLocation, { - latitude: api.city.geo[0], - longitude: api.city.geo[1] - }) - ) - ); return ( - Distance to Air Quality Station: {distance} + Distance to Air Quality Station: {distanceToStation} km diff --git a/App/Screens/Home/Home.js b/App/Screens/Home/Home.js index 533bc414..57bfa41e 100644 --- a/App/Screens/Home/Home.js +++ b/App/Screens/Home/Home.js @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; import { ScrollView, Share, @@ -17,18 +18,9 @@ import { Header } from './Header'; import { pm25ToCigarettes } from '../../utils/pm25ToCigarettes'; import * as theme from '../../utils/theme'; +@inject('stores') +@observer export class Home extends Component { - static navigationOptions = { - header: props => { - return ( -
props.navigation.navigate('Map')} // TODO Possible not to create a new function every time? - /> - ); - } - }; - goToMap = () => this.props.navigation.navigate('Map'); handleShare = () => @@ -42,27 +34,30 @@ export class Home extends Component { render() { const { - screenProps: { + stores: { api: { rawPm25 } } } = this.props; return ( - - - - {this.renderText()} - - - SHARE WITH YOUR FRIENDS - - - + +
+ + + + {this.renderText()} + + + SHARE WITH YOUR FRIENDS + + + -