import PropTypes from 'prop-types'; import React from 'react'; import { StyleSheet, Platform, NativeModules, Animated, findNodeHandle, ViewPropTypes, View, } from 'react-native'; import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import decorateMapComponent, { SUPPORTED, USES_DEFAULT_IMPLEMENTATION, } from './decorateMapComponent'; const viewConfig = { uiViewClassName: 'AIRMapMarker', validAttributes: { coordinate: true, }, }; // if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44) const viewPropTypes = ViewPropTypes || View.propTypes; const propTypes = { ...viewPropTypes, // TODO(lmr): get rid of these? identifier: PropTypes.string, reuseIdentifier: PropTypes.string, /** * The title of the marker. This is only used if the component has no children that * are an ``, in which case the default callout behavior will be used, which * will show both the `title` and the `description`, if provided. */ title: PropTypes.string, /** * The description of the marker. This is only used if the component has no children * that are an ``, in which case the default callout behavior will be used, * which will show both the `title` and the `description`, if provided. */ description: PropTypes.string, /** * A custom image to be used as the marker's icon. Only local image resources are allowed to be * used. */ image: PropTypes.any, /** * Opacity level of view/image based markers */ opacity: PropTypes.number, /** * If no custom marker view or custom image is provided, the platform default pin will be used, * which can be customized by this color. Ignored if a custom marker is being used. */ pinColor: PropTypes.string, /** * The coordinate for the marker. */ coordinate: PropTypes.shape({ /** * Coordinates for the anchor point of the marker. */ latitude: PropTypes.number.isRequired, longitude: PropTypes.number.isRequired, }).isRequired, /** * The offset (in points) at which to display the view. * * By default, the center point of an annotation view is placed at the coordinate point of the * associated annotation. You can use this property to reposition the annotation view as * needed. This x and y offset values are measured in points. Positive offset values move the * annotation view down and to the right, while negative values move it up and to the left. * * For android, see the `anchor` prop. * * @platform ios */ centerOffset: PropTypes.shape({ /** * Offset from the anchor point */ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), /** * The offset (in points) at which to place the callout bubble. * * This property determines the additional distance by which to move the callout bubble. When * this property is set to (0, 0), the anchor point of the callout bubble is placed on the * top-center point of the marker view’s frame. Specifying positive offset values moves the * callout bubble down and to the right, while specifying negative values moves it up and to * the left. * * For android, see the `calloutAnchor` prop. * * @platform ios */ calloutOffset: PropTypes.shape({ /** * Offset to the callout */ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), /** * Sets the anchor point for the marker. * * The anchor specifies the point in the icon image that is anchored to the marker's position * on the Earth's surface. * * The anchor point is specified in the continuous space [0.0, 1.0] x [0.0, 1.0], where (0, 0) * is the top-left corner of the image, and (1, 1) is the bottom-right corner. The anchoring * point in a W x H image is the nearest discrete grid point in a (W + 1) x (H + 1) grid, * obtained by scaling the then rounding. For example, in a 4 x 2 image, the anchor point * (0.7, 0.6) resolves to the grid point at (3, 1). * * For ios, see the `centerOffset` prop. * * @platform android */ anchor: PropTypes.shape({ /** * Offset to the callout */ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), /** * Specifies the point in the marker image at which to anchor the callout when it is displayed. * This is specified in the same coordinate system as the anchor. See the `andor` prop for more * details. * * The default is the top middle of the image. * * For ios, see the `calloutOffset` prop. * * @platform android */ calloutAnchor: PropTypes.shape({ /** * Offset to the callout */ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), /** * Sets whether this marker should be flat against the map true or a billboard facing the * camera false. * * @platform android */ flat: PropTypes.bool, draggable: PropTypes.bool, /** * Sets whether this marker should track view changes true. * * @platform ios */ tracksViewChanges: PropTypes.bool, /** * Callback that is called when the user presses on the marker */ onPress: PropTypes.func, /** * Callback that is called when the user selects the marker, before the callout is shown. * * @platform ios */ onSelect: PropTypes.func, /** * Callback that is called when the marker is deselected, before the callout is hidden. * * @platform ios */ onDeselect: PropTypes.func, /** * Callback that is called when the user taps the callout view. */ onCalloutPress: PropTypes.func, /** * Callback that is called when the user initiates a drag on this marker (if it is draggable) */ onDragStart: PropTypes.func, /** * Callback called continuously as the marker is dragged */ onDrag: PropTypes.func, /** * Callback that is called when a drag on this marker finishes. This is usually the point you * will want to setState on the marker's coordinate again */ onDragEnd: PropTypes.func, }; const defaultProps = { onPress() {}, }; class MapMarker extends React.Component { constructor(props) { super(props); this.showCallout = this.showCallout.bind(this); this.hideCallout = this.hideCallout.bind(this); } setNativeProps(props) { this.marker.setNativeProps(props); } showCallout() { this._runCommand('showCallout', []); } hideCallout() { this._runCommand('hideCallout', []); } _getHandle() { return findNodeHandle(this.marker); } _runCommand(name, args) { switch (Platform.OS) { case 'android': NativeModules.UIManager.dispatchViewManagerCommand( this._getHandle(), this.getUIManagerCommand(name), args ); break; case 'ios': this.getMapManagerCommand(name)(this._getHandle(), ...args); break; default: break; } } render() { let image; if (this.props.image) { image = resolveAssetSource(this.props.image) || {}; image = image.uri || this.props.image; } const AIRMapMarker = this.getAirComponent(); return ( { this.marker = ref; }} {...this.props} image={image} style={[styles.marker, this.props.style]} /> ); } } MapMarker.propTypes = propTypes; MapMarker.defaultProps = defaultProps; MapMarker.viewConfig = viewConfig; const styles = StyleSheet.create({ marker: { position: 'absolute', backgroundColor: 'transparent', }, }); MapMarker.Animated = Animated.createAnimatedComponent(MapMarker); module.exports = decorateMapComponent(MapMarker, { componentType: 'Marker', providers: { google: { ios: SUPPORTED, android: USES_DEFAULT_IMPLEMENTATION, }, }, });