diff --git a/expo_project/components/MapWithMarkers.js b/expo_project/components/MapWithMarkers.js index 736b20a..485e87d 100644 --- a/expo_project/components/MapWithMarkers.js +++ b/expo_project/components/MapWithMarkers.js @@ -1,44 +1,46 @@ +import { MapView } from "expo"; import PropTypes from "prop-types"; import React from "react"; -import { Platform, StyleSheet } from "react-native"; -import { MapView } from "expo"; -import PersonIcon from "./PersonIcon"; -// import { Location, Permissions } from "expo"; - +import { Platform, StyleSheet, TouchableOpacity } from "react-native"; +import { AnimatedCircularProgress } from "react-native-circular-progress"; +import { iconColors } from "../constants/Colors"; import MapConfig from "../constants/Map"; +import PersonIcon from "../components/PersonIcon"; + +// NOTE: A longPress is more like 500ms, +// however there's a delay between when the longPress is registered +// and when a new marker is created in firestore and thereafter rendered +// Currently setting halo animation to 1000 to account for that +// but that might confuse users who release early (e.g. at 600ms) which still ends up creating a marker +const CIRCULAR_PROGRESS_ANIMATION_DURATION = 1000; +const CIRCULAR_PROGRESS_SIZE = 100; class MapWithMarkers extends React.Component { constructor(props) { super(props); - this.state = { region: MapConfig.defaultRegion }; + this.state = { + region: MapConfig.defaultRegion, + circularProgressLocation: null, + nextMarkerColor: this.getRandomIconColor() + }; } - // componentDidMount() { - // this._getLocationAsync(); - // } + getRandomIconColor = () => { + const iconOptions = Object.values(iconColors); + return iconOptions[Math.floor(Math.random() * iconOptions.length)]; + }; - // _getLocationAsync = async () => { - // let region = MapConfig.defaultRegion; - // // react native maps (the belly of expo's MapView ) requests location permissions for us - // // so here we are only retrieving permission, not asking for it - // const { status } = await Permissions.askAsync(Permissions.LOCATION); - // if (status === "granted") { - // const location = await Location.getCurrentPositionAsync({ - // enableHighAccuracy: true - // }); - // const { latitude, longitude } = location.coords; - // region = { - // latitude, - // longitude, - // latitudeDelta: 0.0043, - // longitudeDelta: 0.0034 - // }; - // } - // this.setState({ region }); - // }; + setNextColor = () => { + this.setState({ nextMarkerColor: this.getRandomIconColor() }); + }; render() { + /* + Note: we're taking advantage of the fact that AnimatedCircularProgress animates on mount + by mounting on pressIn and unmounting on pressOut. + Unmounting so often might give us a noticeable performance hit, so if that happens, we can instead manage fill in state. + */ const { markers, activeMarkerId, @@ -48,50 +50,95 @@ class MapWithMarkers extends React.Component { onMapLongPress } = this.props; return this.state.region ? ( - onMapPress()} - onLongPress={e => onMapLongPress(e.nativeEvent.coordinate)} - initialRegion={this.state.region} - showsUserLocation - scrollEnabled - zoomEnabled - pitchEnabled={false} + { + const { nativeEvent } = e; + this.setState({ + circularProgressLocation: { + top: nativeEvent.locationY - CIRCULAR_PROGRESS_SIZE / 2, + left: nativeEvent.locationX - CIRCULAR_PROGRESS_SIZE / 2 + } + }); + }} + onPressOut={e => { + this.setState({ + circularProgressLocation: null, + nextMarkerColor: this.getRandomIconColor() + }); + }} + style={styles.container} > - - {markers.map(marker => { - const selected = marker.id === activeMarkerId; - const key = marker.id + (selected ? "-selected" : ""); //trigger a re render when switching states, so it recenters itself - return ( - onMarkerPress(marker.id)} - anchor={{ x: 0, y: 0 }} - calloutAnchor={{ x: 0, y: 0 }} - > - - - ); - })} - + onMapPress()} + onLongPress={e => + onMapLongPress(e.nativeEvent.coordinate, this.state.nextMarkerColor) + } + initialRegion={this.state.region} + showsUserLocation + scrollEnabled + zoomEnabled + pitchEnabled={false} + > + + {markers.map(marker => { + const selected = marker.id === activeMarkerId; + const key = marker.id + (selected ? "-selected" : ""); //trigger a re render when switching states, so it recenters itself + return ( + + onMarkerDragEnd(e.nativeEvent.id, e.nativeEvent.coordinate) + } + onPress={() => onMarkerPress(marker.id)} + anchor={{ x: 0.5, y: 0.5 }} + > + + + ); + })} + + {this.state.circularProgressLocation && ( + (this.circularProgress = ref)} + style={[ + styles.circularProgress, + { + top: this.state.circularProgressLocation.top, + left: this.state.circularProgressLocation.left + } + ]} + size={CIRCULAR_PROGRESS_SIZE} + width={3} + tintColor={this.state.nextMarkerColor} + backgroundColor="transparent" + duration={CIRCULAR_PROGRESS_ANIMATION_DURATION} + fill={100} + /> + )} + ) : null; } } const styles = StyleSheet.create({ + container: { + flex: 1, + position: "relative" + }, mapStyle: { ...Platform.select({ ios: { @@ -105,6 +152,11 @@ const styles = StyleSheet.create({ right: 0 } }) + }, + circularProgress: { + alignSelf: "center", + position: "absolute", + backgroundColor: "transparent" } }); diff --git a/expo_project/package.json b/expo_project/package.json index b6b03cd..9207b98 100644 --- a/expo_project/package.json +++ b/expo_project/package.json @@ -16,6 +16,8 @@ "prop-types": "^15.6.2", "react": "16.3.1", "react-native": "https://github.com/expo/react-native/archive/sdk-28.0.0.tar.gz", + "react-native-circular-progress": "^1.0.1", + "react-native-svg": "^6.5.2", "react-navigation": "2.3.1" }, "devDependencies": { diff --git a/expo_project/screens/HomeScreen.js b/expo_project/screens/HomeScreen.js index 3f9a452..77e7a2a 100644 --- a/expo_project/screens/HomeScreen.js +++ b/expo_project/screens/HomeScreen.js @@ -1,23 +1,21 @@ import React from "react"; import { + Animated, PanResponder, Platform, StyleSheet, + ScrollView, View, - Animated, TouchableOpacity } from "react-native"; import { withNavigation } from "react-navigation"; import * as _ from "lodash"; -import { iconColors } from "../constants/Colors"; -import { ScrollView } from "../node_modules/react-native-gesture-handler"; import moment from "moment"; -import Layout from "../constants/Layout"; - import MapWithMarkers from "../components/MapWithMarkers"; import MarkerCarousel from "../components/MarkerCarousel"; import Survey from "../components/Survey"; import ColoredButton from "../components/ColoredButton"; +import Layout from "../constants/Layout"; // TODO (Ananta): shouold be dynamically set const INITIAL_DRAWER_TRANSLATE_Y = Layout.drawer.height; @@ -77,7 +75,6 @@ class HomeScreen extends React.Component { this.resetDrawer = this.resetDrawer.bind(this); this.toggleDrawer = this.toggleDrawer.bind(this); this.selectMarker = this.selectMarker.bind(this); - this.getRandomIconColor = this.getRandomIconColor.bind(this); this.createNewMarker = this.createNewMarker.bind(this); this.setMarkerLocation = this.setMarkerLocation.bind(this); this.setFormResponse = this.setFormResponse.bind(this); @@ -235,7 +232,7 @@ class HomeScreen extends React.Component { } } - createNewMarker(coordinate) { + createNewMarker(coordinate, color) { const markersCopy = [...this.state.markers]; const date = moment(); const dateLabel = date.format("HH:mm"); @@ -243,11 +240,13 @@ class HomeScreen extends React.Component { const marker = { coordinate: coordinate, - color: this.getRandomIconColor(), + color, title, dateLabel }; + // TODO (Seabass or Ananta): Figure out a way to get faster UI feedback + // Would be nice for UI to optimistically render before firestore returns this.firestore .collection("study") .doc(studyId) @@ -267,9 +266,8 @@ class HomeScreen extends React.Component { }); } - setMarkerLocation(e) { + setMarkerLocation(id, coordinate) { // TODO: add logic for updating in db - const { id, coordinate } = e.nativeEvent; const markersCopy = [...this.state.markers]; const marker = _.find(markersCopy, { id }); @@ -290,11 +288,6 @@ class HomeScreen extends React.Component { } } - getRandomIconColor() { - const iconOptions = Object.values(iconColors); - return iconOptions[Math.floor(Math.random() * iconOptions.length)]; - } - render() { const { activeMarkerId, markers } = this.state; const activeMarker = _.find(markers, { id: activeMarkerId }); diff --git a/expo_project/yarn.lock b/expo_project/yarn.lock index a3bd7cc..3d3aa70 100644 --- a/expo_project/yarn.lock +++ b/expo_project/yarn.lock @@ -5512,6 +5512,12 @@ react-native-branch@2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/react-native-branch/-/react-native-branch-2.2.5.tgz#4074dd63b4973e6397d9ce50e97b57c77a518e9d" +react-native-circular-progress@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-native-circular-progress/-/react-native-circular-progress-1.0.1.tgz#94a0e9b1f2ec28ce0fa56665ef29dfba99d1d2d6" + dependencies: + prop-types "^15.6.0" + react-native-dismiss-keyboard@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz#32886242b3f2317e121f3aeb9b0a585e2b879b49" @@ -5577,6 +5583,14 @@ react-native-svg@6.2.2: lodash "^4.16.6" pegjs "^0.10.0" +react-native-svg@^6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-6.5.2.tgz#1105896b8873b0856821b18daa0c6898cea6c00c" + dependencies: + color "^2.0.1" + lodash "^4.16.6" + pegjs "^0.10.0" + react-native-tab-view@^0.0.77: version "0.0.77" resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz#11ceb8e7c23100d07e628dc151b57797524d00d4"