aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mobile/App.js3
-rw-r--r--mobile/components/GreetingPage.js58
-rw-r--r--mobile/screens/Form1.js3
-rw-r--r--mobile/screens/MapPage.js121
-rw-r--r--web/models/points.js18
-rw-r--r--web/public/javascripts/map.js73
-rw-r--r--web/routes/index.js31
7 files changed, 263 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 07ca190..6f06e40 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: '&copy; <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,14 +124,38 @@ function showDetails(e) {
}
function markAsPending(e) {
+ let currPointDetails = currPoint.feature.geometry;
+ let currPointId = currPointDetails._id;
+ if (currPointDetails.status === "pending") return;
+
currPoint._icon.src = '../assets/orange-icon-focused.png';
- currPoint.feature.geometry.status = "pending";
+ currPointDetails.status = "pending";
+
+ 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";
- currPoint = undefined;
+ 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);
+ currPoint = undefined;
+ })
+ .catch(function(error) {
+ alert(error);
+ });
+ closeDetails();
}
function closeDetails(e) {
@@ -140,6 +170,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> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
@@ -161,7 +208,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();