diff --git a/expo_project/components/MapWithMarkers.js b/expo_project/components/MapWithMarkers.js
index c3f449c..8505f0f 100644
--- a/expo_project/components/MapWithMarkers.js
+++ b/expo_project/components/MapWithMarkers.js
@@ -6,6 +6,7 @@ import { AnimatedCircularProgress } from 'react-native-circular-progress';
import { iconColors } from '../constants/Colors';
import MapConfig from '../constants/Map';
import PersonIcon from '../components/PersonIcon';
+import * as _ from 'lodash';
// NOTE: A longPress is more like 500ms,
// however there's a delay between when the longPress is registered
@@ -22,21 +23,22 @@ class MapWithMarkers extends React.Component {
this.state = {
region: MapConfig.defaultRegion,
circularProgressLocation: null,
- nextMarkerColor: this.getRandomIconColor(),
+ nextMarkerColor: null,
};
}
getRandomIconColor = () => {
- const iconOptions = Object.values(iconColors);
- return iconOptions[Math.floor(Math.random() * iconOptions.length)];
- };
-
- setNextColor = () => {
- this.setState({ nextMarkerColor: this.getRandomIconColor() });
+ // enforce next color is not current color
+ const iconColorOptions = _.filter(
+ _.values(iconColors),
+ color => color !== this.state.nextMarkerColor,
+ );
+ return _.sample(iconColorOptions);
};
startProgressAnimation = (locationX, locationY) => {
this.setState({
+ nextMarkerColor: this.getRandomIconColor(),
circularProgressLocation: {
top: locationY - CIRCULAR_PROGRESS_SIZE / 2,
left: locationX - CIRCULAR_PROGRESS_SIZE / 2,
@@ -47,7 +49,6 @@ class MapWithMarkers extends React.Component {
stopProgressAnimation = () => {
this.setState({
circularProgressLocation: null,
- nextMarkerColor: this.getRandomIconColor(),
});
};
diff --git a/expo_project/components/NoteModal.js b/expo_project/components/NoteModal.js
new file mode 100644
index 0000000..f538d14
--- /dev/null
+++ b/expo_project/components/NoteModal.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import { Modal, StyleSheet, Text, View } from 'react-native';
+import { Button, TextInput } from 'react-native-paper';
+import PropTypes from 'prop-types';
+
+class NoteModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ text: props.initialValue,
+ };
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ this.setState({ text })}
+ onSubmitEditing={() => {
+ this.props.onClose(this.state.text);
+ }}
+ />
+
+
+
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ padding: 20,
+ backgroundColor: 'white',
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ modalBody: {
+ padding: 20,
+ },
+ buttonWrapper: {
+ marginTop: 20,
+ },
+});
+
+NoteModal.propTypes = {
+ initialValue: PropTypes.string,
+ onClose: PropTypes.func,
+};
+
+NoteModal.defaultProps = {
+ initialValue: '',
+ onClose: () => null,
+};
+
+export default NoteModal;
diff --git a/expo_project/components/PersonIcon.js b/expo_project/components/PersonIcon.js
index 09f62ab..d45f778 100644
--- a/expo_project/components/PersonIcon.js
+++ b/expo_project/components/PersonIcon.js
@@ -48,8 +48,12 @@ const styles = StyleSheet.create({
PersonIcon.propTypes = {
size: PropTypes.number.isRequired,
- backgroundColor: PropTypes.string.isRequired,
+ backgroundColor: PropTypes.string,
shadow: PropTypes.bool,
};
+PropTypes.defaultProps = {
+ backgroundColor: 'white',
+};
+
export default PersonIcon;
diff --git a/expo_project/components/Selectable.js b/expo_project/components/Selectable.js
index c429f24..28e6fc6 100644
--- a/expo_project/components/Selectable.js
+++ b/expo_project/components/Selectable.js
@@ -64,7 +64,7 @@ const styles = StyleSheet.create({
borderWidth: 1,
backgroundColor: '#FAFAFA',
borderRadius: 3,
- borderColor: 'rgba(0, 0, 0, 0.0980392)',
+ borderColor: 'rgba(0, 0, 0, 0.12)',
padding: 5,
marginRight: 5,
marginTop: 10,
diff --git a/expo_project/components/Survey.js b/expo_project/components/Survey.js
index 761a7ec..e572a81 100644
--- a/expo_project/components/Survey.js
+++ b/expo_project/components/Survey.js
@@ -11,10 +11,6 @@ class Survey extends React.Component {
const { activeMarker, onSelect } = this.props;
return (
-
- {activeMarker.title}
- {activeMarker.dateLabel}
-
{_.map(QUESTION_CONFIG, question => {
const { questionKey, questionLabel, options } = question;
return (
@@ -30,14 +26,31 @@ class Survey extends React.Component {
/>
);
})}
+ {activeMarker.note && (
+
+ Note
+
+ {activeMarker.note}
+
+
+ )}
);
}
}
const styles = StyleSheet.create({
- titleContainer: { paddingVertical: 10, paddingHorizontal: 20 },
- title: { fontWeight: 'bold' },
+ noteContainer: { paddingVertical: 10 },
+ noteTitle: {
+ // match selectable style
+ marginBottom: 5,
+ paddingHorizontal: 20,
+ },
+ noteBody: {
+ fontFamily: 'monaco',
+ marginVertical: 10,
+ marginHorizontal: 20,
+ },
});
Survey.propTypes = {
diff --git a/expo_project/components/SurveyHeader.js b/expo_project/components/SurveyHeader.js
index 29164d4..57bc918 100644
--- a/expo_project/components/SurveyHeader.js
+++ b/expo_project/components/SurveyHeader.js
@@ -31,7 +31,7 @@ const styles = StyleSheet.create({
text: {
color: '#fff',
fontWeight: '600',
- fontSize: 24,
+ fontSize: 18,
fontFamily: Theme.fonts.medium,
},
});
diff --git a/expo_project/config/studies.js b/expo_project/config/studies.js
index 823b0a6..c7737b4 100644
--- a/expo_project/config/studies.js
+++ b/expo_project/config/studies.js
@@ -8,19 +8,9 @@ export default [
surveys: [
{
type: 'activity',
- title: 'Stationary Mapping tool',
+ title: '2pm - 3pm shift',
routeName: 'SurveyScreen',
},
- {
- type: 'lineOfSight',
- title: 'Line of Sight tool',
- routeName: 'ComingSoonScreen',
- },
- {
- type: 'intercept',
- title: 'Intercept study tool',
- routeName: 'ComingSoonScreen',
- },
],
},
];
diff --git a/expo_project/screens/StudyIndexScreen.js b/expo_project/screens/StudyIndexScreen.js
index 7656f23..2f142cf 100644
--- a/expo_project/screens/StudyIndexScreen.js
+++ b/expo_project/screens/StudyIndexScreen.js
@@ -21,14 +21,14 @@ class SurveyIndexScreen extends React.Component {
Your studies
{studies.map(study => (
-
+
{study.studyName}
by {study.studyAuthor}
{study.surveys.map(survey => {
return (
-
+
@@ -78,7 +78,6 @@ const styles = StyleSheet.create({
},
sectionTitle: {
backgroundColor: 'white',
- fontWeight: 'bold',
marginBottom: 10,
},
surveyRow: {
diff --git a/expo_project/screens/SurveyScreen.js b/expo_project/screens/SurveyScreen.js
index f4183fd..9ad3185 100644
--- a/expo_project/screens/SurveyScreen.js
+++ b/expo_project/screens/SurveyScreen.js
@@ -6,21 +6,22 @@ import {
Platform,
StyleSheet,
ScrollView,
- View,
+ Text,
TouchableOpacity,
+ View,
} from 'react-native';
-import { Button, Paragraph } from 'react-native-paper';
+import { Button, Paragraph, Divider } from 'react-native-paper';
import { withNavigation } from 'react-navigation';
import * as _ from 'lodash';
import moment from 'moment';
import MapWithMarkers from '../components/MapWithMarkers';
-import MarkerCarousel from '../components/MarkerCarousel';
+import PersonIcon from '../components/PersonIcon';
import Survey from '../components/Survey';
-import { iconColors } from '../constants/Colors';
import Layout from '../constants/Layout';
import firebase from '../lib/firebaseSingleton';
-import SurveyHeader from '../components/SurveyHeader';
+import Theme from '../constants/Theme';
+import NoteModal from '../components/NoteModal';
// TODO (Ananta): shouold be dynamically set
const MIN_DRAWER_TRANSLATE_Y = 0;
@@ -28,25 +29,9 @@ const MID_DRAWER_TRANSLATE_Y = Layout.drawer.height - 300;
const MAX_DRAWER_TRANSLATE_Y = Layout.drawer.height - 100; // mostly collapsed, with just the header peaking out
const INITIAL_DRAWER_TRANSLATE_Y = MAX_DRAWER_TRANSLATE_Y;
-function _markerToDataPoint(marker) {
- const dataPoint = {};
- fields = ['gender', 'groupSize', 'mode', 'object', 'posture', 'timestamp', 'location'];
- fields.forEach(field => {
- if (marker[field]) {
- dataPoint[field] = marker[field];
- }
- });
-
- return dataPoint;
-}
-
class Indicator extends React.Component {
render() {
- return (
-
-
-
- );
+ return ;
}
}
@@ -66,9 +51,25 @@ class Instructions extends React.Component {
}
class SurveyScreen extends React.Component {
- static navigationOptions = {
- headerTitle: ,
- };
+ static navigationOptions = ({ navigation }) => ({
+ headerTitle: navigation.getParam('surveyTitle'),
+ headerLeft: (
+
+ ),
+ });
constructor(props) {
super(props);
@@ -80,6 +81,7 @@ class SurveyScreen extends React.Component {
this.state = {
activeMarkerId: null,
markers: [],
+ modalVisible: false,
formScrollPosition: 0,
pan: new Animated.ValueXY({ x: 0, y: INITIAL_DRAWER_TRANSLATE_Y }),
};
@@ -87,6 +89,7 @@ class SurveyScreen extends React.Component {
this.state.pan.addListener(value => (this._drawerY = value.y));
this.resetDrawer = this.resetDrawer.bind(this);
+ this.getToggleDirection = this.getToggleDirection.bind(this);
this.toggleDrawer = this.toggleDrawer.bind(this);
this.selectMarker = this.selectMarker.bind(this);
this.createNewMarker = this.createNewMarker.bind(this);
@@ -113,11 +116,12 @@ class SurveyScreen extends React.Component {
const marker = {
id: doc.id,
...doc.data(),
- color: _.sample(_.values(iconColors)),
};
markers.push(marker);
});
- this.setState({ markers });
+ if (markers.length) {
+ this.setState({ markers, activeMarkerId: markers[0].id });
+ }
});
}
@@ -171,8 +175,8 @@ class SurveyScreen extends React.Component {
y,
},
useNativeDriver: true,
- friction: 5,
- }).start();
+ friction: 6,
+ }).start(() => this.setState(this.state));
if (this.state.formScrollPosition) {
this.scrollView.scrollTo({
@@ -184,14 +188,22 @@ class SurveyScreen extends React.Component {
});
}
+ getToggleDirection() {
+ const direction = this._drawerY === MIN_DRAWER_TRANSLATE_Y ? 'down' : 'up';
+ return direction;
+ }
+
toggleDrawer() {
const y =
- this._drawerY === MIN_DRAWER_TRANSLATE_Y ? MAX_DRAWER_TRANSLATE_Y : MIN_DRAWER_TRANSLATE_Y;
+ this.getToggleDirection() === 'down' ? MAX_DRAWER_TRANSLATE_Y : MIN_DRAWER_TRANSLATE_Y;
Animated.timing(this.state.pan, {
toValue: { x: 0, y },
duration: 200,
useNativeDriver: true,
- }).start();
+ }).start(() => {
+ // hack to trigger a re-render
+ this.setState(this.state);
+ });
if (this.state.formScrollPosition) {
this.scrollView.scrollTo({ y: 0, animated: false });
@@ -211,7 +223,7 @@ class SurveyScreen extends React.Component {
}
}
- setFormResponse(id, key, value, selectableHeight) {
+ setFormResponse(id, key, value, heightToScroll) {
const markersCopy = [...this.state.markers];
const marker = _.find(markersCopy, {
id,
@@ -231,34 +243,36 @@ class SurveyScreen extends React.Component {
.doc(surveyId)
.collection('dataPoints')
.doc(marker.id)
- .set(_markerToDataPoint(marker));
-
- const currentScrollPosition = this.state.formScrollPosition;
- const currentDrawerOffset = this._drawerY;
- const newDrawerOffset = currentDrawerOffset - selectableHeight;
-
- if (newDrawerOffset >= MIN_DRAWER_TRANSLATE_Y) {
- Animated.timing(this.state.pan, {
- toValue: { x: 0, y: newDrawerOffset },
- duration: 200,
- useNativeDriver: true,
- }).start();
- } else if (currentDrawerOffset > MIN_DRAWER_TRANSLATE_Y) {
- // Animate drawer to the top
- // then scroll the remaining amount to ensure next question is visible
- const remainder = currentDrawerOffset - MIN_DRAWER_TRANSLATE_Y;
- Animated.timing(this.state.pan, {
- toValue: { x: 0, y: MIN_DRAWER_TRANSLATE_Y },
- duration: 200,
- useNativeDriver: true,
- }).start();
- this.scrollView.scrollTo({
- y: currentScrollPosition + selectableHeight - remainder,
- });
- } else {
- this.scrollView.scrollTo({
- y: currentScrollPosition + selectableHeight,
- });
+ .set(marker);
+
+ if (heightToScroll) {
+ const currentScrollPosition = this.state.formScrollPosition;
+ const currentDrawerOffset = this._drawerY;
+ const newDrawerOffset = currentDrawerOffset - heightToScroll;
+
+ if (newDrawerOffset >= MIN_DRAWER_TRANSLATE_Y) {
+ Animated.timing(this.state.pan, {
+ toValue: { x: 0, y: newDrawerOffset },
+ duration: 200,
+ useNativeDriver: true,
+ }).start();
+ } else if (currentDrawerOffset > MIN_DRAWER_TRANSLATE_Y) {
+ // Animate drawer to the top
+ // then scroll the remaining amount to ensure next question is visible
+ const remainder = currentDrawerOffset - MIN_DRAWER_TRANSLATE_Y;
+ Animated.timing(this.state.pan, {
+ toValue: { x: 0, y: MIN_DRAWER_TRANSLATE_Y },
+ duration: 200,
+ useNativeDriver: true,
+ }).start();
+ this.scrollView.scrollTo({
+ y: currentScrollPosition + heightToScroll - remainder,
+ });
+ } else {
+ this.scrollView.scrollTo({
+ y: currentScrollPosition + heightToScroll,
+ });
+ }
}
}
}
@@ -295,11 +309,10 @@ class SurveyScreen extends React.Component {
.collection('survey')
.doc(surveyId)
.collection('dataPoints')
- .add(_markerToDataPoint(marker))
+ .add(marker)
.then(doc => {
- const { id, timestamp } = doc;
+ const { id } = doc;
marker.id = id;
- marker.timestamp = timestamp;
markersCopy.push(marker);
this.setState({ markers: markersCopy, activeMarkerId: id }, this.resetDrawer);
});
@@ -332,6 +345,11 @@ class SurveyScreen extends React.Component {
render() {
const { activeMarkerId, markers } = this.state;
const activeMarker = _.find(markers, { id: activeMarkerId });
+ const note = _.get(activeMarker, 'note', '');
+ const noteButtonLabel = note ? 'Edit note' : 'Add note';
+ const direction = this.getToggleDirection();
+ const chevronIconName = `ios-arrow-${direction}`;
+
return (
-
+
- {activeMarkerId ? (
-
- ) : (
-
+ {activeMarker && (
+
+
+
+
+
+ {activeMarker.title}
+ {activeMarker.dateLabel}
+
+
+
+
+
)}
-
+ {!activeMarker && }
+
{activeMarker && (
-
+
+
+
+
+
)}
+ {this.state.modalVisible && (
+ {
+ this.setFormResponse(activeMarker.id, 'note', note, 0);
+ this.setState({ modalVisible: false });
+ }}
+ />
+ )}
);
}
@@ -411,9 +463,31 @@ const styles = StyleSheet.create({
drawerHeader: {
alignSelf: 'stretch',
},
+ headerContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ titleContainer: { paddingVertical: 10, paddingHorizontal: 20 },
+ title: { fontWeight: 'bold' },
+ personIconWrapper: {
+ padding: 12,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
formContainer: {
alignSelf: 'stretch',
},
+ drawerFooter: {
+ padding: 10,
+ flexDirection: 'row',
+ },
+ greyButton: {
+ width: Layout.window.width,
+ flexShrink: 1,
+ backgroundColor: '#FAFAFA',
+ borderWidth: 1,
+ borderColor: 'rgba(0, 0, 0, 0.12)',
+ },
bottomGuard: {
// This view adds whitespace below the drawer, in case the user over-pans it
position: 'absolute',
@@ -431,6 +505,10 @@ const styles = StyleSheet.create({
backgroundColor: '#D8D8D8',
borderRadius: 10,
},
+ chevron: {
+ position: 'absolute',
+ right: 20,
+ },
instructionsContainer: {
display: 'flex',
flexDirection: 'row',