Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Add animated Feeback to map long presses (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
pandananta authored Sep 3, 2018
1 parent acad2cb commit 75864c9
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 81 deletions.
184 changes: 118 additions & 66 deletions expo_project/components/MapWithMarkers.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -48,50 +50,95 @@ class MapWithMarkers extends React.Component {
onMapLongPress
} = this.props;
return this.state.region ? (
<MapView
style={styles.mapStyle}
provider="google"
onPress={() => onMapPress()}
onLongPress={e => onMapLongPress(e.nativeEvent.coordinate)}
initialRegion={this.state.region}
showsUserLocation
scrollEnabled
zoomEnabled
pitchEnabled={false}
<TouchableOpacity
activeOpacity={1}
onPressIn={e => {
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}
>
<MapView.Polyline
coordinates={MapConfig.polylineCoordinates}
strokeColor="#000"
strokeWidth={6}
/>
{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 (
<MapView.Marker
coordinate={marker.coordinate}
key={key}
identifier={marker.id}
stopPropagation
draggable
onDragEnd={onMarkerDragEnd}
onPress={() => onMarkerPress(marker.id)}
anchor={{ x: 0, y: 0 }}
calloutAnchor={{ x: 0, y: 0 }}
>
<PersonIcon
backgroundColor={marker.color}
size={selected ? 24 : 16}
/>
</MapView.Marker>
);
})}
</MapView>
<MapView
style={styles.mapStyle}
provider="google"
onPress={() => onMapPress()}
onLongPress={e =>
onMapLongPress(e.nativeEvent.coordinate, this.state.nextMarkerColor)
}
initialRegion={this.state.region}
showsUserLocation
scrollEnabled
zoomEnabled
pitchEnabled={false}
>
<MapView.Polyline
coordinates={MapConfig.polylineCoordinates}
strokeColor="#000"
strokeWidth={6}
/>
{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 (
<MapView.Marker
coordinate={marker.coordinate}
key={key}
identifier={marker.id}
stopPropagation
draggable
onDragEnd={e =>
onMarkerDragEnd(e.nativeEvent.id, e.nativeEvent.coordinate)
}
onPress={() => onMarkerPress(marker.id)}
anchor={{ x: 0.5, y: 0.5 }}
>
<PersonIcon
backgroundColor={marker.color}
size={selected ? 24 : 16}
/>
</MapView.Marker>
);
})}
</MapView>
{this.state.circularProgressLocation && (
<AnimatedCircularProgress
ref={ref => (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}
/>
)}
</TouchableOpacity>
) : null;
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
position: "relative"
},
mapStyle: {
...Platform.select({
ios: {
Expand All @@ -105,6 +152,11 @@ const styles = StyleSheet.create({
right: 0
}
})
},
circularProgress: {
alignSelf: "center",
position: "absolute",
backgroundColor: "transparent"
}
});

Expand Down
2 changes: 2 additions & 0 deletions expo_project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 8 additions & 15 deletions expo_project/screens/HomeScreen.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -235,19 +232,21 @@ class HomeScreen extends React.Component {
}
}

createNewMarker(coordinate) {
createNewMarker(coordinate, color) {
const markersCopy = [...this.state.markers];
const date = moment();
const dateLabel = date.format("HH:mm");
const title = "Person " + (markersCopy.length + 1);

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)
Expand All @@ -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 });

Expand All @@ -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 });
Expand Down
14 changes: 14 additions & 0 deletions expo_project/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5512,6 +5512,12 @@ [email protected]:
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"

[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz#32886242b3f2317e121f3aeb9b0a585e2b879b49"
Expand Down Expand Up @@ -5577,6 +5583,14 @@ [email protected]:
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"
Expand Down

0 comments on commit 75864c9

Please sign in to comment.