foreground-location 0.0.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.
- package/ForegroundLocation.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +931 -0
- package/android/build.gradle +74 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/java/in/xconcepts/foreground/location/APIService.java +449 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocation.java +58 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocationPlugin.java +650 -0
- package/android/src/main/java/in/xconcepts/foreground/location/LocationForegroundService.java +526 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/esm/definitions.d.ts +240 -0
- package/dist/esm/definitions.js +26 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +16 -0
- package/dist/esm/web.js +97 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +138 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +141 -0
- package/dist/plugin.js.map +1 -0
- package/docs/CHANGES-SUMMARY.md +69 -0
- package/docs/setup-and-examples.md +2851 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocation.swift +75 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocationPlugin.swift +125 -0
- package/ios/Tests/ForeGroundLocationPluginTests/ForeGroundLocationPluginTests.swift +36 -0
- package/package.json +82 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreLocation
|
|
3
|
+
|
|
4
|
+
@objc public class ForeGroundLocation: NSObject, CLLocationManagerDelegate {
|
|
5
|
+
private var locationManager: CLLocationManager?
|
|
6
|
+
private var locationCompletion: ((Result<CLLocation, Error>) -> Void)?
|
|
7
|
+
|
|
8
|
+
public func getCurrentLocation(completion: @escaping (Result<CLLocation, Error>) -> Void) {
|
|
9
|
+
locationManager = CLLocationManager()
|
|
10
|
+
locationManager?.delegate = self
|
|
11
|
+
self.locationCompletion = completion
|
|
12
|
+
|
|
13
|
+
guard CLLocationManager.locationServicesEnabled() else {
|
|
14
|
+
completion(.failure(LocationError.locationServicesDisabled))
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let authStatus = CLLocationManager.authorizationStatus()
|
|
19
|
+
guard authStatus == .authorizedWhenInUse || authStatus == .authorizedAlways else {
|
|
20
|
+
completion(.failure(LocationError.permissionDenied))
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
|
|
25
|
+
locationManager?.requestLocation()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// MARK: - CLLocationManagerDelegate
|
|
29
|
+
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
30
|
+
guard let location = locations.first else { return }
|
|
31
|
+
locationCompletion?(.success(location))
|
|
32
|
+
locationCompletion = nil
|
|
33
|
+
|
|
34
|
+
// Clean up
|
|
35
|
+
locationManager?.delegate = nil
|
|
36
|
+
locationManager = nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
40
|
+
locationCompletion?(.failure(error))
|
|
41
|
+
locationCompletion = nil
|
|
42
|
+
|
|
43
|
+
// Clean up
|
|
44
|
+
locationManager?.delegate = nil
|
|
45
|
+
locationManager = nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
|
|
49
|
+
// Handle authorization changes if needed
|
|
50
|
+
switch status {
|
|
51
|
+
case .denied, .restricted:
|
|
52
|
+
locationCompletion?(.failure(LocationError.permissionDenied))
|
|
53
|
+
locationCompletion = nil
|
|
54
|
+
default:
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
enum LocationError: Error {
|
|
61
|
+
case locationServicesDisabled
|
|
62
|
+
case permissionDenied
|
|
63
|
+
case locationUnavailable
|
|
64
|
+
|
|
65
|
+
var localizedDescription: String {
|
|
66
|
+
switch self {
|
|
67
|
+
case .locationServicesDisabled:
|
|
68
|
+
return "Location services are disabled"
|
|
69
|
+
case .permissionDenied:
|
|
70
|
+
return "Location permission denied"
|
|
71
|
+
case .locationUnavailable:
|
|
72
|
+
return "Location unavailable"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import CoreLocation
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Foreground Location Plugin for iOS
|
|
7
|
+
* Limited iOS implementation - Foreground service pattern not available
|
|
8
|
+
*/
|
|
9
|
+
@objc(ForeGroundLocationPlugin)
|
|
10
|
+
public class ForeGroundLocationPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
+
public let identifier = "ForeGroundLocationPlugin"
|
|
12
|
+
public let jsName = "ForeGroundLocation"
|
|
13
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
14
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "startForegroundLocationService", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "stopForegroundLocationService", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "isServiceRunning", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "getCurrentLocation", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "updateLocationSettings", returnType: CAPPluginReturnPromise)
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
private let implementation = ForeGroundLocation()
|
|
24
|
+
|
|
25
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
26
|
+
let locationManager = CLLocationManager()
|
|
27
|
+
let authStatus = locationManager.authorizationStatus
|
|
28
|
+
|
|
29
|
+
let permissionState: String
|
|
30
|
+
switch authStatus {
|
|
31
|
+
case .notDetermined:
|
|
32
|
+
permissionState = "prompt"
|
|
33
|
+
case .denied, .restricted:
|
|
34
|
+
permissionState = "denied"
|
|
35
|
+
case .authorizedWhenInUse, .authorizedAlways:
|
|
36
|
+
permissionState = "granted"
|
|
37
|
+
@unknown default:
|
|
38
|
+
permissionState = "prompt"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
call.resolve([
|
|
42
|
+
"location": permissionState,
|
|
43
|
+
"backgroundLocation": permissionState == "granted" && authStatus == .authorizedAlways ? "granted" : "denied",
|
|
44
|
+
"notifications": "denied"
|
|
45
|
+
])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
49
|
+
let locationManager = CLLocationManager()
|
|
50
|
+
|
|
51
|
+
switch locationManager.authorizationStatus {
|
|
52
|
+
case .notDetermined:
|
|
53
|
+
// For iOS, we can only request when-in-use permission
|
|
54
|
+
// Background location requires additional setup in Info.plist
|
|
55
|
+
locationManager.requestWhenInUseAuthorization()
|
|
56
|
+
|
|
57
|
+
// Since we can't easily wait for the delegate callback in this context,
|
|
58
|
+
// we'll return the current state and recommend checking again
|
|
59
|
+
call.resolve([
|
|
60
|
+
"location": "prompt",
|
|
61
|
+
"backgroundLocation": "denied",
|
|
62
|
+
"notifications": "denied"
|
|
63
|
+
])
|
|
64
|
+
case .denied, .restricted:
|
|
65
|
+
call.resolve([
|
|
66
|
+
"location": "denied",
|
|
67
|
+
"backgroundLocation": "denied",
|
|
68
|
+
"notifications": "denied"
|
|
69
|
+
])
|
|
70
|
+
case .authorizedWhenInUse:
|
|
71
|
+
call.resolve([
|
|
72
|
+
"location": "granted",
|
|
73
|
+
"backgroundLocation": "denied", // Would need always authorization
|
|
74
|
+
"notifications": "denied"
|
|
75
|
+
])
|
|
76
|
+
case .authorizedAlways:
|
|
77
|
+
call.resolve([
|
|
78
|
+
"location": "granted",
|
|
79
|
+
"backgroundLocation": "granted",
|
|
80
|
+
"notifications": "denied"
|
|
81
|
+
])
|
|
82
|
+
@unknown default:
|
|
83
|
+
call.resolve([
|
|
84
|
+
"location": "prompt",
|
|
85
|
+
"backgroundLocation": "denied",
|
|
86
|
+
"notifications": "denied"
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@objc func startForegroundLocationService(_ call: CAPPluginCall) {
|
|
92
|
+
call.unimplemented("Foreground location service pattern not available on iOS. Use iOS background location modes instead.")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc func stopForegroundLocationService(_ call: CAPPluginCall) {
|
|
96
|
+
call.unimplemented("Foreground location service pattern not available on iOS.")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@objc func isServiceRunning(_ call: CAPPluginCall) {
|
|
100
|
+
call.resolve(["isRunning": false])
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objc func getCurrentLocation(_ call: CAPPluginCall) {
|
|
104
|
+
implementation.getCurrentLocation { result in
|
|
105
|
+
switch result {
|
|
106
|
+
case .success(let location):
|
|
107
|
+
call.resolve([
|
|
108
|
+
"latitude": location.coordinate.latitude,
|
|
109
|
+
"longitude": location.coordinate.longitude,
|
|
110
|
+
"accuracy": location.horizontalAccuracy,
|
|
111
|
+
"altitude": location.altitude,
|
|
112
|
+
"bearing": location.course >= 0 ? location.course : nil,
|
|
113
|
+
"speed": location.speed >= 0 ? location.speed : nil,
|
|
114
|
+
"timestamp": ISO8601DateFormatter().string(from: location.timestamp)
|
|
115
|
+
])
|
|
116
|
+
case .failure(let error):
|
|
117
|
+
call.reject("Location error: \(error.localizedDescription)")
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@objc func updateLocationSettings(_ call: CAPPluginCall) {
|
|
123
|
+
call.unimplemented("Location service settings not supported on iOS.")
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ForeGroundLocationPlugin
|
|
3
|
+
|
|
4
|
+
class ForeGroundLocationTests: XCTestCase {
|
|
5
|
+
func testLocationErrorCases() {
|
|
6
|
+
// Test that LocationError enum has proper descriptions
|
|
7
|
+
let locationServicesDisabledError = LocationError.locationServicesDisabled
|
|
8
|
+
XCTAssertEqual(locationServicesDisabledError.localizedDescription, "Location services are disabled")
|
|
9
|
+
|
|
10
|
+
let permissionDeniedError = LocationError.permissionDenied
|
|
11
|
+
XCTAssertEqual(permissionDeniedError.localizedDescription, "Location permission denied")
|
|
12
|
+
|
|
13
|
+
let locationUnavailableError = LocationError.locationUnavailable
|
|
14
|
+
XCTAssertEqual(locationUnavailableError.localizedDescription, "Location unavailable")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func testPluginMethodsRegistration() {
|
|
18
|
+
// Test that plugin has all required methods registered
|
|
19
|
+
let plugin = ForeGroundLocationPlugin()
|
|
20
|
+
let methodNames = plugin.pluginMethods.map { $0.name }
|
|
21
|
+
|
|
22
|
+
let expectedMethods = [
|
|
23
|
+
"checkPermissions",
|
|
24
|
+
"requestPermissions",
|
|
25
|
+
"startForegroundLocationService",
|
|
26
|
+
"stopForegroundLocationService",
|
|
27
|
+
"isServiceRunning",
|
|
28
|
+
"getCurrentLocation",
|
|
29
|
+
"updateLocationSettings"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
for method in expectedMethods {
|
|
33
|
+
XCTAssertTrue(methodNames.contains(method), "Missing method: \(method)")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "foreground-location",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Location Tracking Using Foreground Service and Fused Location API",
|
|
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
|
+
"ForegroundLocation.podspec",
|
|
17
|
+
"docs/"
|
|
18
|
+
],
|
|
19
|
+
"author": "xConcepts",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/xconcepts17/foreground-location.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/xconcepts17/foreground-location/issues"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"capacitor",
|
|
30
|
+
"plugin",
|
|
31
|
+
"native"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
35
|
+
"verify:ios": "xcodebuild -scheme ForegroundLocation -destination generic/platform=iOS",
|
|
36
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
37
|
+
"verify:web": "npm run build",
|
|
38
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
39
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
40
|
+
"eslint": "eslint . --ext ts",
|
|
41
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
42
|
+
"swiftlint": "node-swiftlint",
|
|
43
|
+
"docgen": "docgen --api ForeGroundLocationPlugin --output-readme README.md --output-json dist/docs.json",
|
|
44
|
+
"build": "npm run clean && tsc && rollup -c rollup.config.mjs",
|
|
45
|
+
"build-with-docs": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
46
|
+
"clean": "rimraf ./dist",
|
|
47
|
+
"watch": "tsc --watch",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@capacitor/android": "^7.0.0",
|
|
52
|
+
"@capacitor/core": "^7.0.0",
|
|
53
|
+
"@capacitor/docgen": "^0.3.0",
|
|
54
|
+
"@capacitor/ios": "^7.0.0",
|
|
55
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
56
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
57
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
58
|
+
"eslint": "^8.57.0",
|
|
59
|
+
"prettier": "^3.4.2",
|
|
60
|
+
"prettier-plugin-java": "^2.6.6",
|
|
61
|
+
"rimraf": "^6.0.1",
|
|
62
|
+
"rollup": "^4.30.1",
|
|
63
|
+
"swiftlint": "^2.0.0",
|
|
64
|
+
"typescript": "~4.1.5"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@capacitor/core": ">=7.0.0"
|
|
68
|
+
},
|
|
69
|
+
"prettier": "@ionic/prettier-config",
|
|
70
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
71
|
+
"eslintConfig": {
|
|
72
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
73
|
+
},
|
|
74
|
+
"capacitor": {
|
|
75
|
+
"ios": {
|
|
76
|
+
"src": "ios"
|
|
77
|
+
},
|
|
78
|
+
"android": {
|
|
79
|
+
"src": "android"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|