229 lines
9.2 KiB
Swift
229 lines
9.2 KiB
Swift
import Capacitor
|
|
import IONGeolocationLib
|
|
import UIKit
|
|
|
|
import Combine
|
|
|
|
@objc(GeolocationPlugin)
|
|
public class GeolocationPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
public let identifier = "GeolocationPlugin"
|
|
public let jsName = "Geolocation"
|
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
.init(name: "getCurrentPosition", returnType: CAPPluginReturnPromise),
|
|
.init(name: "watchPosition", returnType: CAPPluginReturnCallback),
|
|
.init(name: "clearWatch", returnType: CAPPluginReturnPromise),
|
|
.init(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
.init(name: "requestPermissions", returnType: CAPPluginReturnPromise)
|
|
]
|
|
|
|
private var locationService: (any IONGLOCService)?
|
|
private var cancellables = Set<AnyCancellable>()
|
|
private var locationCancellable: AnyCancellable?
|
|
private var callbackManager: GeolocationCallbackManager?
|
|
private var statusInitialized = false
|
|
private var locationInitialized: Bool = false
|
|
|
|
override public func load() {
|
|
self.locationService = IONGLOCManagerWrapper()
|
|
self.callbackManager = .init(capacitorBridge: bridge)
|
|
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(appDidBecomeActive),
|
|
name: UIApplication.didBecomeActiveNotification,
|
|
object: nil
|
|
)
|
|
}
|
|
|
|
@objc private func appDidBecomeActive() {
|
|
if let watchCallbacksEmpty = callbackManager?.watchCallbacks.isEmpty, !watchCallbacksEmpty {
|
|
print("App became active. Restarting location monitoring for watch callbacks.")
|
|
locationCancellable?.cancel()
|
|
locationCancellable = nil
|
|
locationInitialized = false
|
|
|
|
locationService?.stopMonitoringLocation()
|
|
locationService?.startMonitoringLocation()
|
|
bindLocationPublisher()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
NotificationCenter.default.removeObserver(self)
|
|
}
|
|
|
|
@objc func getCurrentPosition(_ call: CAPPluginCall) {
|
|
shouldSetupBindings()
|
|
let enableHighAccuracy = call.getBool(Constants.Arguments.enableHighAccuracy, false)
|
|
handleLocationRequest(enableHighAccuracy, call: call)
|
|
}
|
|
|
|
@objc func watchPosition(_ call: CAPPluginCall) {
|
|
shouldSetupBindings()
|
|
let enableHighAccuracy = call.getBool(Constants.Arguments.enableHighAccuracy, false)
|
|
let watchUUID = call.callbackId
|
|
handleLocationRequest(enableHighAccuracy, watchUUID: watchUUID, call: call)
|
|
}
|
|
|
|
@objc func clearWatch(_ call: CAPPluginCall) {
|
|
shouldSetupBindings()
|
|
guard let callbackId = call.getString(Constants.Arguments.id) else {
|
|
callbackManager?.sendError(.inputArgumentsIssue(target: .clearWatch))
|
|
return
|
|
}
|
|
callbackManager?.clearWatchCallbackIfExists(callbackId)
|
|
|
|
if (callbackManager?.watchCallbacks.isEmpty) ?? false {
|
|
locationService?.stopMonitoringLocation()
|
|
locationCancellable?.cancel()
|
|
locationCancellable = nil
|
|
locationInitialized = false
|
|
}
|
|
|
|
callbackManager?.sendSuccess(call)
|
|
}
|
|
|
|
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
guard checkIfLocationServicesAreEnabled(call) else { return }
|
|
|
|
let status = switch locationService?.authorisationStatus {
|
|
case .restricted, .denied: Constants.AuthorisationStatus.Status.denied
|
|
case .authorisedAlways, .authorisedWhenInUse: Constants.AuthorisationStatus.Status.granted
|
|
default: Constants.AuthorisationStatus.Status.prompt
|
|
}
|
|
|
|
let callResultData = [
|
|
Constants.AuthorisationStatus.ResultKey.location: status,
|
|
Constants.AuthorisationStatus.ResultKey.coarseLocation: status
|
|
]
|
|
callbackManager?.sendSuccess(call, with: callResultData)
|
|
}
|
|
|
|
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
guard checkIfLocationServicesAreEnabled(call) else { return }
|
|
|
|
if locationService?.authorisationStatus == .notDetermined {
|
|
shouldSetupBindings()
|
|
callbackManager?.addRequestPermissionsCallback(capacitorCall: call)
|
|
} else {
|
|
checkPermissions(call)
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension GeolocationPlugin {
|
|
func shouldSetupBindings() {
|
|
bindAuthorisationStatusPublisher()
|
|
bindLocationPublisher()
|
|
}
|
|
|
|
func bindAuthorisationStatusPublisher() {
|
|
guard !statusInitialized else { return }
|
|
statusInitialized = true
|
|
locationService?.authorisationStatusPublisher
|
|
.sink(receiveValue: { [weak self] status in
|
|
guard let self else { return }
|
|
|
|
switch status {
|
|
case .denied:
|
|
self.onLocationPermissionNotGranted(error: .permissionDenied)
|
|
case .notDetermined:
|
|
self.requestLocationAuthorisation(type: .whenInUse)
|
|
case .restricted:
|
|
self.onLocationPermissionNotGranted(error: .permissionRestricted)
|
|
case .authorisedAlways, .authorisedWhenInUse:
|
|
self.onLocationPermissionGranted()
|
|
@unknown default: break
|
|
}
|
|
})
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
func bindLocationPublisher() {
|
|
guard !locationInitialized else { return }
|
|
locationInitialized = true
|
|
locationCancellable = locationService?.currentLocationPublisher
|
|
.catch { [weak self] error -> AnyPublisher<IONGLOCPositionModel, Never> in
|
|
print("An error was found while retrieving the location: \(error)")
|
|
|
|
if case IONGLOCLocationError.locationUnavailable = error {
|
|
print("Location unavailable (likely due to backgrounding). Keeping watch callbacks alive.")
|
|
self?.callbackManager?.sendError(.positionUnavailable)
|
|
return Empty<IONGLOCPositionModel, Never>()
|
|
.eraseToAnyPublisher()
|
|
} else {
|
|
self?.callbackManager?.sendError(.positionUnavailable)
|
|
return Empty<IONGLOCPositionModel, Never>()
|
|
.eraseToAnyPublisher()
|
|
}
|
|
}
|
|
.sink(receiveValue: { [weak self] position in
|
|
self?.callbackManager?.sendSuccess(with: position)
|
|
})
|
|
}
|
|
|
|
func requestLocationAuthorisation(type requestType: IONGLOCAuthorisationRequestType) {
|
|
DispatchQueue.global(qos: .background).async {
|
|
guard self.checkIfLocationServicesAreEnabled() else { return }
|
|
self.locationService?.requestAuthorisation(withType: requestType)
|
|
}
|
|
}
|
|
|
|
func checkIfLocationServicesAreEnabled(_ call: CAPPluginCall? = nil) -> Bool {
|
|
guard locationService?.areLocationServicesEnabled() == true else {
|
|
call.map { callbackManager?.sendError($0, error: .locationServicesDisabled) }
|
|
?? callbackManager?.sendError(.locationServicesDisabled)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func onLocationPermissionNotGranted(error: GeolocationError) {
|
|
let shouldNotifyRequestPermissionsResult = callbackManager?.requestPermissionsCallbacks.isEmpty == false
|
|
let shouldNotifyPermissionError = callbackManager?.locationCallbacks.isEmpty == false || callbackManager?.watchCallbacks.isEmpty == false
|
|
|
|
if shouldNotifyRequestPermissionsResult {
|
|
self.callbackManager?.sendRequestPermissionsSuccess(Constants.AuthorisationStatus.Status.denied)
|
|
}
|
|
if shouldNotifyPermissionError {
|
|
self.callbackManager?.sendError(error)
|
|
}
|
|
}
|
|
|
|
func onLocationPermissionGranted() {
|
|
let shouldNotifyPermissionGranted = callbackManager?.requestPermissionsCallbacks.isEmpty == false
|
|
// should request location if callbacks below exist and are not empty
|
|
let shouldRequestCurrentPosition = callbackManager?.locationCallbacks.isEmpty == false
|
|
let shouldRequestLocationMonitoring = callbackManager?.watchCallbacks.isEmpty == false
|
|
|
|
if shouldNotifyPermissionGranted {
|
|
callbackManager?.sendRequestPermissionsSuccess(Constants.AuthorisationStatus.Status.granted)
|
|
}
|
|
if shouldRequestCurrentPosition {
|
|
locationService?.requestSingleLocation()
|
|
}
|
|
if shouldRequestLocationMonitoring {
|
|
locationService?.startMonitoringLocation()
|
|
}
|
|
}
|
|
|
|
func handleLocationRequest(_ enableHighAccuracy: Bool, watchUUID: String? = nil, call: CAPPluginCall) {
|
|
bindLocationPublisher()
|
|
let configurationModel = IONGLOCConfigurationModel(enableHighAccuracy: enableHighAccuracy)
|
|
locationService?.updateConfiguration(configurationModel)
|
|
|
|
if let watchUUID {
|
|
callbackManager?.addWatchCallback(watchUUID, capacitorCall: call)
|
|
} else {
|
|
callbackManager?.addLocationCallback(capacitorCall: call)
|
|
}
|
|
|
|
switch locationService?.authorisationStatus {
|
|
case .authorisedAlways, .authorisedWhenInUse: onLocationPermissionGranted()
|
|
case .denied: callbackManager?.sendError(.permissionDenied)
|
|
case .restricted: callbackManager?.sendError(.permissionRestricted)
|
|
default: break
|
|
}
|
|
}
|
|
}
|