import React from 'react'; import { EventEmitter, EventSubscription } from 'fbemitter'; import { NavigationEvents, NavigationScreenProp } from 'react-navigation'; import { AppState, AsyncStorage, Platform, StyleSheet, Text, View } from 'react-native'; import { FontAwesome, MaterialIcons } from '@expo/vector-icons'; import MapView from 'react-native-maps'; import * as Location from 'expo-location'; import * as Permissions from 'expo-permissions'; import * as TaskManager from 'expo-task-manager'; import Button from '../../components/Button'; import Colors from '../../constants/Colors'; const STORAGE_KEY = 'expo-home-locations'; const LOCATION_UPDATES_TASK = 'location-updates'; const locationEventsEmitter = new EventEmitter(); const locationAccuracyStates: { [key in Location.Accuracy]: Location.Accuracy } = { [Location.Accuracy.Lowest]: Location.Accuracy.Low, [Location.Accuracy.Low]: Location.Accuracy.Balanced, [Location.Accuracy.Balanced]: Location.Accuracy.High, [Location.Accuracy.High]: Location.Accuracy.Highest, [Location.Accuracy.Highest]: Location.Accuracy.BestForNavigation, [Location.Accuracy.BestForNavigation]: Location.Accuracy.Lowest, }; const locationActivityTypes: { [key in Location.ActivityType]: Location.ActivityType | undefined } = { [Location.ActivityType.Other]: Location.ActivityType.AutomotiveNavigation, [Location.ActivityType.AutomotiveNavigation]: Location.ActivityType.Fitness, [Location.ActivityType.Fitness]: Location.ActivityType.OtherNavigation, [Location.ActivityType.OtherNavigation]: Location.ActivityType.Airborne, [Location.ActivityType.Airborne]: undefined, }; interface Props { navigation: NavigationScreenProp<{}, any>; } interface State { accuracy: Location.Accuracy; activityType?: Location.ActivityType; isTracking: boolean; savedLocations: []; geofencingRegions: []; initialRegion?: any; showsBackgroundLocationIndicator: boolean; error?: string; } export default class BackgroundLocationMapScreen extends React.Component { static navigationOptions = { title: 'Background location', }; mapViewRef = React.createRef(); readonly state: State = { accuracy: Location.Accuracy.High, isTracking: false, showsBackgroundLocationIndicator: false, savedLocations: [], geofencingRegions: [], }; eventSubscription?: EventSubscription; didFocus = async () => { if (!await Location.isBackgroundLocationAvailableAsync()) { alert('Background location is not available in this application.'); this.props.navigation.goBack(); return; } const { status } = await Permissions.askAsync(Permissions.LOCATION); if (status !== 'granted') { AppState.addEventListener('change', this.handleAppStateChange); this.setState({ // tslint:disable-next-line max-line-length error: 'Location permissions are required in order to use this feature. You can manually enable them at any time in the "Location Services" section of the Settings app.', }); return; } else { this.setState({ error: undefined }); } const { coords } = await Location.getCurrentPositionAsync(); const isTracking = await Location.hasStartedLocationUpdatesAsync(LOCATION_UPDATES_TASK); const task = (await TaskManager.getRegisteredTasksAsync()).find( ({ taskName }) => taskName === LOCATION_UPDATES_TASK ); const savedLocations = await getSavedLocations(); const accuracy = (task && task.options.accuracy) || this.state.accuracy; this.eventSubscription = locationEventsEmitter.addListener('update', (locations: any) => { this.setState({ savedLocations: locations }); }); if (!isTracking) { alert('Click `Start tracking` to start getting location updates.'); } this.setState({ accuracy, activityType: task && task.options.activityType, isTracking, showsBackgroundLocationIndicator: task && task.options.showsBackgroundLocationIndicator, savedLocations, initialRegion: { latitude: coords.latitude, longitude: coords.longitude, latitudeDelta: 0.004, longitudeDelta: 0.002, }, }); } handleAppStateChange = (nextAppState: string) => { if (nextAppState !== 'active') { return; } if (this.state.initialRegion) { AppState.removeEventListener('change', this.handleAppStateChange); return; } this.didFocus(); } componentWillUnmount() { if (this.eventSubscription) { this.eventSubscription.remove(); } AppState.removeEventListener('change', this.handleAppStateChange); } async startLocationUpdates(accuracy = this.state.accuracy) { await Location.startLocationUpdatesAsync(LOCATION_UPDATES_TASK, { accuracy, activityType: this.state.activityType, pausesUpdatesAutomatically: this.state.activityType != null, showsBackgroundLocationIndicator: this.state.showsBackgroundLocationIndicator, deferredUpdatesInterval: 60 * 1000, // 1 minute deferredUpdatesDistance: 100, // 100 meters foregroundService: { notificationTitle: 'expo-location-demo', notificationBody: 'Background location is running...', notificationColor: Colors.tintColor, }, }); if (!this.state.isTracking) { alert( // tslint:disable-next-line max-line-length 'Now you can send app to the background, go somewhere and come back here! You can even terminate the app and it will be woken up when the new significant location change comes out.' ); } this.setState({ isTracking: true }); } async stopLocationUpdates() { await Location.stopLocationUpdatesAsync(LOCATION_UPDATES_TASK); this.setState({ isTracking: false }); } clearLocations = async () => { await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify([])); this.setState({ savedLocations: [] }); } toggleTracking = async () => { await AsyncStorage.removeItem(STORAGE_KEY); if (this.state.isTracking) { await this.stopLocationUpdates(); } else { await this.startLocationUpdates(); } this.setState({ savedLocations: [] }); } onAccuracyChange = () => { const accuracy = locationAccuracyStates[this.state.accuracy]; this.setState({ accuracy }); if (this.state.isTracking) { // Restart background task with the new accuracy. this.startLocationUpdates(accuracy); } } toggleLocationIndicator = async () => { const showsBackgroundLocationIndicator = !this.state.showsBackgroundLocationIndicator; this.setState({ showsBackgroundLocationIndicator }, async () => { if (this.state.isTracking) { await this.startLocationUpdates(); } }); } toggleActivityType = () => { if (this.state.activityType) { const nextActivityType = locationActivityTypes[this.state.activityType]; this.setState({ activityType: nextActivityType }); } else { this.setState({ activityType: Location.ActivityType.Other }); } if (this.state.isTracking) { // Restart background task with the new activity type this.startLocationUpdates(); } } onCenterMap = async () => { const { coords } = await Location.getCurrentPositionAsync(); const mapView = this.mapViewRef.current; if (mapView) { mapView.animateToRegion({ latitude: coords.latitude, longitude: coords.longitude, latitudeDelta: 0.004, longitudeDelta: 0.002, }); } } renderPolyline() { const { savedLocations } = this.state; if (savedLocations.length === 0) { return null; } return ( // @ts-ignore ); } render() { if (this.state.error) { return {this.state.error}; } if (!this.state.initialRegion) { return ; } return ( {this.renderPolyline()} {Platform.OS === 'android' ? null : ( )} {Platform.OS === 'android' ? null : (