@nitra/geolocation 7.1.1

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.
@@ -0,0 +1,18 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorGeolocation'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '14.0'
15
+ s.dependency 'Capacitor'
16
+ s.dependency 'IONGeolocationLib', spec='~> 1.0'
17
+ s.swift_version = '5.1'
18
+ end
package/Package.swift ADDED
@@ -0,0 +1,34 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "GeolocationCapacitor",
6
+ platforms: [.iOS(.v13)],
7
+ products: [
8
+ .library(
9
+ name: "GeolocationCapacitor",
10
+ targets: ["GeolocationPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main")
14
+ ],
15
+ targets: [
16
+ .binaryTarget(
17
+ name: "IONGeolocationLib",
18
+ url: "https://github.com/ionic-team/ion-ios-geolocation/releases/download/1.0.0/IONGeolocationLib.zip",
19
+ checksum: "b117d3681a947f5d367e79abdb3bfc9abf7ab070ea5279d7da634ddd2d54ffdb" // sha-256
20
+ ),
21
+ .target(
22
+ name: "GeolocationPlugin",
23
+ dependencies: [
24
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
25
+ .product(name: "Cordova", package: "capacitor-swift-pm"),
26
+ "IONGeolocationLib"
27
+ ],
28
+ path: "ios/Sources/GeolocationPlugin"),
29
+ .testTarget(
30
+ name: "GeolocationPluginTests",
31
+ dependencies: ["GeolocationPlugin"],
32
+ path: "ios/Tests/GeolocationTests")
33
+ ]
34
+ )
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # @nitra/geolocation
2
+
3
+ The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @nitra/geolocation
9
+ npx cap sync
10
+ ```
11
+
12
+ ## iOS
13
+
14
+ Apple requires privacy descriptions to be specified in `Info.plist` for location information:
15
+
16
+ - `NSLocationAlwaysAndWhenInUseUsageDescription` (`Privacy - Location Always and When In Use Usage Description`)
17
+ - `NSLocationWhenInUseUsageDescription` (`Privacy - Location When In Use Usage Description`)
18
+
19
+ Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configuration#configuring-infoplist) in the [iOS Guide](https://capacitorjs.com/docs/ios) for more information on setting iOS permissions in Xcode
20
+
21
+ ## Android
22
+
23
+ This plugin requires the following permissions be added to your `AndroidManifest.xml`:
24
+
25
+ ```xml
26
+ <!-- Geolocation Plugin -->
27
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
28
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
29
+ <uses-feature android:name="android.hardware.location.gps" />
30
+ ```
31
+
32
+ The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app _requires_ GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware.
33
+
34
+ Read about [Setting Permissions](https://capacitorjs.com/docs/android/configuration#setting-permissions) in the [Android Guide](https://capacitorjs.com/docs/android) for more information on setting Android permissions.
35
+
36
+
37
+ ## API
38
+
39
+ <docgen-index>
40
+
41
+ * [`getCurrentPosition(...)`](#getcurrentposition)
42
+ * [`watchPosition(...)`](#watchposition)
43
+ * [`clearWatch(...)`](#clearwatch)
44
+ * [`checkPermissions()`](#checkpermissions)
45
+ * [`requestPermissions(...)`](#requestpermissions)
46
+ * [Interfaces](#interfaces)
47
+ * [Type Aliases](#type-aliases)
48
+
49
+ </docgen-index>
50
+
51
+ For list of error codes, see [Errors](#errors)
52
+
53
+ <docgen-api>
54
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
55
+
56
+ ### getCurrentPosition(...)
57
+
58
+ ```typescript
59
+ getCurrentPosition(options?: PositionOptions | undefined) => Promise<Position>
60
+ ```
61
+
62
+ Get the current GPS location of the device
63
+
64
+ | Param | Type |
65
+ | ------------- | ----------------------------------------------------------- |
66
+ | **`options`** | <code><a href="#positionoptions">PositionOptions</a></code> |
67
+
68
+ **Returns:** <code>Promise&lt;<a href="#position">Position</a>&gt;</code>
69
+
70
+ **Since:** 1.0.0
71
+
72
+ --------------------
73
+
74
+
75
+ ### watchPosition(...)
76
+
77
+ ```typescript
78
+ watchPosition(options: PositionOptions, callback: WatchPositionCallback) => Promise<CallbackID>
79
+ ```
80
+
81
+ Set up a watch for location changes. Note that watching for location changes
82
+ can consume a large amount of energy. Be smart about listening only when you need to.
83
+
84
+ | Param | Type |
85
+ | -------------- | ----------------------------------------------------------------------- |
86
+ | **`options`** | <code><a href="#positionoptions">PositionOptions</a></code> |
87
+ | **`callback`** | <code><a href="#watchpositioncallback">WatchPositionCallback</a></code> |
88
+
89
+ **Returns:** <code>Promise&lt;string&gt;</code>
90
+
91
+ **Since:** 1.0.0
92
+
93
+ --------------------
94
+
95
+
96
+ ### clearWatch(...)
97
+
98
+ ```typescript
99
+ clearWatch(options: ClearWatchOptions) => Promise<void>
100
+ ```
101
+
102
+ Clear a given watch
103
+
104
+ | Param | Type |
105
+ | ------------- | --------------------------------------------------------------- |
106
+ | **`options`** | <code><a href="#clearwatchoptions">ClearWatchOptions</a></code> |
107
+
108
+ **Since:** 1.0.0
109
+
110
+ --------------------
111
+
112
+
113
+ ### checkPermissions()
114
+
115
+ ```typescript
116
+ checkPermissions() => Promise<PermissionStatus>
117
+ ```
118
+
119
+ Check location permissions. Will throw if system location services are disabled.
120
+
121
+ **Returns:** <code>Promise&lt;<a href="#permissionstatus">PermissionStatus</a>&gt;</code>
122
+
123
+ **Since:** 1.0.0
124
+
125
+ --------------------
126
+
127
+
128
+ ### requestPermissions(...)
129
+
130
+ ```typescript
131
+ requestPermissions(permissions?: GeolocationPluginPermissions | undefined) => Promise<PermissionStatus>
132
+ ```
133
+
134
+ Request location permissions. Will throw if system location services are disabled.
135
+
136
+ Not available on web.
137
+
138
+ | Param | Type |
139
+ | ----------------- | ------------------------------------------------------------------------------------- |
140
+ | **`permissions`** | <code><a href="#geolocationpluginpermissions">GeolocationPluginPermissions</a></code> |
141
+
142
+ **Returns:** <code>Promise&lt;<a href="#permissionstatus">PermissionStatus</a>&gt;</code>
143
+
144
+ **Since:** 1.0.0
145
+
146
+ --------------------
147
+
148
+
149
+ ### Interfaces
150
+
151
+
152
+ #### Position
153
+
154
+ | Prop | Type | Description | Since |
155
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----- |
156
+ | **`timestamp`** | <code>number</code> | Creation timestamp for coords | 1.0.0 |
157
+ | **`coords`** | <code>{ latitude: number; longitude: number; accuracy: number; altitudeAccuracy: number \| null; altitude: number \| null; speed: number \| null; heading: number \| null; }</code> | The GPS coordinates along with the accuracy of the data | 1.0.0 |
158
+
159
+
160
+ #### PositionOptions
161
+
162
+ | Prop | Type | Description | Default | Since |
163
+ | --------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
164
+ | **`enableHighAccuracy`** | <code>boolean</code> | High accuracy mode (such as GPS, if available) On Android 12+ devices it will be ignored if users didn't grant ACCESS_FINE_LOCATION permissions (can be checked with location alias). | <code>false</code> | 1.0.0 |
165
+ | **`timeout`** | <code>number</code> | The maximum wait time in milliseconds for location updates. In Android, since version 7.1.0 of the plugin, it is also used to determine the interval of location updates for `watchPosition`. | <code>10000</code> | 1.0.0 |
166
+ | **`maximumAge`** | <code>number</code> | The maximum age in milliseconds of a possible cached position that is acceptable to return | <code>0</code> | 1.0.0 |
167
+ | **`minimumUpdateInterval`** | <code>number</code> | The minumum update interval for location updates. If location updates are available faster than this interval then an update will only occur if the minimum update interval has expired since the last location update. This parameter is only available for Android. It has no effect on iOS or Web platforms. | <code>5000</code> | 6.1.0 |
168
+
169
+
170
+ #### ClearWatchOptions
171
+
172
+ | Prop | Type |
173
+ | -------- | ------------------------------------------------- |
174
+ | **`id`** | <code><a href="#callbackid">CallbackID</a></code> |
175
+
176
+
177
+ #### PermissionStatus
178
+
179
+ | Prop | Type | Description | Since |
180
+ | -------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
181
+ | **`location`** | <code><a href="#permissionstate">PermissionState</a></code> | Permission state for location alias. On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions. On iOS and web it requests/checks location permission. | 1.0.0 |
182
+ | **`coarseLocation`** | <code><a href="#permissionstate">PermissionState</a></code> | Permission state for coarseLocation alias. On Android it requests/checks ACCESS_COARSE_LOCATION. On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) or Precise location (ACCESS_FINE_LOCATION), so this alias can be used if the app doesn't need high accuracy. On iOS and web it will have the same value as location alias. | 1.2.0 |
183
+
184
+
185
+ #### GeolocationPluginPermissions
186
+
187
+ | Prop | Type |
188
+ | ----------------- | ---------------------------------------- |
189
+ | **`permissions`** | <code>GeolocationPermissionType[]</code> |
190
+
191
+
192
+ ### Type Aliases
193
+
194
+
195
+ #### WatchPositionCallback
196
+
197
+ <code>(position: <a href="#position">Position</a> | null, err?: any): void</code>
198
+
199
+
200
+ #### CallbackID
201
+
202
+ <code>string</code>
203
+
204
+
205
+ #### PermissionState
206
+
207
+ <code>'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'</code>
208
+
209
+
210
+ #### GeolocationPermissionType
211
+
212
+ <code>'location' | 'coarseLocation'</code>
213
+
214
+ </docgen-api>
215
+
216
+ ### Errors
217
+
218
+ The plugin returns specific errors with specific codes on native Android and iOS. Web does not follow this standard for errors.
219
+
220
+ The following table list all the plugin errors:
221
+
222
+ | Error code | Platform(s) | Message |
223
+ | -------------------- | ------------ | ---------------------------------------- |
224
+ | OS-PLUG-GLOC-0002 | Android, iOS | There was en error trying to obtain the location. |
225
+ | OS-PLUG-GLOC-0003 | Android, iOS | Location permission request was denied. |
226
+ | OS-PLUG-GLOC-0004 | iOS | The 'getCurrentPosition' input parameters aren't valid. |
227
+ | OS-PLUG-GLOC-0005 | iOS | The 'watchPosition' input parameters aren't valid. |
228
+ | OS-PLUG-GLOC-0006 | iOS | The 'clearWatch' input parameters aren't valid. |
229
+ | OS-PLUG-GLOC-0007 | Android, iOS | Location services are not enabled. |
230
+ | OS-PLUG-GLOC-0008 | iOS | Application's use of location services was restricted. |
231
+ | OS-PLUG-GLOC-0009 | Android | Request to enable location was denied. |
232
+ | OS-PLUG-GLOC-0010 | Android | Could not obtain location in time. Try with a higher timeout. |
233
+ | OS-PLUG-GLOC-0011 | Android | Timeout needs to be a positive value. |
234
+ | OS-PLUG-GLOC-0012 | Android | WatchId not found. |
235
+ | OS-PLUG-GLOC-0013 | Android | WatchId needs to be provided. |
236
+ | OS-PLUG-GLOC-0014 | Android | Google Play Services error user resolvable. |
237
+ | OS-PLUG-GLOC-0015 | Android | Google Play Services error. |
238
+ | OS-PLUG-GLOC-0016 | Android | Location settings error. |
@@ -0,0 +1,72 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6
+ playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '21.3.0'
7
+ }
8
+
9
+ buildscript {
10
+ ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '1.9.24'
11
+ repositories {
12
+ google()
13
+ mavenCentral()
14
+ }
15
+ dependencies {
16
+ classpath 'com.android.tools.build:gradle:8.7.1'
17
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18
+ }
19
+ }
20
+
21
+ apply plugin: 'com.android.library'
22
+ apply plugin: 'kotlin-android'
23
+
24
+ android {
25
+ namespace "com.capacitorjs.plugins.geolocation"
26
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
27
+ defaultConfig {
28
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
29
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
30
+ versionCode 1
31
+ versionName "1.0"
32
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
33
+ }
34
+ buildTypes {
35
+ release {
36
+ minifyEnabled false
37
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
38
+ }
39
+ }
40
+ lintOptions {
41
+ abortOnError false
42
+ }
43
+ compileOptions {
44
+ sourceCompatibility JavaVersion.VERSION_21
45
+ targetCompatibility JavaVersion.VERSION_21
46
+ }
47
+ }
48
+
49
+ kotlin {
50
+ jvmToolchain(21)
51
+ }
52
+
53
+ repositories {
54
+ google()
55
+ mavenCentral()
56
+ }
57
+
58
+
59
+ dependencies {
60
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
61
+ implementation("io.ionic.libs:iongeolocation-android:1.0.0")
62
+ implementation project(':capacitor-android')
63
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
64
+
65
+ implementation 'com.google.code.gson:gson:2.10.1'
66
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4")
67
+ implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion"
68
+
69
+ testImplementation "junit:junit:$junitVersion"
70
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
71
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
72
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,71 @@
1
+ package com.capacitorjs.plugins.geolocation
2
+
3
+ /**
4
+ * Object with plugin errors
5
+ */
6
+ object GeolocationErrors {
7
+
8
+ private fun formatErrorCode(number: Int): String {
9
+ return "OS-PLUG-GLOC-" + number.toString().padStart(4, '0')
10
+ }
11
+
12
+ data class ErrorInfo(
13
+ val code: String,
14
+ val message: String
15
+ )
16
+
17
+ val POSITION_UNAVAILABLE = ErrorInfo(
18
+ code = formatErrorCode(2),
19
+ message = "There was en error trying to obtain the location."
20
+ )
21
+
22
+ val LOCATION_PERMISSIONS_DENIED = ErrorInfo(
23
+ code = formatErrorCode(3),
24
+ message = "Location permission request was denied."
25
+ )
26
+
27
+ val LOCATION_DISABLED = ErrorInfo(
28
+ code = formatErrorCode(7),
29
+ message = "Location services are not enabled."
30
+ )
31
+
32
+ val LOCATION_ENABLE_REQUEST_DENIED = ErrorInfo(
33
+ code = formatErrorCode(9),
34
+ message = "Request to enable location was denied."
35
+ )
36
+
37
+ val GET_LOCATION_TIMEOUT = ErrorInfo(
38
+ code = formatErrorCode(10),
39
+ message = "Could not obtain location in time. Try with a higher timeout."
40
+ )
41
+
42
+ val INVALID_TIMEOUT = ErrorInfo(
43
+ code = formatErrorCode(11),
44
+ message = "Timeout needs to be a positive value."
45
+ )
46
+
47
+ val WATCH_ID_NOT_FOUND = ErrorInfo(
48
+ code = formatErrorCode(12),
49
+ message = "WatchId not found."
50
+ )
51
+
52
+ val WATCH_ID_NOT_PROVIDED = ErrorInfo(
53
+ code = formatErrorCode(13),
54
+ message = "WatchId needs to be provided."
55
+ )
56
+
57
+ val GOOGLE_SERVICES_RESOLVABLE = ErrorInfo(
58
+ code = formatErrorCode(14),
59
+ message = "Google Play Services error user resolvable."
60
+ )
61
+
62
+ val GOOGLE_SERVICES_ERROR = ErrorInfo(
63
+ code = formatErrorCode(15),
64
+ message = "Google Play Services error."
65
+ )
66
+
67
+ val LOCATION_SETTINGS_ERROR = ErrorInfo(
68
+ code = formatErrorCode(16),
69
+ message = "Location settings error."
70
+ )
71
+ }
@@ -0,0 +1,338 @@
1
+ package com.capacitorjs.plugins.geolocation
2
+
3
+ import android.Manifest
4
+ import android.os.Build
5
+ import androidx.activity.result.contract.ActivityResultContracts
6
+ import com.getcapacitor.JSObject
7
+ import com.getcapacitor.PermissionState
8
+ import com.getcapacitor.Plugin
9
+ import com.getcapacitor.PluginCall
10
+ import com.getcapacitor.PluginMethod
11
+ import com.getcapacitor.annotation.CapacitorPlugin
12
+ import com.getcapacitor.annotation.Permission
13
+ import com.getcapacitor.annotation.PermissionCallback
14
+ import com.google.android.gms.location.LocationServices
15
+ import io.ionic.libs.iongeolocationlib.controller.IONGLOCController
16
+ import io.ionic.libs.iongeolocationlib.model.IONGLOCException
17
+ import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationOptions
18
+ import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationResult
19
+ import kotlinx.coroutines.CoroutineScope
20
+ import kotlinx.coroutines.Dispatchers
21
+ import kotlinx.coroutines.cancel
22
+ import kotlinx.coroutines.launch
23
+
24
+ @CapacitorPlugin(
25
+ name = "Geolocation",
26
+ permissions = [Permission(
27
+ strings = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION],
28
+ alias = GeolocationPlugin.LOCATION_ALIAS
29
+ ), Permission(
30
+ strings = [Manifest.permission.ACCESS_COARSE_LOCATION],
31
+ alias = GeolocationPlugin.COARSE_LOCATION_ALIAS
32
+ )]
33
+ )
34
+ class GeolocationPlugin : Plugin() {
35
+
36
+ private lateinit var controller: IONGLOCController
37
+ private lateinit var coroutineScope: CoroutineScope
38
+ private val watchingCalls: MutableMap<String, PluginCall> = mutableMapOf()
39
+
40
+ companion object {
41
+ const val LOCATION_ALIAS: String = "location"
42
+ const val COARSE_LOCATION_ALIAS: String = "coarseLocation"
43
+ }
44
+
45
+ override fun load() {
46
+ super.load()
47
+
48
+ coroutineScope = CoroutineScope(Dispatchers.Main)
49
+ val activityLauncher = activity.registerForActivityResult(
50
+ ActivityResultContracts.StartIntentSenderForResult()
51
+ ) { result ->
52
+ CoroutineScope(Dispatchers.Main).launch {
53
+ controller.onResolvableExceptionResult(result.resultCode)
54
+ }
55
+ }
56
+
57
+ this.controller = IONGLOCController(
58
+ LocationServices.getFusedLocationProviderClient(context),
59
+ activityLauncher
60
+ )
61
+
62
+ }
63
+
64
+ override fun handleOnDestroy() {
65
+ super.handleOnDestroy()
66
+ coroutineScope.cancel()
67
+ }
68
+
69
+ @PluginMethod
70
+ override fun checkPermissions(call: PluginCall) {
71
+ checkLocationState(call) { super.checkPermissions(call) }
72
+ }
73
+
74
+ @PluginMethod
75
+ override fun requestPermissions(call: PluginCall) {
76
+ checkLocationState(call) { super.requestPermissions(call) }
77
+ }
78
+
79
+ /**
80
+ * Helper function to determine if location services are enabled or not
81
+ * @param call the PluginCall to use in case we want to send an error
82
+ * @param onLocationEnabled lambda function to use in case location services are enabled
83
+ */
84
+ private fun checkLocationState(call: PluginCall, onLocationEnabled: () -> Unit) {
85
+ if (controller.areLocationServicesEnabled(context)) {
86
+ onLocationEnabled()
87
+ } else {
88
+ call.sendError(GeolocationErrors.LOCATION_DISABLED)
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Checks location permission state, requesting them if necessary.
94
+ * If not, calls getPosition to get the device's position
95
+ * @param call the plugin call
96
+ */
97
+ @PluginMethod
98
+ fun getCurrentPosition(call: PluginCall) {
99
+ handlePermissionRequest(call, "completeCurrentPosition") { getPosition(call) }
100
+ }
101
+
102
+ /**
103
+ * Checks location permission state, requesting them if necessary.
104
+ * If not, calls startWatch to start getting location updates
105
+ * @param call the plugin call
106
+ */
107
+ @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
108
+ fun watchPosition(call: PluginCall) {
109
+ handlePermissionRequest(call, "completeWatchPosition") { startWatch(call) }
110
+ }
111
+
112
+ /**
113
+ * Helper function to determine if a permission is granted or not and request it if necessary
114
+ * @param call the PluginCall to use in case we want to send an error
115
+ * @param callbackName a string identifying the callback to call once the permission prompt is answered
116
+ * @param onPermissionGranted lambda function to use in case the permission is enabled
117
+ */
118
+ private fun handlePermissionRequest(
119
+ call: PluginCall,
120
+ callbackName: String,
121
+ onPermissionGranted: () -> Unit
122
+ ) {
123
+ val alias = getAlias(call)
124
+ if (getPermissionState(alias) != PermissionState.GRANTED) {
125
+ requestPermissionForAlias(alias, call, callbackName)
126
+ } else {
127
+ onPermissionGranted()
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Completes the getCurrentPosition plugin call after a permission request
133
+ * @see .getCurrentPosition
134
+ * @param call the plugin call
135
+ */
136
+ @PermissionCallback
137
+ private fun completeCurrentPosition(call: PluginCall) {
138
+ handlePermissionResult(call) { getPosition(call) }
139
+ }
140
+
141
+ /**
142
+ * Completes the watchPosition plugin call after a permission request
143
+ * @see .startWatch
144
+ * @param call the plugin call
145
+ */
146
+ @PermissionCallback
147
+ private fun completeWatchPosition(call: PluginCall) {
148
+ handlePermissionResult(call) { startWatch(call) }
149
+ }
150
+
151
+ /**
152
+ * Helper function to handle the result of a location permission request
153
+ * @param call the PluginCall to use in case we want to send an error
154
+ * @param onPermissionGranted lambda function to use in case the permission was granted
155
+ */
156
+ private fun handlePermissionResult(call: PluginCall, onPermissionGranted: () -> Unit) {
157
+ if (getPermissionState(COARSE_LOCATION_ALIAS) == PermissionState.GRANTED) {
158
+ onPermissionGranted()
159
+ } else {
160
+ call.sendError(GeolocationErrors.LOCATION_PERMISSIONS_DENIED)
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Clears the watch, removing location updates.
166
+ * @param call the plugin call
167
+ */
168
+ @PluginMethod
169
+ fun clearWatch(call: PluginCall) {
170
+ val id = call.getString("id")
171
+ if (id.isNullOrBlank()) {
172
+ call.sendError(GeolocationErrors.WATCH_ID_NOT_PROVIDED)
173
+ } else {
174
+ watchingCalls.remove(id)?.release(bridge)
175
+ val watchCleared = controller.clearWatch(id)
176
+ if (watchCleared) {
177
+ call.sendSuccess()
178
+ } else {
179
+ call.sendError(GeolocationErrors.WATCH_ID_NOT_FOUND)
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Gets the appropriate permission alias
186
+ * @param call the plugin call
187
+ * @return String with correct alias
188
+ */
189
+ private fun getAlias(call: PluginCall): String {
190
+ var alias = LOCATION_ALIAS
191
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
192
+ val enableHighAccuracy = call.getBoolean("enableHighAccuracy") ?: false
193
+ if (!enableHighAccuracy) {
194
+ alias = COARSE_LOCATION_ALIAS
195
+ }
196
+ }
197
+ return alias
198
+ }
199
+
200
+ /**
201
+ * Gets the current position
202
+ * @param call the plugin call
203
+ */
204
+ private fun getPosition(call: PluginCall) {
205
+ coroutineScope.launch {
206
+ val locationOptions = createOptions(call)
207
+
208
+ // call getCurrentPosition method from controller
209
+ val locationResult = controller.getCurrentPosition(activity, locationOptions)
210
+
211
+ locationResult
212
+ .onSuccess { location ->
213
+ call.sendSuccess(getJSObjectForLocation(location))
214
+ }
215
+ .onFailure { exception ->
216
+ onLocationError(exception, call)
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Starts watching the device's location by requesting location updates
223
+ * @param call the plugin call
224
+ */
225
+ private fun startWatch(call: PluginCall) {
226
+ coroutineScope.launch {
227
+ val watchId = call.callbackId
228
+
229
+ val locationOptions = createOptions(call)
230
+
231
+ // call addWatch method from controller
232
+ controller.addWatch(activity, locationOptions, watchId).collect { result ->
233
+ result.onSuccess { locationList ->
234
+ locationList.forEach { locationResult ->
235
+ call.sendSuccess(
236
+ result = getJSObjectForLocation(locationResult),
237
+ keepCallback = true)
238
+ }
239
+ }
240
+ result.onFailure { exception ->
241
+ onLocationError(exception, call)
242
+ }
243
+ }
244
+ }
245
+ watchingCalls[call.callbackId] = call
246
+ }
247
+
248
+ /**
249
+ * Helper function to convert IONGLOCLocationResult object into the format accepted by the Capacitor bridge
250
+ * @param locationResult IONGLOCLocationResult object with the location to convert
251
+ * @return JSObject with converted JSON object
252
+ */
253
+ private fun getJSObjectForLocation(locationResult: IONGLOCLocationResult): JSObject {
254
+ val coords = JSObject().apply {
255
+ put("latitude", locationResult.latitude)
256
+ put("longitude", locationResult.longitude)
257
+ put("accuracy", locationResult.accuracy)
258
+ put("altitude", locationResult.altitude)
259
+ locationResult.altitudeAccuracy?.let { put("altitudeAccuracy", it) }
260
+ put("speed", locationResult.speed)
261
+ put("heading", locationResult.heading)
262
+ }
263
+ return JSObject().apply {
264
+ put("timestamp", locationResult.timestamp)
265
+ put("coords", coords)
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Helper function to handle error cases
271
+ * @param exception Throwable to handle as an error
272
+ * @param call the plugin call
273
+ */
274
+ private fun onLocationError(exception: Throwable?, call: PluginCall) {
275
+ when (exception) {
276
+ is IONGLOCException.IONGLOCRequestDeniedException -> {
277
+ call.sendError(GeolocationErrors.LOCATION_ENABLE_REQUEST_DENIED)
278
+ }
279
+ is IONGLOCException.IONGLOCSettingsException -> {
280
+ call.sendError(GeolocationErrors.LOCATION_SETTINGS_ERROR)
281
+ }
282
+ is IONGLOCException.IONGLOCInvalidTimeoutException -> {
283
+ call.sendError(GeolocationErrors.INVALID_TIMEOUT)
284
+ }
285
+ is IONGLOCException.IONGLOCGoogleServicesException -> {
286
+ if (exception.resolvable) {
287
+ call.sendError(GeolocationErrors.GOOGLE_SERVICES_RESOLVABLE)
288
+ } else {
289
+ call.sendError(GeolocationErrors.GOOGLE_SERVICES_ERROR)
290
+ }
291
+ }
292
+ is IONGLOCException.IONGLOCLocationRetrievalTimeoutException -> {
293
+ call.sendError(GeolocationErrors.GET_LOCATION_TIMEOUT)
294
+ }
295
+ else -> {
296
+ call.sendError(GeolocationErrors.POSITION_UNAVAILABLE)
297
+ }
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Extension function to return a successful plugin result
303
+ * @param result JSOObject with the JSON content to return
304
+ * @param keepCallback boolean to determine if callback should be kept for future calls or not
305
+ */
306
+ private fun PluginCall.sendSuccess(result: JSObject? = null, keepCallback: Boolean? = false) {
307
+ this.setKeepAlive(keepCallback)
308
+ if (result != null) {
309
+ this.resolve(result)
310
+ } else {
311
+ this.resolve()
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Extension function to return a unsuccessful plugin result
317
+ * @param error error class representing the error to return, containing a code and message
318
+ */
319
+ private fun PluginCall.sendError(error: GeolocationErrors.ErrorInfo) {
320
+ this.reject(error.message, error.code)
321
+ }
322
+
323
+ /**
324
+ * Creates the location options to pass to the native controller
325
+ * @param call the plugin call
326
+ * @return IONGLOCLocationOptions object
327
+ */
328
+ private fun createOptions(call: PluginCall): IONGLOCLocationOptions {
329
+ val timeout = call.getLong("timeout", 10000) ?: 10000
330
+ val maximumAge = call.getLong("maximumAge", 0) ?: 0
331
+ val enableHighAccuracy = call.getBoolean("enableHighAccuracy", false) ?: false
332
+ val minimumUpdateInterval = call.getLong("minimumUpdateInterval", 5000) ?: 5000
333
+
334
+ val locationOptions = IONGLOCLocationOptions(timeout, maximumAge, enableHighAccuracy, minimumUpdateInterval)
335
+
336
+ return locationOptions
337
+ }
338
+ }
File without changes
@@ -0,0 +1,154 @@
1
+ import Capacitor
2
+ import IONGeolocationLib
3
+
4
+ private enum GeolocationCallbackType {
5
+ case requestPermissions
6
+ case location
7
+ case watch
8
+
9
+ var shouldKeepCallback: Bool {
10
+ self == .watch
11
+ }
12
+
13
+ var shouldClearAfterSending: Bool {
14
+ self == .location || self == .requestPermissions
15
+ }
16
+ }
17
+
18
+ private struct GeolocationCallbackGroup {
19
+ let ids: [CAPPluginCall]
20
+ let type: GeolocationCallbackType
21
+ }
22
+
23
+ final class GeolocationCallbackManager {
24
+ private(set) var requestPermissionsCallbacks: [CAPPluginCall]
25
+ private(set) var locationCallbacks: [CAPPluginCall]
26
+ private(set) var watchCallbacks: [String: CAPPluginCall]
27
+ private let capacitorBridge: CAPBridgeProtocol?
28
+
29
+ private var allCallbackGroups: [GeolocationCallbackGroup] {
30
+ [
31
+ .init(ids: requestPermissionsCallbacks, type: .requestPermissions),
32
+ .init(ids: locationCallbacks, type: .location),
33
+ .init(ids: Array(watchCallbacks.values), type: .watch)
34
+ ]
35
+ }
36
+ private var requestPermissionsCallbackGroup: GeolocationCallbackGroup? {
37
+ allCallbackGroups.first { $0.type == .requestPermissions }
38
+ }
39
+
40
+ init(capacitorBridge: CAPBridgeProtocol?) {
41
+ self.capacitorBridge = capacitorBridge
42
+ self.requestPermissionsCallbacks = []
43
+ self.locationCallbacks = []
44
+ self.watchCallbacks = [:]
45
+ }
46
+
47
+ func addRequestPermissionsCallback(capacitorCall call: CAPPluginCall) {
48
+ capacitorBridge?.saveCall(call)
49
+ requestPermissionsCallbacks.append(call)
50
+ }
51
+
52
+ func addLocationCallback(capacitorCall call: CAPPluginCall) {
53
+ capacitorBridge?.saveCall(call)
54
+ locationCallbacks.append(call)
55
+ }
56
+
57
+ func addWatchCallback(_ watchId: String, capacitorCall call: CAPPluginCall) {
58
+ capacitorBridge?.saveCall(call)
59
+ watchCallbacks[watchId] = call
60
+ }
61
+
62
+ func clearRequestPermissionsCallbacks() {
63
+ requestPermissionsCallbacks.forEach {
64
+ capacitorBridge?.releaseCall($0)
65
+ }
66
+ requestPermissionsCallbacks.removeAll()
67
+ }
68
+
69
+ func clearWatchCallbackIfExists(_ watchId: String) {
70
+ if let callbackToRemove = watchCallbacks[watchId] {
71
+ capacitorBridge?.releaseCall(callbackToRemove)
72
+ watchCallbacks.removeValue(forKey: watchId)
73
+ }
74
+ }
75
+
76
+ func clearLocationCallbacks() {
77
+ locationCallbacks.forEach {
78
+ capacitorBridge?.releaseCall($0)
79
+ }
80
+ locationCallbacks.removeAll()
81
+ }
82
+
83
+ func sendSuccess(_ call: CAPPluginCall) {
84
+ call.resolve()
85
+ }
86
+
87
+ func sendSuccess(_ call: CAPPluginCall, with data: PluginCallResultData) {
88
+ call.resolve(data)
89
+ }
90
+
91
+ func sendRequestPermissionsSuccess(_ permissionsResult: String) {
92
+ if let group = requestPermissionsCallbackGroup {
93
+ let data = [
94
+ Constants.AuthorisationStatus.ResultKey.location: permissionsResult,
95
+ Constants.AuthorisationStatus.ResultKey.coarseLocation: permissionsResult
96
+ ]
97
+ send(.success(data), to: group)
98
+ }
99
+ }
100
+
101
+ func sendSuccess(with position: IONGLOCPositionModel) {
102
+ createPluginResult(status: .success(position.toJSObject()))
103
+ }
104
+
105
+ func sendError(_ call: CAPPluginCall, error: GeolocationError) {
106
+ let errorModel = error.toCodeMessagePair()
107
+ call.reject(errorModel.1, errorModel.0)
108
+ }
109
+
110
+ func sendError(_ error: GeolocationError) {
111
+ createPluginResult(status: .error(error.toCodeMessagePair()))
112
+ }
113
+ }
114
+
115
+ private enum CallResultStatus {
116
+ typealias SuccessModel = JSObject
117
+ typealias ErrorModel = (code: String, message: String)
118
+
119
+ case success(_ data: SuccessModel)
120
+ case error(_ codeAndMessage: ErrorModel)
121
+ }
122
+
123
+ private extension GeolocationCallbackManager {
124
+
125
+ func createPluginResult(status: CallResultStatus) {
126
+ allCallbackGroups.forEach {
127
+ send(status, to: $0)
128
+ }
129
+ }
130
+
131
+ func send(_ callResultStatus: CallResultStatus, to group: GeolocationCallbackGroup) {
132
+ group.ids.forEach { call in
133
+ call.keepAlive = group.type.shouldKeepCallback
134
+ switch callResultStatus {
135
+ case .success(let data):
136
+ call.resolve(data)
137
+ case .error(let error):
138
+ call.reject(error.message, error.code)
139
+ }
140
+ }
141
+
142
+ if group.type.shouldClearAfterSending {
143
+ clearCallbacks(for: group.type)
144
+ }
145
+ }
146
+
147
+ func clearCallbacks(for type: GeolocationCallbackType) {
148
+ if case .location = type {
149
+ clearLocationCallbacks()
150
+ } else if case .requestPermissions = type {
151
+ clearRequestPermissionsCallbacks()
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,36 @@
1
+ enum Constants {
2
+ enum Arguments {
3
+ static let enableHighAccuracy = "enableHighAccuracy"
4
+ static let id = "id"
5
+ }
6
+
7
+ enum AuthorisationStatus {
8
+ enum ResultKey {
9
+ static let location = "location"
10
+ static let coarseLocation = "coarseLocation"
11
+ }
12
+
13
+ enum Status {
14
+ static let denied: String = "denied"
15
+ static let granted: String = "granted"
16
+ static let prompt: String = "prompt"
17
+ }
18
+ }
19
+
20
+ enum LocationUsageDescription {
21
+ static let always: String = "NSLocationAlwaysAndWhenInUseUsageDescription"
22
+ static let whenInUse: String = "NSLocationWhenInUseUsageDescription"
23
+ }
24
+
25
+ enum Position {
26
+ static let altitude: String = "altitude"
27
+ static let coords: String = "coords"
28
+ static let heading: String = "heading"
29
+ static let accuracy: String = "accuracy"
30
+ static let latitude: String = "latitude"
31
+ static let longitude: String = "longitude"
32
+ static let speed: String = "speed"
33
+ static let timestamp: String = "timestamp"
34
+ static let altitudeAccuracy: String = "altitudeAccuracy"
35
+ }
36
+ }
@@ -0,0 +1,44 @@
1
+ enum GeolocationMethod: String {
2
+ case getCurrentPosition
3
+ case watchPosition
4
+ case clearWatch
5
+ }
6
+
7
+ enum GeolocationError: Error {
8
+ case locationServicesDisabled
9
+ case permissionDenied
10
+ case permissionRestricted
11
+ case positionUnavailable
12
+ case inputArgumentsIssue(target: GeolocationMethod)
13
+
14
+ func toCodeMessagePair() -> (String, String) {
15
+ ("OS-PLUG-GLOC-\(String(format: "%04d", code))", description)
16
+ }
17
+ }
18
+
19
+ private extension GeolocationError {
20
+ var code: Int {
21
+ switch self {
22
+ case .positionUnavailable: 2
23
+ case .permissionDenied: 3
24
+ case .locationServicesDisabled: 7
25
+ case .permissionRestricted: 8
26
+ case .inputArgumentsIssue(let target):
27
+ switch target {
28
+ case .getCurrentPosition: 4
29
+ case .watchPosition: 5
30
+ case .clearWatch: 6
31
+ }
32
+ }
33
+ }
34
+
35
+ var description: String {
36
+ switch self {
37
+ case .positionUnavailable: "There was en error trying to obtain the location."
38
+ case .permissionDenied: "Location permission request was denied."
39
+ case .locationServicesDisabled: "Location services are not enabled."
40
+ case .permissionRestricted: "Application's use of location services was restricted."
41
+ case .inputArgumentsIssue(let target): "The '\(target.rawValue)' input parameters aren't valid."
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,184 @@
1
+ import Capacitor
2
+ import IONGeolocationLib
3
+
4
+ import Combine
5
+
6
+ @objc(GeolocationPlugin)
7
+ public class GeolocationPlugin: CAPPlugin, CAPBridgedPlugin {
8
+ public let identifier = "GeolocationPlugin"
9
+ public let jsName = "Geolocation"
10
+ public let pluginMethods: [CAPPluginMethod] = [
11
+ .init(name: "getCurrentPosition", returnType: CAPPluginReturnPromise),
12
+ .init(name: "watchPosition", returnType: CAPPluginReturnCallback),
13
+ .init(name: "clearWatch", returnType: CAPPluginReturnPromise),
14
+ .init(name: "checkPermissions", returnType: CAPPluginReturnPromise),
15
+ .init(name: "requestPermissions", returnType: CAPPluginReturnPromise)
16
+ ]
17
+
18
+ private var locationService: (any IONGLOCService)?
19
+ private var cancellables = Set<AnyCancellable>()
20
+ private var callbackManager: GeolocationCallbackManager?
21
+ private var isInitialised: Bool = false
22
+
23
+ override public func load() {
24
+ self.locationService = IONGLOCManagerWrapper()
25
+ self.callbackManager = .init(capacitorBridge: bridge)
26
+ }
27
+
28
+ @objc func getCurrentPosition(_ call: CAPPluginCall) {
29
+ shouldSetupBindings()
30
+ let enableHighAccuracy = call.getBool(Constants.Arguments.enableHighAccuracy, false)
31
+ handleLocationRequest(enableHighAccuracy, call: call)
32
+ }
33
+
34
+ @objc func watchPosition(_ call: CAPPluginCall) {
35
+ shouldSetupBindings()
36
+ let enableHighAccuracy = call.getBool(Constants.Arguments.enableHighAccuracy, false)
37
+ let watchUUID = call.callbackId
38
+ handleLocationRequest(enableHighAccuracy, watchUUID: watchUUID, call: call)
39
+ }
40
+
41
+ @objc func clearWatch(_ call: CAPPluginCall) {
42
+ shouldSetupBindings()
43
+ guard let callbackId = call.getString(Constants.Arguments.id) else {
44
+ callbackManager?.sendError(.inputArgumentsIssue(target: .clearWatch))
45
+ return
46
+ }
47
+ callbackManager?.clearWatchCallbackIfExists(callbackId)
48
+
49
+ if (callbackManager?.watchCallbacks.isEmpty) ?? false {
50
+ locationService?.stopMonitoringLocation()
51
+ }
52
+
53
+ callbackManager?.sendSuccess(call)
54
+ }
55
+
56
+ @objc override public func checkPermissions(_ call: CAPPluginCall) {
57
+ guard checkIfLocationServicesAreEnabled(call) else { return }
58
+
59
+ let status = switch locationService?.authorisationStatus {
60
+ case .restricted, .denied: Constants.AuthorisationStatus.Status.denied
61
+ case .authorisedAlways, .authorisedWhenInUse: Constants.AuthorisationStatus.Status.granted
62
+ default: Constants.AuthorisationStatus.Status.prompt
63
+ }
64
+
65
+ let callResultData = [
66
+ Constants.AuthorisationStatus.ResultKey.location: status,
67
+ Constants.AuthorisationStatus.ResultKey.coarseLocation: status
68
+ ]
69
+ callbackManager?.sendSuccess(call, with: callResultData)
70
+ }
71
+
72
+ @objc override public func requestPermissions(_ call: CAPPluginCall) {
73
+ guard checkIfLocationServicesAreEnabled(call) else { return }
74
+
75
+ if locationService?.authorisationStatus == .notDetermined {
76
+ shouldSetupBindings()
77
+ callbackManager?.addRequestPermissionsCallback(capacitorCall: call)
78
+ } else {
79
+ checkPermissions(call)
80
+ }
81
+ }
82
+ }
83
+
84
+ private extension GeolocationPlugin {
85
+ func shouldSetupBindings() {
86
+ guard !isInitialised else { return }
87
+ isInitialised = true
88
+ setupBindings()
89
+ }
90
+
91
+ func setupBindings() {
92
+ locationService?.authorisationStatusPublisher
93
+ .sink(receiveValue: { [weak self] status in
94
+ guard let self else { return }
95
+
96
+ switch status {
97
+ case .denied:
98
+ self.onLocationPermissionNotGranted(error: .permissionDenied)
99
+ case .notDetermined:
100
+ self.requestLocationAuthorisation(type: .whenInUse)
101
+ case .restricted:
102
+ self.onLocationPermissionNotGranted(error: .permissionRestricted)
103
+ case .authorisedAlways, .authorisedWhenInUse:
104
+ self.onLocationPermissionGranted()
105
+ @unknown default: break
106
+ }
107
+ })
108
+ .store(in: &cancellables)
109
+
110
+ locationService?.currentLocationPublisher
111
+ .sink(receiveCompletion: { [weak self] completion in
112
+ if case .failure(let error) = completion {
113
+ print("An error was found while retrieving the location: \(error)")
114
+ self?.callbackManager?.sendError(.positionUnavailable)
115
+ }
116
+ }, receiveValue: { [weak self] position in
117
+ self?.callbackManager?.sendSuccess(with: position)
118
+ })
119
+ .store(in: &cancellables)
120
+ }
121
+
122
+ func requestLocationAuthorisation(type requestType: IONGLOCAuthorisationRequestType) {
123
+ DispatchQueue.global(qos: .background).async {
124
+ guard self.checkIfLocationServicesAreEnabled() else { return }
125
+ self.locationService?.requestAuthorisation(withType: requestType)
126
+ }
127
+ }
128
+
129
+ func checkIfLocationServicesAreEnabled(_ call: CAPPluginCall? = nil) -> Bool {
130
+ guard locationService?.areLocationServicesEnabled() == true else {
131
+ call.map { callbackManager?.sendError($0, error: .locationServicesDisabled) }
132
+ ?? callbackManager?.sendError(.locationServicesDisabled)
133
+ return false
134
+ }
135
+ return true
136
+ }
137
+
138
+ func onLocationPermissionNotGranted(error: GeolocationError) {
139
+ let shouldNotifyRequestPermissionsResult = callbackManager?.requestPermissionsCallbacks.isEmpty == false
140
+ let shouldNotifyPermissionError = callbackManager?.locationCallbacks.isEmpty == false || callbackManager?.watchCallbacks.isEmpty == false
141
+
142
+ if shouldNotifyRequestPermissionsResult {
143
+ self.callbackManager?.sendRequestPermissionsSuccess(Constants.AuthorisationStatus.Status.denied)
144
+ }
145
+ if shouldNotifyPermissionError {
146
+ self.callbackManager?.sendError(error)
147
+ }
148
+ }
149
+
150
+ func onLocationPermissionGranted() {
151
+ let shouldNotifyPermissionGranted = callbackManager?.requestPermissionsCallbacks.isEmpty == false
152
+ // should request location if callbacks below exist and are not empty
153
+ let shouldRequestCurrentPosition = callbackManager?.locationCallbacks.isEmpty == false
154
+ let shouldRequestLocationMonitoring = callbackManager?.watchCallbacks.isEmpty == false
155
+
156
+ if shouldNotifyPermissionGranted {
157
+ callbackManager?.sendRequestPermissionsSuccess(Constants.AuthorisationStatus.Status.granted)
158
+ }
159
+ if shouldRequestCurrentPosition {
160
+ locationService?.requestSingleLocation()
161
+ }
162
+ if shouldRequestLocationMonitoring {
163
+ locationService?.startMonitoringLocation()
164
+ }
165
+ }
166
+
167
+ func handleLocationRequest(_ enableHighAccuracy: Bool, watchUUID: String? = nil, call: CAPPluginCall) {
168
+ let configurationModel = IONGLOCConfigurationModel(enableHighAccuracy: enableHighAccuracy)
169
+ locationService?.updateConfiguration(configurationModel)
170
+
171
+ if let watchUUID {
172
+ callbackManager?.addWatchCallback(watchUUID, capacitorCall: call)
173
+ } else {
174
+ callbackManager?.addLocationCallback(capacitorCall: call)
175
+ }
176
+
177
+ switch locationService?.authorisationStatus {
178
+ case .authorisedAlways, .authorisedWhenInUse: onLocationPermissionGranted()
179
+ case .denied: callbackManager?.sendError(.permissionDenied)
180
+ case .restricted: callbackManager?.sendError(.permissionRestricted)
181
+ default: break
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,23 @@
1
+ import Capacitor
2
+ import IONGeolocationLib
3
+
4
+ extension IONGLOCPositionModel {
5
+ func toJSObject() -> JSObject {
6
+ [
7
+ Constants.Position.timestamp: timestamp,
8
+ Constants.Position.coords: coordsJSObject
9
+ ]
10
+ }
11
+
12
+ private var coordsJSObject: JSObject {
13
+ [
14
+ Constants.Position.altitude: altitude,
15
+ Constants.Position.heading: course,
16
+ Constants.Position.accuracy: horizontalAccuracy,
17
+ Constants.Position.latitude: latitude,
18
+ Constants.Position.longitude: longitude,
19
+ Constants.Position.speed: speed,
20
+ Constants.Position.altitudeAccuracy: verticalAccuracy
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import GeolocationCapacitorPlugin
3
+
4
+ class GeolocationPluginCapacitorTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = GeolocationPluginCapacitor()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@nitra/geolocation",
3
+ "version": "7.1.1",
4
+ "description": "The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available.",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapacitorGeolocation.podspec"
17
+ ],
18
+ "author": "Outsystems",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/ionic-team/capacitor-geolocation.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/ionic-team/capacitor-geolocation/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme GeolocationCapacitor -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api GeolocationPlugin --output-readme README.md --output-json dist/docs.json && cp README.md ../../README.md",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "dependencies": {
49
+ "@capacitor/synapse": "^1.0.1"
50
+ },
51
+ "devDependencies": {
52
+ "@capacitor/android": "next",
53
+ "@capacitor/core": "next",
54
+ "@capacitor/docgen": "^0.2.2",
55
+ "@capacitor/ios": "next",
56
+ "@ionic/eslint-config": "^0.4.0",
57
+ "@ionic/prettier-config": "^4.0.0",
58
+ "@ionic/swiftlint-config": "^2.0.0",
59
+ "@semantic-release/changelog": "^6.0.3",
60
+ "@semantic-release/git": "^10.0.1",
61
+ "@semantic-release/github": "^11.0.1",
62
+ "@semantic-release/npm": "^12.0.1",
63
+ "@types/node": "^20.14.8",
64
+ "eslint": "^8.57.0",
65
+ "prettier": "^3.3.3",
66
+ "prettier-plugin-java": "^2.6.4",
67
+ "rimraf": "^6.0.1",
68
+ "rollup": "^2.78.1",
69
+ "semantic-release": "^24.0.0",
70
+ "swiftlint": "^2.0.0",
71
+ "typescript": "~5.4.5"
72
+ },
73
+ "peerDependencies": {
74
+ "@capacitor/core": ">=7.0.0"
75
+ },
76
+ "prettier": "@ionic/prettier-config",
77
+ "swiftlint": "@ionic/swiftlint-config",
78
+ "eslintConfig": {
79
+ "extends": "@ionic/eslint-config/recommended"
80
+ },
81
+ "capacitor": {
82
+ "ios": {
83
+ "src": "ios"
84
+ },
85
+ "android": {
86
+ "src": "android"
87
+ }
88
+ }
89
+ }