expo-location 18.0.10-canary-20250331-817737a → 18.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -12
- package/android/build.gradle +11 -7
- package/android/libs/io.nlopez.smartlocation-3.3.3-jetified.aar +0 -0
- package/android/src/main/java/expo/modules/location/LocationHelpers.kt +41 -12
- package/android/src/main/java/expo/modules/location/LocationModule.kt +28 -64
- package/build/LocationEventEmitter.web.d.ts +1 -1
- package/build/LocationEventEmitter.web.d.ts.map +1 -1
- package/expo-module.config.json +16 -3
- package/ios/LocationModule.swift +1 -5
- package/ios/Requesters/EXBackgroundLocationPermissionRequester.m +65 -7
- package/ios/Requesters/EXBaseLocationRequester.h +2 -3
- package/ios/Requesters/EXBaseLocationRequester.m +43 -45
- package/package.json +5 -4
- package/android/proguard-rules.pro +0 -3
- package/android/src/main/java/expo/modules/location/LocationParams.kt +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -8,47 +8,54 @@
|
|
|
8
8
|
|
|
9
9
|
### 🐛 Bug fixes
|
|
10
10
|
|
|
11
|
-
- [iOS] Remove restarting all services when CLLocationManager reports an error ([#35478](https://github.com/expo/expo/pull/35478) by [@chrfalch](https://github.com/chrfalch))
|
|
12
|
-
- [Android] Add missing ProGuard rule to fix task consumer failed ([#34098](https://github.com/expo/expo/pull/34098) by [@cornejobarraza](https://github.com/cornejobarraza))
|
|
13
|
-
- [iOS] `startLocationUpdatesAsync` should not require background permissions ([#33617](https://github.com/expo/expo/pull/33617) by [@andrejpavlovic](https://github.com/andrejpavlovic)
|
|
14
|
-
|
|
15
11
|
### 💡 Others
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
- [Android] Started using expo modules gradle plugin. ([#34176](https://github.com/expo/expo/pull/34176) by [@lukmccall](https://github.com/lukmccall))
|
|
13
|
+
## 18.0.10 — 2025-04-01
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Fixed issue with some permission request flows resolving too soon on iOS. ([#35693](https://github.com/expo/expo/pull/35693) by [@chrfalch](https://github.com/chrfalch))
|
|
18
|
+
- [iOS] Remove restarting all services when CLLocationManager reports an error ([#35478](https://github.com/expo/expo/pull/35478) by [@chrfalch](https://github.com/chrfalch))
|
|
19
|
+
|
|
20
|
+
## 18.0.9 — 2025-03-31
|
|
21
21
|
|
|
22
22
|
_This version does not introduce any user-facing changes._
|
|
23
23
|
|
|
24
|
-
## 18.0.8
|
|
24
|
+
## 18.0.8 — 2025-03-14
|
|
25
25
|
|
|
26
26
|
### 💡 Others
|
|
27
27
|
|
|
28
28
|
- On iOS, added setting the scope value as per our documentation. ([#35452](https://github.com/expo/expo/pull/35452) by [@chrfalch](https://github.com/chrfalch))
|
|
29
29
|
|
|
30
|
+
## 18.0.7 — 2025-02-19
|
|
31
|
+
|
|
32
|
+
- On Android, remove dependency on `smart-location-lib`. ([#33609](https://github.com/expo/expo/pull/33609) by [@alanjhughes](https://github.com/alanjhughes))
|
|
33
|
+
- [Android] Started using expo modules gradle plugin. ([#34176](https://github.com/expo/expo/pull/34176) by [@lukmccall](https://github.com/lukmccall))
|
|
34
|
+
|
|
30
35
|
## 18.0.7 - 2025-02-19
|
|
31
36
|
|
|
37
|
+
- ([iOS][location] Add scope to foreground and background iOS permissions as pr. documentation (#35452))
|
|
38
|
+
|
|
32
39
|
### 🐛 Bug fixes
|
|
33
40
|
|
|
34
41
|
- [iOS] Added guards to avoid task options to crash the app. ([#35477](https://github.com/expo/expo/pull/35477) by [@chrfalch](https://github.com/chrfalch))
|
|
35
42
|
- [iOS] Added error handler to the streaming location/heading methods since these can fail while streaming ([#35004](https://github.com/expo/expo/pull/35004) by [@chrfalch](https://github.com/chrfalch))
|
|
36
43
|
|
|
37
|
-
## 18.0.6
|
|
44
|
+
## 18.0.6 — 2025-02-10
|
|
38
45
|
|
|
39
46
|
### 🐛 Bug fixes
|
|
40
47
|
|
|
41
48
|
- [Android] Use less specific exception in catch block of `resolveUserSettingsForRequest`. ([#34784](https://github.com/expo/expo/pull/34784) by [@alanjhughes](https://github.com/alanjhughes))
|
|
42
49
|
|
|
43
|
-
## 18.0.5
|
|
50
|
+
## 18.0.5 — 2025-01-10
|
|
44
51
|
|
|
45
52
|
_This version does not introduce any user-facing changes._
|
|
46
53
|
|
|
47
|
-
## 18.0.4
|
|
54
|
+
## 18.0.4 — 2024-12-10
|
|
48
55
|
|
|
49
56
|
_This version does not introduce any user-facing changes._
|
|
50
57
|
|
|
51
|
-
## 18.0.3
|
|
58
|
+
## 18.0.3 — 2024-11-29
|
|
52
59
|
|
|
53
60
|
_This version does not introduce any user-facing changes._
|
|
54
61
|
|
package/android/build.gradle
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
id 'com.android.library'
|
|
3
|
-
id 'expo-module-gradle-plugin'
|
|
4
|
-
}
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
5
2
|
|
|
6
3
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '18.0.
|
|
4
|
+
version = '18.0.10'
|
|
5
|
+
|
|
6
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
|
+
apply from: expoModulesCorePlugin
|
|
8
|
+
applyKotlinExpoModulesCorePlugin()
|
|
9
|
+
useCoreDependencies()
|
|
10
|
+
useDefaultAndroidSdkVersions()
|
|
11
|
+
useExpoPublishing()
|
|
8
12
|
|
|
9
13
|
android {
|
|
10
14
|
namespace "expo.modules.location"
|
|
11
15
|
defaultConfig {
|
|
12
16
|
versionCode 29
|
|
13
|
-
versionName "18.0.
|
|
14
|
-
consumerProguardFiles("proguard-rules.pro")
|
|
17
|
+
versionName "18.0.10"
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
dependencies {
|
|
19
22
|
api 'com.google.android.gms:play-services-location:21.0.1'
|
|
23
|
+
implementation project(":${project.name}\$io.nlopez.smartlocation-jetified-aar")
|
|
20
24
|
}
|
|
Binary file
|
|
@@ -16,6 +16,8 @@ import expo.modules.location.records.LocationLastKnownOptions
|
|
|
16
16
|
import expo.modules.location.records.LocationOptions
|
|
17
17
|
import expo.modules.location.records.LocationResponse
|
|
18
18
|
import expo.modules.location.records.PermissionRequestResponse
|
|
19
|
+
import io.nlopez.smartlocation.location.config.LocationAccuracy
|
|
20
|
+
import io.nlopez.smartlocation.location.config.LocationParams
|
|
19
21
|
import kotlin.coroutines.resume
|
|
20
22
|
import kotlin.coroutines.resumeWithException
|
|
21
23
|
import kotlin.coroutines.suspendCoroutine
|
|
@@ -108,16 +110,16 @@ class LocationHelpers {
|
|
|
108
110
|
|
|
109
111
|
private fun mapOptionsToLocationParams(options: LocationOptions): LocationParams {
|
|
110
112
|
val accuracy = options.accuracy
|
|
111
|
-
val
|
|
113
|
+
val locationParamsBuilder = buildLocationParamsForAccuracy(accuracy)
|
|
112
114
|
|
|
113
115
|
options.timeInterval?.let {
|
|
114
|
-
|
|
116
|
+
locationParamsBuilder.setInterval(it)
|
|
115
117
|
}
|
|
116
118
|
options.distanceInterval?.let {
|
|
117
|
-
|
|
119
|
+
locationParamsBuilder.setDistance(it.toFloat())
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
return
|
|
122
|
+
return locationParamsBuilder.build()
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
private fun mapAccuracyToPriority(accuracy: Int): Int {
|
|
@@ -129,15 +131,42 @@ class LocationHelpers {
|
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
private fun buildLocationParamsForAccuracy(accuracy: Int): LocationParams {
|
|
134
|
+
private fun buildLocationParamsForAccuracy(accuracy: Int): LocationParams.Builder {
|
|
133
135
|
return when (accuracy) {
|
|
134
|
-
LocationModule.ACCURACY_LOWEST -> LocationParams(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
LocationModule.
|
|
140
|
-
|
|
136
|
+
LocationModule.ACCURACY_LOWEST -> LocationParams.Builder()
|
|
137
|
+
.setAccuracy(LocationAccuracy.LOWEST)
|
|
138
|
+
.setDistance(3000f)
|
|
139
|
+
.setInterval(10000)
|
|
140
|
+
|
|
141
|
+
LocationModule.ACCURACY_LOW -> LocationParams.Builder()
|
|
142
|
+
.setAccuracy(LocationAccuracy.LOW)
|
|
143
|
+
.setDistance(1000f)
|
|
144
|
+
.setInterval(5000)
|
|
145
|
+
|
|
146
|
+
LocationModule.ACCURACY_BALANCED -> LocationParams.Builder()
|
|
147
|
+
.setAccuracy(LocationAccuracy.MEDIUM)
|
|
148
|
+
.setDistance(100f)
|
|
149
|
+
.setInterval(3000)
|
|
150
|
+
|
|
151
|
+
LocationModule.ACCURACY_HIGH -> LocationParams.Builder()
|
|
152
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
153
|
+
.setDistance(50f)
|
|
154
|
+
.setInterval(2000)
|
|
155
|
+
|
|
156
|
+
LocationModule.ACCURACY_HIGHEST -> LocationParams.Builder()
|
|
157
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
158
|
+
.setDistance(25f)
|
|
159
|
+
.setInterval(1000)
|
|
160
|
+
|
|
161
|
+
LocationModule.ACCURACY_BEST_FOR_NAVIGATION -> LocationParams.Builder()
|
|
162
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
163
|
+
.setDistance(0f)
|
|
164
|
+
.setInterval(500)
|
|
165
|
+
|
|
166
|
+
else -> LocationParams.Builder()
|
|
167
|
+
.setAccuracy(LocationAccuracy.MEDIUM)
|
|
168
|
+
.setDistance(100f)
|
|
169
|
+
.setInterval(3000)
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
172
|
|
|
@@ -4,7 +4,6 @@ import android.Manifest
|
|
|
4
4
|
import android.app.Activity
|
|
5
5
|
import android.content.Context
|
|
6
6
|
import android.content.Intent
|
|
7
|
-
import android.content.pm.PackageManager
|
|
8
7
|
import android.hardware.GeomagneticField
|
|
9
8
|
import android.hardware.Sensor
|
|
10
9
|
import android.hardware.SensorEvent
|
|
@@ -12,14 +11,11 @@ import android.hardware.SensorEventListener
|
|
|
12
11
|
import android.hardware.SensorManager
|
|
13
12
|
import android.location.Geocoder
|
|
14
13
|
import android.location.Location
|
|
15
|
-
import android.location.LocationManager
|
|
16
14
|
import android.os.Build
|
|
17
15
|
import android.os.Bundle
|
|
18
16
|
import android.os.Looper
|
|
19
17
|
import android.util.Log
|
|
20
18
|
import androidx.annotation.ChecksSdkIntAtLeast
|
|
21
|
-
import androidx.core.app.ActivityCompat
|
|
22
|
-
import androidx.core.location.LocationManagerCompat
|
|
23
19
|
import androidx.core.os.bundleOf
|
|
24
20
|
import com.google.android.gms.common.api.ApiException
|
|
25
21
|
import com.google.android.gms.common.api.CommonStatusCodes
|
|
@@ -55,6 +51,9 @@ import expo.modules.location.records.ReverseGeocodeLocation
|
|
|
55
51
|
import expo.modules.location.records.ReverseGeocodeResponse
|
|
56
52
|
import expo.modules.location.taskConsumers.GeofencingTaskConsumer
|
|
57
53
|
import expo.modules.location.taskConsumers.LocationTaskConsumer
|
|
54
|
+
import io.nlopez.smartlocation.SmartLocation
|
|
55
|
+
import io.nlopez.smartlocation.geocoding.utils.LocationAddress
|
|
56
|
+
import io.nlopez.smartlocation.location.config.LocationParams
|
|
58
57
|
import java.util.Locale
|
|
59
58
|
import kotlin.coroutines.resume
|
|
60
59
|
import kotlin.coroutines.resumeWithException
|
|
@@ -158,12 +157,21 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
AsyncFunction<LocationProviderStatus>("getProviderStatusAsync") {
|
|
161
|
-
|
|
160
|
+
val state = SmartLocation.with(mContext).location().state()
|
|
161
|
+
|
|
162
|
+
return@AsyncFunction LocationProviderStatus().apply {
|
|
163
|
+
backgroundModeEnabled = state.locationServicesEnabled()
|
|
164
|
+
gpsAvailable = state.isGpsAvailable
|
|
165
|
+
networkAvailable = state.isNetworkAvailable
|
|
166
|
+
locationServicesEnabled = state.locationServicesEnabled()
|
|
167
|
+
passiveAvailable = state.isPassiveAvailable
|
|
168
|
+
}
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
AsyncFunction("watchDeviceHeading") { watchId: Int ->
|
|
165
172
|
mHeadingId = watchId
|
|
166
|
-
|
|
173
|
+
startHeadingUpdate()
|
|
174
|
+
return@AsyncFunction
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
AsyncFunction("watchPositionImplAsync") { watchId: Int, options: LocationOptions, promise: Promise ->
|
|
@@ -206,6 +214,7 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
206
214
|
} else {
|
|
207
215
|
removeLocationUpdatesForRequest(watchId)
|
|
208
216
|
}
|
|
217
|
+
return@AsyncFunction
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
AsyncFunction("geocodeAsync") Coroutine { address: String ->
|
|
@@ -332,23 +341,6 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
332
341
|
} ?: throw NoPermissionsModuleException()
|
|
333
342
|
}
|
|
334
343
|
|
|
335
|
-
private fun getProviderStatus(): LocationProviderStatus {
|
|
336
|
-
val manager = mContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
337
|
-
|
|
338
|
-
val isGpsAvailable = manager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
|
339
|
-
val isNetworkAvailable = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
|
340
|
-
val isLocationServicesEnabled = LocationManagerCompat.isLocationEnabled(manager)
|
|
341
|
-
val isPassiveAvailable = manager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)
|
|
342
|
-
|
|
343
|
-
return LocationProviderStatus().apply {
|
|
344
|
-
backgroundModeEnabled = isLocationServicesEnabled
|
|
345
|
-
gpsAvailable = isGpsAvailable
|
|
346
|
-
networkAvailable = isNetworkAvailable
|
|
347
|
-
locationServicesEnabled = isLocationServicesEnabled
|
|
348
|
-
passiveAvailable = isPassiveAvailable
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
344
|
private suspend fun requestBackgroundPermissionsAsync(): PermissionRequestResponse {
|
|
353
345
|
if (!isBackgroundPermissionInManifest()) {
|
|
354
346
|
throw NoPermissionInManifestException("ACCESS_BACKGROUND_LOCATION")
|
|
@@ -508,54 +500,27 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
508
500
|
}
|
|
509
501
|
|
|
510
502
|
private fun startHeadingUpdate() {
|
|
511
|
-
val
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
) {
|
|
515
|
-
return
|
|
516
|
-
}
|
|
517
|
-
val lastLocation =
|
|
518
|
-
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
|
519
|
-
?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
|
|
520
|
-
|
|
521
|
-
if (lastLocation != null) {
|
|
503
|
+
val locationControl = SmartLocation.with(mContext).location().oneFix().config(LocationParams.BEST_EFFORT)
|
|
504
|
+
val currLoc = locationControl.lastLocation
|
|
505
|
+
if (currLoc != null) {
|
|
522
506
|
mGeofield = GeomagneticField(
|
|
523
|
-
|
|
524
|
-
lastLocation.longitude.toFloat(),
|
|
525
|
-
lastLocation.altitude.toFloat(),
|
|
507
|
+
currLoc.latitude.toFloat(), currLoc.longitude.toFloat(), currLoc.altitude.toFloat(),
|
|
526
508
|
System.currentTimeMillis()
|
|
527
509
|
)
|
|
528
510
|
} else {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
val locationCallback = object : LocationCallback() {
|
|
536
|
-
override fun onLocationResult(locationResult: LocationResult) {
|
|
537
|
-
locationResult.lastLocation?.let {
|
|
538
|
-
mGeofield = GeomagneticField(
|
|
539
|
-
it.latitude.toFloat(),
|
|
540
|
-
it.longitude.toFloat(),
|
|
541
|
-
it.altitude.toFloat(),
|
|
542
|
-
System.currentTimeMillis()
|
|
543
|
-
)
|
|
544
|
-
}
|
|
545
|
-
}
|
|
511
|
+
locationControl.start { location: Location ->
|
|
512
|
+
mGeofield = GeomagneticField(
|
|
513
|
+
location.latitude.toFloat(), location.longitude.toFloat(), location.altitude.toFloat(),
|
|
514
|
+
System.currentTimeMillis()
|
|
515
|
+
)
|
|
546
516
|
}
|
|
547
|
-
mLocationProvider.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
|
|
548
517
|
}
|
|
549
518
|
mSensorManager.registerListener(
|
|
550
519
|
this,
|
|
551
520
|
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
|
|
552
521
|
SensorManager.SENSOR_DELAY_NORMAL
|
|
553
522
|
)
|
|
554
|
-
mSensorManager.registerListener(
|
|
555
|
-
this,
|
|
556
|
-
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
|
|
557
|
-
SensorManager.SENSOR_DELAY_NORMAL
|
|
558
|
-
)
|
|
523
|
+
mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL)
|
|
559
524
|
}
|
|
560
525
|
|
|
561
526
|
private fun sendUpdate() {
|
|
@@ -633,6 +598,7 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
633
598
|
private fun stopWatching() {
|
|
634
599
|
// if permissions not granted it won't work anyway, but this can be invoked when permission dialog appears
|
|
635
600
|
if (Geocoder.isPresent() && !isMissingForegroundPermissions()) {
|
|
601
|
+
SmartLocation.with(mContext).geocoding().stop()
|
|
636
602
|
mGeocoderPaused = true
|
|
637
603
|
}
|
|
638
604
|
for (requestId in mLocationCallbacks.keys) {
|
|
@@ -699,10 +665,8 @@ class LocationModule : Module(), LifecycleEventListener, SensorEventListener, Ac
|
|
|
699
665
|
locations?.let { location ->
|
|
700
666
|
location.let {
|
|
701
667
|
val results = it.mapNotNull { address ->
|
|
702
|
-
val
|
|
703
|
-
|
|
704
|
-
newLocation.longitude = address.longitude
|
|
705
|
-
GeocodeResponse.from(newLocation)
|
|
668
|
+
val locationAddress = LocationAddress(address)
|
|
669
|
+
GeocodeResponse.from(locationAddress.location)
|
|
706
670
|
}
|
|
707
671
|
continuation.resume(results)
|
|
708
672
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const LocationEventEmitter: import("expo").
|
|
1
|
+
export declare const LocationEventEmitter: import("expo-modules-core/types").EventEmitter<Record<never, never>>;
|
|
2
2
|
//# sourceMappingURL=LocationEventEmitter.web.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocationEventEmitter.web.d.ts","sourceRoot":"","sources":["../src/LocationEventEmitter.web.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"LocationEventEmitter.web.d.ts","sourceRoot":"","sources":["../src/LocationEventEmitter.web.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,sEAAqB,CAAC"}
|
package/expo-module.config.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
"platforms": [
|
|
2
|
+
"platforms": [
|
|
3
|
+
"apple",
|
|
4
|
+
"android"
|
|
5
|
+
],
|
|
3
6
|
"apple": {
|
|
4
|
-
"modules": [
|
|
7
|
+
"modules": [
|
|
8
|
+
"LocationModule"
|
|
9
|
+
]
|
|
5
10
|
},
|
|
6
11
|
"android": {
|
|
7
|
-
"modules": [
|
|
12
|
+
"modules": [
|
|
13
|
+
"expo.modules.location.LocationModule"
|
|
14
|
+
],
|
|
15
|
+
"gradleAarProjects": [
|
|
16
|
+
{
|
|
17
|
+
"name": "io.nlopez.smartlocation-jetified-aar",
|
|
18
|
+
"aarFilePath": "android/libs/io.nlopez.smartlocation-3.3.3-jetified.aar"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
8
21
|
}
|
|
9
22
|
}
|
package/ios/LocationModule.swift
CHANGED
|
@@ -163,13 +163,9 @@ public final class LocationModule: Module {
|
|
|
163
163
|
// Background location
|
|
164
164
|
|
|
165
165
|
AsyncFunction("startLocationUpdatesAsync") { (taskName: String, options: [String: Any]) in
|
|
166
|
-
// There are two ways of starting this service.
|
|
167
|
-
// 1. As a background location service, this requires the background location permission.
|
|
168
|
-
// 2. As a user-initiated foreground service, this does NOT require the background location permission.
|
|
169
|
-
// Unfortunately, we cannot distinguish between those cases.
|
|
170
|
-
// So we only check foreground permission which needs to be granted in both cases.
|
|
171
166
|
try ensureLocationServicesEnabled()
|
|
172
167
|
try ensureForegroundLocationPermissions(appContext)
|
|
168
|
+
try ensureBackgroundLocationPermissions(appContext)
|
|
173
169
|
|
|
174
170
|
guard CLLocationManager.significantLocationChangeMonitoringAvailable() else {
|
|
175
171
|
throw Exceptions.LocationUpdatesUnavailable()
|
|
@@ -10,6 +10,7 @@ static SEL alwaysAuthorizationSelector;
|
|
|
10
10
|
@interface EXBackgroundLocationPermissionRequester ()
|
|
11
11
|
|
|
12
12
|
@property (nonatomic, assign) bool wasAsked;
|
|
13
|
+
@property (nonatomic, assign) bool isWaitingForTimeout;
|
|
13
14
|
|
|
14
15
|
@end
|
|
15
16
|
|
|
@@ -19,6 +20,7 @@ static SEL alwaysAuthorizationSelector;
|
|
|
19
20
|
{
|
|
20
21
|
if (self = [super init]) {
|
|
21
22
|
_wasAsked = false;
|
|
23
|
+
_isWaitingForTimeout = false;
|
|
22
24
|
}
|
|
23
25
|
return self;
|
|
24
26
|
}
|
|
@@ -37,10 +39,28 @@ static SEL alwaysAuthorizationSelector;
|
|
|
37
39
|
{
|
|
38
40
|
if ([EXBaseLocationRequester isConfiguredForAlwaysAuthorization] && [self.locationManager respondsToSelector:alwaysAuthorizationSelector]) {
|
|
39
41
|
_wasAsked = true;
|
|
40
|
-
[
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
CLAuthorizationStatus status = [self.locationManager authorizationStatus];
|
|
43
|
+
|
|
44
|
+
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
|
|
45
|
+
// We already have a foreground permission granted:
|
|
46
|
+
// When asking for background location, we might or might not have asked for foreground permission
|
|
47
|
+
// before we get here. An issue here is if the user has a temporary permission ("Allow once") - which
|
|
48
|
+
// results in the status being "kCLAuthorizationStatusAuthorizedWhenInUse" - without us knowing.
|
|
49
|
+
// We need to handle this special case which is not possible to detect through the API.
|
|
50
|
+
// What we do is that we'll wait 1.5 seconds on an UIApplicationWillResignActiveNotification
|
|
51
|
+
// notification (which will be emitted almost directly if the permission dialog is displayed). If the permission
|
|
52
|
+
// dialog is not displayed we'll timeout and can resolve the waiting promise with an updated denied status.
|
|
53
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
54
|
+
selector:@selector(handleAppBecomingInactive)
|
|
55
|
+
name:UIApplicationWillResignActiveNotification
|
|
56
|
+
object:nil];
|
|
57
|
+
|
|
58
|
+
// Setup timeout - if no permission dialog was displayed we can just stop listening and deny
|
|
59
|
+
// the request
|
|
60
|
+
[self setupAppInactivateTimeout];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Request permissions
|
|
44
64
|
((void (*)(id, SEL))objc_msgSend)(self.locationManager, alwaysAuthorizationSelector);
|
|
45
65
|
} else {
|
|
46
66
|
self.reject(@"ERR_LOCATION_INFO_PLIST", @"One of the `NSLocation*UsageDescription` keys must be present in Info.plist to be able to use geolocation.", nil);
|
|
@@ -50,9 +70,6 @@ static SEL alwaysAuthorizationSelector;
|
|
|
50
70
|
}
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
// If user selects "Keep Only While Using" option, the `locationManagerDidChangeAuthorization` won't be called.
|
|
54
|
-
// So we don't know when we should resolve promise.
|
|
55
|
-
// Hovewer, we can check for `UIApplicationDidBecomeActiveNotification` event which is called when permissions modal disappears.
|
|
56
73
|
- (void)handleAppBecomingActive
|
|
57
74
|
{
|
|
58
75
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
@@ -63,6 +80,47 @@ static SEL alwaysAuthorizationSelector;
|
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
- (void)handleAppBecomingInactive
|
|
84
|
+
{
|
|
85
|
+
// Let's wait until the app becomes inactive - this happens when OS displays the
|
|
86
|
+
// permission dialog - then we can cancel the timeout handler.
|
|
87
|
+
|
|
88
|
+
_isWaitingForTimeout = false;
|
|
89
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
90
|
+
|
|
91
|
+
// When the app is inactive it means that a permission dialog is showing and we should ask to be
|
|
92
|
+
// notified when the dialog is closed:
|
|
93
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
94
|
+
selector:@selector(handleAppBecomingActive)
|
|
95
|
+
name:UIApplicationDidBecomeActiveNotification
|
|
96
|
+
object:nil];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)setupAppInactivateTimeout
|
|
100
|
+
{
|
|
101
|
+
_isWaitingForTimeout = true;
|
|
102
|
+
|
|
103
|
+
// Obtain a reference to the current queue
|
|
104
|
+
dispatch_queue_t currentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
|
105
|
+
|
|
106
|
+
// Calculate the time for the delay
|
|
107
|
+
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC));
|
|
108
|
+
|
|
109
|
+
EX_WEAKIFY(self);
|
|
110
|
+
|
|
111
|
+
// Schedule the block to be executed after the delay
|
|
112
|
+
dispatch_after(delayTime, currentQueue, ^{
|
|
113
|
+
EX_ENSURE_STRONGIFY(self)
|
|
114
|
+
// Check if we are still waiting - ie. we haven't seen a permission dialog
|
|
115
|
+
if (self.isWaitingForTimeout && self.resolve) {
|
|
116
|
+
self.isWaitingForTimeout = false;
|
|
117
|
+
self.resolve([self getPermissions]);
|
|
118
|
+
self.resolve = nil;
|
|
119
|
+
self.reject = nil;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
66
124
|
- (NSDictionary *)parsePermissions:(CLAuthorizationStatus)systemStatus
|
|
67
125
|
{
|
|
68
126
|
EXPermissionStatus status;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
#import <CoreLocation/CLLocationManager.h>
|
|
4
4
|
|
|
5
5
|
#import <ExpoModulesCore/EXPermissionsInterface.h>
|
|
6
|
+
#import <CoreLocation/CLLocationManagerDelegate.h>
|
|
6
7
|
|
|
7
|
-
@interface EXBaseLocationRequester : NSObject<EXPermissionsRequester>
|
|
8
|
+
@interface EXBaseLocationRequester : NSObject<EXPermissionsRequester, CLLocationManagerDelegate>
|
|
8
9
|
|
|
9
10
|
@property (nonatomic, strong) CLLocationManager *locationManager;
|
|
10
11
|
@property (nonatomic, strong) EXPromiseResolveBlock resolve;
|
|
@@ -16,6 +17,4 @@
|
|
|
16
17
|
- (void)requestLocationPermissions;
|
|
17
18
|
- (NSDictionary *)parsePermissions:(CLAuthorizationStatus)systemStatus;
|
|
18
19
|
|
|
19
|
-
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status;
|
|
20
|
-
|
|
21
20
|
@end
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
#import <ExpoModulesCore/EXUtilities.h>
|
|
5
5
|
|
|
6
6
|
#import <objc/message.h>
|
|
7
|
-
#import <CoreLocation/CLLocationManagerDelegate.h>
|
|
8
7
|
|
|
9
8
|
@interface EXBaseLocationRequester () <CLLocationManagerDelegate>
|
|
10
9
|
|
|
11
10
|
@property (nonatomic, assign) bool locationManagerWasCalled;
|
|
12
|
-
|
|
11
|
+
@property (nonatomic, assign) CLAuthorizationStatus beginStatus;
|
|
13
12
|
|
|
14
13
|
@end
|
|
15
14
|
|
|
@@ -73,25 +72,47 @@
|
|
|
73
72
|
// make Expo developers receive this kind of messages nor add our own default usage description,
|
|
74
73
|
// we try to fool the static analyzer and construct the selector in runtime.
|
|
75
74
|
// This way behavior of this requester is governed by provided NSLocationUsageDescriptions.
|
|
75
|
+
|
|
76
|
+
// 2. Location permission request types
|
|
76
77
|
//
|
|
77
|
-
//
|
|
78
|
+
// Foreground
|
|
79
|
+
// - "Allow once"
|
|
80
|
+
// - "Allow while using App"
|
|
81
|
+
// - "Don't allow"
|
|
78
82
|
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
83
|
+
// Background
|
|
84
|
+
// - "Keep only while using"
|
|
85
|
+
// - "Change to always allow"
|
|
86
|
+
//
|
|
87
|
+
// Requesting background permissions directly without first asking for foreground permissions is the
|
|
88
|
+
// same as asking for foreground permissions and then asking for background permissions.
|
|
89
|
+
//
|
|
90
|
+
// "Allow once" is a temporary permission (limited to the current app session). It is not possible to get
|
|
91
|
+
// info from the API about wether or not the current permission is temporary. You cannot request background
|
|
92
|
+
// permissions with a temporary token - a background request will then return denied.
|
|
93
|
+
//
|
|
94
|
+
// Requesting background permissions directly and "Allow while using the App" gives you a provisional
|
|
95
|
+
// background permission that can later be elevated to a full "Always allow" permission.
|
|
96
|
+
// You will be asked at a later point if you want to convert to "Always allow". The system waits until
|
|
97
|
+
// you have started using the newly aquired permission before showing the permission dialog.
|
|
98
|
+
//
|
|
99
|
+
// Test the following scenarios in BareExpo -> APIs -> Location
|
|
100
|
+
// ------------------------------------------------------------
|
|
101
|
+
// (before tests, make sure to clear any location permissions and restart the app)
|
|
102
|
+
//
|
|
103
|
+
// rfp = requestForegroundPermissionsAsync, fp: Actual foreground permission given
|
|
104
|
+
// rbp = requestBackgroundPermissionsAsync, bg: Actual background permission given
|
|
105
|
+
//
|
|
106
|
+
// - rfp -> "Allow once", then rbp -> no dialog = (fp: granted (temporary), bg: denied after 1.5 seconds)
|
|
107
|
+
// - rfp -> "Allow while using App", then rbp -> "Keep only while using" = (fp: granted, bg: denied)
|
|
108
|
+
// - rfp -> "Allow while using App", then rbp -> "Change to always allow" = (fp: granted, bg: granted)
|
|
109
|
+
// - rfp -> "Don't allow", then rbp -> no dialog = (fp: denied, bg: denied)
|
|
110
|
+
// - rbp -> "Allow once", no more dalogs = (fp: granted (temporary), bg: denied)
|
|
111
|
+
// - rbp -> "Allow while using App", no more dialogs = (fp: granted, bg: granted (provisional))
|
|
112
|
+
// - rbp -> "Don't allow" = (fp: denied, bg: denied)
|
|
113
|
+
|
|
114
|
+
// Save start statue and call requestLocationPermissions
|
|
115
|
+
_beginStatus = [self.locationManager authorizationStatus];
|
|
95
116
|
[self requestLocationPermissions];
|
|
96
117
|
}
|
|
97
118
|
}
|
|
@@ -107,38 +128,15 @@
|
|
|
107
128
|
}
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
|
|
111
|
-
{
|
|
112
|
-
// TODO: Permissions.LOCATION issue (search by this phrase)
|
|
113
|
-
// 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
|
|
114
|
-
// it happens if user requests more that one permission at the same time via Permissions.askAsync(...) and LOCATION dialog is not being called first
|
|
115
|
-
// to reproduce this find NCL code testing that
|
|
116
|
-
if (status == kCLAuthorizationStatusNotDetermined || !_locationManagerWasCalled) {
|
|
117
|
-
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
|
|
118
|
-
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
|
|
119
|
-
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
|
|
120
|
-
_locationManagerWasCalled = true;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (_resolve) {
|
|
125
|
-
_resolve([self getPermissions]);
|
|
126
|
-
_resolve = nil;
|
|
127
|
-
_reject = nil;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
131
|
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager
|
|
132
132
|
{
|
|
133
|
-
CLAuthorizationStatus
|
|
134
|
-
if (
|
|
133
|
+
CLAuthorizationStatus nextState = [manager authorizationStatus];
|
|
134
|
+
if (_beginStatus == nextState && !_locationManagerWasCalled) {
|
|
135
135
|
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
|
|
136
136
|
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
|
|
137
137
|
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
|
|
138
138
|
_locationManagerWasCalled = true;
|
|
139
|
-
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
139
|
+
return;
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
if (_resolve) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-location",
|
|
3
|
-
"version": "18.0.10
|
|
3
|
+
"version": "18.0.10",
|
|
4
4
|
"description": "Allows reading geolocation information from the device. Your app can poll for the current location or subscribe to location update events.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
"preset": "expo-module-scripts"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"expo-module-scripts": "4.0.
|
|
42
|
+
"expo-module-scripts": "^4.0.4"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"expo": "
|
|
46
|
-
}
|
|
45
|
+
"expo": "*"
|
|
46
|
+
},
|
|
47
|
+
"gitHead": "b08a0bd52965f85871c12c31da16a45e2cd26c4c"
|
|
47
48
|
}
|