diff options
| -rw-r--r-- | mobile/App.js | 3 | ||||
| -rw-r--r-- | mobile/components/GreetingPage.js | 58 | ||||
| -rw-r--r-- | mobile/screens/Form1.js | 3 | ||||
| -rw-r--r-- | mobile/screens/MapPage.js | 121 | ||||
| -rw-r--r-- | web/models/points.js | 18 | ||||
| -rw-r--r-- | web/public/javascripts/map.js | 70 | ||||
| -rw-r--r-- | web/routes/index.js | 31 |
7 files changed, 260 insertions, 44 deletions
diff --git a/mobile/App.js b/mobile/App.js index f36b696..703c6eb 100644 --- a/mobile/App.js +++ b/mobile/App.js @@ -5,9 +5,10 @@ import { createStackNavigator, createAppContainer } from 'react-navigation'; import GreetingPage from './components/GreetingPage'; import FormPage from './screens/Form1'; +import MapPage from './screens/MapPage'; -const RootStack = createStackNavigator( {GreetingPage: GreetingPage, FormPage: FormPage,}, +const RootStack = createStackNavigator( {GreetingPage: GreetingPage, FormPage: FormPage, MapPage: MapPage}, {headerMode: 'none'}) const App = createAppContainer(RootStack) diff --git a/mobile/components/GreetingPage.js b/mobile/components/GreetingPage.js index 0bc61cf..0bfa63b 100644 --- a/mobile/components/GreetingPage.js +++ b/mobile/components/GreetingPage.js @@ -6,28 +6,31 @@ import { Location, Permissions, Constants } from 'expo'; export default class GreetingPage extends Component { state = { - isLoading: true, + isLoading: false, errorMessage: null, locationResult: null, }; - // runs as soon as page is loaded - componentDidMount() { - // immediately attempt to get the location - this._getLocationAsync(); - } - - _getLocationAsync = async () => { + moveToFormPage = async () => { + this.setState({isLoading: true}); // ask location services to allow location access to THIS app. // Note: location services itself must still be enabled for this! const {status} = await Permissions.askAsync(Permissions.LOCATION); if (status === 'granted') { // try to actually get the location asynchronously - a Promise Location.getCurrentPositionAsync({enableHighAccuracy: true}).then((position) => { - this.setState({locationResult: position.coords}); + this.setState({locationResult: position.coords}, () => { + this.props.navigation.navigate('FormPage', + {coordinates : [ + this.state.locationResult.longitude, + this.state.locationResult.latitude] + }); + }); // since we are guaranteed to have the location here, its safe to load the full view this.setState({isLoading: false}); + // move to form page }).catch((e) => { + this.setState({isLoading: false}); alert(e + ' Please make sure your location (GPS) is turned on.'); }); @@ -37,13 +40,13 @@ export default class GreetingPage extends Component { } }; - moveToFormPage = () => { - this.props.navigation.navigate('FormPage', - {coordinates : [ - this.state.locationResult.longitude, - this.state.locationResult.latitude] - }); - }; + moveToMapPage = () => { + this.props.navigation.navigate('MapPage'); + } + + moveToMapPage = () => { + this.props.navigation.navigate('MapPage'); + } render() { @@ -84,13 +87,13 @@ export default class GreetingPage extends Component { <View style={styles.press_button}> - <View> + <View padding={15}> <Text style={styles.current_location_title}> Use Current Location </Text> </View> - <View marginTop = {20}> + <View marginTop = {5}> <Ionicons name="md-locate" size={40} color="white"/> </View> @@ -98,16 +101,18 @@ export default class GreetingPage extends Component { </TouchableHighlight> - <TouchableHighlight disabled style={styles.second_button_container}> + <TouchableHighlight + style={styles.second_button_container} + onPress={() => { this.moveToMapPage() }}> <View style={styles.press_button}> - <View> + <View padding={15}> <Text style={styles.current_location_title}> Drop Pin </Text> </View> - <View marginTop = {20}> + <View> <Ionicons name="md-pin" size={40} color="white"/> </View> @@ -149,7 +154,6 @@ const styles = StyleSheet.create( { }, current_location_title: { color: '#FFF', - marginTop: 15, textAlign: 'center', opacity: 0.9, fontSize: 20 @@ -164,12 +168,16 @@ const styles = StyleSheet.create( { alignItems: 'stretch' }, press_button: { - flexDirection: 'column', - justifyContent: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + // padding:5, alignItems: 'center' }, second_button_container: { flex: 1, - backgroundColor: '#535c68' + backgroundColor: '#535c68', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'stretch' } }) diff --git a/mobile/screens/Form1.js b/mobile/screens/Form1.js index a14d647..de538d7 100644 --- a/mobile/screens/Form1.js +++ b/mobile/screens/Form1.js @@ -101,7 +101,8 @@ export default class Form1 extends Component { race : values.race, longhair : values.longhair, longbeard : values.longbeard, - extra : (values.extra) ? values.extra : "" + extra : (values.extra) ? values.extra : "", + status : "new" }), }); //// TODO: handle the response from the serve and decide what to display diff --git a/mobile/screens/MapPage.js b/mobile/screens/MapPage.js new file mode 100644 index 0000000..f8e2a79 --- /dev/null +++ b/mobile/screens/MapPage.js @@ -0,0 +1,121 @@ +import React, { Component } from 'react'; +import { Text, View, Alert, StyleSheet, Button, AppRegistry, Navigator, TouchableHighlight, Platform} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { createStackNavigator, createAppContainer } from 'react-navigation'; +import { Location, Permissions, Constants } from 'expo'; +import {MapView} from 'expo'; + +// press location on map +// on press, drop pin at location +// press proceed to redirect to the formpage + + +// default initial map region is Toronto + +export default class Form1 extends Component { + + constructor(props) { + super(props); + this.state = { + initial_region_coordinates: [-79.3832, 43.6532], + markers: [], + canProceed: false, + } + } + + addMarker(e) { + this.setState( + { markers : [{ latlng: e.nativeEvent.coordinate}], canProceed: true} + ); + } + deleteMarker() { + this.setState( + { markers : [], canProceed: false} + ); + } + moveToFormPage = () => { + if(this.state.canProceed === false) { + alert("Please press on the map to drop a pin"); + return; + } + this.props.navigation.navigate('FormPage', + {coordinates : [ + this.state.markers[0].latlng.longitude, + this.state.markers[0].latlng.latitude] + }); + }; + moveToGreetingPage = () => { + this.props.navigation.navigate('GreetingPage'); + } + + render() { + return ( + <View style={{flex:1,}}> + <View style={{flex:3}}> + <MapView + style={{ flex: 1 }} + initialRegion={{ + latitude: this.state.initial_region_coordinates[1], + longitude: this.state.initial_region_coordinates[0], + latitudeDelta: 0.0922, + longitudeDelta: 0.0421, + }} + onPress={this.addMarker.bind(this)} + > + + { + this.state.markers.map((marker, i) => (<MapView.Marker key={i} coordinate={marker.latlng} + onPress={(e) => {e.stopPropagation(); this.deleteMarker(i)}} />)) + } + </MapView> + </View> + + <TouchableHighlight + style={styles.first_button_container} + onPress={() => { this.moveToFormPage() }}> + <Text style={styles.proceed_title}> + Proceed + </Text> + </TouchableHighlight> + <TouchableHighlight + style={styles.second_button_container} + onPress={() => { this.moveToGreetingPage() }}> + <Text style={styles.proceed_title}> + Back + </Text> + </TouchableHighlight> + </View> + ); + } + +} +const styles = StyleSheet.create( { + first_button_container: { + width: '100%', + flex: 1, + position: 'absolute', + bottom:60, + backgroundColor: '#c0392b', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'stretch' + }, + second_button_container: { + width: '100%', + flex: 1, + position: 'absolute', + bottom:0, + backgroundColor: '#535c68', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'stretch' + }, + proceed_title: { + color: '#FFF', + marginTop: 15, + marginBottom: 35, + textAlign: 'center', + opacity: 0.9, + fontSize: 20 + } +})
\ No newline at end of file diff --git a/web/models/points.js b/web/models/points.js index 7bb4e24..e8c181e 100644 --- a/web/models/points.js +++ b/web/models/points.js @@ -26,6 +26,14 @@ exports.get_points = function(callback) { }); } +exports.get_all_active_points = function(callback) { + var activeStatusTypes = ["new", "pending"]; + Json.find({ status: { $in: activeStatusTypes } }).exec(function(err, docs) { + if (err) callback(err, null); + callback(null, docs); + }); +} + // this function stores A user report into the db exports.save_request = function(data, callback) { Json.create(data, function(err, result) { @@ -33,3 +41,13 @@ exports.save_request = function(data, callback) { callback(null, result); }); } + +exports.update_point_status = function(id, newStatus, callback) { + Json.findByIdAndUpdate(id, {status: newStatus}, {new: true}, + function(err, result) { + if (err) callback(err, null); + // result contains the updated point document + callback(null, result); + } + ); +} diff --git a/web/public/javascripts/map.js b/web/public/javascripts/map.js index c5b1583..3cd5821 100644 --- a/web/public/javascripts/map.js +++ b/web/public/javascripts/map.js @@ -2,13 +2,7 @@ // L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { // attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' // }).addTo(map); -let currPoint = null; - -const orangeIcon = L.icon({ - iconUrl: '../assets/orange-icon.png', - iconSize: [25, 41], - iconAnchor: [12, 41] -}); +let currPoint; const Races = { "White" : "European or White", @@ -19,12 +13,24 @@ const Races = { "Other" : "Other", }; +const PendingIcon = new L.Icon({ + iconAnchor: [ 12, 41 ], + iconUrl: "../assets/orange-icon.png", + iconSize: [ 25, 41 ], + popupAnchor: [ 1, -34 ], + shadowSize: [ 41, 41 ], + tooltipAnchor: [ 16, -28 ] +}); + function plotPointsOnMap(points) { L.geoJson(points, { pointToLayer: function (feature, latlng) { //return L.circleMarker(latlng); latlngbounds.extend(latlng); - + if (feature.status === "pending") { + return L.marker(latlng, {icon: PendingIcon}); + } + return L.marker(latlng); } }).on('click', showDetails).addTo(map); @@ -118,13 +124,34 @@ function showDetails(e) { } function markAsPending(e) { - currPoint._icon.src = '../assets/orange-icon-focused.png'; - currPoint.feature.geometry.status = "pending"; + let currPointDetails = currPoint.feature.geometry; + let currPointId = currPointDetails._id; + if (currPointDetails.status === "pending") return; + + updatePointStatusInDb(currPointId, "pending") + .then(function(responseJson) { + alert("Your change has been saved."); + currPoint._icon.src = '../assets/orange-icon.png'; + }) + .catch(function(error) { + alert(error); + }); } function markAsCompleted(e) { - map.removeLayer(currPoint); - currPoint.feature.geometry.status = "completed"; + let currPointDetails = currPoint.feature.geometry; + let currPointId = currPointDetails._id; + if (currPointDetails.status === "complete") return; + + updatePointStatusInDb(currPointId, "complete") + .then(function(responseJson) { + alert("Your change has been saved."); + map.removeLayer(currPoint); + }) + .catch(function(error) { + alert(error); + }); + closeDetails(); } function closeDetails(e) { @@ -139,6 +166,23 @@ function closeDetails(e) { } } +// returns a Promise object +function updatePointStatusInDb(pointId, pointStatus) { + return fetch('/savestatus/' + pointStatus, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({id: pointId}), + }) + .then(function(response) { + if (response.ok) { + return response.json(); + } + throw new Error("We were unable to save your changes."); + }); +} + // different basemap L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.{ext}', { attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', @@ -160,7 +204,7 @@ if (window.EventSource) { var data = JSON.parse(e.data); if (data.coordinates) { points.push(data); - plotPointsOnMap(points); + plotPointsOnMap(data); } }, false) diff --git a/web/routes/index.js b/web/routes/index.js index 1ecce83..2a828bc 100644 --- a/web/routes/index.js +++ b/web/routes/index.js @@ -17,9 +17,7 @@ router.get('/', function(req, res, next) { /* GET Map page. */ router.get('/map', function(req,res) { - // load the map with all the points - // Mockpoints.get_points(function(err, points){ - Points.get_points(function(err, points){ + Points.get_all_active_points(function(err, points){ res.render('map', { lat : 43.665234, lng : -79.383370, @@ -49,7 +47,7 @@ router.post('/mobilerequest', function(req, res) { } else { // send event to all connections for(var i = 0; i < connections.length; i++) { - connections[i].sseSend(point); + connections[i].sseSend(result); } console.log(result); res.status(201).send({'status': 'success'}); @@ -57,6 +55,31 @@ router.post('/mobilerequest', function(req, res) { }); }); +router.post('/savestatus/:status', function(req, res) { + var data = req.body; + var pointId = data.id; + var setStatus = req.params.status; + if ( + setStatus !== "new" && + setStatus !== "pending" && + setStatus !== "complete" + ) { + console.log("Invalid status supplied."); + return res.status(422).send({error: "Error: Status must be one of 'new', 'pending', or 'complete'"}); + } + + // here we are guaranteed to be able to do the query + Points.update_point_status(pointId, setStatus, function(err, result) { + if (err) { + console.log(`err updating point status in db: ${err}`); + res.status(500).send({ error: "Oops. Something went wrong on our end." }); + } else { + console.log(result); + res.status(200).send({'status': 'success'}); + } + }); +}); + router.get('/stream', function(req, res){ // set up server side event (communication line between front end and server) res.sseSetup(); |
