mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-01-31 00:45:24 +01:00
* chore(ios): consolidate optional argument standard mark all optional iOS arguments as `var <name>: Type?`, following the pattern described in the documentation: https://v2.tauri.app/develop/plugins/develop-mobile/#ios * chore: add missing Info.plist to example
251 lines
7.1 KiB
Swift
251 lines
7.1 KiB
Swift
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import CoreLocation
|
|
import SwiftRs
|
|
import Tauri
|
|
import UIKit
|
|
import WebKit
|
|
|
|
class GetPositionArgs: Decodable {
|
|
var enableHighAccuracy: Bool?
|
|
}
|
|
|
|
class WatchPositionArgs: Decodable {
|
|
let options: GetPositionArgs
|
|
let channel: Channel
|
|
}
|
|
|
|
class ClearWatchArgs: Decodable {
|
|
let channelId: UInt32
|
|
}
|
|
|
|
class GeolocationPlugin: Plugin, CLLocationManagerDelegate {
|
|
private let locationManager = CLLocationManager()
|
|
private var isUpdatingLocation: Bool = false
|
|
private var permissionRequests: [Invoke] = []
|
|
private var positionRequests: [Invoke] = []
|
|
private var watcherChannels: [Channel] = []
|
|
|
|
override init() {
|
|
super.init()
|
|
locationManager.delegate = self
|
|
}
|
|
|
|
//
|
|
// Tauri commands
|
|
//
|
|
|
|
@objc public func getCurrentPosition(_ invoke: Invoke) throws {
|
|
let args = try invoke.parseArgs(GetPositionArgs.self)
|
|
|
|
self.positionRequests.append(invoke)
|
|
|
|
DispatchQueue.main.async {
|
|
if args.enableHighAccuracy == true {
|
|
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
|
} else {
|
|
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
}
|
|
|
|
// TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead.
|
|
if CLLocationManager.authorizationStatus() == .notDetermined {
|
|
self.locationManager.requestWhenInUseAuthorization()
|
|
} else {
|
|
self.locationManager.requestLocation()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc public func watchPosition(_ invoke: Invoke) throws {
|
|
let args = try invoke.parseArgs(WatchPositionArgs.self)
|
|
|
|
self.watcherChannels.append(args.channel)
|
|
|
|
DispatchQueue.main.async {
|
|
if args.options.enableHighAccuracy == true {
|
|
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
|
} else {
|
|
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
}
|
|
|
|
// TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead.
|
|
if CLLocationManager.authorizationStatus() == .notDetermined {
|
|
self.locationManager.requestWhenInUseAuthorization()
|
|
} else {
|
|
self.locationManager.startUpdatingLocation()
|
|
self.isUpdatingLocation = true
|
|
}
|
|
}
|
|
|
|
invoke.resolve()
|
|
}
|
|
|
|
@objc public func clearWatch(_ invoke: Invoke) throws {
|
|
let args = try invoke.parseArgs(ClearWatchArgs.self)
|
|
|
|
self.watcherChannels = self.watcherChannels.filter { $0.id != args.channelId }
|
|
|
|
// TODO: capacitor plugin calls stopUpdating unconditionally
|
|
if self.watcherChannels.isEmpty {
|
|
self.stopUpdating()
|
|
}
|
|
|
|
invoke.resolve()
|
|
}
|
|
|
|
@objc override public func checkPermissions(_ invoke: Invoke) {
|
|
var status: String = ""
|
|
|
|
if CLLocationManager.locationServicesEnabled() {
|
|
// TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead.
|
|
switch CLLocationManager.authorizationStatus() {
|
|
case .notDetermined:
|
|
status = "prompt"
|
|
case .restricted, .denied:
|
|
status = "denied"
|
|
case .authorizedAlways, .authorizedWhenInUse:
|
|
status = "granted"
|
|
@unknown default:
|
|
status = "prompt"
|
|
}
|
|
} else {
|
|
invoke.reject("Location services are not enabled.")
|
|
return
|
|
}
|
|
|
|
let result = ["location": status, "coarseLocation": status]
|
|
|
|
invoke.resolve(result)
|
|
}
|
|
|
|
@objc override public func requestPermissions(_ invoke: Invoke) {
|
|
if CLLocationManager.locationServicesEnabled() {
|
|
// TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead.
|
|
if CLLocationManager.authorizationStatus() == .notDetermined {
|
|
self.permissionRequests.append(invoke)
|
|
|
|
DispatchQueue.main.async {
|
|
self.locationManager.requestWhenInUseAuthorization()
|
|
}
|
|
} else {
|
|
checkPermissions(invoke)
|
|
}
|
|
} else {
|
|
invoke.reject("Location services are not enabled.")
|
|
}
|
|
}
|
|
|
|
//
|
|
// Delegate methods
|
|
//
|
|
|
|
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
Logger.error(error)
|
|
|
|
let requests = self.positionRequests + self.permissionRequests
|
|
self.positionRequests.removeAll()
|
|
self.permissionRequests.removeAll()
|
|
|
|
for request in requests {
|
|
request.reject(error.localizedDescription)
|
|
}
|
|
|
|
for channel in self.watcherChannels {
|
|
do {
|
|
try channel.send(error.localizedDescription)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func locationManager(
|
|
_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]
|
|
) {
|
|
// Respond to all getCurrentPosition() calls.
|
|
for request in self.positionRequests {
|
|
// The capacitor plugin uses locations.first but .last should be the most current one
|
|
// and i don't see a reason to use old locations
|
|
if let location = locations.last {
|
|
let result = convertLocation(location)
|
|
request.resolve(result)
|
|
} else {
|
|
request.reject("Location service returned an empty Location array.")
|
|
}
|
|
}
|
|
|
|
for channel in self.watcherChannels {
|
|
// The capacitor plugin uses locations.first but .last should be the most recent one
|
|
// and i don't see a reason to use old locations
|
|
if let location = locations.last {
|
|
let result = convertLocation(location)
|
|
do {
|
|
try channel.send(result)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
} else {
|
|
do {
|
|
try channel.send("Location service returned an empty Location array.")
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func locationManager(
|
|
_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus
|
|
) {
|
|
let requests = self.permissionRequests
|
|
self.permissionRequests.removeAll()
|
|
|
|
for request in requests {
|
|
checkPermissions(request)
|
|
}
|
|
|
|
if !self.positionRequests.isEmpty {
|
|
self.locationManager.requestLocation()
|
|
}
|
|
|
|
if !self.watcherChannels.isEmpty && !self.isUpdatingLocation {
|
|
self.locationManager.startUpdatingLocation()
|
|
self.isUpdatingLocation = true
|
|
}
|
|
}
|
|
|
|
//
|
|
// Internal/Helper methods
|
|
//
|
|
|
|
// TODO: Why is this pub in capacitor
|
|
private func stopUpdating() {
|
|
self.locationManager.stopUpdatingLocation()
|
|
self.isUpdatingLocation = false
|
|
}
|
|
|
|
private func convertLocation(_ location: CLLocation) -> JsonObject {
|
|
var ret: JsonObject = [:]
|
|
var coords: JsonObject = [:]
|
|
|
|
coords["latitude"] = location.coordinate.latitude
|
|
coords["longitude"] = location.coordinate.longitude
|
|
coords["accuracy"] = location.horizontalAccuracy
|
|
coords["altitude"] = location.altitude
|
|
coords["altitudeAccuracy"] = location.verticalAccuracy
|
|
coords["speed"] = location.speed
|
|
coords["heading"] = location.course
|
|
ret["timestamp"] = Int((location.timestamp.timeIntervalSince1970 * 1000))
|
|
ret["coords"] = coords
|
|
|
|
return ret
|
|
}
|
|
}
|
|
|
|
@_cdecl("init_plugin_geolocation")
|
|
func initPlugin() -> Plugin {
|
|
return GeolocationPlugin()
|
|
}
|