This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

2
node_modules/expo-location/.eslintrc.js generated vendored Normal file
View File

@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

63
node_modules/expo-location/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,63 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
## 10.0.0 — 2020-11-17
### 🛠 Breaking changes
- Make background location an opt-in permission on Android. ([#10989](https://github.com/expo/expo/pull/10989) by [@bycedric](https://github.com/bycedric))
## 9.0.1 — 2020-10-02
### 🐛 Bug fixes
- Redeliver intent when restarting task service. ([#10410](https://github.com/expo/expo/pull/10410) by [@byCedric](https://github.com/byCedric))
## 9.0.0 — 2020-08-18
### 🛠 Breaking changes
- Add `scope` field in returned value to indicate whether background permissions are granted. Add `android.accuracy` field to determine whether `coarse` or `fine` location permission is granted. ([#9446](https://github.com/expo/expo/pull/9446) by [@mczernek](https://github.com/mczernek))
- `getLastKnownPositionAsync` no longer rejects when the last known location is not available now it returns `null`. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Removed the deprecated `enableHighAccuracy` option of `getCurrentPositionAsync`. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Removed `maximumAge` and `timeout` options from `getCurrentPositionAsync`  it's been Android only and the same behavior can be achieved on all platforms on the JavaScript side. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Made type and enum names more consistent and in line with our standards — they all are now prefixed by `Location`. The most common ones are still accessible without the prefix, but it's not the recommended way. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- `geocodeAsync` and `reverseGeocodeAsync` no longer falls back to Google Maps API on Android. ([#9444](https://github.com/expo/expo/pull/9444) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- Added missing `altitudeAccuracy` to the location object on Android (requires at least Android 8.0). ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Improved support for Web — added missing methods for requesting permissions and getting last known position. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Added `maxAge` and `requiredAccuracy` options to `getLastKnownPositionAsync`. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Google Maps Geocoding API can now be used on all platforms with the new `useGoogleMaps` option. ([#9444](https://github.com/expo/expo/pull/9444) by [@tsapeta](https://github.com/tsapeta))
- Added `district`, `subregion` and `timezone` values to reverse-geocoded address object. ([#9444](https://github.com/expo/expo/pull/9444) by [@tsapeta](https://github.com/tsapeta))
### 🐛 Bug fixes
- Fixed different types being used on Web platform. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- `getLastKnownPositionAsync` no longer requests for the current location on iOS and just returns the last known one as it should be. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Fixed `getCurrentPositionAsync` not resolving on Android when the lowest accuracy is used. ([#9251](https://github.com/expo/expo/pull/9251) by [@tsapeta](https://github.com/tsapeta))
- Fixed `LocationGeocodedAddress` type to reflect the possibility of receiving `null` values. ([#9444](https://github.com/expo/expo/pull/9444) by [@tsapeta](https://github.com/tsapeta))
## 8.3.0 — 2020-07-16
### 🐛 Bug fixes
- Added some safety checks to prevent `NullPointerExceptions` in background location on Android. ([#8864](https://github.com/expo/expo/pull/8864) by [@mczernek](https://github.com/mczernek))
- Add `isoCountryCode` to `Address` type and reverse lookup. ([#8913](https://github.com/expo/expo/pull/8913) by [@bycedric](https://github.com/bycedric))
- Fix geocoding requests not resolving/rejecting on iOS when the app is in the background or inactive state. It makes it possible to use geocoding in such app states, however it's still discouraged. ([#9178](https://github.com/expo/expo/pull/9178) by [@tsapeta](https://github.com/tsapeta))
## 8.2.1 — 2020-05-29
_This version does not introduce any user-facing changes._
## 8.2.0 — 2020-05-27
_This version does not introduce any user-facing changes._

57
node_modules/expo-location/README.md generated vendored Normal file
View File

@ -0,0 +1,57 @@
# expo-location
Allows reading geolocation information from the device. Your app can poll for the current location or subscribe to location update events.
# API documentation
- [Documentation for the master branch](https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/sdk/location.md)
- [Documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/location/)
# Installation in managed Expo projects
For managed [managed](https://docs.expo.io/versions/latest/introduction/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/location/).
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `react-native-unimodules` package](https://github.com/expo/expo/tree/master/packages/react-native-unimodules) before continuing.
### Add the package to your npm dependencies
```
expo install expo-location
```
### Configure for iOS
Add `NSLocationAlwaysAndWhenInUseUsageDescription`, `NSLocationAlwaysUsageDescription` and `NSLocationWhenInUseUsageDescription` keys to your `Info.plist`:
```xml
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to use your location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to use your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to use your location</string>
```
Run `npx pod-install` after installing the npm package.
### Configure for Android
This module requires the permissions for approximate and exact device location. It also needs the foreground service permission to subscribe to location updates, while the app is in use. These permissions are automatically added.
```xml
<!-- Added permissions -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Optional permissions -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
```
> **Note:** on Android, you have to [submit your app for review and request access to use the background location permission](https://support.google.com/googleplay/android-developer/answer/9799150?hl=en).
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

73
node_modules/expo-location/android/build.gradle generated vendored Normal file
View File

@ -0,0 +1,73 @@
apply plugin: 'com.android.library'
apply plugin: 'maven'
group = 'host.exp.exponent'
version = '10.0.0'
// Simple helper that allows the root project to override versions declared by this library.
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
// Upload android library to maven with javadoc and android sources
configurations {
deployerJars
}
// Creating sources with comments
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
// Put the androidSources and javadoc to the artifacts
artifacts {
archives androidSourcesJar
}
uploadArchives {
repositories {
mavenDeployer {
configuration = configurations.deployerJars
repository(url: mavenLocal().url)
}
}
}
android {
compileSdkVersion safeExtGet("compileSdkVersion", 29)
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 29)
versionCode 28
versionName "10.0.0"
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
if (new File(rootProject.projectDir.parentFile, 'package.json').exists()) {
apply from: project(":unimodules-core").file("../unimodules-core.gradle")
} else {
throw new GradleException(
"'unimodules-core.gradle' was not found in the usual React Native dependency location. " +
"This package can only be used in such projects. Are you sure you've installed the dependencies properly?")
}
dependencies {
unimodule "unimodules-core"
unimodule "unimodules-permissions-interface"
unimodule "unimodules-task-manager-interface"
api 'com.google.android.gms:play-services-location:16.0.0'
api('io.nlopez.smartlocation:library:3.2.11') {
transitive = false
}
}

View File

@ -0,0 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="expo.modules.location">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
<service
android:name=".services.LocationTaskService"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,5 @@
package expo.modules.location;
public interface LocationActivityResultListener {
void onResult(int resultCode);
}

View File

@ -0,0 +1,266 @@
package expo.modules.location;
import android.content.Context;
import android.location.Address;
import android.location.Location;
import android.location.LocationManager;
import android.os.BaseBundle;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;
import com.google.android.gms.location.LocationRequest;
import java.util.Map;
import org.unimodules.core.Promise;
import org.unimodules.core.errors.CodedException;
import io.nlopez.smartlocation.location.config.LocationAccuracy;
import io.nlopez.smartlocation.location.config.LocationParams;
import static expo.modules.location.LocationModule.*;
public class LocationHelpers {
private static final String TAG = LocationHelpers.class.getSimpleName();
//region public methods
public static boolean isAnyProviderAvailable(Context context) {
if (context == null) {
return false;
}
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
return locationManager != null && (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
}
public static boolean hasNetworkProviderEnabled(Context context) {
if (context == null) {
return false;
}
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
return locationManager != null && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}
public static <BundleType extends BaseBundle> BundleType locationToBundle(Location location, Class<BundleType> bundleTypeClass) {
if (location == null) {
return null;
}
try {
BundleType map = bundleTypeClass.newInstance();
BundleType coords = locationToCoordsBundle(location, bundleTypeClass);
if (coords == null) {
return null;
}
if (map instanceof PersistableBundle) {
((PersistableBundle) map).putPersistableBundle("coords", (PersistableBundle) coords);
} else if (map instanceof Bundle) {
((Bundle) map).putBundle("coords", (Bundle) coords);
((Bundle) map).putBoolean("mocked", location.isFromMockProvider());
}
map.putDouble("timestamp", location.getTime());
return map;
} catch (IllegalAccessException | InstantiationException e) {
Log.e(TAG, "Unexpected exception was thrown when converting location to the bundle: " + e.toString());
return null;
}
}
static <BundleType extends BaseBundle> BundleType locationToCoordsBundle(Location location, Class<BundleType> bundleTypeClass) {
try {
BundleType coords = bundleTypeClass.newInstance();
coords.putDouble("latitude", location.getLatitude());
coords.putDouble("longitude", location.getLongitude());
coords.putDouble("altitude", location.getAltitude());
coords.putDouble("accuracy", location.getAccuracy());
coords.putDouble("heading", location.getBearing());
coords.putDouble("speed", location.getSpeed());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
coords.putDouble("altitudeAccuracy", location.getVerticalAccuracyMeters());
} else {
coords.putString("altitudeAccuracy", null);
}
return coords;
} catch (IllegalAccessException | InstantiationException e) {
Log.e(TAG, "Unexpected exception was thrown when converting location to coords bundle: " + e.toString());
return null;
}
}
static Bundle addressToBundle(Address address) {
Bundle map = new Bundle();
map.putString("city", address.getLocality());
map.putString("district", address.getSubLocality());
map.putString("street", address.getThoroughfare());
map.putString("region", address.getAdminArea());
map.putString("subregion", address.getSubAdminArea());
map.putString("country", address.getCountryName());
map.putString("postalCode", address.getPostalCode());
map.putString("name", address.getFeatureName());
map.putString("isoCountryCode", address.getCountryCode());
map.putString("timezone", null);
return map;
}
static Bundle headingToBundle(double trueNorth, double magneticNorth, int accuracy) {
Bundle heading = new Bundle();
heading.putDouble("trueHeading", trueNorth);
heading.putDouble("magHeading", magneticNorth);
heading.putInt("accuracy", accuracy);
return heading;
}
public static LocationRequest prepareLocationRequest(Map<String, Object> options) {
LocationParams locationParams = LocationHelpers.mapOptionsToLocationParams(options);
int accuracy = LocationHelpers.getAccuracyFromOptions(options);
return new LocationRequest()
.setFastestInterval(locationParams.getInterval())
.setInterval(locationParams.getInterval())
.setMaxWaitTime(locationParams.getInterval())
.setSmallestDisplacement(locationParams.getDistance())
.setPriority(mapAccuracyToPriority(accuracy));
}
public static LocationParams mapOptionsToLocationParams(Map<String, Object> options) {
int accuracy = getAccuracyFromOptions(options);
LocationParams.Builder locationParamsBuilder = buildLocationParamsForAccuracy(accuracy);
if (options.containsKey("timeInterval")) {
Number timeInterval = (Number) options.get("timeInterval");
locationParamsBuilder.setInterval(timeInterval.longValue());
}
if (options.containsKey("distanceInterval")) {
Number distanceInterval = (Number) options.get("distanceInterval");
locationParamsBuilder.setDistance(distanceInterval.floatValue());
}
return locationParamsBuilder.build();
}
static void requestSingleLocation(final LocationModule locationModule, final LocationRequest locationRequest, final Promise promise) {
// we want just one update
locationRequest.setNumUpdates(1);
locationModule.requestLocationUpdates(locationRequest, null, new LocationRequestCallbacks() {
@Override
public void onLocationChanged(Location location) {
promise.resolve(LocationHelpers.locationToBundle(location, Bundle.class));
}
@Override
public void onLocationError(CodedException exception) {
promise.reject(exception);
}
@Override
public void onRequestFailed(CodedException exception) {
promise.reject(exception);
}
});
}
static void requestContinuousUpdates(final LocationModule locationModule, final LocationRequest locationRequest, final int watchId, final Promise promise) {
locationModule.requestLocationUpdates(locationRequest, watchId, new LocationRequestCallbacks() {
@Override
public void onLocationChanged(Location location) {
Bundle response = new Bundle();
response.putBundle("location", LocationHelpers.locationToBundle(location, Bundle.class));
locationModule.sendLocationResponse(watchId, response);
}
@Override
public void onRequestSuccess() {
promise.resolve(null);
}
@Override
public void onRequestFailed(CodedException exception) {
promise.reject(exception);
}
});
}
/**
* Checks whether given location didn't exceed given `maxAge` and fits in the required accuracy.
*/
public static boolean isLocationValid(Location location, final Map<String, Object> options) {
if (location == null) {
return false;
}
double maxAge = options.containsKey("maxAge") ? (double) options.get("maxAge") : Double.MAX_VALUE;
double requiredAccuracy = options.containsKey("requiredAccuracy") ? (double) options.get("requiredAccuracy") : Double.MAX_VALUE;
double timeDiff = System.currentTimeMillis() - location.getTime();
return timeDiff <= maxAge && location.getAccuracy() <= requiredAccuracy;
}
//endregion
//region private methods
private static int getAccuracyFromOptions(Map<String, Object> options) {
return options.containsKey("accuracy") ? ((Number) options.get("accuracy")).intValue() : ACCURACY_BALANCED;
}
private static LocationParams.Builder buildLocationParamsForAccuracy(int accuracy) {
switch (accuracy) {
case ACCURACY_LOWEST:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.LOWEST)
.setDistance(3000)
.setInterval(10000);
case ACCURACY_LOW:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.LOW)
.setDistance(1000)
.setInterval(5000);
case ACCURACY_BALANCED:
default:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.MEDIUM)
.setDistance(100)
.setInterval(3000);
case ACCURACY_HIGH:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.HIGH)
.setDistance(50)
.setInterval(2000);
case ACCURACY_HIGHEST:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.HIGH)
.setDistance(25)
.setInterval(1000);
case ACCURACY_BEST_FOR_NAVIGATION:
return new LocationParams.Builder()
.setAccuracy(LocationAccuracy.HIGH)
.setDistance(0)
.setInterval(500);
}
}
private static int mapAccuracyToPriority(int accuracy) {
switch (accuracy) {
case LocationModule.ACCURACY_BEST_FOR_NAVIGATION:
case LocationModule.ACCURACY_HIGHEST:
case LocationModule.ACCURACY_HIGH:
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case LocationModule.ACCURACY_BALANCED:
case LocationModule.ACCURACY_LOW:
default:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
case LocationModule.ACCURACY_LOWEST:
return LocationRequest.PRIORITY_LOW_POWER;
}
}
//endregion
}

View File

@ -0,0 +1,847 @@
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules.location;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.hardware.GeomagneticField;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationAvailability;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.Task;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.Promise;
import org.unimodules.core.interfaces.ActivityEventListener;
import org.unimodules.core.interfaces.ActivityProvider;
import org.unimodules.core.interfaces.ExpoMethod;
import org.unimodules.core.interfaces.LifecycleEventListener;
import org.unimodules.core.interfaces.services.EventEmitter;
import org.unimodules.core.interfaces.services.UIManager;
import org.unimodules.interfaces.permissions.Permissions;
import org.unimodules.interfaces.permissions.PermissionsResponse;
import org.unimodules.interfaces.permissions.PermissionsStatus;
import org.unimodules.interfaces.taskManager.TaskManagerInterface;
import expo.modules.location.exceptions.LocationBackgroundUnauthorizedException;
import expo.modules.location.exceptions.LocationRequestRejectedException;
import expo.modules.location.exceptions.LocationSettingsUnsatisfiedException;
import expo.modules.location.exceptions.LocationUnauthorizedException;
import expo.modules.location.exceptions.LocationUnavailableException;
import expo.modules.location.taskConsumers.GeofencingTaskConsumer;
import expo.modules.location.taskConsumers.LocationTaskConsumer;
import io.nlopez.smartlocation.SmartLocation;
import io.nlopez.smartlocation.geocoding.utils.LocationAddress;
import io.nlopez.smartlocation.location.config.LocationParams;
import io.nlopez.smartlocation.location.utils.LocationState;
public class LocationModule extends ExportedModule implements LifecycleEventListener, SensorEventListener, ActivityEventListener {
private static final String TAG = LocationModule.class.getSimpleName();
private static final String LOCATION_EVENT_NAME = "Expo.locationChanged";
private static final String HEADING_EVENT_NAME = "Expo.headingChanged";
private static final int CHECK_SETTINGS_REQUEST_CODE = 42;
private static final String SHOW_USER_SETTINGS_DIALOG_KEY = "mayShowUserSettingsDialog";
public static final int ACCURACY_LOWEST = 1;
public static final int ACCURACY_LOW = 2;
public static final int ACCURACY_BALANCED = 3;
public static final int ACCURACY_HIGH = 4;
public static final int ACCURACY_HIGHEST = 5;
public static final int ACCURACY_BEST_FOR_NAVIGATION = 6;
public static final int GEOFENCING_EVENT_ENTER = 1;
public static final int GEOFENCING_EVENT_EXIT = 2;
public static final int GEOFENCING_REGION_STATE_UNKNOWN = 0;
public static final int GEOFENCING_REGION_STATE_INSIDE = 1;
public static final int GEOFENCING_REGION_STATE_OUTSIDE = 2;
public interface OnResultListener {
void onResult(Location location);
}
private Context mContext;
private SensorManager mSensorManager;
private GeomagneticField mGeofield;
private FusedLocationProviderClient mLocationProvider;
private Map<Integer, LocationCallback> mLocationCallbacks = new HashMap<>();
private Map<Integer, LocationRequest> mLocationRequests = new HashMap<>();
private List<LocationActivityResultListener> mPendingLocationRequests = new ArrayList<>();
// modules
private EventEmitter mEventEmitter;
private UIManager mUIManager;
private Permissions mPermissionsManager;
private TaskManagerInterface mTaskManager;
private ActivityProvider mActivityProvider;
private float[] mGravity;
private float[] mGeomagnetic;
private int mHeadingId;
private float mLastAzimut = 0;
private int mAccuracy = 0;
private long mLastUpdate = 0;
private boolean mGeocoderPaused = false;
private static final double DEGREE_DELTA = 0.0355; // in radians, about 2 degrees
private static final float TIME_DELTA = 50; // in milliseconds
public LocationModule(Context context) {
super(context);
mContext = context;
}
@Override
public String getName() {
return "ExpoLocation";
}
@Override
public void onCreate(ModuleRegistry moduleRegistry) {
if (mUIManager != null) {
mUIManager.unregisterLifecycleEventListener(this);
}
mEventEmitter = moduleRegistry.getModule(EventEmitter.class);
mUIManager = moduleRegistry.getModule(UIManager.class);
mPermissionsManager = moduleRegistry.getModule(Permissions.class);
mTaskManager = moduleRegistry.getModule(TaskManagerInterface.class);
mActivityProvider = moduleRegistry.getModule(ActivityProvider.class);
if (mUIManager != null) {
mUIManager.registerLifecycleEventListener(this);
}
}
//region Expo methods
@ExpoMethod
public void requestPermissionsAsync(final Promise promise) {
if (mPermissionsManager == null) {
promise.reject("E_NO_PERMISSIONS", "Permissions module is null. Are you sure all the installed Expo modules are properly linked?");
return;
}
mPermissionsManager.askForPermissions(result -> {
promise.resolve(handleLocationPermissions(result));
}, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
}
@ExpoMethod
public void getPermissionsAsync(final Promise promise) {
if (mPermissionsManager == null) {
promise.reject("E_NO_PERMISSIONS", "Permissions module is null. Are you sure all the installed Expo modules are properly linked?");
return;
}
mPermissionsManager.getPermissions(result -> {
promise.resolve(handleLocationPermissions(result));
}, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
}
/**
* Resolves to the last known position if it is available and matches given requirements or null otherwise.
*/
@ExpoMethod
public void getLastKnownPositionAsync(final Map<String, Object> options, final Promise promise) {
// Check for permissions
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
getLastKnownLocation(location -> {
if (LocationHelpers.isLocationValid(location, options)) {
promise.resolve(LocationHelpers.locationToBundle(location, Bundle.class));
} else {
promise.resolve(null);
}
});
}
/**
* Requests for the current position. Depending on given accuracy, it may take some time to resolve.
* If you don't need an up-to-date location see `getLastKnownPosition`.
*/
@ExpoMethod
public void getCurrentPositionAsync(final Map<String, Object> options, final Promise promise) {
// Read options
final LocationRequest locationRequest = LocationHelpers.prepareLocationRequest(options);
boolean showUserSettingsDialog = !options.containsKey(SHOW_USER_SETTINGS_DIALOG_KEY) || (boolean) options.get(SHOW_USER_SETTINGS_DIALOG_KEY);
// Check for permissions
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
if (LocationHelpers.hasNetworkProviderEnabled(mContext) || !showUserSettingsDialog) {
LocationHelpers.requestSingleLocation(this, locationRequest, promise);
} else {
// Pending requests can ask the user to turn on improved accuracy mode in user's settings.
addPendingLocationRequest(locationRequest, resultCode -> {
if (resultCode == Activity.RESULT_OK) {
LocationHelpers.requestSingleLocation(LocationModule.this, locationRequest, promise);
} else {
promise.reject(new LocationSettingsUnsatisfiedException());
}
});
}
}
@ExpoMethod
public void getProviderStatusAsync(final Promise promise) {
if (mContext == null) {
promise.reject("E_CONTEXT_UNAVAILABLE", "Context is not available");
return;
}
LocationState state = SmartLocation.with(mContext).location().state();
Bundle map = new Bundle();
map.putBoolean("locationServicesEnabled", state.locationServicesEnabled()); // If location is off
map.putBoolean("gpsAvailable", state.isGpsAvailable()); // If GPS provider is enabled
map.putBoolean("networkAvailable", state.isNetworkAvailable()); // If network provider is enabled
map.putBoolean("passiveAvailable", state.isPassiveAvailable()); // If passive provider is enabled
map.putBoolean("backgroundModeEnabled", state.locationServicesEnabled()); // background mode is always available if location services are on
promise.resolve(map);
}
// Start Compass Module
@ExpoMethod
public void watchDeviceHeading(final int watchId, final Promise promise) {
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
this.mHeadingId = watchId;
startHeadingUpdate();
promise.resolve(null);
}
@ExpoMethod
public void watchPositionImplAsync(final int watchId, final Map<String, Object> options, final Promise promise) {
// Check for permissions
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
final LocationRequest locationRequest = LocationHelpers.prepareLocationRequest(options);
boolean showUserSettingsDialog = !options.containsKey(SHOW_USER_SETTINGS_DIALOG_KEY) || (boolean) options.get(SHOW_USER_SETTINGS_DIALOG_KEY);
if (LocationHelpers.hasNetworkProviderEnabled(mContext) || !showUserSettingsDialog) {
LocationHelpers.requestContinuousUpdates(this, locationRequest, watchId, promise);
} else {
// Pending requests can ask the user to turn on improved accuracy mode in user's settings.
addPendingLocationRequest(locationRequest, resultCode -> {
if (resultCode == Activity.RESULT_OK) {
LocationHelpers.requestContinuousUpdates(LocationModule.this, locationRequest, watchId, promise);
} else {
promise.reject(new LocationSettingsUnsatisfiedException());
}
});
}
}
@ExpoMethod
public void removeWatchAsync(final int watchId, final Promise promise) {
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
// Check if we want to stop watching location or compass
if (watchId == mHeadingId) {
destroyHeadingWatch();
} else {
removeLocationUpdatesForRequest(watchId);
}
promise.resolve(null);
}
@ExpoMethod
public void geocodeAsync(final String address, final Promise promise) {
if (mGeocoderPaused) {
promise.reject("E_CANNOT_GEOCODE", "Geocoder is not running.");
return;
}
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
if (Geocoder.isPresent()) {
SmartLocation.with(mContext).geocoding()
.direct(address, (s, list) -> {
List<Bundle> results = new ArrayList<>(list.size());
for (LocationAddress locationAddress : list) {
Bundle coords = LocationHelpers.locationToCoordsBundle(locationAddress.getLocation(), Bundle.class);
if (coords != null) {
results.add(coords);
}
}
SmartLocation.with(mContext).geocoding().stop();
promise.resolve(results);
});
} else {
promise.reject("E_NO_GEOCODER", "Geocoder service is not available for this device.");
}
}
@ExpoMethod
public void reverseGeocodeAsync(final Map<String, Object> locationMap, final Promise promise) {
if (mGeocoderPaused) {
promise.reject("E_CANNOT_GEOCODE", "Geocoder is not running.");
return;
}
if (isMissingPermissions()) {
promise.reject(new LocationUnauthorizedException());
return;
}
Location location = new Location("");
location.setLatitude((double) locationMap.get("latitude"));
location.setLongitude((double) locationMap.get("longitude"));
if (Geocoder.isPresent()) {
SmartLocation.with(mContext).geocoding()
.reverse(location, (original, addresses) -> {
List<Bundle> results = new ArrayList<>(addresses.size());
for (Address address : addresses) {
results.add(LocationHelpers.addressToBundle(address));
}
SmartLocation.with(mContext).geocoding().stop();
promise.resolve(results);
});
} else {
promise.reject("E_NO_GEOCODER", "Geocoder service is not available for this device.");
}
}
@ExpoMethod
public void enableNetworkProviderAsync(final Promise promise) {
if (LocationHelpers.hasNetworkProviderEnabled(mContext)) {
promise.resolve(null);
return;
}
LocationRequest locationRequest = LocationHelpers.prepareLocationRequest(new HashMap<>());
addPendingLocationRequest(locationRequest, resultCode -> {
if (resultCode == Activity.RESULT_OK) {
promise.resolve(null);
} else {
promise.reject(new LocationSettingsUnsatisfiedException());
}
});
}
@ExpoMethod
public void hasServicesEnabledAsync(final Promise promise) {
boolean servicesEnabled = LocationHelpers.isAnyProviderAvailable(getContext());
promise.resolve(servicesEnabled);
}
//region Background location
@ExpoMethod
public void startLocationUpdatesAsync(String taskName, Map<String, Object> options, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
try {
mTaskManager.registerTask(taskName, LocationTaskConsumer.class, options);
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}
@ExpoMethod
public void stopLocationUpdatesAsync(String taskName, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
try {
mTaskManager.unregisterTask(taskName, LocationTaskConsumer.class);
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}
@ExpoMethod
public void hasStartedLocationUpdatesAsync(String taskName, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
promise.resolve(mTaskManager.taskHasConsumerOfClass(taskName, LocationTaskConsumer.class));
}
//endregion Background location
//region Geofencing
@ExpoMethod
public void startGeofencingAsync(String taskName, Map<String, Object> options, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
try {
mTaskManager.registerTask(taskName, GeofencingTaskConsumer.class, options);
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}
@ExpoMethod
public void stopGeofencingAsync(String taskName, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
try {
mTaskManager.unregisterTask(taskName, GeofencingTaskConsumer.class);
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}
@ExpoMethod
public void hasStartedGeofencingAsync(String taskName, final Promise promise) {
if (isMissingBackgroundPermissions()) {
promise.reject(new LocationBackgroundUnauthorizedException());
return;
}
promise.resolve(mTaskManager.taskHasConsumerOfClass(taskName, GeofencingTaskConsumer.class));
}
//endregion Geofencing
//endregion Expo methods
//region public methods
void requestLocationUpdates(final LocationRequest locationRequest, Integer requestId, final LocationRequestCallbacks callbacks) {
final FusedLocationProviderClient locationProvider = getLocationProvider();
LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Location location = locationResult != null ? locationResult.getLastLocation() : null;
if (location != null) {
callbacks.onLocationChanged(location);
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
if (!locationAvailability.isLocationAvailable()) {
callbacks.onLocationError(new LocationUnavailableException());
}
}
};
if (requestId != null) {
// Save location callback and request so we will be able to pause/resume receiving updates.
mLocationCallbacks.put(requestId, locationCallback);
mLocationRequests.put(requestId, locationRequest);
}
try {
locationProvider.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
callbacks.onRequestSuccess();
} catch (SecurityException e) {
callbacks.onRequestFailed(new LocationRequestRejectedException(e));
}
}
//region private methods
/**
* Checks whether all required permissions have been granted by the user.
*/
private boolean isMissingPermissions() {
return mPermissionsManager == null || !mPermissionsManager.hasGrantedPermissions(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
}
/**
* Checks if the background location permission is granted by the user.
*/
private boolean isMissingBackgroundPermissions() {
return mPermissionsManager == null || !mPermissionsManager.hasGrantedPermissions(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
/**
* Helper method that lazy-loads the location provider for the context that the module was created.
*/
private FusedLocationProviderClient getLocationProvider() {
if (mLocationProvider == null) {
mLocationProvider = LocationServices.getFusedLocationProviderClient(mContext);
}
return mLocationProvider;
}
/**
* Gets the best most recent location found by the provider.
*/
private void getLastKnownLocation(final OnResultListener callback) {
try {
getLocationProvider().getLastLocation()
.addOnSuccessListener(location -> callback.onResult(location))
.addOnCanceledListener(() -> callback.onResult(null))
.addOnFailureListener(exception -> callback.onResult(null));
} catch (SecurityException e) {
callback.onResult(null);
}
}
private void addPendingLocationRequest(LocationRequest locationRequest, LocationActivityResultListener listener) {
// Add activity result listener to an array of pending requests.
mPendingLocationRequests.add(listener);
// If it's the first pending request, let's ask the user to turn on high accuracy location.
if (mPendingLocationRequests.size() == 1) {
resolveUserSettingsForRequest(locationRequest);
}
}
/**
* Triggers system's dialog to ask the user to enable settings required for given location request.
*/
private void resolveUserSettingsForRequest(LocationRequest locationRequest) {
final Activity activity = mActivityProvider.getCurrentActivity();
if (activity == null) {
// Activity not found. It could have been called in a headless mode.
executePendingRequests(Activity.RESULT_CANCELED);
return;
}
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);
SettingsClient client = LocationServices.getSettingsClient(mContext);
Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());
task.addOnSuccessListener(locationSettingsResponse -> {
// All location settings requirements are satisfied.
executePendingRequests(Activity.RESULT_OK);
});
task.addOnFailureListener(e -> {
int statusCode = ((ApiException) e).getStatusCode();
if (statusCode == CommonStatusCodes.RESOLUTION_REQUIRED) {
// Location settings are not satisfied, but this can be fixed by showing the user a dialog.
// Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult().
try {
ResolvableApiException resolvable = (ResolvableApiException) e;
mUIManager.registerActivityEventListener(LocationModule.this);
resolvable.startResolutionForResult(activity, CHECK_SETTINGS_REQUEST_CODE);
} catch (IntentSender.SendIntentException sendEx) {
// Ignore the error.
executePendingRequests(Activity.RESULT_CANCELED);
}
} else {// Location settings are not satisfied. However, we have no way to fix the settings so we won't show the dialog.
executePendingRequests(Activity.RESULT_CANCELED);
}
});
}
private void pauseLocationUpdatesForRequest(Integer requestId) {
LocationCallback locationCallback = mLocationCallbacks.get(requestId);
if (locationCallback != null) {
getLocationProvider().removeLocationUpdates(locationCallback);
}
}
private void resumeLocationUpdates() {
final FusedLocationProviderClient locationClient = getLocationProvider();
for (Integer requestId : mLocationCallbacks.keySet()) {
LocationCallback locationCallback = mLocationCallbacks.get(requestId);
LocationRequest locationRequest = mLocationRequests.get(requestId);
if (locationCallback != null && locationRequest != null) {
try {
locationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
} catch (SecurityException e) {
Log.e(TAG, "Error occurred while resuming location updates: " + e.toString());
}
}
}
}
private void removeLocationUpdatesForRequest(Integer requestId) {
pauseLocationUpdatesForRequest(requestId);
mLocationCallbacks.remove(requestId);
mLocationRequests.remove(requestId);
}
void sendLocationResponse(int watchId, Bundle response) {
response.putInt("watchId", watchId);
mEventEmitter.emit(LOCATION_EVENT_NAME, response);
}
private void executePendingRequests(int resultCode) {
// Propagate result to pending location requests.
for (LocationActivityResultListener listener : mPendingLocationRequests) {
listener.onResult(resultCode);
}
mPendingLocationRequests.clear();
}
private void startHeadingUpdate() {
if (mSensorManager == null || mContext == null) {
return;
}
SmartLocation.LocationControl locationControl = SmartLocation.with(mContext).location().oneFix().config(LocationParams.BEST_EFFORT);
Location currLoc = locationControl.getLastLocation();
if (currLoc != null) {
mGeofield = new GeomagneticField(
(float) currLoc.getLatitude(),
(float) currLoc.getLongitude(),
(float) currLoc.getAltitude(),
System.currentTimeMillis());
} else {
locationControl.start(location -> mGeofield = new GeomagneticField(
(float) location.getLatitude(),
(float) location.getLongitude(),
(float) location.getAltitude(),
System.currentTimeMillis()));
}
mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
}
private void sendUpdate() {
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
if (success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
// Make sure Delta is big enough to warrant an update
// Currently: 50ms and ~2 degrees of change (android has a lot of useless updates block up the sending)
if ((Math.abs(orientation[0] - mLastAzimut)) > DEGREE_DELTA && (System.currentTimeMillis() - mLastUpdate) > TIME_DELTA) {
mLastAzimut = orientation[0];
mLastUpdate = System.currentTimeMillis();
float magneticNorth = calcMagNorth(orientation[0]);
float trueNorth = calcTrueNorth(magneticNorth);
// Write data to send back to React
Bundle response = new Bundle();
Bundle heading = LocationHelpers.headingToBundle(trueNorth, magneticNorth, mAccuracy);
response.putInt("watchId", mHeadingId);
response.putBundle("heading", heading);
mEventEmitter.emit(HEADING_EVENT_NAME, response);
}
}
}
private float calcMagNorth(float azimut) {
float azimutDeg = (float) Math.toDegrees(azimut);
return (azimutDeg + 360) % 360;
}
private float calcTrueNorth(float magNorth) {
// Need to request geo location info to calculate true north
if (isMissingPermissions() || mGeofield == null) {
return -1;
}
return magNorth + mGeofield.getDeclination();
}
private void stopHeadingWatch() {
if (mSensorManager == null) {
return;
}
mSensorManager.unregisterListener(this);
}
private void destroyHeadingWatch() {
stopHeadingWatch();
mSensorManager = null;
mGravity = null;
mGeomagnetic = null;
mGeofield = null;
mHeadingId = 0;
mLastAzimut = 0;
mAccuracy = 0;
}
private void startWatching() {
if (mContext == null) {
return;
}
// if permissions not granted it won't work anyway, but this can be invoked when permission dialog disappears
if (!isMissingPermissions()) {
mGeocoderPaused = false;
}
// Resume paused location updates
resumeLocationUpdates();
}
private void stopWatching() {
if (mContext == null) {
return;
}
// if permissions not granted it won't work anyway, but this can be invoked when permission dialog appears
if (Geocoder.isPresent() && !isMissingPermissions()) {
SmartLocation.with(mContext).geocoding().stop();
mGeocoderPaused = true;
}
for (Integer requestId : mLocationCallbacks.keySet()) {
pauseLocationUpdatesForRequest(requestId);
}
}
private Bundle handleLocationPermissions(Map<String, PermissionsResponse> result) {
PermissionsResponse accessFineLocation = result.get(Manifest.permission.ACCESS_FINE_LOCATION);
PermissionsResponse accessCoarseLocation = result.get(Manifest.permission.ACCESS_COARSE_LOCATION);
PermissionsStatus status = PermissionsStatus.UNDETERMINED;
String scope = "none";
Boolean canAskAgain = accessCoarseLocation.getCanAskAgain() && accessFineLocation.getCanAskAgain();
if (accessFineLocation.getStatus() == PermissionsStatus.GRANTED) {
scope = "fine";
status = PermissionsStatus.GRANTED;
} else if (accessCoarseLocation.getStatus() == PermissionsStatus.GRANTED) {
scope = "coarse";
status = PermissionsStatus.GRANTED;
} else if (accessFineLocation.getStatus() == PermissionsStatus.DENIED && accessCoarseLocation.getStatus() == PermissionsStatus.DENIED) {
status = PermissionsStatus.DENIED;
}
Bundle resultBundle = new Bundle();
Bundle scopeBundle = new Bundle();
scopeBundle.putString("scope", scope);
resultBundle.putString(PermissionsResponse.STATUS_KEY, status.getStatus());
resultBundle.putString(PermissionsResponse.EXPIRES_KEY, PermissionsResponse.PERMISSION_EXPIRES_NEVER);
resultBundle.putBoolean(PermissionsResponse.CAN_ASK_AGAIN_KEY, canAskAgain);
resultBundle.putBoolean(PermissionsResponse.GRANTED_KEY, status == PermissionsStatus.GRANTED);
resultBundle.putBundle("android", scopeBundle);
return resultBundle;
}
//endregion
//region SensorEventListener
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mGravity = event.values;
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mGeomagnetic = event.values;
}
if (mGravity != null && mGeomagnetic != null) {
sendUpdate();
}
}
// Android returns 4 different values for accuracy
// 3: high accuracy, 2: medium, 1: low, 0: none
public void onAccuracyChanged(Sensor sensor, int accuracy) {
mAccuracy = accuracy;
}
//endregion
//region ActivityEventListener
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode != CHECK_SETTINGS_REQUEST_CODE) {
return;
}
executePendingRequests(resultCode);
mUIManager.unregisterActivityEventListener(this);
}
@Override
public void onNewIntent(Intent intent) {
}
//endregion
//region LifecycleEventListener
@Override
public void onHostResume() {
startWatching();
startHeadingUpdate();
}
@Override
public void onHostPause() {
stopWatching();
stopHeadingWatch();
}
@Override
public void onHostDestroy() {
stopWatching();
stopHeadingWatch();
}
//endregion
}

View File

@ -0,0 +1,16 @@
package expo.modules.location;
import android.content.Context;
import java.util.Collections;
import java.util.List;
import org.unimodules.core.ExportedModule;
import org.unimodules.core.BasePackage;
public class LocationPackage extends BasePackage {
@Override
public List<ExportedModule> createExportedModules(Context context) {
return Collections.singletonList((ExportedModule) new LocationModule(context));
}
}

View File

@ -0,0 +1,12 @@
package expo.modules.location;
import android.location.Location;
import org.unimodules.core.errors.CodedException;
abstract class LocationRequestCallbacks {
public void onLocationChanged(Location location) {}
public void onLocationError(CodedException throwable) {}
public void onRequestSuccess() {}
public void onRequestFailed(CodedException throwable) {}
}

View File

@ -0,0 +1,15 @@
package expo.modules.location.exceptions;
import org.unimodules.core.interfaces.CodedThrowable;
import org.unimodules.core.errors.CodedException;
public class LocationBackgroundUnauthorizedException extends CodedException implements CodedThrowable {
public LocationBackgroundUnauthorizedException() {
super("Not authorized to use background location services.");
}
@Override
public String getCode() {
return "E_LOCATION_BACKGROUND_UNAUTHORIZED";
}
}

View File

@ -0,0 +1,15 @@
package expo.modules.location.exceptions;
import org.unimodules.core.interfaces.CodedThrowable;
import org.unimodules.core.errors.CodedException;
public class LocationRequestRejectedException extends CodedException implements CodedThrowable {
public LocationRequestRejectedException(Exception cause) {
super("Location request has been rejected: " + cause.getMessage());
}
@Override
public String getCode() {
return "E_LOCATION_REQUEST_REJECTED";
}
}

View File

@ -0,0 +1,15 @@
package expo.modules.location.exceptions;
import org.unimodules.core.interfaces.CodedThrowable;
import org.unimodules.core.errors.CodedException;
public class LocationSettingsUnsatisfiedException extends CodedException implements CodedThrowable {
public LocationSettingsUnsatisfiedException() {
super("Location request failed due to unsatisfied device settings.");
}
@Override
public String getCode() {
return "E_LOCATION_SETTINGS_UNSATISFIED";
}
}

View File

@ -0,0 +1,15 @@
package expo.modules.location.exceptions;
import org.unimodules.core.interfaces.CodedThrowable;
import org.unimodules.core.errors.CodedException;
public class LocationUnauthorizedException extends CodedException implements CodedThrowable {
public LocationUnauthorizedException() {
super("Not authorized to use location services.");
}
@Override
public String getCode() {
return "E_LOCATION_UNAUTHORIZED";
}
}

View File

@ -0,0 +1,15 @@
package expo.modules.location.exceptions;
import org.unimodules.core.interfaces.CodedThrowable;
import org.unimodules.core.errors.CodedException;
public class LocationUnavailableException extends CodedException implements CodedThrowable {
public LocationUnavailableException() {
super("Location provider is unavailable. Make sure that location services are enabled.");
}
@Override
public String getCode() {
return "E_LOCATION_UNAVAILABLE";
}
}

View File

@ -0,0 +1,132 @@
package expo.modules.location.services;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.Nullable;
import android.util.Log;
public class LocationTaskService extends Service {
private static final String TAG = "LocationTaskService";
private static int sServiceId = 481756;
private String mChannelId;
private Context mParentContext;
private int mServiceId = sServiceId++;
private final IBinder mBinder = new ServiceBinder();
public class ServiceBinder extends Binder {
public LocationTaskService getService() {
return LocationTaskService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.w(TAG, "onBind");
return mBinder;
}
@Override
@TargetApi(26)
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle extras = intent.getExtras();
if (extras != null) {
mChannelId = extras.getString("appId") + ":" + extras.getString("taskName");
}
return START_REDELIVER_INTENT;
}
public void setParentContext(Context context) {
// Background location logic is still outside LocationTaskService,
// so we have to save parent context in order to make sure it won't be destroyed by the OS.
mParentContext = context;
}
public void stop() {
stopForeground(true);
stopSelf();
}
public void startForeground(Bundle serviceOptions) {
Notification notification = buildServiceNotification(serviceOptions);
startForeground(mServiceId, notification);
}
//region private
@TargetApi(26)
private Notification buildServiceNotification(Bundle serviceOptions) {
prepareChannel(mChannelId);
Notification.Builder builder = new Notification.Builder(this, mChannelId);
String title = serviceOptions.getString("notificationTitle");
String body = serviceOptions.getString("notificationBody");
Integer color = colorStringToInteger(serviceOptions.getString("notificationColor"));
if (title != null) {
builder.setContentTitle(title);
}
if (body != null) {
builder.setContentText(body);
}
if (color != null) {
builder.setColorized(true).setColor(color);
} else {
builder.setColorized(false);
}
Intent intent = mParentContext.getPackageManager().getLaunchIntentForPackage(mParentContext.getPackageName());
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
}
return builder.setCategory(Notification.CATEGORY_SERVICE)
.setSmallIcon(getApplicationInfo().icon)
.build();
}
@TargetApi(26)
private void prepareChannel(String id) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
if (notificationManager != null) {
String appName = getApplicationInfo().loadLabel(getPackageManager()).toString();
NotificationChannel channel = notificationManager.getNotificationChannel(id);
if (channel == null) {
channel = new NotificationChannel(id, appName, NotificationManager.IMPORTANCE_LOW);
channel.setDescription("Background location notification channel");
notificationManager.createNotificationChannel(channel);
}
}
}
private Integer colorStringToInteger(String color) {
try {
return Color.parseColor(color);
} catch (Exception e) {
return null;
}
}
//endregion
}

View File

@ -0,0 +1,280 @@
package expo.modules.location.taskConsumers;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofenceStatusCodes;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingEvent;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.unimodules.interfaces.taskManager.TaskConsumer;
import org.unimodules.interfaces.taskManager.TaskManagerUtilsInterface;
import org.unimodules.interfaces.taskManager.TaskConsumerInterface;
import org.unimodules.interfaces.taskManager.TaskInterface;
import expo.modules.location.LocationHelpers;
import expo.modules.location.LocationModule;
public class GeofencingTaskConsumer extends TaskConsumer implements TaskConsumerInterface {
public static int VERSION = 1;
private static final String TAG = "GeofencingTaskConsumer";
private TaskInterface mTask;
private PendingIntent mPendingIntent;
private GeofencingClient mGeofencingClient;
private GeofencingRequest mGeofencingRequest;
private List<Geofence> mGeofencingList;
private Map<String, PersistableBundle> mRegions;
public GeofencingTaskConsumer(Context context, TaskManagerUtilsInterface taskManagerUtils) {
super(context, taskManagerUtils);
}
//region TaskConsumerInterface
public String taskType() {
return "geofencing";
}
@Override
@SuppressWarnings("unchecked")
public void didRegister(TaskInterface task) {
if (task == null) {
return;
}
mTask = task;
startGeofencing();
}
@Override
public void didUnregister() {
stopGeofencing();
mTask = null;
mPendingIntent = null;
mGeofencingClient = null;
mGeofencingRequest = null;
mGeofencingList = null;
}
@Override
public void setOptions(Map<String, Object> options) {
super.setOptions(options);
stopGeofencing();
startGeofencing();
}
@Override
public void didReceiveBroadcast(Intent intent) {
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event.hasError()) {
String errorMessage = getErrorString(event.getErrorCode());
Error error = new Error(errorMessage);
mTask.execute(null, error);
return;
}
// Get region state and event type from given transition type.
int geofenceTransition = event.getGeofenceTransition();
int regionState = regionStateForTransitionType(geofenceTransition);
int eventType = eventTypeFromTransitionType(geofenceTransition);
// Get the geofences that were triggered. A single event can trigger multiple geofences.
List<Geofence> triggeringGeofences = event.getTriggeringGeofences();
for (Geofence geofence : triggeringGeofences) {
PersistableBundle region = mRegions.get(geofence.getRequestId());
if (region != null) {
PersistableBundle data = new PersistableBundle();
// Update region state in region bundle.
region.putInt("state", regionState);
data.putInt("eventType", eventType);
data.putPersistableBundle("region", region);
Context context = getContext().getApplicationContext();
getTaskManagerUtils().scheduleJob(context, mTask, Collections.singletonList(data));
}
}
}
@Override
public boolean didExecuteJob(JobService jobService, JobParameters params) {
if (mTask == null) {
return false;
}
List<PersistableBundle> data = getTaskManagerUtils().extractDataFromJobParams(params);
for (PersistableBundle item : data) {
Bundle bundle = new Bundle();
Bundle region = new Bundle();
region.putAll(item.getPersistableBundle("region"));
bundle.putInt("eventType", item.getInt("eventType"));
bundle.putBundle("region", region);
if (mTask == null) {
return false;
}
mTask.execute(bundle, null);
}
return true;
}
//endregion
//region helpers
private void startGeofencing() {
Context context = getContext();
if (context == null) {
Log.w(TAG, "The context has been abandoned.");
return;
}
if (!LocationHelpers.isAnyProviderAvailable(context)) {
Log.w(TAG, "There is no location provider available.");
return;
}
mRegions = new HashMap<>();
mGeofencingList = new ArrayList<>();
// Create geofences from task options.
Map<String, Object> options = mTask.getOptions();
List<HashMap<String, Object>> regions = (ArrayList<HashMap<String, Object>>) options.get("regions");
for (Map<String, Object> region : regions) {
Geofence geofence = geofenceFromRegion(region);
String regionIdentifier = geofence.getRequestId();
// Make a bundle for the region to remember its attributes. Only request ID is public in Geofence object.
mRegions.put(regionIdentifier, bundleFromRegion(regionIdentifier, region));
// Add geofence to the list of observed regions.
mGeofencingList.add(geofence);
}
// Prepare pending intent, geofencing request and client.
mPendingIntent = preparePendingIntent();
mGeofencingRequest = prepareGeofencingRequest(mGeofencingList);
mGeofencingClient = LocationServices.getGeofencingClient(getContext());
try {
mGeofencingClient.addGeofences(mGeofencingRequest, mPendingIntent);
} catch (SecurityException e) {
Log.w(TAG, "Geofencing request has been rejected.", e);
}
}
private void stopGeofencing() {
if (mGeofencingClient != null && mPendingIntent != null) {
mGeofencingClient.removeGeofences(mPendingIntent);
mPendingIntent.cancel();
}
}
private GeofencingRequest prepareGeofencingRequest(List<Geofence> geofences) {
return new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_EXIT)
.addGeofences(geofences)
.build();
}
private PendingIntent preparePendingIntent() {
return getTaskManagerUtils().createTaskIntent(getContext(), mTask);
}
private Geofence geofenceFromRegion(Map<String, Object> region) {
String identifier = region.containsKey("identifier") ? (String) region.get("identifier") : UUID.randomUUID().toString();
double latitude = doubleFromObject(region.get("latitude"));
double longitude = doubleFromObject(region.get("longitude"));
double radius = doubleFromObject(region.get("radius"));
boolean notifyOnEnter = !region.containsKey("notifyOnEnter") || (boolean) region.get("notifyOnEnter");
boolean notifyOnExit = !region.containsKey("notifyOnExit") || (boolean) region.get("notifyOnExit");
int transitionTypes = (notifyOnEnter ? Geofence.GEOFENCE_TRANSITION_ENTER : 0) | (notifyOnExit ? Geofence.GEOFENCE_TRANSITION_EXIT : 0);
return new Geofence.Builder()
.setRequestId(identifier)
.setCircularRegion(latitude, longitude, (float) radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(transitionTypes)
.build();
}
private PersistableBundle bundleFromRegion(String identifier, Map<String, Object> region) {
PersistableBundle bundle = new PersistableBundle();
bundle.putString("identifier", identifier);
bundle.putDouble("radius", doubleFromObject(region.get("radius")));
bundle.putDouble("latitude", doubleFromObject(region.get("latitude")));
bundle.putDouble("longitude", doubleFromObject(region.get("longitude")));
bundle.putInt("state", LocationModule.GEOFENCING_REGION_STATE_UNKNOWN);
return bundle;
}
private static double doubleFromObject(Object object) {
if (object instanceof Integer) {
return ((Integer) object).doubleValue();
}
return (Double) object;
}
private int regionStateForTransitionType(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
case Geofence.GEOFENCE_TRANSITION_DWELL:
return LocationModule.GEOFENCING_REGION_STATE_INSIDE;
case Geofence.GEOFENCE_TRANSITION_EXIT:
return LocationModule.GEOFENCING_REGION_STATE_OUTSIDE;
default:
return LocationModule.GEOFENCING_REGION_STATE_UNKNOWN;
}
}
private int eventTypeFromTransitionType(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return LocationModule.GEOFENCING_EVENT_ENTER;
case Geofence.GEOFENCE_TRANSITION_EXIT:
return LocationModule.GEOFENCING_EVENT_EXIT;
default:
return 0;
}
}
private static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "Geofencing not available.";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many geofences.";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many pending intents.";
default:
return "Unknown geofencing error.";
}
}
//endregion
}

View File

@ -0,0 +1,353 @@
package expo.modules.location.taskConsumers;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.util.Log;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import org.unimodules.core.MapHelper;
import org.unimodules.core.arguments.MapArguments;
import org.unimodules.core.arguments.ReadableArguments;
import org.unimodules.core.interfaces.Arguments;
import org.unimodules.core.interfaces.LifecycleEventListener;
import org.unimodules.interfaces.taskManager.TaskConsumer;
import org.unimodules.interfaces.taskManager.TaskConsumerInterface;
import org.unimodules.interfaces.taskManager.TaskExecutionCallback;
import org.unimodules.interfaces.taskManager.TaskInterface;
import org.unimodules.interfaces.taskManager.TaskManagerUtilsInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import androidx.annotation.NonNull;
import expo.modules.location.LocationHelpers;
import expo.modules.location.services.LocationTaskService;
public class LocationTaskConsumer extends TaskConsumer implements TaskConsumerInterface, LifecycleEventListener {
private static final String TAG = "LocationTaskConsumer";
private static final String FOREGROUND_SERVICE_KEY = "foregroundService";
public static int VERSION = 1;
private static long sLastTimestamp = 0;
private TaskInterface mTask;
private PendingIntent mPendingIntent;
private LocationTaskService mService;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient mLocationClient;
private Location mLastReportedLocation;
private double mDeferredDistance = 0.0;
private List<Location> mDeferredLocations = new ArrayList<>();
private boolean mIsHostPaused = true;
public LocationTaskConsumer(Context context, TaskManagerUtilsInterface taskManagerUtils) {
super(context, taskManagerUtils);
}
//region TaskConsumerInterface
public String taskType() {
return "location";
}
@Override
public void didRegister(TaskInterface task) {
mTask = task;
startLocationUpdates();
maybeStartForegroundService();
}
@Override
public void didUnregister() {
stopLocationUpdates();
stopForegroundService();
mTask = null;
mPendingIntent = null;
mLocationRequest = null;
mLocationClient = null;
}
@Override
public void setOptions(Map<String, Object> options) {
super.setOptions(options);
// Restart location updates
stopLocationUpdates();
startLocationUpdates();
// Restart foreground service if its option has changed.
maybeStartForegroundService();
}
@Override
public void didReceiveBroadcast(Intent intent) {
if (mTask == null) {
return;
}
LocationResult result = LocationResult.extractResult(intent);
if (result != null) {
List<Location> locations = result.getLocations();
deferLocations(locations);
maybeReportDeferredLocations();
} else {
try {
mLocationClient.getLastLocation().addOnCompleteListener(new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
Location location = task.getResult();
if (location != null) {
Log.i(TAG, "get last location: " + location);
deferLocations(Collections.singletonList(location));
maybeReportDeferredLocations();
}
}
});
} catch (SecurityException e) {
Log.e(TAG, "Cannot get last location: " + e.getMessage());
}
}
}
@Override
public boolean didExecuteJob(final JobService jobService, final JobParameters params) {
List<PersistableBundle> data = getTaskManagerUtils().extractDataFromJobParams(params);
ArrayList<Bundle> locationBundles = new ArrayList<>();
for (PersistableBundle persistableLocationBundle : data) {
Bundle locationBundle = new Bundle();
Bundle coordsBundle = new Bundle();
if (persistableLocationBundle != null) {
coordsBundle.putAll(persistableLocationBundle.getPersistableBundle("coords"));
locationBundle.putAll(persistableLocationBundle);
locationBundle.putBundle("coords", coordsBundle);
locationBundles.add(locationBundle);
}
}
executeTaskWithLocationBundles(locationBundles, new TaskExecutionCallback() {
@Override
public void onFinished(Map<String, Object> response) {
jobService.jobFinished(params, false);
}
});
// Returning `true` indicates that the job is still running, but in async mode.
// In that case we're obligated to call `jobService.jobFinished` as soon as the async block finishes.
return true;
}
//region private
private void startLocationUpdates() {
Context context = getContext();
if (context == null) {
Log.w(TAG, "The context has been abandoned.");
return;
}
if (!LocationHelpers.isAnyProviderAvailable(context)) {
Log.w(TAG, "There is no location provider available.");
return;
}
mLocationRequest = LocationHelpers.prepareLocationRequest(mTask.getOptions());
mPendingIntent = preparePendingIntent();
try {
mLocationClient = LocationServices.getFusedLocationProviderClient(context);
mLocationClient.requestLocationUpdates(mLocationRequest, mPendingIntent);
} catch (SecurityException e) {
Log.w(TAG, "Location request has been rejected.", e);
}
}
private void stopLocationUpdates() {
if (mLocationClient != null && mPendingIntent != null) {
mLocationClient.removeLocationUpdates(mPendingIntent);
mPendingIntent.cancel();
}
}
private void maybeStartForegroundService() {
// Foreground service is available as of Android Oreo.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
ReadableArguments options = new MapArguments(mTask.getOptions());
final Context context = getContext();
boolean useForegroundService = options.containsKey(FOREGROUND_SERVICE_KEY);
if (context == null) {
Log.w(TAG, "Context not found when trying to start foreground service.");
return;
}
// Service is already running, but the task has been registered again without `foregroundService` option.
if (mService != null && !useForegroundService) {
stopForegroundService();
return;
}
// Service is not running and the user don't want to start foreground service.
if (!useForegroundService) {
return;
}
// Foreground service is requested but not running.
if (mService == null) {
Intent serviceIntent = new Intent(context, LocationTaskService.class);
Bundle extras = new Bundle();
final Bundle serviceOptions = options.getArguments(FOREGROUND_SERVICE_KEY).toBundle();
extras.putString("appId", mTask.getAppId());
extras.putString("taskName", mTask.getName());
serviceIntent.putExtras(extras);
context.startForegroundService(serviceIntent);
context.bindService(serviceIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ((LocationTaskService.ServiceBinder) service).getService();
mService.setParentContext(context);
mService.startForeground(serviceOptions);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService.stop();
mService = null;
}
}, Context.BIND_AUTO_CREATE);
} else {
// Restart the service with new service options.
mService.startForeground(options.getArguments(FOREGROUND_SERVICE_KEY).toBundle());
}
}
private void stopForegroundService() {
if (mService != null) {
mService.stop();
}
}
private void deferLocations(List<Location> locations) {
int size = mDeferredLocations.size();
Location lastLocation = size > 0 ? mDeferredLocations.get(size - 1) : mLastReportedLocation;
for (Location location : locations) {
if (lastLocation != null) {
mDeferredDistance += Math.abs(location.distanceTo(lastLocation));
}
lastLocation = location;
}
mDeferredLocations.addAll(locations);
}
private void maybeReportDeferredLocations() {
if (!shouldReportDeferredLocations()) {
// Don't report locations yet - continue deferring them.
return;
}
Context context = getContext().getApplicationContext();
List<PersistableBundle> data = new ArrayList<>();
for (Location location : mDeferredLocations) {
long timestamp = location.getTime();
// Some devices may broadcast the same location multiple times (mostly twice) so we're filtering out these locations,
// so only one location at the specific timestamp can schedule a job.
if (timestamp > sLastTimestamp) {
PersistableBundle bundle = LocationHelpers.locationToBundle(location, PersistableBundle.class);
data.add(bundle);
sLastTimestamp = timestamp;
}
}
if (data.size() > 0) {
// Save last reported location, reset the distance and clear a list of locations.
mLastReportedLocation = mDeferredLocations.get(mDeferredLocations.size() - 1);
mDeferredDistance = 0.0;
mDeferredLocations.clear();
// Schedule new job.
getTaskManagerUtils().scheduleJob(context, mTask, data);
}
}
private boolean shouldReportDeferredLocations() {
if (mDeferredLocations.size() == 0 || mTask == null) {
return false;
}
if (!mIsHostPaused) {
// Don't defer location updates when the activity is in foreground state.
return true;
}
Location oldestLocation = mLastReportedLocation != null ? mLastReportedLocation : mDeferredLocations.get(0);
Location newestLocation = mDeferredLocations.get(mDeferredLocations.size() - 1);
Arguments options = new MapHelper(mTask.getOptions());
double distance = options.getDouble("deferredUpdatesDistance");
long interval = options.getLong("deferredUpdatesInterval");
return newestLocation.getTime() - oldestLocation.getTime() >= interval && mDeferredDistance >= distance;
}
private PendingIntent preparePendingIntent() {
return getTaskManagerUtils().createTaskIntent(getContext(), mTask);
}
private void executeTaskWithLocationBundles(ArrayList<Bundle> locationBundles, TaskExecutionCallback callback) {
if (locationBundles.size() > 0 && mTask != null) {
Bundle data = new Bundle();
data.putParcelableArrayList("locations", locationBundles);
mTask.execute(data, null, callback);
} else {
callback.onFinished(null);
}
}
@Override
public void onHostResume() {
mIsHostPaused = false;
maybeReportDeferredLocations();
}
@Override
public void onHostPause() {
mIsHostPaused = true;
}
@Override
public void onHostDestroy() {
mIsHostPaused = true;
}
//endregion
}

View File

@ -0,0 +1,55 @@
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules.location.utils;
import android.os.Handler;
public class TimeoutObject {
public interface TimeoutListener {
void onTimeout();
}
private Long mTimeout;
private boolean mIsDone = false;
private TimeoutListener mListener;
public TimeoutObject(final Long timeout) {
mTimeout = timeout;
}
public void onTimeout(final TimeoutListener listener) {
mListener = listener;
}
public void start() {
if (mTimeout == null) {
return;
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
synchronized (this) {
if (!mIsDone) {
if (mListener != null) {
mListener.onTimeout();
}
}
mIsDone = true;
}
}
}, mTimeout);
}
public boolean markDoneIfNotTimedOut() {
synchronized (this) {
if (mIsDone) {
return false;
} else {
mIsDone = true;
return true;
}
}
}
}

2
node_modules/expo-location/build/ExpoLocation.d.ts generated vendored Normal file
View File

@ -0,0 +1,2 @@
declare const _default: import("@unimodules/core").ProxyNativeModule;
export default _default;

3
node_modules/expo-location/build/ExpoLocation.js generated vendored Normal file
View File

@ -0,0 +1,3 @@
import { NativeModulesProxy } from '@unimodules/core';
export default NativeModulesProxy.ExpoLocation;
//# sourceMappingURL=ExpoLocation.js.map

1
node_modules/expo-location/build/ExpoLocation.js.map generated vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"ExpoLocation.js","sourceRoot":"","sources":["../src/ExpoLocation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,eAAe,kBAAkB,CAAC,YAAY,CAAC","sourcesContent":["import { NativeModulesProxy } from '@unimodules/core';\nexport default NativeModulesProxy.ExpoLocation;\n"]}

26
node_modules/expo-location/build/ExpoLocation.web.d.ts generated vendored Normal file
View File

@ -0,0 +1,26 @@
import { PermissionResponse } from 'unimodules-permissions-interface';
import { LocationLastKnownOptions, LocationObject, LocationOptions } from './Location.types';
/**
* Gets the permission details. The implementation is not very good as it actually requests
* for the current location, but there is no better way on web so far :(
*/
declare function getPermissionsAsync(): Promise<PermissionResponse>;
declare const _default: {
readonly name: string;
getProviderStatusAsync(): Promise<{
locationServicesEnabled: boolean;
}>;
getLastKnownPositionAsync(options?: LocationLastKnownOptions): Promise<LocationObject | null>;
getCurrentPositionAsync(options: LocationOptions): Promise<LocationObject>;
removeWatchAsync(watchId: any): Promise<void>;
watchDeviceHeading(headingId: any): Promise<void>;
hasServicesEnabledAsync(): Promise<boolean>;
geocodeAsync(): Promise<any[]>;
reverseGeocodeAsync(): Promise<any[]>;
watchPositionImplAsync(watchId: string, options: LocationOptions): Promise<string>;
getPermissionsAsync: typeof getPermissionsAsync;
requestPermissionsAsync(): Promise<PermissionResponse>;
startObserving(): void;
stopObserving(): void;
};
export default _default;

126
node_modules/expo-location/build/ExpoLocation.web.js generated vendored Normal file
View File

@ -0,0 +1,126 @@
import { PermissionStatus } from 'unimodules-permissions-interface';
import { LocationAccuracy, } from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
class GeocoderError extends Error {
constructor() {
super('Geocoder service is not available for this device.');
this.code = 'E_NO_GEOCODER';
}
}
/**
* Converts `GeolocationPosition` to JavaScript object.
*/
function geolocationPositionToJSON(position) {
const { coords, timestamp } = position;
return {
coords: {
latitude: coords.latitude,
longitude: coords.longitude,
altitude: coords.altitude,
accuracy: coords.accuracy,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed,
},
timestamp,
};
}
/**
* Checks whether given location didn't exceed given `maxAge` and fits in the required accuracy.
*/
function isLocationValid(location, options) {
const maxAge = typeof options.maxAge === 'number' ? options.maxAge : Infinity;
const requiredAccuracy = typeof options.requiredAccuracy === 'number' ? options.requiredAccuracy : Infinity;
const locationAccuracy = location.coords.accuracy ?? Infinity;
return Date.now() - location.timestamp <= maxAge && locationAccuracy <= requiredAccuracy;
}
/**
* Gets the permission details. The implementation is not very good as it actually requests
* for the current location, but there is no better way on web so far :(
*/
async function getPermissionsAsync() {
return new Promise(resolve => {
const resolveWithStatus = status => resolve({
status,
granted: status === PermissionStatus.GRANTED,
canAskAgain: true,
expires: 0,
});
navigator.geolocation.getCurrentPosition(() => resolveWithStatus(PermissionStatus.GRANTED), ({ code }) => {
if (code === 1 /* PERMISSION_DENIED */) {
resolveWithStatus(PermissionStatus.DENIED);
}
else {
resolveWithStatus(PermissionStatus.UNDETERMINED);
}
}, { enableHighAccuracy: false, maximumAge: Infinity });
});
}
let lastKnownPosition = null;
export default {
get name() {
return 'ExpoLocation';
},
async getProviderStatusAsync() {
return {
locationServicesEnabled: 'geolocation' in navigator,
};
},
async getLastKnownPositionAsync(options = {}) {
if (lastKnownPosition && isLocationValid(lastKnownPosition, options)) {
return lastKnownPosition;
}
return null;
},
async getCurrentPositionAsync(options) {
return new Promise((resolve, reject) => {
const resolver = position => {
lastKnownPosition = geolocationPositionToJSON(position);
resolve(lastKnownPosition);
};
navigator.geolocation.getCurrentPosition(resolver, reject, {
maximumAge: Infinity,
enableHighAccuracy: (options.accuracy ?? 0) > LocationAccuracy.Balanced,
...options,
});
});
},
async removeWatchAsync(watchId) {
navigator.geolocation.clearWatch(watchId);
},
async watchDeviceHeading(headingId) {
console.warn('Location.watchDeviceHeading: is not supported on web');
},
async hasServicesEnabledAsync() {
return 'geolocation' in navigator;
},
async geocodeAsync() {
throw new GeocoderError();
},
async reverseGeocodeAsync() {
throw new GeocoderError();
},
async watchPositionImplAsync(watchId, options) {
return new Promise(resolve => {
// @ts-ignore: the types here need to be fixed
watchId = global.navigator.geolocation.watchPosition(position => {
lastKnownPosition = geolocationPositionToJSON(position);
LocationEventEmitter.emit('Expo.locationChanged', {
watchId,
location: lastKnownPosition,
});
}, undefined,
// @ts-ignore: the options object needs to be fixed
options);
resolve(watchId);
});
},
getPermissionsAsync,
async requestPermissionsAsync() {
return getPermissionsAsync();
},
// no-op
startObserving() { },
stopObserving() { },
};
//# sourceMappingURL=ExpoLocation.web.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
export declare function installWebGeolocationPolyfill(): void;

View File

@ -0,0 +1,53 @@
import { Platform } from '@unimodules/core';
import ExpoLocation from './ExpoLocation';
import { LocationAccuracy } from './Location.types';
import { LocationSubscriber } from './LocationSubscribers';
export function installWebGeolocationPolyfill() {
if (Platform.OS !== 'web') {
// Polyfill navigator.geolocation for interop with the core react-native and web API approach to
// geolocation
// @ts-ignore
window.navigator.geolocation = {
getCurrentPosition,
watchPosition,
clearWatch,
// We don't polyfill stopObserving, this is an internal method that probably should not even exist
// in react-native docs
stopObserving: () => { },
};
}
}
function convertGeolocationOptions(options) {
return {
accuracy: options.enableHighAccuracy ? LocationAccuracy.High : LocationAccuracy.Balanced,
};
}
function getCurrentPosition(success, error = () => { }, options = {}) {
_getCurrentPositionAsyncWrapper(success, error, options);
}
// This function exists to let us continue to return undefined from getCurrentPosition, while still
// using async/await for the internal implementation of it
async function _getCurrentPositionAsyncWrapper(success, error, options) {
try {
await ExpoLocation.requestPermissionsAsync();
const result = await ExpoLocation.getCurrentPositionAsync(convertGeolocationOptions(options));
success(result);
}
catch (e) {
error(e);
}
}
// Polyfill: navigator.geolocation.watchPosition
function watchPosition(success, error, options) {
const watchId = LocationSubscriber.registerCallback(success);
ExpoLocation.watchPositionImplAsync(watchId, options).catch(err => {
LocationSubscriber.unregisterCallback(watchId);
error({ watchId, message: err.message, code: err.code });
});
return watchId;
}
// Polyfill: navigator.geolocation.clearWatch
function clearWatch(watchId) {
LocationSubscriber.unregisterCallback(watchId);
}
//# sourceMappingURL=GeolocationPolyfill.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"GeolocationPolyfill.js","sourceRoot":"","sources":["../src/GeolocationPolyfill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAkB,gBAAgB,EAAmB,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAS3D,MAAM,UAAU,6BAA6B;IAC3C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE;QACzB,gGAAgG;QAChG,cAAc;QACd,aAAa;QACb,MAAM,CAAC,SAAS,CAAC,WAAW,GAAG;YAC7B,kBAAkB;YAClB,aAAa;YACb,UAAU;YAEV,kGAAkG;YAClG,uBAAuB;YACvB,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;SACxB,CAAC;KACH;AACH,CAAC;AAED,SAAS,yBAAyB,CAAC,OAA2B;IAC5D,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,QAAQ;KACzF,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CACzB,OAAmC,EACnC,QAAkC,GAAG,EAAE,GAAE,CAAC,EAC1C,UAA8B,EAAE;IAEhC,+BAA+B,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,mGAAmG;AACnG,0DAA0D;AAC1D,KAAK,UAAU,+BAA+B,CAC5C,OAAmC,EACnC,KAA+B,EAC/B,OAA2B;IAE3B,IAAI;QACF,MAAM,YAAY,CAAC,uBAAuB,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,uBAAuB,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9F,OAAO,CAAC,MAAM,CAAC,CAAC;KACjB;IAAC,OAAO,CAAC,EAAE;QACV,KAAK,CAAC,CAAC,CAAC,CAAC;KACV;AACH,CAAC;AAED,gDAAgD;AAChD,SAAS,aAAa,CACpB,OAAmC,EACnC,KAA+B,EAC/B,OAA2B;IAE3B,MAAM,OAAO,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE7D,YAAY,CAAC,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QAChE,kBAAkB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC/C,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,6CAA6C;AAC7C,SAAS,UAAU,CAAC,OAAe;IACjC,kBAAkB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC","sourcesContent":["import { Platform } from '@unimodules/core';\n\nimport ExpoLocation from './ExpoLocation';\nimport { LocationObject, LocationAccuracy, LocationOptions } from './Location.types';\nimport { LocationSubscriber } from './LocationSubscribers';\n\ntype GeolocationSuccessCallback = (data: LocationObject) => void;\ntype GeolocationErrorCallback = (error: any) => void;\n\ntype GeolocationOptions = {\n enableHighAccuracy?: boolean;\n};\n\nexport function installWebGeolocationPolyfill(): void {\n if (Platform.OS !== 'web') {\n // Polyfill navigator.geolocation for interop with the core react-native and web API approach to\n // geolocation\n // @ts-ignore\n window.navigator.geolocation = {\n getCurrentPosition,\n watchPosition,\n clearWatch,\n\n // We don't polyfill stopObserving, this is an internal method that probably should not even exist\n // in react-native docs\n stopObserving: () => {},\n };\n }\n}\n\nfunction convertGeolocationOptions(options: GeolocationOptions): LocationOptions {\n return {\n accuracy: options.enableHighAccuracy ? LocationAccuracy.High : LocationAccuracy.Balanced,\n };\n}\n\nfunction getCurrentPosition(\n success: GeolocationSuccessCallback,\n error: GeolocationErrorCallback = () => {},\n options: GeolocationOptions = {}\n): void {\n _getCurrentPositionAsyncWrapper(success, error, options);\n}\n\n// This function exists to let us continue to return undefined from getCurrentPosition, while still\n// using async/await for the internal implementation of it\nasync function _getCurrentPositionAsyncWrapper(\n success: GeolocationSuccessCallback,\n error: GeolocationErrorCallback,\n options: GeolocationOptions\n): Promise<any> {\n try {\n await ExpoLocation.requestPermissionsAsync();\n const result = await ExpoLocation.getCurrentPositionAsync(convertGeolocationOptions(options));\n success(result);\n } catch (e) {\n error(e);\n }\n}\n\n// Polyfill: navigator.geolocation.watchPosition\nfunction watchPosition(\n success: GeolocationSuccessCallback,\n error: GeolocationErrorCallback,\n options: GeolocationOptions\n) {\n const watchId = LocationSubscriber.registerCallback(success);\n\n ExpoLocation.watchPositionImplAsync(watchId, options).catch(err => {\n LocationSubscriber.unregisterCallback(watchId);\n error({ watchId, message: err.message, code: err.code });\n });\n\n return watchId;\n}\n\n// Polyfill: navigator.geolocation.clearWatch\nfunction clearWatch(watchId: number) {\n LocationSubscriber.unregisterCallback(watchId);\n}\n"]}

73
node_modules/expo-location/build/Location.d.ts generated vendored Normal file
View File

@ -0,0 +1,73 @@
import { PermissionStatus } from 'unimodules-permissions-interface';
import { LocationAccuracy, LocationCallback, LocationGeocodedAddress, LocationGeocodedLocation, LocationHeadingCallback, LocationHeadingObject, LocationLastKnownOptions, LocationObject, LocationOptions, LocationPermissionResponse, LocationProviderStatus, LocationRegion, LocationSubscription, LocationTaskOptions, LocationActivityType, LocationGeofencingEventType, LocationGeofencingRegionState, LocationGeocodingOptions } from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
import { setGoogleApiKey } from './LocationGoogleGeocoding';
import { _getCurrentWatchId } from './LocationSubscribers';
export declare function getProviderStatusAsync(): Promise<LocationProviderStatus>;
export declare function enableNetworkProviderAsync(): Promise<void>;
/**
* Requests for one-time delivery of the user's current location.
* Depending on given `accuracy` option it may take some time to resolve,
* especially when you're inside a building.
*/
export declare function getCurrentPositionAsync(options?: LocationOptions): Promise<LocationObject>;
/**
* Gets the last known position of the device or `null` if it's not available
* or doesn't match given requirements such as maximum age or required accuracy.
* It's considered to be faster than `getCurrentPositionAsync` as it doesn't request for the current location.
*/
export declare function getLastKnownPositionAsync(options?: LocationLastKnownOptions): Promise<LocationObject | null>;
/**
* Starts watching for location changes.
* Given callback will be called once the new location is available.
*/
export declare function watchPositionAsync(options: LocationOptions, callback: LocationCallback): Promise<{
remove(): void;
}>;
/**
* Resolves to an object with current heading details.
* To simplify, it calls `watchHeadingAsync` and waits for a couple of updates
* and returns the one that is accurate enough.
*/
export declare function getHeadingAsync(): Promise<LocationHeadingObject>;
/**
* Starts watching for heading changes.
* Given callback will be called once the new heading is available.
*/
export declare function watchHeadingAsync(callback: LocationHeadingCallback): Promise<LocationSubscription>;
/**
* Geocodes given address to an array of latitude-longitude coordinates.
*/
export declare function geocodeAsync(address: string, options?: LocationGeocodingOptions): Promise<LocationGeocodedLocation[]>;
/**
* The opposite behavior of `geocodeAsync` — translates location coordinates to an array of addresses.
*/
export declare function reverseGeocodeAsync(location: Pick<LocationGeocodedLocation, 'latitude' | 'longitude'>, options?: LocationGeocodingOptions): Promise<LocationGeocodedAddress[]>;
/**
* Gets the current state of location permissions.
*/
export declare function getPermissionsAsync(): Promise<LocationPermissionResponse>;
/**
* Requests the user to grant location permissions.
*/
export declare function requestPermissionsAsync(): Promise<LocationPermissionResponse>;
/**
* Returns `true` if the device has location services enabled or `false` otherwise.
*/
export declare function hasServicesEnabledAsync(): Promise<boolean>;
export declare function isBackgroundLocationAvailableAsync(): Promise<boolean>;
export declare function startLocationUpdatesAsync(taskName: string, options?: LocationTaskOptions): Promise<void>;
export declare function stopLocationUpdatesAsync(taskName: string): Promise<void>;
export declare function hasStartedLocationUpdatesAsync(taskName: string): Promise<boolean>;
export declare function startGeofencingAsync(taskName: string, regions?: LocationRegion[]): Promise<void>;
export declare function stopGeofencingAsync(taskName: string): Promise<void>;
export declare function hasStartedGeofencingAsync(taskName: string): Promise<boolean>;
/**
* @deprecated
* Deprecated as of SDK39 in favour of `setGoogleApiKey`.
*/
export declare function setApiKey(apiKey: string): void;
export { LocationEventEmitter as EventEmitter, _getCurrentWatchId };
export { LocationAccuracy as Accuracy, LocationActivityType as ActivityType, LocationGeofencingEventType as GeofencingEventType, LocationGeofencingRegionState as GeofencingRegionState, PermissionStatus, setGoogleApiKey, };
export { installWebGeolocationPolyfill } from './GeolocationPolyfill';
export * from './Location.types';

190
node_modules/expo-location/build/Location.js generated vendored Normal file
View File

@ -0,0 +1,190 @@
import { Platform } from '@unimodules/core';
import { PermissionStatus } from 'unimodules-permissions-interface';
import ExpoLocation from './ExpoLocation';
import { LocationAccuracy, LocationActivityType, LocationGeofencingEventType, LocationGeofencingRegionState, } from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
import { setGoogleApiKey, googleGeocodeAsync, googleReverseGeocodeAsync, } from './LocationGoogleGeocoding';
import { LocationSubscriber, HeadingSubscriber, _getCurrentWatchId } from './LocationSubscribers';
export async function getProviderStatusAsync() {
return ExpoLocation.getProviderStatusAsync();
}
export async function enableNetworkProviderAsync() {
// If network provider is disabled (user's location mode is set to "Device only"),
// Android's location provider may not give you any results. Use this method in order to ask the user
// to change the location mode to "High accuracy" which uses Google Play services and enables network provider.
// `getCurrentPositionAsync` and `watchPositionAsync` are doing it automatically anyway.
if (Platform.OS === 'android') {
return ExpoLocation.enableNetworkProviderAsync();
}
}
/**
* Requests for one-time delivery of the user's current location.
* Depending on given `accuracy` option it may take some time to resolve,
* especially when you're inside a building.
*/
export async function getCurrentPositionAsync(options = {}) {
return ExpoLocation.getCurrentPositionAsync(options);
}
/**
* Gets the last known position of the device or `null` if it's not available
* or doesn't match given requirements such as maximum age or required accuracy.
* It's considered to be faster than `getCurrentPositionAsync` as it doesn't request for the current location.
*/
export async function getLastKnownPositionAsync(options = {}) {
return ExpoLocation.getLastKnownPositionAsync(options);
}
/**
* Starts watching for location changes.
* Given callback will be called once the new location is available.
*/
export async function watchPositionAsync(options, callback) {
const watchId = LocationSubscriber.registerCallback(callback);
await ExpoLocation.watchPositionImplAsync(watchId, options);
return {
remove() {
LocationSubscriber.unregisterCallback(watchId);
},
};
}
/**
* Resolves to an object with current heading details.
* To simplify, it calls `watchHeadingAsync` and waits for a couple of updates
* and returns the one that is accurate enough.
*/
export async function getHeadingAsync() {
return new Promise(async (resolve) => {
let tries = 0;
const subscription = await watchHeadingAsync(heading => {
if (heading.accuracy > 1 || tries > 5) {
subscription.remove();
resolve(heading);
}
else {
tries += 1;
}
});
});
}
/**
* Starts watching for heading changes.
* Given callback will be called once the new heading is available.
*/
export async function watchHeadingAsync(callback) {
const watchId = HeadingSubscriber.registerCallback(callback);
await ExpoLocation.watchDeviceHeading(watchId);
return {
remove() {
HeadingSubscriber.unregisterCallback(watchId);
},
};
}
/**
* Geocodes given address to an array of latitude-longitude coordinates.
*/
export async function geocodeAsync(address, options) {
if (typeof address !== 'string') {
throw new TypeError(`Address to geocode must be a string. Got ${address} instead.`);
}
if (options?.useGoogleMaps || Platform.OS === 'web') {
return await googleGeocodeAsync(address);
}
return await ExpoLocation.geocodeAsync(address);
}
/**
* The opposite behavior of `geocodeAsync` — translates location coordinates to an array of addresses.
*/
export async function reverseGeocodeAsync(location, options) {
if (typeof location.latitude !== 'number' || typeof location.longitude !== 'number') {
throw new TypeError('Location to reverse-geocode must be an object with number properties `latitude` and `longitude`.');
}
if (options?.useGoogleMaps || Platform.OS === 'web') {
return await googleReverseGeocodeAsync(location);
}
return await ExpoLocation.reverseGeocodeAsync(location);
}
/**
* Gets the current state of location permissions.
*/
export async function getPermissionsAsync() {
return await ExpoLocation.getPermissionsAsync();
}
/**
* Requests the user to grant location permissions.
*/
export async function requestPermissionsAsync() {
return await ExpoLocation.requestPermissionsAsync();
}
// --- Location service
/**
* Returns `true` if the device has location services enabled or `false` otherwise.
*/
export async function hasServicesEnabledAsync() {
return await ExpoLocation.hasServicesEnabledAsync();
}
// --- Background location updates
function _validateTaskName(taskName) {
if (!taskName || typeof taskName !== 'string') {
throw new Error(`\`taskName\` must be a non-empty string. Got ${taskName} instead.`);
}
}
export async function isBackgroundLocationAvailableAsync() {
const providerStatus = await getProviderStatusAsync();
return providerStatus.backgroundModeEnabled;
}
export async function startLocationUpdatesAsync(taskName, options = { accuracy: LocationAccuracy.Balanced }) {
_validateTaskName(taskName);
await ExpoLocation.startLocationUpdatesAsync(taskName, options);
}
export async function stopLocationUpdatesAsync(taskName) {
_validateTaskName(taskName);
await ExpoLocation.stopLocationUpdatesAsync(taskName);
}
export async function hasStartedLocationUpdatesAsync(taskName) {
_validateTaskName(taskName);
return ExpoLocation.hasStartedLocationUpdatesAsync(taskName);
}
// --- Geofencing
function _validateRegions(regions) {
if (!regions || regions.length === 0) {
throw new Error('Regions array cannot be empty. Use `stopGeofencingAsync` if you want to stop geofencing all regions');
}
for (const region of regions) {
if (typeof region.latitude !== 'number') {
throw new TypeError(`Region's latitude must be a number. Got '${region.latitude}' instead.`);
}
if (typeof region.longitude !== 'number') {
throw new TypeError(`Region's longitude must be a number. Got '${region.longitude}' instead.`);
}
if (typeof region.radius !== 'number') {
throw new TypeError(`Region's radius must be a number. Got '${region.radius}' instead.`);
}
}
}
export async function startGeofencingAsync(taskName, regions = []) {
_validateTaskName(taskName);
_validateRegions(regions);
await ExpoLocation.startGeofencingAsync(taskName, { regions });
}
export async function stopGeofencingAsync(taskName) {
_validateTaskName(taskName);
await ExpoLocation.stopGeofencingAsync(taskName);
}
export async function hasStartedGeofencingAsync(taskName) {
_validateTaskName(taskName);
return ExpoLocation.hasStartedGeofencingAsync(taskName);
}
/**
* @deprecated
* Deprecated as of SDK39 in favour of `setGoogleApiKey`.
*/
export function setApiKey(apiKey) {
console.warn("Location's method `setApiKey` is deprecated in favor of `setGoogleApiKey`.");
setGoogleApiKey(apiKey);
}
// For internal purposes
export { LocationEventEmitter as EventEmitter, _getCurrentWatchId };
// Export as namespaced types.
export { LocationAccuracy as Accuracy, LocationActivityType as ActivityType, LocationGeofencingEventType as GeofencingEventType, LocationGeofencingRegionState as GeofencingRegionState, PermissionStatus, setGoogleApiKey, };
export { installWebGeolocationPolyfill } from './GeolocationPolyfill';
export * from './Location.types';
//# sourceMappingURL=Location.js.map

1
node_modules/expo-location/build/Location.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

194
node_modules/expo-location/build/Location.types.d.ts generated vendored Normal file
View File

@ -0,0 +1,194 @@
import { PermissionResponse as UMPermissionResponse } from 'unimodules-permissions-interface';
/**
* Enum with available location accuracies.
*/
export declare enum LocationAccuracy {
Lowest = 1,
Low = 2,
Balanced = 3,
High = 4,
Highest = 5,
BestForNavigation = 6
}
/**
* Enum with available activity types of background location tracking.
*/
export declare enum LocationActivityType {
Other = 1,
AutomotiveNavigation = 2,
Fitness = 3,
OtherNavigation = 4,
Airborne = 5
}
/**
* A type of the event that geofencing task can receive.
*/
export declare enum LocationGeofencingEventType {
Enter = 1,
Exit = 2
}
/**
* State of the geofencing region that you receive through the geofencing task.
*/
export declare enum LocationGeofencingRegionState {
Unknown = 0,
Inside = 1,
Outside = 2
}
/**
* Type representing options argument in `getCurrentPositionAsync`.
*/
export declare type LocationOptions = {
/**
* Location manager accuracy. Pass one of `LocationAccuracy` enum values.
* For low-accuracies the implementation can avoid geolocation providers
* that consume a significant amount of power (such as GPS).
*/
accuracy?: LocationAccuracy;
/**
* (Android only) Specifies whether to ask the user to turn on improved accuracy location mode
* which uses Wi-Fi, cell networks and GPS sensor. Defaults to `true`.
*/
mayShowUserSettingsDialog?: boolean;
/**
* (Android only) Minimum time to wait between each update in milliseconds.
* Default value may depend on `accuracy` option.
*/
timeInterval?: number;
/**
* Receive updates only when the location has changed by at least this distance in meters.
* Default value may depend on `accuracy` option.
*/
distanceInterval?: number;
};
/**
* Type representing options object that can be passed to `getLastKnownPositionAsync`.
*/
export declare type LocationLastKnownOptions = {
/**
* Maximum age of the location in miliseconds.
*/
maxAge?: number;
/**
* Maximum radius of horizontal accuracy in meters.
*/
requiredAccuracy?: number;
};
/**
* Type representing background location task options.
*/
export declare type LocationTaskOptions = LocationOptions & {
showsBackgroundLocationIndicator?: boolean;
deferredUpdatesDistance?: number;
deferredUpdatesTimeout?: number;
deferredUpdatesInterval?: number;
activityType?: LocationActivityType;
pausesUpdatesAutomatically?: boolean;
foregroundService?: {
notificationTitle: string;
notificationBody: string;
notificationColor?: string;
};
};
/**
* Type representing geofencing region object.
*/
export declare type LocationRegion = {
identifier?: string;
latitude: number;
longitude: number;
radius: number;
notifyOnEnter?: boolean;
notifyOnExit?: boolean;
};
/**
* Type representing the location object.
*/
export declare type LocationObject = {
coords: {
latitude: number;
longitude: number;
altitude: number | null;
accuracy: number | null;
altitudeAccuracy: number | null;
heading: number | null;
speed: number | null;
};
timestamp: number;
};
/**
* Represents `watchPositionAsync` callback.
*/
export declare type LocationCallback = (location: LocationObject) => any;
/**
* Represents the object containing details about location provider.
*/
export declare type LocationProviderStatus = {
locationServicesEnabled: boolean;
backgroundModeEnabled: boolean;
gpsAvailable?: boolean;
networkAvailable?: boolean;
passiveAvailable?: boolean;
};
/**
* Type of the object containing heading details and provided by `watchHeadingAsync` callback.
*/
export declare type LocationHeadingObject = {
trueHeading: number;
magHeading: number;
accuracy: number;
};
/**
* Represents `watchHeadingAsync` callback.
*/
export declare type LocationHeadingCallback = (location: LocationHeadingObject) => any;
/**
* An object of options for forward and reverse geocoding.
*/
export declare type LocationGeocodingOptions = {
/**
* Whether to force using Google Maps API instead of the native implementation.
* Used by default only on Web platform. Requires providing an API key by `setGoogleApiKey`.
*/
useGoogleMaps?: boolean;
};
/**
* Type representing a result of `geocodeAsync`.
*/
export declare type LocationGeocodedLocation = {
latitude: number;
longitude: number;
altitude?: number;
accuracy?: number;
};
/**
* Type representing a result of `reverseGeocodeAsync`.
*/
export declare type LocationGeocodedAddress = {
city: string | null;
district: string | null;
street: string | null;
region: string | null;
subregion: string | null;
country: string | null;
postalCode: string | null;
name: string | null;
isoCountryCode: string | null;
timezone: string | null;
};
/**
* Represents subscription object returned by methods watching for new locations or headings.
*/
export declare type LocationSubscription = {
remove: () => void;
};
export declare type PermissionDetailsLocationIOS = {
scope: 'whenInUse' | 'always' | 'none';
};
export declare type PermissionDetailsLocationAndroid = {
scope: 'fine' | 'coarse' | 'none';
};
export interface LocationPermissionResponse extends UMPermissionResponse {
ios?: PermissionDetailsLocationIOS;
android?: PermissionDetailsLocationAndroid;
}

41
node_modules/expo-location/build/Location.types.js generated vendored Normal file
View File

@ -0,0 +1,41 @@
/**
* Enum with available location accuracies.
*/
export var LocationAccuracy;
(function (LocationAccuracy) {
LocationAccuracy[LocationAccuracy["Lowest"] = 1] = "Lowest";
LocationAccuracy[LocationAccuracy["Low"] = 2] = "Low";
LocationAccuracy[LocationAccuracy["Balanced"] = 3] = "Balanced";
LocationAccuracy[LocationAccuracy["High"] = 4] = "High";
LocationAccuracy[LocationAccuracy["Highest"] = 5] = "Highest";
LocationAccuracy[LocationAccuracy["BestForNavigation"] = 6] = "BestForNavigation";
})(LocationAccuracy || (LocationAccuracy = {}));
/**
* Enum with available activity types of background location tracking.
*/
export var LocationActivityType;
(function (LocationActivityType) {
LocationActivityType[LocationActivityType["Other"] = 1] = "Other";
LocationActivityType[LocationActivityType["AutomotiveNavigation"] = 2] = "AutomotiveNavigation";
LocationActivityType[LocationActivityType["Fitness"] = 3] = "Fitness";
LocationActivityType[LocationActivityType["OtherNavigation"] = 4] = "OtherNavigation";
LocationActivityType[LocationActivityType["Airborne"] = 5] = "Airborne";
})(LocationActivityType || (LocationActivityType = {}));
/**
* A type of the event that geofencing task can receive.
*/
export var LocationGeofencingEventType;
(function (LocationGeofencingEventType) {
LocationGeofencingEventType[LocationGeofencingEventType["Enter"] = 1] = "Enter";
LocationGeofencingEventType[LocationGeofencingEventType["Exit"] = 2] = "Exit";
})(LocationGeofencingEventType || (LocationGeofencingEventType = {}));
/**
* State of the geofencing region that you receive through the geofencing task.
*/
export var LocationGeofencingRegionState;
(function (LocationGeofencingRegionState) {
LocationGeofencingRegionState[LocationGeofencingRegionState["Unknown"] = 0] = "Unknown";
LocationGeofencingRegionState[LocationGeofencingRegionState["Inside"] = 1] = "Inside";
LocationGeofencingRegionState[LocationGeofencingRegionState["Outside"] = 2] = "Outside";
})(LocationGeofencingRegionState || (LocationGeofencingRegionState = {}));
//# sourceMappingURL=Location.types.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { EventEmitter } from '@unimodules/core';
export declare const LocationEventEmitter: EventEmitter;

View File

@ -0,0 +1,4 @@
import { EventEmitter } from '@unimodules/core';
import ExpoLocation from './ExpoLocation';
export const LocationEventEmitter = new EventEmitter(ExpoLocation);
//# sourceMappingURL=LocationEventEmitter.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"LocationEventEmitter.js","sourceRoot":"","sources":["../src/LocationEventEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,YAAY,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { EventEmitter } from '@unimodules/core';\n\nimport ExpoLocation from './ExpoLocation';\n\nexport const LocationEventEmitter = new EventEmitter(ExpoLocation);\n"]}

View File

@ -0,0 +1,2 @@
import { EventEmitter } from '@unimodules/core';
export declare const LocationEventEmitter: EventEmitter;

View File

@ -0,0 +1,3 @@
import { EventEmitter } from '@unimodules/core';
export const LocationEventEmitter = new EventEmitter({});
//# sourceMappingURL=LocationEventEmitter.web.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"LocationEventEmitter.web.js","sourceRoot":"","sources":["../src/LocationEventEmitter.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,YAAY,CAAC,EAAS,CAAC,CAAC","sourcesContent":["import { EventEmitter } from '@unimodules/core';\n\nexport const LocationEventEmitter = new EventEmitter({} as any);\n"]}

View File

@ -0,0 +1,7 @@
import { LocationGeocodedAddress, LocationGeocodedLocation } from './Location.types';
export declare function setGoogleApiKey(apiKey: string): void;
export declare function googleGeocodeAsync(address: string): Promise<LocationGeocodedLocation[]>;
export declare function googleReverseGeocodeAsync(options: {
latitude: number;
longitude: number;
}): Promise<LocationGeocodedAddress[]>;

View File

@ -0,0 +1,113 @@
import { CodedError } from '@unimodules/core';
const GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json';
let googleApiKey;
export function setGoogleApiKey(apiKey) {
googleApiKey = apiKey;
}
export async function googleGeocodeAsync(address) {
assertGoogleApiKey();
const result = await requestGoogleApiAsync({ address });
if (result.status === 'ZERO_RESULTS') {
return [];
}
assertGeocodeResults(result);
return result.results.map(geocodingResultToLocation);
}
export async function googleReverseGeocodeAsync(options) {
assertGoogleApiKey();
const result = await requestGoogleApiAsync({
latlng: `${options.latitude},${options.longitude}`,
});
if (result.status === 'ZERO_RESULTS') {
return [];
}
assertGeocodeResults(result);
return result.results.map(reverseGeocodingResultToAddress);
}
// https://developers.google.com/maps/documentation/geocoding/intro
function assertGeocodeResults(resultObject) {
const { status, error_message } = resultObject;
if (status !== 'ZERO_RESULTS' && status !== 'OK') {
if (error_message) {
throw new CodedError(status, error_message);
}
else if (status === 'UNKNOWN_ERROR') {
throw new CodedError(status, 'the request could not be processed due to a server error. The request may succeed if you try again.');
}
throw new CodedError(status, `An error occurred during geocoding.`);
}
}
/**
* Makes sure the Google API key is set.
*/
function assertGoogleApiKey() {
if (!googleApiKey) {
throw new Error('Google API key is required to use geocoding. Please set it using `setGoogleApiKey` method.');
}
}
/**
* Generic and handy method for sending requests to Google Maps API endpoint.
*/
async function requestGoogleApiAsync(params) {
const query = Object.entries(params)
.map(entry => `${entry[0]}=${encodeURI(entry[1])}`)
.join('&');
const result = await fetch(`${GOOGLE_API_URL}?key=${googleApiKey}&${query}`);
return await result.json();
}
/**
* Converts Google's result to the location object.
*/
function geocodingResultToLocation(result) {
const { location } = result.geometry;
return {
latitude: location.lat,
longitude: location.lng,
};
}
/**
* Converts Google's result to address object.
*/
function reverseGeocodingResultToAddress(result) {
const address = {};
for (const { long_name, short_name, types } of result.address_components) {
if (types.includes('locality')) {
address.city = long_name;
continue;
}
if (types.includes('sublocality')) {
address.district = long_name;
continue;
}
if (types.includes('street_address') || types.includes('route')) {
address.street = long_name;
continue;
}
if (types.includes('administrative_area_level_1')) {
address.region = long_name;
continue;
}
if (types.includes('administrative_area_level_2')) {
address.subregion = long_name;
continue;
}
if (types.includes('country')) {
address.country = long_name;
address.isoCountryCode = short_name;
continue;
}
if (types.includes('postal_code')) {
address.postalCode = long_name;
continue;
}
if (types.includes('point_of_interest')) {
address.name = long_name;
continue;
}
}
if (!address.name) {
address.name = result.formatted_address.replace(/,.*$/, '');
}
return address;
}
//# sourceMappingURL=LocationGoogleGeocoding.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
import { LocationCallback, LocationHeadingCallback } from './Location.types';
declare type EventObject = {
watchId: number;
[key: string]: any;
};
declare class Subscriber<CallbackType extends LocationCallback | LocationHeadingCallback> {
private eventName;
private eventDataField;
private callbacks;
private eventSubscription;
constructor(eventName: string, eventDataField: string);
maybeInitializeSubscription(): void;
/**
* Registers given callback under new id which is then returned.
*/
registerCallback(callback: CallbackType): number;
/**
* Unregisters a callback with given id and revokes the subscription if possible.
*/
unregisterCallback(id: number): void;
trigger(event: EventObject): void;
}
export declare const LocationSubscriber: Subscriber<LocationCallback>;
export declare const HeadingSubscriber: Subscriber<LocationHeadingCallback>;
/**
* Necessary for some unit tests.
*/
export declare function _getCurrentWatchId(): number;
export {};

View File

@ -0,0 +1,60 @@
import ExpoLocation from './ExpoLocation';
import { LocationEventEmitter } from './LocationEventEmitter';
let nextWatchId = 0;
class Subscriber {
constructor(eventName, eventDataField) {
this.callbacks = {};
this.eventSubscription = null;
this.eventName = eventName;
this.eventDataField = eventDataField;
}
maybeInitializeSubscription() {
if (this.eventSubscription) {
return;
}
this.eventSubscription = LocationEventEmitter.addListener(this.eventName, (event) => this.trigger(event));
}
/**
* Registers given callback under new id which is then returned.
*/
registerCallback(callback) {
this.maybeInitializeSubscription();
const id = ++nextWatchId;
this.callbacks[id] = callback;
return id;
}
/**
* Unregisters a callback with given id and revokes the subscription if possible.
*/
unregisterCallback(id) {
// Do nothing if we have already unregistered the callback.
if (!this.callbacks[id]) {
return;
}
delete this.callbacks[id];
ExpoLocation.removeWatchAsync(id);
if (Object.keys(this.callbacks).length === 0 && this.eventSubscription) {
LocationEventEmitter.removeSubscription(this.eventSubscription);
this.eventSubscription = null;
}
}
trigger(event) {
const watchId = event.watchId;
const callback = this.callbacks[watchId];
if (callback) {
callback(event[this.eventDataField]);
}
else {
ExpoLocation.removeWatchAsync(watchId);
}
}
}
export const LocationSubscriber = new Subscriber('Expo.locationChanged', 'location');
export const HeadingSubscriber = new Subscriber('Expo.headingChanged', 'heading');
/**
* Necessary for some unit tests.
*/
export function _getCurrentWatchId() {
return nextWatchId;
}
//# sourceMappingURL=LocationSubscribers.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"LocationSubscribers.js","sourceRoot":"","sources":["../src/LocationSubscribers.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAO9D,IAAI,WAAW,GAAG,CAAC,CAAC;AAEpB,MAAM,UAAU;IAMd,YAAY,SAAiB,EAAE,cAAsB;QAH7C,cAAS,GAAmC,EAAE,CAAC;QAC/C,sBAAiB,GAAwB,IAAI,CAAC;QAGpD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IAED,2BAA2B;QACzB,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC1B,OAAO;SACR;QACD,IAAI,CAAC,iBAAiB,GAAG,oBAAoB,CAAC,WAAW,CACvD,IAAI,CAAC,SAAS,EACd,CAAC,KAAkB,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAC5C,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,QAAsB;QACrC,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACnC,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,EAAU;QAC3B,2DAA2D;QAC3D,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE;YACvB,OAAO;SACR;QAED,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC1B,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAElC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE;YACtE,oBAAoB,CAAC,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAChE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;SAC/B;IACH,CAAC;IAED,OAAO,CAAC,KAAkB;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAEzC,IAAI,QAAQ,EAAE;YACZ,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;SACtC;aAAM;YACL,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;SACxC;IACH,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,UAAU,CAC9C,sBAAsB,EACtB,UAAU,CACX,CAAC;AACF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,UAAU,CAC7C,qBAAqB,EACrB,SAAS,CACV,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["import { Subscription } from '@unimodules/core';\n\nimport ExpoLocation from './ExpoLocation';\nimport { LocationCallback, LocationHeadingCallback } from './Location.types';\nimport { LocationEventEmitter } from './LocationEventEmitter';\n\ntype EventObject = {\n watchId: number;\n [key: string]: any;\n};\n\nlet nextWatchId = 0;\n\nclass Subscriber<CallbackType extends LocationCallback | LocationHeadingCallback> {\n private eventName: string;\n private eventDataField: string;\n private callbacks: { [id: string]: CallbackType } = {};\n private eventSubscription: Subscription | null = null;\n\n constructor(eventName: string, eventDataField: string) {\n this.eventName = eventName;\n this.eventDataField = eventDataField;\n }\n\n maybeInitializeSubscription() {\n if (this.eventSubscription) {\n return;\n }\n this.eventSubscription = LocationEventEmitter.addListener(\n this.eventName,\n (event: EventObject) => this.trigger(event)\n );\n }\n\n /**\n * Registers given callback under new id which is then returned.\n */\n registerCallback(callback: CallbackType): number {\n this.maybeInitializeSubscription();\n const id = ++nextWatchId;\n this.callbacks[id] = callback;\n return id;\n }\n\n /**\n * Unregisters a callback with given id and revokes the subscription if possible.\n */\n unregisterCallback(id: number): void {\n // Do nothing if we have already unregistered the callback.\n if (!this.callbacks[id]) {\n return;\n }\n\n delete this.callbacks[id];\n ExpoLocation.removeWatchAsync(id);\n\n if (Object.keys(this.callbacks).length === 0 && this.eventSubscription) {\n LocationEventEmitter.removeSubscription(this.eventSubscription);\n this.eventSubscription = null;\n }\n }\n\n trigger(event: EventObject): void {\n const watchId = event.watchId;\n const callback = this.callbacks[watchId];\n\n if (callback) {\n callback(event[this.eventDataField]);\n } else {\n ExpoLocation.removeWatchAsync(watchId);\n }\n }\n}\n\nexport const LocationSubscriber = new Subscriber<LocationCallback>(\n 'Expo.locationChanged',\n 'location'\n);\nexport const HeadingSubscriber = new Subscriber<LocationHeadingCallback>(\n 'Expo.headingChanged',\n 'heading'\n);\n\n/**\n * Necessary for some unit tests.\n */\nexport function _getCurrentWatchId(): number {\n return nextWatchId;\n}\n"]}

22
node_modules/expo-location/ios/EXLocation.podspec generated vendored Normal file
View File

@ -0,0 +1,22 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'EXLocation'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platform = :ios, '10.0'
s.source = { git: 'https://github.com/expo/expo.git' }
s.source_files = 'EXLocation/**/*.{h,m}'
s.preserve_paths = 'EXLocation/**/*.{h,m}'
s.requires_arc = true
s.dependency 'UMCore'
s.dependency 'UMPermissionsInterface'
s.dependency 'UMTaskManagerInterface'
end

39
node_modules/expo-location/ios/EXLocation/EXLocation.h generated vendored Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <CoreLocation/CLLocation.h>
#import <CoreLocation/CLLocationManager.h>
#import <UMCore/UMEventEmitter.h>
#import <UMCore/UMExportedModule.h>
#import <UMCore/UMModuleRegistryConsumer.h>
// Location accuracies
typedef NS_ENUM(NSUInteger, EXLocationAccuracy) {
EXLocationAccuracyLowest = 1,
EXLocationAccuracyLow = 2,
EXLocationAccuracyBalanced = 3,
EXLocationAccuracyHigh = 4,
EXLocationAccuracyHighest = 5,
EXLocationAccuracyBestForNavigation = 6,
};
// Geofencing event types
typedef NS_ENUM(NSUInteger, EXGeofencingEventType) {
EXGeofencingEventTypeEnter = 1,
EXGeofencingEventTypeExit = 2,
};
// Geofencing region states
typedef NS_ENUM(NSUInteger, EXGeofencingRegionState) {
EXGeofencingRegionStateUnknown = 0,
EXGeofencingRegionStateInside = 1,
EXGeofencingRegionStateOutside = 2,
};
@interface EXLocation : UMExportedModule <UMEventEmitter, UMModuleRegistryConsumer>
+ (NSDictionary *)exportLocation:(CLLocation *)location;
+ (CLLocationAccuracy)CLLocationAccuracyFromOption:(EXLocationAccuracy)accuracy;
+ (CLActivityType)CLActivityTypeFromOption:(NSInteger)activityType;
@end

563
node_modules/expo-location/ios/EXLocation/EXLocation.m generated vendored Normal file
View File

@ -0,0 +1,563 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <EXLocation/EXLocation.h>
#import <EXLocation/EXLocationDelegate.h>
#import <EXLocation/EXLocationTaskConsumer.h>
#import <EXLocation/EXGeofencingTaskConsumer.h>
#import <EXLocation/EXLocationPermissionRequester.h>
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <CoreLocation/CLHeading.h>
#import <CoreLocation/CLGeocoder.h>
#import <CoreLocation/CLPlacemark.h>
#import <CoreLocation/CLError.h>
#import <CoreLocation/CLCircularRegion.h>
#import <UMCore/UMEventEmitterService.h>
#import <UMPermissionsInterface/UMPermissionsInterface.h>
#import <UMPermissionsInterface/UMPermissionsMethodsDelegate.h>
#import <UMTaskManagerInterface/UMTaskManagerInterface.h>
NS_ASSUME_NONNULL_BEGIN
NSString * const EXLocationChangedEventName = @"Expo.locationChanged";
NSString * const EXHeadingChangedEventName = @"Expo.headingChanged";
@interface EXLocation ()
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, EXLocationDelegate*> *delegates;
@property (nonatomic, strong) NSMutableSet<EXLocationDelegate *> *retainedDelegates;
@property (nonatomic, weak) id<UMEventEmitterService> eventEmitter;
@property (nonatomic, weak) id<UMPermissionsInterface> permissionsManager;
@property (nonatomic, weak) id<UMTaskManagerInterface> tasksManager;
@end
@implementation EXLocation
UM_EXPORT_MODULE(ExpoLocation);
- (instancetype)init
{
if (self = [super init]) {
_delegates = [NSMutableDictionary dictionary];
_retainedDelegates = [NSMutableSet set];
}
return self;
}
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
{
_eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(UMEventEmitterService)];
_permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMPermissionsInterface)];
[UMPermissionsMethodsDelegate registerRequesters:@[[EXLocationPermissionRequester new]] withPermissionsManager:_permissionsManager];
_tasksManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMTaskManagerInterface)];
}
- (dispatch_queue_t)methodQueue
{
// Location managers must be created on the main thread
return dispatch_get_main_queue();
}
# pragma mark - UMEventEmitter
- (NSArray<NSString *> *)supportedEvents
{
return @[EXLocationChangedEventName, EXHeadingChangedEventName];
}
- (void)startObserving {}
- (void)stopObserving {}
# pragma mark - Exported methods
UM_EXPORT_METHOD_AS(getProviderStatusAsync,
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
resolve(@{
@"locationServicesEnabled": @([CLLocationManager locationServicesEnabled]),
@"backgroundModeEnabled": @([_tasksManager hasBackgroundModeEnabled:@"location"]),
});
}
UM_EXPORT_METHOD_AS(getCurrentPositionAsync,
options:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
if (![self checkPermissions:reject]) {
return;
}
CLLocationManager *locMgr = [self locationManagerWithOptions:options];
__weak typeof(self) weakSelf = self;
__block EXLocationDelegate *delegate;
delegate = [[EXLocationDelegate alloc] initWithId:nil withLocMgr:locMgr onUpdateLocations:^(NSArray<CLLocation *> * _Nonnull locations) {
if (delegate != nil) {
if (locations.lastObject != nil) {
resolve([EXLocation exportLocation:locations.lastObject]);
} else {
reject(@"E_LOCATION_NOT_FOUND", @"Current location not found.", nil);
}
[weakSelf.retainedDelegates removeObject:delegate];
delegate = nil;
}
} onUpdateHeadings:nil onError:^(NSError *error) {
reject(@"E_LOCATION_UNAVAILABLE", [@"Cannot obtain current location: " stringByAppendingString:error.description], nil);
}];
// retain location manager delegate so it will not dealloc until onUpdateLocations gets called
[_retainedDelegates addObject:delegate];
locMgr.delegate = delegate;
[locMgr requestLocation];
}
UM_EXPORT_METHOD_AS(watchPositionImplAsync,
watchId:(nonnull NSNumber *)watchId
options:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
if (![self checkPermissions:reject]) {
return;
}
__weak typeof(self) weakSelf = self;
CLLocationManager *locMgr = [self locationManagerWithOptions:options];
EXLocationDelegate *delegate = [[EXLocationDelegate alloc] initWithId:watchId withLocMgr:locMgr onUpdateLocations:^(NSArray<CLLocation *> *locations) {
if (locations.lastObject != nil && weakSelf != nil) {
__strong typeof(weakSelf) strongSelf = weakSelf;
CLLocation *loc = locations.lastObject;
NSDictionary *body = @{
@"watchId": watchId,
@"location": [EXLocation exportLocation:loc],
};
[strongSelf->_eventEmitter sendEventWithName:EXLocationChangedEventName body:body];
}
} onUpdateHeadings:nil onError:^(NSError *error) {
// TODO: report errors
// (ben) error could be (among other things):
// - kCLErrorDenied - we should use the same UNAUTHORIZED behavior as elsewhere
// - kCLErrorLocationUnknown - we can actually ignore this error and keep tracking
// location (I think -- my knowledge might be a few months out of date)
}];
_delegates[delegate.watchId] = delegate;
locMgr.delegate = delegate;
[locMgr startUpdatingLocation];
resolve([NSNull null]);
}
UM_EXPORT_METHOD_AS(getLastKnownPositionAsync,
getLastKnownPositionWithOptions:(NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
if (![self checkPermissions:reject]) {
return;
}
CLLocation *location = [[self locationManagerWithOptions:nil] location];
if ([self.class isLocation:location validWithOptions:options]) {
resolve([EXLocation exportLocation:location]);
} else {
resolve([NSNull null]);
}
}
// Watch method for getting compass updates
UM_EXPORT_METHOD_AS(watchDeviceHeading,
watchHeadingWithWatchId:(nonnull NSNumber *)watchId
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject) {
if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXLocationPermissionRequester class]]) {
reject(@"E_LOCATION_UNAUTHORIZED", @"Not authorized to use location services", nil);
return;
}
__weak typeof(self) weakSelf = self;
CLLocationManager *locMgr = [[CLLocationManager alloc] init];
locMgr.distanceFilter = kCLDistanceFilterNone;
locMgr.desiredAccuracy = kCLLocationAccuracyBest;
locMgr.allowsBackgroundLocationUpdates = NO;
EXLocationDelegate *delegate = [[EXLocationDelegate alloc] initWithId:watchId withLocMgr:locMgr onUpdateLocations: nil onUpdateHeadings:^(CLHeading *newHeading) {
if (newHeading != nil && weakSelf != nil) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSNumber *accuracy;
// Convert iOS heading accuracy to Android system
// 3: high accuracy, 2: medium, 1: low, 0: none
if (newHeading.headingAccuracy > 50 || newHeading.headingAccuracy < 0) {
accuracy = @(0);
} else if (newHeading.headingAccuracy > 35) {
accuracy = @(1);
} else if (newHeading.headingAccuracy > 20) {
accuracy = @(2);
} else {
accuracy = @(3);
}
NSDictionary *body = @{@"watchId": watchId,
@"heading": @{
@"trueHeading": @(newHeading.trueHeading),
@"magHeading": @(newHeading.magneticHeading),
@"accuracy": accuracy,
},
};
[strongSelf->_eventEmitter sendEventWithName:EXHeadingChangedEventName body:body];
}
} onError:^(NSError *error) {
// Error getting updates
}];
_delegates[delegate.watchId] = delegate;
locMgr.delegate = delegate;
[locMgr startUpdatingHeading];
resolve([NSNull null]);
}
UM_EXPORT_METHOD_AS(removeWatchAsync,
watchId:(nonnull NSNumber *)watchId
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
EXLocationDelegate *delegate = _delegates[watchId];
if (delegate) {
// Unsuscribe from both location and heading updates
[delegate.locMgr stopUpdatingLocation];
[delegate.locMgr stopUpdatingHeading];
delegate.locMgr.delegate = nil;
[_delegates removeObjectForKey:watchId];
}
resolve([NSNull null]);
}
UM_EXPORT_METHOD_AS(geocodeAsync,
address:(nonnull NSString *)address
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray* placemarks, NSError* error){
if (!error) {
NSMutableArray *results = [NSMutableArray arrayWithCapacity:placemarks.count];
for (CLPlacemark* placemark in placemarks) {
CLLocation *location = placemark.location;
[results addObject:@{
@"latitude": @(location.coordinate.latitude),
@"longitude": @(location.coordinate.longitude),
@"altitude": @(location.altitude),
@"accuracy": @(location.horizontalAccuracy),
}];
}
resolve(results);
} else if (error.code == kCLErrorGeocodeFoundNoResult || error.code == kCLErrorGeocodeFoundPartialResult) {
resolve(@[]);
} else if (error.code == kCLErrorNetwork) {
reject(@"E_RATE_EXCEEDED", @"Rate limit exceeded - too many requests", error);
} else {
reject(@"E_GEOCODING_FAILED", @"Error while geocoding an address", error);
}
}];
}
UM_EXPORT_METHOD_AS(reverseGeocodeAsync,
locationMap:(nonnull NSDictionary *)locationMap
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocation *location = [[CLLocation alloc] initWithLatitude:[locationMap[@"latitude"] floatValue] longitude:[locationMap[@"longitude"] floatValue]];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray* placemarks, NSError* error){
if (!error) {
NSMutableArray *results = [NSMutableArray arrayWithCapacity:placemarks.count];
for (CLPlacemark* placemark in placemarks) {
NSDictionary *address = @{
@"city": UMNullIfNil(placemark.locality),
@"district": UMNullIfNil(placemark.subLocality),
@"street": UMNullIfNil(placemark.thoroughfare),
@"region": UMNullIfNil(placemark.administrativeArea),
@"subregion": UMNullIfNil(placemark.subAdministrativeArea),
@"country": UMNullIfNil(placemark.country),
@"postalCode": UMNullIfNil(placemark.postalCode),
@"name": UMNullIfNil(placemark.name),
@"isoCountryCode": UMNullIfNil(placemark.ISOcountryCode),
@"timezone": UMNullIfNil(placemark.timeZone.name),
};
[results addObject:address];
}
resolve(results);
} else if (error.code == kCLErrorGeocodeFoundNoResult || error.code == kCLErrorGeocodeFoundPartialResult) {
resolve(@[]);
} else if (error.code == kCLErrorNetwork) {
reject(@"E_RATE_EXCEEDED", @"Rate limit exceeded - too many requests", error);
} else {
reject(@"E_REVGEOCODING_FAILED", @"Error while reverse-geocoding a location", error);
}
}];
}
UM_EXPORT_METHOD_AS(getPermissionsAsync,
getPermissionsAsync:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
[UMPermissionsMethodsDelegate getPermissionWithPermissionsManager:_permissionsManager
withRequester:[EXLocationPermissionRequester class]
resolve:resolve
reject:reject];
}
UM_EXPORT_METHOD_AS(requestPermissionsAsync,
requestPermissionsAsync:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
[UMPermissionsMethodsDelegate askForPermissionWithPermissionsManager:_permissionsManager
withRequester:[EXLocationPermissionRequester class]
resolve:resolve
reject:reject];
}
UM_EXPORT_METHOD_AS(hasServicesEnabledAsync,
hasServicesEnabled:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
BOOL servicesEnabled = [CLLocationManager locationServicesEnabled];
resolve(@(servicesEnabled));
}
# pragma mark - Background location
UM_EXPORT_METHOD_AS(startLocationUpdatesAsync,
startLocationUpdatesForTaskWithName:(nonnull NSString *)taskName
withOptions:(nonnull NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkPermissions:reject] || ![self checkTaskManagerExists:reject] || ![self checkBackgroundServices:reject]) {
return;
}
if (![CLLocationManager significantLocationChangeMonitoringAvailable]) {
return reject(@"E_SIGNIFICANT_CHANGES_UNAVAILABLE", @"Significant location changes monitoring is not available.", nil);
}
@try {
[_tasksManager registerTaskWithName:taskName consumer:[EXLocationTaskConsumer class] options:options];
}
@catch (NSException *e) {
return reject(e.name, e.reason, nil);
}
resolve(nil);
}
UM_EXPORT_METHOD_AS(stopLocationUpdatesAsync,
stopLocationUpdatesForTaskWithName:(NSString *)taskName
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkTaskManagerExists:reject]) {
return;
}
@try {
[_tasksManager unregisterTaskWithName:taskName consumerClass:[EXLocationTaskConsumer class]];
} @catch (NSException *e) {
return reject(e.name, e.reason, nil);
}
resolve(nil);
}
UM_EXPORT_METHOD_AS(hasStartedLocationUpdatesAsync,
hasStartedLocationUpdatesForTaskWithName:(nonnull NSString *)taskName
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkTaskManagerExists:reject]) {
return;
}
resolve(@([_tasksManager taskWithName:taskName hasConsumerOfClass:[EXLocationTaskConsumer class]]));
}
# pragma mark - Geofencing
UM_EXPORT_METHOD_AS(startGeofencingAsync,
startGeofencingWithTaskName:(nonnull NSString *)taskName
withOptions:(nonnull NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkPermissions:reject] || ![self checkTaskManagerExists:reject]) {
return;
}
if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
return reject(@"E_GEOFENCING_UNAVAILABLE", @"Geofencing is not available", nil);
}
@try {
[_tasksManager registerTaskWithName:taskName consumer:[EXGeofencingTaskConsumer class] options:options];
} @catch (NSException *e) {
return reject(e.name, e.reason, nil);
}
resolve(nil);
}
UM_EXPORT_METHOD_AS(stopGeofencingAsync,
stopGeofencingWithTaskName:(nonnull NSString *)taskName
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkTaskManagerExists:reject]) {
return;
}
@try {
[_tasksManager unregisterTaskWithName:taskName consumerClass:[EXGeofencingTaskConsumer class]];
} @catch (NSException *e) {
return reject(e.name, e.reason, nil);
}
resolve(nil);
}
UM_EXPORT_METHOD_AS(hasStartedGeofencingAsync,
hasStartedGeofencingForTaskWithName:(NSString *)taskName
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
if (![self checkTaskManagerExists:reject]) {
return;
}
resolve(@([_tasksManager taskWithName:taskName hasConsumerOfClass:[EXGeofencingTaskConsumer class]]));
}
# pragma mark - helpers
- (CLLocationManager *)locationManagerWithOptions:(nullable NSDictionary *)options
{
CLLocationManager *locMgr = [[CLLocationManager alloc] init];
locMgr.allowsBackgroundLocationUpdates = NO;
if (options) {
locMgr.distanceFilter = options[@"distanceInterval"] ? [options[@"distanceInterval"] doubleValue] ?: kCLDistanceFilterNone : kCLLocationAccuracyHundredMeters;
if (options[@"accuracy"]) {
EXLocationAccuracy accuracy = [options[@"accuracy"] unsignedIntegerValue] ?: EXLocationAccuracyBalanced;
locMgr.desiredAccuracy = [self.class CLLocationAccuracyFromOption:accuracy];
}
}
return locMgr;
}
- (BOOL)checkPermissions:(UMPromiseRejectBlock)reject
{
if (![CLLocationManager locationServicesEnabled]) {
reject(@"E_LOCATION_SERVICES_DISABLED", @"Location services are disabled", nil);
return NO;
}
if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXLocationPermissionRequester class]]) {
reject(@"E_NO_PERMISSIONS", @"LOCATION permission is required to do this operation.", nil);
return NO;
}
return YES;
}
- (BOOL)checkTaskManagerExists:(UMPromiseRejectBlock)reject
{
if (_tasksManager == nil) {
reject(@"E_TASKMANAGER_NOT_FOUND", @"`expo-task-manager` module is required to use background services.", nil);
return NO;
}
return YES;
}
- (BOOL)checkBackgroundServices:(UMPromiseRejectBlock)reject
{
if (![_tasksManager hasBackgroundModeEnabled:@"location"]) {
reject(@"E_BACKGROUND_SERVICES_DISABLED", @"Background Location has not been configured. To enable it, add `location` to `UIBackgroundModes` in Info.plist file.", nil);
return NO;
}
return YES;
}
# pragma mark - static helpers
+ (NSDictionary *)exportLocation:(CLLocation *)location
{
return @{
@"coords": @{
@"latitude": @(location.coordinate.latitude),
@"longitude": @(location.coordinate.longitude),
@"altitude": @(location.altitude),
@"accuracy": @(location.horizontalAccuracy),
@"altitudeAccuracy": @(location.verticalAccuracy),
@"heading": @(location.course),
@"speed": @(location.speed),
},
@"timestamp": @([location.timestamp timeIntervalSince1970] * 1000),
};
}
+ (CLLocationAccuracy)CLLocationAccuracyFromOption:(EXLocationAccuracy)accuracy
{
switch (accuracy) {
case EXLocationAccuracyLowest:
return kCLLocationAccuracyThreeKilometers;
case EXLocationAccuracyLow:
return kCLLocationAccuracyKilometer;
case EXLocationAccuracyBalanced:
return kCLLocationAccuracyHundredMeters;
case EXLocationAccuracyHigh:
return kCLLocationAccuracyNearestTenMeters;
case EXLocationAccuracyHighest:
return kCLLocationAccuracyBest;
case EXLocationAccuracyBestForNavigation:
return kCLLocationAccuracyBestForNavigation;
default:
return kCLLocationAccuracyHundredMeters;
}
}
+ (CLActivityType)CLActivityTypeFromOption:(NSInteger)activityType
{
if (activityType >= CLActivityTypeOther && activityType <= CLActivityTypeOtherNavigation) {
return activityType;
}
if (@available(iOS 12.0, *)) {
if (activityType == CLActivityTypeAirborne) {
return activityType;
}
}
return CLActivityTypeOther;
}
+ (BOOL)isLocation:(nullable CLLocation *)location validWithOptions:(nullable NSDictionary *)options
{
if (location == nil) {
return NO;
}
NSTimeInterval maxAge = options[@"maxAge"] ? [options[@"maxAge"] doubleValue] : DBL_MAX;
CLLocationAccuracy requiredAccuracy = options[@"requiredAccuracy"] ? [options[@"requiredAccuracy"] doubleValue] : DBL_MAX;
NSTimeInterval timeDiff = -location.timestamp.timeIntervalSinceNow;
return location != nil && timeDiff * 1000 <= maxAge && location.horizontalAccuracy <= requiredAccuracy;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,32 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <CoreLocation/CLHeading.h>
#import <CoreLocation/CLLocation.h>
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXLocationDelegate : NSObject <CLLocationManagerDelegate>
@property (nonatomic, strong) NSNumber *watchId;
@property (nonatomic, strong) CLLocationManager *locMgr;
@property (nonatomic, strong) void (^onUpdateLocations)(NSArray<CLLocation *> *locations);
@property (nonatomic, strong) void (^onUpdateHeadings)(CLHeading *newHeading);
@property (nonatomic, strong) void (^onError)(NSError *error);
- (instancetype)initWithId:(nullable NSNumber *)watchId
withLocMgr:(CLLocationManager *)locMgr
onUpdateLocations:(nullable void (^)(NSArray<CLLocation *> *locations))onUpdateLocations
onUpdateHeadings:(nullable void (^)(CLHeading *newHeading))onUpdateHeadings
onError:(nullable void (^)(NSError *error))onError;
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations;
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading;
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(nonnull NSError *)error;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,46 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <EXLocation/EXLocationDelegate.h>
@implementation EXLocationDelegate
- (instancetype)initWithId:(nullable NSNumber *)watchId
withLocMgr:(CLLocationManager *)locMgr
onUpdateLocations:(nullable void (^)(NSArray<CLLocation *> *locations))onUpdateLocations
onUpdateHeadings:(nullable void (^)(CLHeading *newHeading))onUpdateHeadings
onError:(nullable void (^)(NSError *error))onError
{
if ((self = [super init])) {
_watchId = watchId;
_locMgr = locMgr;
_onUpdateLocations = onUpdateLocations;
_onUpdateHeadings = onUpdateHeadings;
_onError = onError;
}
return self;
}
// Delegate method called by CLLocationManager
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
if (_onUpdateLocations) {
_onUpdateLocations(locations);
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if (_onUpdateHeadings) {
_onUpdateHeadings(newHeading);
}
}
// Delegate method called by CLLocationManager
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(nonnull NSError *)error
{
if (_onError) {
_onError(error);
}
}
@end

View File

@ -0,0 +1,7 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <UMPermissionsInterface/UMPermissionsInterface.h>
@interface EXLocationPermissionRequester : NSObject<UMPermissionsRequester>
@end

View File

@ -0,0 +1,177 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <EXLocation/EXLocationPermissionRequester.h>
#import <UMCore/UMUtilities.h>
#import <objc/message.h>
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
static SEL alwaysAuthorizationSelector;
static SEL whenInUseAuthorizationSelector;
@interface EXLocationPermissionRequester () <CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *locMgr;
@property (nonatomic, strong) UMPromiseResolveBlock resolve;
@property (nonatomic, strong) UMPromiseRejectBlock reject;
@end
@implementation EXLocationPermissionRequester
+ (NSString *)permissionType
{
return @"location";
}
+ (void)load
{
alwaysAuthorizationSelector = NSSelectorFromString([@"request" stringByAppendingString:@"AlwaysAuthorization"]);
whenInUseAuthorizationSelector = NSSelectorFromString([@"request" stringByAppendingString:@"WhenInUseAuthorization"]);
}
- (NSDictionary *)getPermissions
{
UMPermissionStatus status;
NSString *scope = @"none";
CLAuthorizationStatus systemStatus;
if (![[self class] isConfiguredForAlwaysAuthorization] && ![[self class] isConfiguredForWhenInUseAuthorization]) {
UMFatal(UMErrorWithMessage(@"This app is missing usage descriptions, so location services will fail. Add one of the `NSLocation*UsageDescription` keys to your bundle's Info.plist. See https://bit.ly/2P5fEbG (https://docs.expo.io/versions/latest/guides/app-stores.html#system-permissions-dialogs-on-ios) for more information."));
systemStatus = kCLAuthorizationStatusDenied;
} else {
systemStatus = [CLLocationManager authorizationStatus];
}
switch (systemStatus) {
case kCLAuthorizationStatusAuthorizedWhenInUse: {
status = UMPermissionStatusGranted;
scope = @"whenInUse";
break;
}
case kCLAuthorizationStatusAuthorizedAlways: {
status = UMPermissionStatusGranted;
scope = @"always";
break;
}
case kCLAuthorizationStatusDenied: case kCLAuthorizationStatusRestricted: {
status = UMPermissionStatusDenied;
break;
}
case kCLAuthorizationStatusNotDetermined: default: {
status = UMPermissionStatusUndetermined;
break;
}
}
return @{
@"status": @(status),
@"scope": scope
};
}
- (void)requestPermissionsWithResolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject
{
NSDictionary *existingPermissions = [self getPermissions];
if (existingPermissions && [existingPermissions[@"status"] intValue] != UMPermissionStatusUndetermined) {
// since permissions are already determined, the iOS request methods will be no-ops.
// just resolve with whatever existing permissions.
resolve(existingPermissions);
} else {
_resolve = resolve;
_reject = reject;
UM_WEAKIFY(self)
[UMUtilities performSynchronouslyOnMainThread:^{
UM_ENSURE_STRONGIFY(self)
self.locMgr = [[CLLocationManager alloc] init];
self.locMgr.delegate = self;
}];
// 1. Why do we call CLLocationManager methods by those dynamically created selectors?
//
// Most probably application code submitted to Apple Store is statically analyzed
// paying special attention to camelcase(request_always_location) being called on CLLocationManager.
// This lets Apple warn developers when it notices that location authorization may be requested
// while there is no NSLocationUsageDescription in Info.plist. Since we want to neither
// make Expo developers receive this kind of messages nor add our own default usage description,
// we try to fool the static analyzer and construct the selector in runtime.
// This way behavior of this requester is governed by provided NSLocationUsageDescriptions.
//
// 2. Why there's no way to call specifically whenInUse or always authorization?
//
// The requester sets itself as the delegate of the CLLocationManager, so when the user responds
// to a permission requesting dialog, manager calls `locationManager:didChangeAuthorizationStatus:` method.
// To be precise, manager calls this method in two circumstances:
// - right when `request*Authorization` method is called,
// - when `authorizationStatus` changes.
// With this behavior we aren't able to support the following use case:
// - app requests `whenInUse` authorization
// - user allows `whenInUse` authorization
// - `authorizationStatus` changes from `undetermined` to `whenInUse`, callback is called, promise is resolved
// - app wants to escalate authorization to `always`
// - user selects `whenInUse` authorization (iOS 11+)
// - `authorizationStatus` doesn't change, so callback is not called and requester can't know whether
// user responded to the dialog selecting `whenInUse` or is still deciding
// To support this use case we will have to change the way location authorization is requested
// from promise-based to listener-based.
if ([[self class] isConfiguredForAlwaysAuthorization] && [_locMgr respondsToSelector:alwaysAuthorizationSelector]) {
((void (*)(id, SEL))objc_msgSend)(_locMgr, alwaysAuthorizationSelector);
} else if ([[self class] isConfiguredForWhenInUseAuthorization] && [_locMgr respondsToSelector:whenInUseAuthorizationSelector]) {
((void (*)(id, SEL))objc_msgSend)(_locMgr, whenInUseAuthorizationSelector);
} else {
_reject(@"E_LOCATION_INFO_PLIST", @"One of the `NSLocation*UsageDescription` keys must be present in Info.plist to be able to use geolocation.", nil);
}
}
}
# pragma mark - internal
+ (BOOL)isConfiguredForWhenInUseAuthorization
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil;
}
+ (BOOL)isConfiguredForAlwaysAuthorization
{
if (@available(iOS 11.0, *)) {
return [self isConfiguredForWhenInUseAuthorization] && [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"];
}
// iOS 10 fallback
return [self isConfiguredForWhenInUseAuthorization] && [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if (_reject) {
_reject(@"E_LOCATION_ERROR_UNKNOWN", error.localizedDescription, error);
_resolve = nil;
_reject = nil;
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
// TODO: Permissions.LOCATION issue (search by this phrase)
// if Permissions.LOCATION is being called for the first time on iOS devide and prompts for user action it might not call this callback at all
// it happens if user requests more that one permission at the same time via Permissions.askAsync(...) and LOCATION dialog is not being called first
// to reproduce this find NCL code testing that
if (status == kCLAuthorizationStatusNotDetermined) {
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
return;
}
if (_resolve) {
_resolve([self getPermissions]);
_resolve = nil;
_reject = nil;
}
}
@end

View File

@ -0,0 +1,14 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <UMTaskManagerInterface/UMTaskConsumerInterface.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXGeofencingTaskConsumer : NSObject <UMTaskConsumerInterface, CLLocationManagerDelegate>
@property (nonatomic, strong) id<UMTaskInterface> task;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,215 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <CoreLocation/CLCircularRegion.h>
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLErrorDomain.h>
#import <UMCore/UMUtilities.h>
#import <EXLocation/EXLocation.h>
#import <EXLocation/EXGeofencingTaskConsumer.h>
#import <UMTaskManagerInterface/UMTaskInterface.h>
@interface EXGeofencingTaskConsumer ()
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *regionStates;
@property (nonatomic, assign) BOOL backgroundOnly;
@end
@implementation EXGeofencingTaskConsumer
- (void)dealloc
{
[self reset];
}
# pragma mark - UMTaskConsumerInterface
- (NSString *)taskType
{
return @"geofencing";
}
- (void)setOptions:(nonnull NSDictionary *)options
{
[self stopMonitoringAllRegions];
[self startMonitoringRegionsForTask:self->_task];
}
- (void)didRegisterTask:(id<UMTaskInterface>)task
{
[self startMonitoringRegionsForTask:task];
}
- (void)didUnregister
{
[self reset];
}
# pragma mark - helpers
- (void)reset
{
[self stopMonitoringAllRegions];
[UMUtilities performSynchronouslyOnMainThread:^{
self->_locationManager = nil;
self->_task = nil;
}];
}
- (void)startMonitoringRegionsForTask:(id<UMTaskInterface>)task
{
[UMUtilities performSynchronouslyOnMainThread:^{
CLLocationManager *locationManager = [CLLocationManager new];
NSMutableDictionary *regionStates = [NSMutableDictionary new];
NSDictionary *options = [task options];
NSArray *regions = options[@"regions"];
self->_task = task;
self->_locationManager = locationManager;
self->_regionStates = regionStates;
locationManager.delegate = self;
locationManager.allowsBackgroundLocationUpdates = YES;
locationManager.pausesLocationUpdatesAutomatically = NO;
for (NSDictionary *regionDict in regions) {
NSString *identifier = regionDict[@"identifier"] ?: [[NSUUID UUID] UUIDString];
CLLocationDistance radius = [regionDict[@"radius"] doubleValue];
CLLocationCoordinate2D center = [self.class coordinateFromDictionary:regionDict];
BOOL notifyOnEntry = [self.class boolValueFrom:regionDict[@"notifyOnEntry"] defaultValue:YES];
BOOL notifyOnExit = [self.class boolValueFrom:regionDict[@"notifyOnExit"] defaultValue:YES];
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:radius identifier:identifier];
region.notifyOnEntry = notifyOnEntry;
region.notifyOnExit = notifyOnExit;
[regionStates setObject:@(CLRegionStateUnknown) forKey:identifier];
[locationManager startMonitoringForRegion:region];
[locationManager requestStateForRegion:region];
}
}];
}
- (void)stopMonitoringAllRegions
{
[UMUtilities performSynchronouslyOnMainThread:^{
for (CLRegion *region in self->_locationManager.monitoredRegions) {
[self->_locationManager stopMonitoringForRegion:region];
}
}];
}
- (void)executeTaskWithRegion:(nonnull CLRegion *)region eventType:(EXGeofencingEventType)eventType
{
if ([region isKindOfClass:[CLCircularRegion class]]) {
CLCircularRegion *circularRegion = (CLCircularRegion *)region;
CLRegionState regionState = [self regionStateForIdentifier:circularRegion.identifier];
NSDictionary *data = @{
@"eventType": @(eventType),
@"region": [[self class] exportRegion:circularRegion withState:regionState],
};
[_task executeWithData:data withError:nil];
}
}
# pragma mark - CLLocationManagerDelegate
// There is a bug in iOS that causes didEnterRegion and didExitRegion to be called multiple times.
// https://stackoverflow.com/questions/36807060/region-monitoring-method-getting-called-multiple-times-in-geo-fencing
// To prevent this behavior, we execute tasks only when the state has changed.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if ([self regionStateForIdentifier:region.identifier] != CLRegionStateInside) {
[self setRegionState:CLRegionStateInside forIdentifier:region.identifier];
[self executeTaskWithRegion:region eventType:EXGeofencingEventTypeEnter];
}
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
if ([self regionStateForIdentifier:region.identifier] != CLRegionStateOutside) {
[self setRegionState:CLRegionStateOutside forIdentifier:region.identifier];
[self executeTaskWithRegion:region eventType:EXGeofencingEventTypeExit];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
[_task executeWithData:nil withError:error];
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
if (error && error.domain == kCLErrorDomain) {
// This error might happen when the device is not able to find out the location. Try to restart monitoring this region.
[_locationManager stopMonitoringForRegion:region];
[_locationManager startMonitoringForRegion:region];
[_locationManager requestStateForRegion:region];
}
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if ([self regionStateForIdentifier:region.identifier] != state) {
EXGeofencingEventType eventType = state == CLRegionStateInside ? EXGeofencingEventTypeEnter : EXGeofencingEventTypeExit;
[self setRegionState:state forIdentifier:region.identifier];
[self executeTaskWithRegion:region eventType:eventType];
}
}
# pragma mark - helpers
- (CLRegionState)regionStateForIdentifier:(NSString *)identifier
{
return [_regionStates[identifier] integerValue];
}
- (void)setRegionState:(CLRegionState)regionState forIdentifier:(NSString *)identifier
{
[_regionStates setObject:@(regionState) forKey:identifier];
}
# pragma mark - static helpers
+ (nonnull NSDictionary *)exportRegion:(nonnull CLCircularRegion *)region withState:(CLRegionState)regionState
{
return @{
@"identifier": region.identifier,
@"state": @([self exportRegionState:regionState]),
@"radius": @(region.radius),
@"latitude": @(region.center.latitude),
@"longitude": @(region.center.longitude),
};
}
+ (EXGeofencingRegionState)exportRegionState:(CLRegionState)regionState
{
switch (regionState) {
case CLRegionStateUnknown:
return EXGeofencingRegionStateUnknown;
case CLRegionStateInside:
return EXGeofencingRegionStateInside;
case CLRegionStateOutside:
return EXGeofencingRegionStateOutside;
}
}
+ (CLLocationCoordinate2D)coordinateFromDictionary:(nonnull NSDictionary *)dict
{
CLLocationDegrees latitude = [dict[@"latitude"] doubleValue];
CLLocationDegrees longitude = [dict[@"longitude"] doubleValue];
return CLLocationCoordinate2DMake(latitude, longitude);
}
+ (BOOL)boolValueFrom:(id)pointer defaultValue:(BOOL)defaultValue
{
return pointer == nil ? defaultValue : [pointer boolValue];
}
@end

View File

@ -0,0 +1,14 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <UMTaskManagerInterface/UMTaskConsumerInterface.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXLocationTaskConsumer : NSObject <UMTaskConsumerInterface, CLLocationManagerDelegate>
@property (nonatomic, strong) id<UMTaskInterface> task;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,190 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLErrorDomain.h>
#import <UMCore/UMUtilities.h>
#import <EXLocation/EXLocation.h>
#import <EXLocation/EXLocationTaskConsumer.h>
#import <UMTaskManagerInterface/UMTaskInterface.h>
@interface EXLocationTaskConsumer ()
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) NSMutableArray<CLLocation *> *deferredLocations;
@property (nonatomic, strong) CLLocation *lastReportedLocation;
@property (nonatomic, assign) CLLocationDistance deferredDistance;
@end
@implementation EXLocationTaskConsumer
- (instancetype)init
{
if (self = [super init]) {
_deferredLocations = [NSMutableArray new];
_deferredDistance = 0.0;
}
return self;
}
- (void)dealloc
{
[self reset];
}
# pragma mark - UMTaskConsumerInterface
- (NSString *)taskType
{
return @"location";
}
- (void)didRegisterTask:(id<UMTaskInterface>)task
{
[UMUtilities performSynchronouslyOnMainThread:^{
CLLocationManager *locationManager = [CLLocationManager new];
self->_task = task;
self->_locationManager = locationManager;
locationManager.delegate = self;
locationManager.allowsBackgroundLocationUpdates = YES;
// Set options-specific things in location manager.
[self setOptions:task.options];
}];
}
- (void)didUnregister
{
[self reset];
}
- (void)setOptions:(NSDictionary *)options
{
[UMUtilities performSynchronouslyOnMainThread:^{
CLLocationManager *locationManager = self->_locationManager;
EXLocationAccuracy accuracy = [options[@"accuracy"] unsignedIntegerValue] ?: EXLocationAccuracyBalanced;
locationManager.desiredAccuracy = [EXLocation CLLocationAccuracyFromOption:accuracy];
locationManager.distanceFilter = [options[@"distanceInterval"] doubleValue] ?: kCLDistanceFilterNone;
locationManager.activityType = [EXLocation CLActivityTypeFromOption:[options[@"activityType"] integerValue]];
locationManager.pausesLocationUpdatesAutomatically = [options[@"pausesUpdatesAutomatically"] boolValue];
if (@available(iOS 11.0, *)) {
locationManager.showsBackgroundLocationIndicator = [options[@"showsBackgroundLocationIndicator"] boolValue];
}
[locationManager startUpdatingLocation];
[locationManager startMonitoringSignificantLocationChanges];
}];
}
# pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
if (_task != nil && locations.count > 0) {
[self deferLocations:locations];
[self maybeReportDeferredLocations];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if (error.domain == kCLErrorDomain) {
// This error might happen when the device is not able to find out the location. Try to restart monitoring location.
[manager stopUpdatingLocation];
[manager stopMonitoringSignificantLocationChanges];
[manager startUpdatingLocation];
[manager startMonitoringSignificantLocationChanges];
} else {
[_task executeWithData:nil withError:error];
}
}
# pragma mark - internal
- (void)reset
{
[UMUtilities performSynchronouslyOnMainThread:^{
[self->_locationManager stopUpdatingLocation];
[self->_locationManager stopMonitoringSignificantLocationChanges];
[self->_deferredLocations removeAllObjects];
self->_lastReportedLocation = nil;
self->_deferredDistance = 0.0;
self->_locationManager = nil;
self->_task = nil;
}];
}
- (void)executeTaskWithDeferredLocations
{
// Execute task with deferred locations.
NSDictionary *data = @{ @"locations": [EXLocationTaskConsumer _exportLocations:_deferredLocations] };
[_task executeWithData:data withError:nil];
// Reset deferring state.
_lastReportedLocation = _deferredLocations.lastObject;
_deferredDistance = 0.0;
[_deferredLocations removeAllObjects];
}
- (void)maybeReportDeferredLocations
{
if ([self shouldReportDeferredLocations]) {
[self executeTaskWithDeferredLocations];
}
}
- (void)deferLocations:(NSArray<CLLocation *> *)locations
{
CLLocation *lastLocation = _deferredLocations.lastObject ?: _lastReportedLocation;
for (CLLocation *location in locations) {
if (lastLocation) {
_deferredDistance += [location distanceFromLocation:lastLocation];
}
lastLocation = location;
}
[_deferredLocations addObjectsFromArray:locations];
}
- (BOOL)shouldReportDeferredLocations
{
if (_deferredLocations.count <= 0) {
return NO;
}
UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
if (appState == UIApplicationStateActive) {
// Don't defer location updates when app is in foreground state.
return YES;
}
CLLocation *oldestLocation = _lastReportedLocation ?: _deferredLocations.firstObject;
CLLocation *newestLocation = _deferredLocations.lastObject;
NSDictionary *options = _task.options;
CLLocationDistance distance = [self numberToDouble:options[@"deferredUpdatesDistance"] defaultValue:0];
NSTimeInterval interval = [self numberToDouble:options[@"deferredUpdatesInterval"] defaultValue:0];
return [newestLocation.timestamp timeIntervalSinceDate:oldestLocation.timestamp] >= interval / 1000.0 && _deferredDistance >= distance;
}
- (double)numberToDouble:(NSNumber *)number defaultValue:(double)defaultValue
{
return number == nil ? defaultValue : [number doubleValue];
}
+ (NSArray<NSDictionary *> *)_exportLocations:(NSArray<CLLocation *> *)locations
{
NSMutableArray<NSDictionary *> *result = [NSMutableArray new];
for (CLLocation *location in locations) {
[result addObject:[EXLocation exportLocation:location]];
}
return result;
}
@end

50
node_modules/expo-location/package.json generated vendored Normal file
View File

@ -0,0 +1,50 @@
{
"name": "expo-location",
"version": "10.0.0",
"description": "Allows reading geolocation information from the device. Your app can poll for the current location or subscribe to location update events.",
"main": "build/Location.js",
"types": "build/Location.d.ts",
"sideEffects": false,
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react-native",
"expo",
"location",
"geolocation",
"coords",
"geocoding",
"compass",
"heading"
],
"repository": {
"type": "git",
"url": "https://github.com/expo/expo.git",
"directory": "packages/expo-location"
},
"bugs": {
"url": "https://github.com/expo/expo/issues"
},
"author": "650 Industries, Inc.",
"license": "MIT",
"homepage": "https://docs.expo.io/versions/latest/sdk/location/",
"jest": {
"preset": "expo-module-scripts"
},
"peerDependencies": {
"@unimodules/core": "*",
"unimodules-permissions-interface": "*",
"unimodules-task-manager-interface": "*"
},
"devDependencies": {
"expo-module-scripts": "~1.2.0"
},
"gitHead": "bc6b4b3bc3cb5e44e477f145c72c07ed09588651"
}

2
node_modules/expo-location/src/ExpoLocation.ts generated vendored Normal file
View File

@ -0,0 +1,2 @@
import { NativeModulesProxy } from '@unimodules/core';
export default NativeModulesProxy.ExpoLocation;

153
node_modules/expo-location/src/ExpoLocation.web.ts generated vendored Normal file
View File

@ -0,0 +1,153 @@
import { PermissionResponse, PermissionStatus } from 'unimodules-permissions-interface';
import {
LocationLastKnownOptions,
LocationObject,
LocationOptions,
LocationAccuracy,
} from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
class GeocoderError extends Error {
code: string;
constructor() {
super('Geocoder service is not available for this device.');
this.code = 'E_NO_GEOCODER';
}
}
/**
* Converts `GeolocationPosition` to JavaScript object.
*/
function geolocationPositionToJSON(position: LocationObject): LocationObject {
const { coords, timestamp } = position;
return {
coords: {
latitude: coords.latitude,
longitude: coords.longitude,
altitude: coords.altitude,
accuracy: coords.accuracy,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed,
},
timestamp,
};
}
/**
* Checks whether given location didn't exceed given `maxAge` and fits in the required accuracy.
*/
function isLocationValid(location: LocationObject, options: LocationLastKnownOptions): boolean {
const maxAge = typeof options.maxAge === 'number' ? options.maxAge : Infinity;
const requiredAccuracy =
typeof options.requiredAccuracy === 'number' ? options.requiredAccuracy : Infinity;
const locationAccuracy = location.coords.accuracy ?? Infinity;
return Date.now() - location.timestamp <= maxAge && locationAccuracy <= requiredAccuracy;
}
/**
* Gets the permission details. The implementation is not very good as it actually requests
* for the current location, but there is no better way on web so far :(
*/
async function getPermissionsAsync(): Promise<PermissionResponse> {
return new Promise<PermissionResponse>(resolve => {
const resolveWithStatus = status =>
resolve({
status,
granted: status === PermissionStatus.GRANTED,
canAskAgain: true,
expires: 0,
});
navigator.geolocation.getCurrentPosition(
() => resolveWithStatus(PermissionStatus.GRANTED),
({ code }) => {
if (code === 1 /* PERMISSION_DENIED */) {
resolveWithStatus(PermissionStatus.DENIED);
} else {
resolveWithStatus(PermissionStatus.UNDETERMINED);
}
},
{ enableHighAccuracy: false, maximumAge: Infinity }
);
});
}
let lastKnownPosition: LocationObject | null = null;
export default {
get name(): string {
return 'ExpoLocation';
},
async getProviderStatusAsync(): Promise<{ locationServicesEnabled: boolean }> {
return {
locationServicesEnabled: 'geolocation' in navigator,
};
},
async getLastKnownPositionAsync(
options: LocationLastKnownOptions = {}
): Promise<LocationObject | null> {
if (lastKnownPosition && isLocationValid(lastKnownPosition, options)) {
return lastKnownPosition;
}
return null;
},
async getCurrentPositionAsync(options: LocationOptions): Promise<LocationObject> {
return new Promise<LocationObject>((resolve, reject) => {
const resolver = position => {
lastKnownPosition = geolocationPositionToJSON(position);
resolve(lastKnownPosition);
};
navigator.geolocation.getCurrentPosition(resolver, reject, {
maximumAge: Infinity,
enableHighAccuracy: (options.accuracy ?? 0) > LocationAccuracy.Balanced,
...options,
});
});
},
async removeWatchAsync(watchId): Promise<void> {
navigator.geolocation.clearWatch(watchId);
},
async watchDeviceHeading(headingId): Promise<void> {
console.warn('Location.watchDeviceHeading: is not supported on web');
},
async hasServicesEnabledAsync(): Promise<boolean> {
return 'geolocation' in navigator;
},
async geocodeAsync(): Promise<any[]> {
throw new GeocoderError();
},
async reverseGeocodeAsync(): Promise<any[]> {
throw new GeocoderError();
},
async watchPositionImplAsync(watchId: string, options: LocationOptions): Promise<string> {
return new Promise<string>(resolve => {
// @ts-ignore: the types here need to be fixed
watchId = global.navigator.geolocation.watchPosition(
position => {
lastKnownPosition = geolocationPositionToJSON(position);
LocationEventEmitter.emit('Expo.locationChanged', {
watchId,
location: lastKnownPosition,
});
},
undefined,
// @ts-ignore: the options object needs to be fixed
options
);
resolve(watchId);
});
},
getPermissionsAsync,
async requestPermissionsAsync(): Promise<PermissionResponse> {
return getPermissionsAsync();
},
// no-op
startObserving() {},
stopObserving() {},
};

80
node_modules/expo-location/src/GeolocationPolyfill.ts generated vendored Normal file
View File

@ -0,0 +1,80 @@
import { Platform } from '@unimodules/core';
import ExpoLocation from './ExpoLocation';
import { LocationObject, LocationAccuracy, LocationOptions } from './Location.types';
import { LocationSubscriber } from './LocationSubscribers';
type GeolocationSuccessCallback = (data: LocationObject) => void;
type GeolocationErrorCallback = (error: any) => void;
type GeolocationOptions = {
enableHighAccuracy?: boolean;
};
export function installWebGeolocationPolyfill(): void {
if (Platform.OS !== 'web') {
// Polyfill navigator.geolocation for interop with the core react-native and web API approach to
// geolocation
// @ts-ignore
window.navigator.geolocation = {
getCurrentPosition,
watchPosition,
clearWatch,
// We don't polyfill stopObserving, this is an internal method that probably should not even exist
// in react-native docs
stopObserving: () => {},
};
}
}
function convertGeolocationOptions(options: GeolocationOptions): LocationOptions {
return {
accuracy: options.enableHighAccuracy ? LocationAccuracy.High : LocationAccuracy.Balanced,
};
}
function getCurrentPosition(
success: GeolocationSuccessCallback,
error: GeolocationErrorCallback = () => {},
options: GeolocationOptions = {}
): void {
_getCurrentPositionAsyncWrapper(success, error, options);
}
// This function exists to let us continue to return undefined from getCurrentPosition, while still
// using async/await for the internal implementation of it
async function _getCurrentPositionAsyncWrapper(
success: GeolocationSuccessCallback,
error: GeolocationErrorCallback,
options: GeolocationOptions
): Promise<any> {
try {
await ExpoLocation.requestPermissionsAsync();
const result = await ExpoLocation.getCurrentPositionAsync(convertGeolocationOptions(options));
success(result);
} catch (e) {
error(e);
}
}
// Polyfill: navigator.geolocation.watchPosition
function watchPosition(
success: GeolocationSuccessCallback,
error: GeolocationErrorCallback,
options: GeolocationOptions
) {
const watchId = LocationSubscriber.registerCallback(success);
ExpoLocation.watchPositionImplAsync(watchId, options).catch(err => {
LocationSubscriber.unregisterCallback(watchId);
error({ watchId, message: err.message, code: err.code });
});
return watchId;
}
// Polyfill: navigator.geolocation.clearWatch
function clearWatch(watchId: number) {
LocationSubscriber.unregisterCallback(watchId);
}

275
node_modules/expo-location/src/Location.ts generated vendored Normal file
View File

@ -0,0 +1,275 @@
import { Platform } from '@unimodules/core';
import { PermissionStatus } from 'unimodules-permissions-interface';
import ExpoLocation from './ExpoLocation';
import {
LocationAccuracy,
LocationCallback,
LocationGeocodedAddress,
LocationGeocodedLocation,
LocationHeadingCallback,
LocationHeadingObject,
LocationLastKnownOptions,
LocationObject,
LocationOptions,
LocationPermissionResponse,
LocationProviderStatus,
LocationRegion,
LocationSubscription,
LocationTaskOptions,
LocationActivityType,
LocationGeofencingEventType,
LocationGeofencingRegionState,
LocationGeocodingOptions,
} from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
import {
setGoogleApiKey,
googleGeocodeAsync,
googleReverseGeocodeAsync,
} from './LocationGoogleGeocoding';
import { LocationSubscriber, HeadingSubscriber, _getCurrentWatchId } from './LocationSubscribers';
export async function getProviderStatusAsync(): Promise<LocationProviderStatus> {
return ExpoLocation.getProviderStatusAsync();
}
export async function enableNetworkProviderAsync(): Promise<void> {
// If network provider is disabled (user's location mode is set to "Device only"),
// Android's location provider may not give you any results. Use this method in order to ask the user
// to change the location mode to "High accuracy" which uses Google Play services and enables network provider.
// `getCurrentPositionAsync` and `watchPositionAsync` are doing it automatically anyway.
if (Platform.OS === 'android') {
return ExpoLocation.enableNetworkProviderAsync();
}
}
/**
* Requests for one-time delivery of the user's current location.
* Depending on given `accuracy` option it may take some time to resolve,
* especially when you're inside a building.
*/
export async function getCurrentPositionAsync(
options: LocationOptions = {}
): Promise<LocationObject> {
return ExpoLocation.getCurrentPositionAsync(options);
}
/**
* Gets the last known position of the device or `null` if it's not available
* or doesn't match given requirements such as maximum age or required accuracy.
* It's considered to be faster than `getCurrentPositionAsync` as it doesn't request for the current location.
*/
export async function getLastKnownPositionAsync(
options: LocationLastKnownOptions = {}
): Promise<LocationObject | null> {
return ExpoLocation.getLastKnownPositionAsync(options);
}
/**
* Starts watching for location changes.
* Given callback will be called once the new location is available.
*/
export async function watchPositionAsync(options: LocationOptions, callback: LocationCallback) {
const watchId = LocationSubscriber.registerCallback(callback);
await ExpoLocation.watchPositionImplAsync(watchId, options);
return {
remove() {
LocationSubscriber.unregisterCallback(watchId);
},
};
}
/**
* Resolves to an object with current heading details.
* To simplify, it calls `watchHeadingAsync` and waits for a couple of updates
* and returns the one that is accurate enough.
*/
export async function getHeadingAsync(): Promise<LocationHeadingObject> {
return new Promise<LocationHeadingObject>(async resolve => {
let tries = 0;
const subscription = await watchHeadingAsync(heading => {
if (heading.accuracy > 1 || tries > 5) {
subscription.remove();
resolve(heading);
} else {
tries += 1;
}
});
});
}
/**
* Starts watching for heading changes.
* Given callback will be called once the new heading is available.
*/
export async function watchHeadingAsync(
callback: LocationHeadingCallback
): Promise<LocationSubscription> {
const watchId = HeadingSubscriber.registerCallback(callback);
await ExpoLocation.watchDeviceHeading(watchId);
return {
remove() {
HeadingSubscriber.unregisterCallback(watchId);
},
};
}
/**
* Geocodes given address to an array of latitude-longitude coordinates.
*/
export async function geocodeAsync(
address: string,
options?: LocationGeocodingOptions
): Promise<LocationGeocodedLocation[]> {
if (typeof address !== 'string') {
throw new TypeError(`Address to geocode must be a string. Got ${address} instead.`);
}
if (options?.useGoogleMaps || Platform.OS === 'web') {
return await googleGeocodeAsync(address);
}
return await ExpoLocation.geocodeAsync(address);
}
/**
* The opposite behavior of `geocodeAsync` — translates location coordinates to an array of addresses.
*/
export async function reverseGeocodeAsync(
location: Pick<LocationGeocodedLocation, 'latitude' | 'longitude'>,
options?: LocationGeocodingOptions
): Promise<LocationGeocodedAddress[]> {
if (typeof location.latitude !== 'number' || typeof location.longitude !== 'number') {
throw new TypeError(
'Location to reverse-geocode must be an object with number properties `latitude` and `longitude`.'
);
}
if (options?.useGoogleMaps || Platform.OS === 'web') {
return await googleReverseGeocodeAsync(location);
}
return await ExpoLocation.reverseGeocodeAsync(location);
}
/**
* Gets the current state of location permissions.
*/
export async function getPermissionsAsync(): Promise<LocationPermissionResponse> {
return await ExpoLocation.getPermissionsAsync();
}
/**
* Requests the user to grant location permissions.
*/
export async function requestPermissionsAsync(): Promise<LocationPermissionResponse> {
return await ExpoLocation.requestPermissionsAsync();
}
// --- Location service
/**
* Returns `true` if the device has location services enabled or `false` otherwise.
*/
export async function hasServicesEnabledAsync(): Promise<boolean> {
return await ExpoLocation.hasServicesEnabledAsync();
}
// --- Background location updates
function _validateTaskName(taskName: string) {
if (!taskName || typeof taskName !== 'string') {
throw new Error(`\`taskName\` must be a non-empty string. Got ${taskName} instead.`);
}
}
export async function isBackgroundLocationAvailableAsync(): Promise<boolean> {
const providerStatus = await getProviderStatusAsync();
return providerStatus.backgroundModeEnabled;
}
export async function startLocationUpdatesAsync(
taskName: string,
options: LocationTaskOptions = { accuracy: LocationAccuracy.Balanced }
): Promise<void> {
_validateTaskName(taskName);
await ExpoLocation.startLocationUpdatesAsync(taskName, options);
}
export async function stopLocationUpdatesAsync(taskName: string): Promise<void> {
_validateTaskName(taskName);
await ExpoLocation.stopLocationUpdatesAsync(taskName);
}
export async function hasStartedLocationUpdatesAsync(taskName: string): Promise<boolean> {
_validateTaskName(taskName);
return ExpoLocation.hasStartedLocationUpdatesAsync(taskName);
}
// --- Geofencing
function _validateRegions(regions: LocationRegion[]) {
if (!regions || regions.length === 0) {
throw new Error(
'Regions array cannot be empty. Use `stopGeofencingAsync` if you want to stop geofencing all regions'
);
}
for (const region of regions) {
if (typeof region.latitude !== 'number') {
throw new TypeError(`Region's latitude must be a number. Got '${region.latitude}' instead.`);
}
if (typeof region.longitude !== 'number') {
throw new TypeError(
`Region's longitude must be a number. Got '${region.longitude}' instead.`
);
}
if (typeof region.radius !== 'number') {
throw new TypeError(`Region's radius must be a number. Got '${region.radius}' instead.`);
}
}
}
export async function startGeofencingAsync(
taskName: string,
regions: LocationRegion[] = []
): Promise<void> {
_validateTaskName(taskName);
_validateRegions(regions);
await ExpoLocation.startGeofencingAsync(taskName, { regions });
}
export async function stopGeofencingAsync(taskName: string): Promise<void> {
_validateTaskName(taskName);
await ExpoLocation.stopGeofencingAsync(taskName);
}
export async function hasStartedGeofencingAsync(taskName: string): Promise<boolean> {
_validateTaskName(taskName);
return ExpoLocation.hasStartedGeofencingAsync(taskName);
}
/**
* @deprecated
* Deprecated as of SDK39 in favour of `setGoogleApiKey`.
*/
export function setApiKey(apiKey: string): void {
console.warn("Location's method `setApiKey` is deprecated in favor of `setGoogleApiKey`.");
setGoogleApiKey(apiKey);
}
// For internal purposes
export { LocationEventEmitter as EventEmitter, _getCurrentWatchId };
// Export as namespaced types.
export {
LocationAccuracy as Accuracy,
LocationActivityType as ActivityType,
LocationGeofencingEventType as GeofencingEventType,
LocationGeofencingRegionState as GeofencingRegionState,
PermissionStatus,
setGoogleApiKey,
};
export { installWebGeolocationPolyfill } from './GeolocationPolyfill';
export * from './Location.types';

221
node_modules/expo-location/src/Location.types.ts generated vendored Normal file
View File

@ -0,0 +1,221 @@
import { PermissionResponse as UMPermissionResponse } from 'unimodules-permissions-interface';
/**
* Enum with available location accuracies.
*/
export enum LocationAccuracy {
Lowest = 1,
Low = 2,
Balanced = 3,
High = 4,
Highest = 5,
BestForNavigation = 6,
}
/**
* Enum with available activity types of background location tracking.
*/
export enum LocationActivityType {
Other = 1,
AutomotiveNavigation = 2,
Fitness = 3,
OtherNavigation = 4,
Airborne = 5,
}
/**
* A type of the event that geofencing task can receive.
*/
export enum LocationGeofencingEventType {
Enter = 1,
Exit = 2,
}
/**
* State of the geofencing region that you receive through the geofencing task.
*/
export enum LocationGeofencingRegionState {
Unknown = 0,
Inside = 1,
Outside = 2,
}
/**
* Type representing options argument in `getCurrentPositionAsync`.
*/
export type LocationOptions = {
/**
* Location manager accuracy. Pass one of `LocationAccuracy` enum values.
* For low-accuracies the implementation can avoid geolocation providers
* that consume a significant amount of power (such as GPS).
*/
accuracy?: LocationAccuracy;
/**
* (Android only) Specifies whether to ask the user to turn on improved accuracy location mode
* which uses Wi-Fi, cell networks and GPS sensor. Defaults to `true`.
*/
mayShowUserSettingsDialog?: boolean;
/**
* (Android only) Minimum time to wait between each update in milliseconds.
* Default value may depend on `accuracy` option.
*/
timeInterval?: number;
/**
* Receive updates only when the location has changed by at least this distance in meters.
* Default value may depend on `accuracy` option.
*/
distanceInterval?: number;
};
/**
* Type representing options object that can be passed to `getLastKnownPositionAsync`.
*/
export type LocationLastKnownOptions = {
/**
* Maximum age of the location in miliseconds.
*/
maxAge?: number;
/**
* Maximum radius of horizontal accuracy in meters.
*/
requiredAccuracy?: number;
};
/**
* Type representing background location task options.
*/
export type LocationTaskOptions = LocationOptions & {
showsBackgroundLocationIndicator?: boolean; // iOS only
deferredUpdatesDistance?: number;
deferredUpdatesTimeout?: number;
deferredUpdatesInterval?: number;
// iOS only
activityType?: LocationActivityType;
pausesUpdatesAutomatically?: boolean;
foregroundService?: {
notificationTitle: string;
notificationBody: string;
notificationColor?: string;
};
};
/**
* Type representing geofencing region object.
*/
export type LocationRegion = {
identifier?: string;
latitude: number;
longitude: number;
radius: number;
notifyOnEnter?: boolean;
notifyOnExit?: boolean;
};
/**
* Type representing the location object.
*/
export type LocationObject = {
coords: {
latitude: number;
longitude: number;
altitude: number | null;
accuracy: number | null;
altitudeAccuracy: number | null;
heading: number | null;
speed: number | null;
};
timestamp: number;
};
/**
* Represents `watchPositionAsync` callback.
*/
export type LocationCallback = (location: LocationObject) => any;
/**
* Represents the object containing details about location provider.
*/
export type LocationProviderStatus = {
locationServicesEnabled: boolean;
backgroundModeEnabled: boolean;
gpsAvailable?: boolean;
networkAvailable?: boolean;
passiveAvailable?: boolean;
};
/**
* Type of the object containing heading details and provided by `watchHeadingAsync` callback.
*/
export type LocationHeadingObject = {
trueHeading: number;
magHeading: number;
accuracy: number;
};
/**
* Represents `watchHeadingAsync` callback.
*/
export type LocationHeadingCallback = (location: LocationHeadingObject) => any;
/**
* An object of options for forward and reverse geocoding.
*/
export type LocationGeocodingOptions = {
/**
* Whether to force using Google Maps API instead of the native implementation.
* Used by default only on Web platform. Requires providing an API key by `setGoogleApiKey`.
*/
useGoogleMaps?: boolean;
};
/**
* Type representing a result of `geocodeAsync`.
*/
export type LocationGeocodedLocation = {
latitude: number;
longitude: number;
altitude?: number;
accuracy?: number;
};
/**
* Type representing a result of `reverseGeocodeAsync`.
*/
export type LocationGeocodedAddress = {
city: string | null;
district: string | null;
street: string | null;
region: string | null;
subregion: string | null;
country: string | null;
postalCode: string | null;
name: string | null;
isoCountryCode: string | null;
timezone: string | null;
};
/**
* Represents subscription object returned by methods watching for new locations or headings.
*/
export type LocationSubscription = {
remove: () => void;
};
export type PermissionDetailsLocationIOS = {
scope: 'whenInUse' | 'always' | 'none';
};
export type PermissionDetailsLocationAndroid = {
scope: 'fine' | 'coarse' | 'none';
};
export interface LocationPermissionResponse extends UMPermissionResponse {
ios?: PermissionDetailsLocationIOS;
android?: PermissionDetailsLocationAndroid;
}

View File

@ -0,0 +1,5 @@
import { EventEmitter } from '@unimodules/core';
import ExpoLocation from './ExpoLocation';
export const LocationEventEmitter = new EventEmitter(ExpoLocation);

View File

@ -0,0 +1,3 @@
import { EventEmitter } from '@unimodules/core';
export const LocationEventEmitter = new EventEmitter({} as any);

View File

@ -0,0 +1,161 @@
import { CodedError } from '@unimodules/core';
import { LocationGeocodedAddress, LocationGeocodedLocation } from './Location.types';
const GOOGLE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json';
let googleApiKey;
type GoogleApiGeocodingAddressComponent = {
long_name: string;
short_name: string;
types: string[];
};
type GoogleApiGeocodingResult = {
address_components: GoogleApiGeocodingAddressComponent[];
formatted_address: string;
geometry: {
location: {
lat: number;
lng: number;
};
};
};
type GoogleApiGeocodingResponse = {
results: GoogleApiGeocodingResult[];
status: string;
};
export function setGoogleApiKey(apiKey: string) {
googleApiKey = apiKey;
}
export async function googleGeocodeAsync(address: string): Promise<LocationGeocodedLocation[]> {
assertGoogleApiKey();
const result = await requestGoogleApiAsync({ address });
if (result.status === 'ZERO_RESULTS') {
return [];
}
assertGeocodeResults(result);
return result.results.map(geocodingResultToLocation);
}
export async function googleReverseGeocodeAsync(options: {
latitude: number;
longitude: number;
}): Promise<LocationGeocodedAddress[]> {
assertGoogleApiKey();
const result = await requestGoogleApiAsync({
latlng: `${options.latitude},${options.longitude}`,
});
if (result.status === 'ZERO_RESULTS') {
return [];
}
assertGeocodeResults(result);
return result.results.map(reverseGeocodingResultToAddress);
}
// https://developers.google.com/maps/documentation/geocoding/intro
function assertGeocodeResults(resultObject: any): void {
const { status, error_message } = resultObject;
if (status !== 'ZERO_RESULTS' && status !== 'OK') {
if (error_message) {
throw new CodedError(status, error_message);
} else if (status === 'UNKNOWN_ERROR') {
throw new CodedError(
status,
'the request could not be processed due to a server error. The request may succeed if you try again.'
);
}
throw new CodedError(status, `An error occurred during geocoding.`);
}
}
/**
* Makes sure the Google API key is set.
*/
function assertGoogleApiKey() {
if (!googleApiKey) {
throw new Error(
'Google API key is required to use geocoding. Please set it using `setGoogleApiKey` method.'
);
}
}
/**
* Generic and handy method for sending requests to Google Maps API endpoint.
*/
async function requestGoogleApiAsync(
params: { address: string } | { latlng: string }
): Promise<GoogleApiGeocodingResponse> {
const query = Object.entries(params)
.map(entry => `${entry[0]}=${encodeURI(entry[1])}`)
.join('&');
const result = await fetch(`${GOOGLE_API_URL}?key=${googleApiKey}&${query}`);
return await result.json();
}
/**
* Converts Google's result to the location object.
*/
function geocodingResultToLocation(result: GoogleApiGeocodingResult): LocationGeocodedLocation {
const { location } = result.geometry;
return {
latitude: location.lat,
longitude: location.lng,
};
}
/**
* Converts Google's result to address object.
*/
function reverseGeocodingResultToAddress(
result: GoogleApiGeocodingResult
): LocationGeocodedAddress {
const address: Partial<LocationGeocodedAddress> = {};
for (const { long_name, short_name, types } of result.address_components) {
if (types.includes('locality')) {
address.city = long_name;
continue;
}
if (types.includes('sublocality')) {
address.district = long_name;
continue;
}
if (types.includes('street_address') || types.includes('route')) {
address.street = long_name;
continue;
}
if (types.includes('administrative_area_level_1')) {
address.region = long_name;
continue;
}
if (types.includes('administrative_area_level_2')) {
address.subregion = long_name;
continue;
}
if (types.includes('country')) {
address.country = long_name;
address.isoCountryCode = short_name;
continue;
}
if (types.includes('postal_code')) {
address.postalCode = long_name;
continue;
}
if (types.includes('point_of_interest')) {
address.name = long_name;
continue;
}
}
if (!address.name) {
address.name = result.formatted_address.replace(/,.*$/, '');
}
return address as LocationGeocodedAddress;
}

89
node_modules/expo-location/src/LocationSubscribers.ts generated vendored Normal file
View File

@ -0,0 +1,89 @@
import { Subscription } from '@unimodules/core';
import ExpoLocation from './ExpoLocation';
import { LocationCallback, LocationHeadingCallback } from './Location.types';
import { LocationEventEmitter } from './LocationEventEmitter';
type EventObject = {
watchId: number;
[key: string]: any;
};
let nextWatchId = 0;
class Subscriber<CallbackType extends LocationCallback | LocationHeadingCallback> {
private eventName: string;
private eventDataField: string;
private callbacks: { [id: string]: CallbackType } = {};
private eventSubscription: Subscription | null = null;
constructor(eventName: string, eventDataField: string) {
this.eventName = eventName;
this.eventDataField = eventDataField;
}
maybeInitializeSubscription() {
if (this.eventSubscription) {
return;
}
this.eventSubscription = LocationEventEmitter.addListener(
this.eventName,
(event: EventObject) => this.trigger(event)
);
}
/**
* Registers given callback under new id which is then returned.
*/
registerCallback(callback: CallbackType): number {
this.maybeInitializeSubscription();
const id = ++nextWatchId;
this.callbacks[id] = callback;
return id;
}
/**
* Unregisters a callback with given id and revokes the subscription if possible.
*/
unregisterCallback(id: number): void {
// Do nothing if we have already unregistered the callback.
if (!this.callbacks[id]) {
return;
}
delete this.callbacks[id];
ExpoLocation.removeWatchAsync(id);
if (Object.keys(this.callbacks).length === 0 && this.eventSubscription) {
LocationEventEmitter.removeSubscription(this.eventSubscription);
this.eventSubscription = null;
}
}
trigger(event: EventObject): void {
const watchId = event.watchId;
const callback = this.callbacks[watchId];
if (callback) {
callback(event[this.eventDataField]);
} else {
ExpoLocation.removeWatchAsync(watchId);
}
}
}
export const LocationSubscriber = new Subscriber<LocationCallback>(
'Expo.locationChanged',
'location'
);
export const HeadingSubscriber = new Subscriber<LocationHeadingCallback>(
'Expo.headingChanged',
'heading'
);
/**
* Necessary for some unit tests.
*/
export function _getCurrentWatchId(): number {
return nextWatchId;
}

9
node_modules/expo-location/tsconfig.json generated vendored Normal file
View File

@ -0,0 +1,9 @@
// @generated by expo-module-scripts
{
"extends": "expo-module-scripts/tsconfig.base",
"compilerOptions": {
"outDir": "./build"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}

4
node_modules/expo-location/unimodule.json generated vendored Normal file
View File

@ -0,0 +1,4 @@
{
"name": "expo-location",
"platforms": ["ios", "android"]
}