mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-05-29 13:31:27 +02:00
refactor(geolocation): simplify API, defer permission checks (#1773)
This commit is contained in:
committed by
GitHub
parent
fd75401aee
commit
60765694f5
@@ -87,16 +87,31 @@ fn main() {
|
||||
Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
|
||||
|
||||
```javascript
|
||||
import { getCurrentPosition, watchPosition } from '@tauri-apps/plugin-log'
|
||||
import {
|
||||
checkPermissions,
|
||||
requestPermissions,
|
||||
getCurrentPosition,
|
||||
watchPosition
|
||||
} from '@tauri-apps/plugin-log'
|
||||
|
||||
const pos = await getCurrentPosition()
|
||||
let permissions = await checkPermissions()
|
||||
if (
|
||||
permissions.location === 'prompt' ||
|
||||
permissions.location === 'prompt-with-rationale'
|
||||
) {
|
||||
permissions = await requestPermissions(['location'])
|
||||
}
|
||||
|
||||
await watchPosition(
|
||||
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 },
|
||||
(pos) => {
|
||||
console.log(pos)
|
||||
}
|
||||
)
|
||||
if (permissions.location === 'granted') {
|
||||
const pos = await getCurrentPosition()
|
||||
|
||||
await watchPosition(
|
||||
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 },
|
||||
(pos) => {
|
||||
console.log(pos)
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -60,18 +60,8 @@ private const val ALIAS_COARSE_LOCATION: String = "coarseLocation"
|
||||
]
|
||||
)
|
||||
class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
private lateinit var implementation: Geolocation// = Geolocation(activity.applicationContext)
|
||||
private var watchers = hashMapOf<Long, Invoke>()
|
||||
|
||||
// If multiple permissions get requested in quick succession not all callbacks will be fired,
|
||||
// So we'll store all requests ourselves instead of using the callback argument.
|
||||
private var positionRequests = mutableListOf<Invoke>()
|
||||
private var watchRequests = mutableListOf<Invoke>()
|
||||
// If getCurrentPosition or watchPosition are called before a prior call is done requesting permission,
|
||||
// the callback will be called with denied for the prior call(s) so we keep track of them to make sure
|
||||
// to only run the logic on the last request.
|
||||
// TODO: Find a better solution after switching to explicit requestPermissions call - likely needs changes in Tauri
|
||||
private var ongoingPermissionRequests = 0;
|
||||
private lateinit var implementation: Geolocation
|
||||
private var watchers = hashMapOf<Long, Pair<Invoke, WatchArgs>>()
|
||||
|
||||
override fun load(webView: WebView) {
|
||||
super.load(webView)
|
||||
@@ -86,8 +76,9 @@ class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
for (watcher in watchers.values) {
|
||||
startWatch(watcher)
|
||||
// resume watchers
|
||||
for ((watcher, args) in watchers.values) {
|
||||
startWatch(watcher, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,83 +103,39 @@ class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
@Command
|
||||
fun getCurrentPosition(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(PositionOptions::class.java)
|
||||
val alias = getAlias(args.enableHighAccuracy)
|
||||
|
||||
if (getPermissionState(alias) != PermissionState.GRANTED) {
|
||||
Logger.error("NOT GRANTED");
|
||||
this.positionRequests.add(invoke)
|
||||
this.ongoingPermissionRequests += 1
|
||||
requestPermissionForAlias(alias, invoke, "positionPermissionCallback")
|
||||
val location = implementation.getLastLocation(args.maximumAge)
|
||||
if (location != null) {
|
||||
invoke.resolve(convertLocation(location))
|
||||
} else {
|
||||
Logger.error("GRANTED");
|
||||
getPosition(invoke, args)
|
||||
implementation.sendLocation(args.enableHighAccuracy,
|
||||
{ loc -> invoke.resolve(convertLocation(loc)) },
|
||||
{ error -> invoke.reject(error) })
|
||||
}
|
||||
}
|
||||
|
||||
@PermissionCallback
|
||||
private fun positionPermissionCallback(invoke: Invoke) {
|
||||
Logger.error("positionPermissionCallback - ongoingRequests: " + this.ongoingPermissionRequests.toString())
|
||||
|
||||
this.ongoingPermissionRequests -= 1
|
||||
|
||||
if (this.ongoingPermissionRequests > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val pRequests = this.positionRequests.toTypedArray()
|
||||
val wRequests = this.watchRequests.toTypedArray()
|
||||
this.positionRequests.clear()
|
||||
this.watchRequests.clear()
|
||||
|
||||
// TODO: capacitor only checks for coarse here
|
||||
val permissionGranted = getPermissionState(ALIAS_COARSE_LOCATION) == PermissionState.GRANTED;
|
||||
Logger.error("positionPermissionCallback - permissionGranted: $permissionGranted");
|
||||
|
||||
for (inv in pRequests) {
|
||||
if (permissionGranted) {
|
||||
val args = inv.parseArgs(PositionOptions::class.java)
|
||||
|
||||
implementation.sendLocation(args.enableHighAccuracy,
|
||||
{ location -> inv.resolve(convertLocation(location)) },
|
||||
{ error -> inv.reject(error) })
|
||||
} else {
|
||||
inv.reject("Location permission was denied.")
|
||||
}
|
||||
}
|
||||
|
||||
for (inv in wRequests) {
|
||||
if (permissionGranted) {
|
||||
startWatch(invoke)
|
||||
} else {
|
||||
inv.reject("Location permission was denied.")
|
||||
}
|
||||
}
|
||||
val permissionsResultJSON = JSObject()
|
||||
permissionsResultJSON.put("location", getPermissionState(ALIAS_LOCATION))
|
||||
permissionsResultJSON.put("coarseLocation", getPermissionState(ALIAS_COARSE_LOCATION))
|
||||
invoke.resolve(permissionsResultJSON)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun watchPosition(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(WatchArgs::class.java)
|
||||
val alias = getAlias(args.options.enableHighAccuracy)
|
||||
|
||||
if (getPermissionState(alias) != PermissionState.GRANTED) {
|
||||
this.watchRequests.add(invoke)
|
||||
this.ongoingPermissionRequests += 1
|
||||
requestPermissionForAlias(alias, invoke, "positionPermissionCallback")
|
||||
} else {
|
||||
startWatch(invoke)
|
||||
}
|
||||
startWatch(invoke, args)
|
||||
}
|
||||
|
||||
private fun startWatch(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(WatchArgs::class.java)
|
||||
|
||||
private fun startWatch(invoke: Invoke, args: WatchArgs) {
|
||||
implementation.requestLocationUpdates(
|
||||
args.options.enableHighAccuracy,
|
||||
args.options.timeout,
|
||||
{ location -> args.channel.send(convertLocation(location)) },
|
||||
{ error -> args.channel.sendObject(error) })
|
||||
|
||||
watchers[args.channel.id] = invoke
|
||||
watchers[args.channel.id] = Pair(invoke, args)
|
||||
}
|
||||
|
||||
@Command
|
||||
@@ -204,19 +151,6 @@ class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
private fun getPosition(invoke: Invoke, options: PositionOptions) {
|
||||
val location = implementation.getLastLocation(options.maximumAge)
|
||||
if (location != null) {
|
||||
Logger.error("getPosition location non-null")
|
||||
invoke.resolve(convertLocation(location))
|
||||
} else {
|
||||
Logger.error("getPosition location null")
|
||||
implementation.sendLocation(options.enableHighAccuracy,
|
||||
{ loc -> invoke.resolve(convertLocation(loc)) },
|
||||
{ error -> invoke.reject(error) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertLocation(location: Location): JSObject {
|
||||
val ret = JSObject()
|
||||
val coords = JSObject()
|
||||
@@ -235,14 +169,4 @@ class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun getAlias(enableHighAccuracy: Boolean): String {
|
||||
var alias = ALIAS_LOCATION;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (!enableHighAccuracy) {
|
||||
alias = ALIAS_COARSE_LOCATION;
|
||||
}
|
||||
}
|
||||
return alias
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function r(t,r,e,s){if("a"===e&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof r?t!==r||!s:!r.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?s:"a"===e?s.call(t):s?s.value:r.get(t)}function e(t,r,e,s,o){if("function"==typeof r?t!==r||!o:!r.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return r.set(t,e),e}var s,o,i,a;"function"==typeof SuppressedError&&SuppressedError;class n{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),o.set(this,0),i.set(this,{}),this.id=function(t,r=!1){return window.__TAURI_INTERNALS__.transformCallback(t,r)}((({message:t,id:a})=>{if(a===r(this,o,"f")){e(this,o,a+1),r(this,s,"f").call(this,t);const n=Object.keys(r(this,i,"f"));if(n.length>0){let t=a+1;for(const e of n.sort()){if(parseInt(e)!==t)break;{const o=r(this,i,"f")[e];delete r(this,i,"f")[e],r(this,s,"f").call(this,o),t+=1}}e(this,o,t)}}else r(this,i,"f")[a.toString()]=t}))}set onmessage(t){e(this,s,t)}get onmessage(){return r(this,s,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function c(t,r={},e){return window.__TAURI_INTERNALS__.invoke(t,r,e)}s=new WeakMap,o=new WeakMap,i=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));const u={async getCurrentPosition(t){try{return{status:"ok",data:await c("plugin:geolocation|get_current_position",{options:t})}}catch(t){if(t instanceof Error)throw t;return{status:"error",error:t}}},async watchPosition(t,r){try{return{status:"ok",data:await c("plugin:geolocation|watch_position",{options:t,channel:r})}}catch(t){if(t instanceof Error)throw t;return{status:"error",error:t}}},async clearWatch(t){try{return{status:"ok",data:await c("plugin:geolocation|clear_watch",{channelId:t})}}catch(t){if(t instanceof Error)throw t;return{status:"error",error:t}}},async checkPermissions(){try{return{status:"ok",data:await c("plugin:geolocation|check_permissions")}}catch(t){if(t instanceof Error)throw t;return{status:"error",error:t}}},async requestPermissions(t){try{return{status:"ok",data:await c("plugin:geolocation|request_permissions",{permissions:t})}}catch(t){if(t instanceof Error)throw t;return{status:"error",error:t}}}};const{getCurrentPosition:_,clearWatch:h,checkPermissions:f,requestPermissions:w}=u;return t.checkPermissions=f,t.clearWatch=h,t.getCurrentPosition=_,t.requestPermissions=w,t.watchPosition=async function(t,r){const e=new n;return e.onmessage=r,await u.watchPosition(t,e),e.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,s;"function"==typeof SuppressedError&&SuppressedError;class r{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),s.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:r})=>{if(r===e(this,o,"f")){n(this,o,r+1),e(this,i,"f").call(this,t);const a=Object.keys(e(this,s,"f"));if(a.length>0){let t=r+1;for(const n of a.sort()){if(parseInt(n)!==t)break;{const o=e(this,s,"f")[n];delete e(this,s,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,s,"f")[r.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}return i=new WeakMap,o=new WeakMap,s=new WeakMap,t.checkPermissions=async function(){return await async function(t){return a(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await a("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await a("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await a("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,e){const n=new r;return n.onmessage=t=>{"string"==typeof t?e(null,t):e(t)},await a("plugin:geolocation|watch_position",{options:t,channel:n}),n.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})}
|
||||
|
||||
@@ -7,7 +7,7 @@ const COMMANDS: &[&str] = &[
|
||||
"watch_position",
|
||||
"clear_watch",
|
||||
"check_permissions",
|
||||
"clear_permissions",
|
||||
"request_permissions",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// @ts-nocheck
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands **/
|
||||
|
||||
export const commands = {
|
||||
async getCurrentPosition(
|
||||
options: PositionOptions | null
|
||||
): Promise<Result<Position, Error>> {
|
||||
try {
|
||||
return {
|
||||
status: 'ok',
|
||||
data: await TAURI_INVOKE('plugin:geolocation|get_current_position', {
|
||||
options
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e
|
||||
else return { status: 'error', error: e as any }
|
||||
}
|
||||
},
|
||||
async watchPosition(
|
||||
options: PositionOptions,
|
||||
channel: any
|
||||
): Promise<Result<null, Error>> {
|
||||
try {
|
||||
return {
|
||||
status: 'ok',
|
||||
data: await TAURI_INVOKE('plugin:geolocation|watch_position', {
|
||||
options,
|
||||
channel
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e
|
||||
else return { status: 'error', error: e as any }
|
||||
}
|
||||
},
|
||||
async clearWatch(channelId: number): Promise<Result<null, Error>> {
|
||||
try {
|
||||
return {
|
||||
status: 'ok',
|
||||
data: await TAURI_INVOKE('plugin:geolocation|clear_watch', {
|
||||
channelId
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e
|
||||
else return { status: 'error', error: e as any }
|
||||
}
|
||||
},
|
||||
async checkPermissions(): Promise<Result<PermissionStatus, Error>> {
|
||||
try {
|
||||
return {
|
||||
status: 'ok',
|
||||
data: await TAURI_INVOKE('plugin:geolocation|check_permissions')
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e
|
||||
else return { status: 'error', error: e as any }
|
||||
}
|
||||
},
|
||||
async requestPermissions(
|
||||
permissions: PermissionType[] | null
|
||||
): Promise<Result<PermissionStatus, Error>> {
|
||||
try {
|
||||
return {
|
||||
status: 'ok',
|
||||
data: await TAURI_INVOKE('plugin:geolocation|request_permissions', {
|
||||
permissions
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e
|
||||
else return { status: 'error', error: e as any }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** user-defined events **/
|
||||
|
||||
/* export const events = __makeEvents__<{
|
||||
randomNumber: RandomNumber;
|
||||
}>({
|
||||
randomNumber: "plugin:geolocation:random-number",
|
||||
}); */
|
||||
|
||||
/** user-defined statics **/
|
||||
|
||||
/** user-defined types **/
|
||||
|
||||
export type Coordinates = {
|
||||
/**
|
||||
* Latitude in decimal degrees.
|
||||
*/
|
||||
latitude: number
|
||||
/**
|
||||
* Longitude in decimal degrees.
|
||||
*/
|
||||
longitude: number
|
||||
/**
|
||||
* Accuracy level of the latitude and longitude coordinates in meters.
|
||||
*/
|
||||
accuracy: number
|
||||
/**
|
||||
* Accuracy level of the altitude coordinate in meters, if available.
|
||||
* Available on all iOS versions and on Android 8 and above.
|
||||
*/
|
||||
altitudeAccuracy: number | null
|
||||
/**
|
||||
* The altitude the user is at, if available.
|
||||
*/
|
||||
altitude: number | null
|
||||
speed: number | null
|
||||
/**
|
||||
* The heading the user is facing, if available.
|
||||
*/
|
||||
heading: number | null
|
||||
}
|
||||
export type Error = never
|
||||
/**
|
||||
* Permission state.
|
||||
*/
|
||||
export type PermissionState =
|
||||
/**
|
||||
* Permission access has been granted.
|
||||
*/
|
||||
| 'granted'
|
||||
/**
|
||||
* Permission access has been denied.
|
||||
*/
|
||||
| 'denied'
|
||||
/**
|
||||
* The end user should be prompted for permission.
|
||||
*/
|
||||
| 'prompt'
|
||||
export type PermissionStatus = {
|
||||
/**
|
||||
* Permission state for the location alias.
|
||||
*
|
||||
* On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions.
|
||||
*
|
||||
* On iOS it requests/checks location permissions.
|
||||
*/
|
||||
location: PermissionState
|
||||
/**
|
||||
* Permissions state for the coarseLoaction alias.
|
||||
*
|
||||
* On Android it requests/checks ACCESS_COARSE_LOCATION.
|
||||
*
|
||||
* On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) and Precise location (ACCESS_FINE_LOCATION).
|
||||
*
|
||||
* On iOS it will have the same value as the `location` alias.
|
||||
*/
|
||||
coarseLocation: PermissionState
|
||||
}
|
||||
export type PermissionType = 'location' | 'coarseLocation'
|
||||
export type Position = {
|
||||
/**
|
||||
* Creation time for these coordinates.
|
||||
*/
|
||||
timestamp: number
|
||||
/**
|
||||
* The GPD coordinates along with the accuracy of the data.
|
||||
*/
|
||||
coords: Coordinates
|
||||
}
|
||||
export type PositionOptions = {
|
||||
/**
|
||||
* High accuracy mode (such as GPS, if available)
|
||||
* Will be ignored on Android 12+ if users didn't grant the ACCESS_FINE_LOCATION permission.
|
||||
*/
|
||||
enableHighAccuracy: boolean
|
||||
/**
|
||||
* The maximum wait time in milliseconds for location updates.
|
||||
* On Android the timeout gets ignored for getCurrentPosition.
|
||||
* Ignored on iOS
|
||||
*/
|
||||
timeout: number
|
||||
/**
|
||||
* The maximum age in milliseconds of a possible cached position that is acceptable to return.
|
||||
* Default: 0
|
||||
* Ignored on iOS
|
||||
*/
|
||||
maximumAge: number
|
||||
}
|
||||
//export type RandomNumber = number;
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
import { invoke as TAURI_INVOKE } from '@tauri-apps/api/core'
|
||||
import * as TAURI_API_EVENT from '@tauri-apps/api/event'
|
||||
import { type WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow'
|
||||
|
||||
type __EventObj__<T> = {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>
|
||||
emit: T extends null
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
}
|
||||
|
||||
export type Result<T, E> =
|
||||
| { status: 'ok'; data: T }
|
||||
| { status: 'error'; error: E }
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
[K in keyof T]: __EventObj__<T[K]> & {
|
||||
(handle: __WebviewWindow__): __EventObj__<T[K]>
|
||||
}
|
||||
},
|
||||
{
|
||||
get: (_, event) => {
|
||||
const name = mappings[event as keyof T]
|
||||
|
||||
return new Proxy((() => {}) as any, {
|
||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||
listen: (arg: any) => window.listen(name, arg),
|
||||
once: (arg: any) => window.once(name, arg),
|
||||
emit: (arg: any) => window.emit(name, arg)
|
||||
}),
|
||||
get: (_, command: keyof __EventObj__<any>) => {
|
||||
switch (command) {
|
||||
case 'listen':
|
||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg)
|
||||
case 'once':
|
||||
return (arg: any) => TAURI_API_EVENT.once(name, arg)
|
||||
case 'emit':
|
||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -2,36 +2,137 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import {
|
||||
Channel,
|
||||
invoke,
|
||||
PermissionState,
|
||||
checkPermissions as checkPluginPermissions
|
||||
} from '@tauri-apps/api/core'
|
||||
|
||||
import { Channel } from '@tauri-apps/api/core'
|
||||
import { commands, type PositionOptions, type Position } from './bindings'
|
||||
export type Coordinates = {
|
||||
/**
|
||||
* Latitude in decimal degrees.
|
||||
*/
|
||||
latitude: number
|
||||
/**
|
||||
* Longitude in decimal degrees.
|
||||
*/
|
||||
longitude: number
|
||||
/**
|
||||
* Accuracy level of the latitude and longitude coordinates in meters.
|
||||
*/
|
||||
accuracy: number
|
||||
/**
|
||||
* Accuracy level of the altitude coordinate in meters, if available.
|
||||
* Available on all iOS versions and on Android 8 and above.
|
||||
*/
|
||||
altitudeAccuracy: number | null
|
||||
/**
|
||||
* The altitude the user is at, if available.
|
||||
*/
|
||||
altitude: number | null
|
||||
speed: number | null
|
||||
/**
|
||||
* The heading the user is facing, if available.
|
||||
*/
|
||||
heading: number | null
|
||||
}
|
||||
|
||||
export type PermissionStatus = {
|
||||
/**
|
||||
* Permission state for the location alias.
|
||||
*
|
||||
* On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions.
|
||||
*
|
||||
* On iOS it requests/checks location permissions.
|
||||
*/
|
||||
location: PermissionState
|
||||
/**
|
||||
* Permissions state for the coarseLoaction alias.
|
||||
*
|
||||
* On Android it requests/checks ACCESS_COARSE_LOCATION.
|
||||
*
|
||||
* On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) and Precise location (ACCESS_FINE_LOCATION).
|
||||
*
|
||||
* On iOS it will have the same value as the `location` alias.
|
||||
*/
|
||||
coarseLocation: PermissionState
|
||||
}
|
||||
|
||||
export type PermissionType = 'location' | 'coarseLocation'
|
||||
|
||||
export type Position = {
|
||||
/**
|
||||
* Creation time for these coordinates.
|
||||
*/
|
||||
timestamp: number
|
||||
/**
|
||||
* The GPD coordinates along with the accuracy of the data.
|
||||
*/
|
||||
coords: Coordinates
|
||||
}
|
||||
|
||||
export type PositionOptions = {
|
||||
/**
|
||||
* High accuracy mode (such as GPS, if available)
|
||||
* Will be ignored on Android 12+ if users didn't grant the ACCESS_FINE_LOCATION permission (`coarseLocation` permission).
|
||||
*/
|
||||
enableHighAccuracy: boolean
|
||||
/**
|
||||
* The maximum wait time in milliseconds for location updates.
|
||||
* On Android the timeout gets ignored for getCurrentPosition.
|
||||
* Ignored on iOS
|
||||
*/
|
||||
timeout: number
|
||||
/**
|
||||
* The maximum age in milliseconds of a possible cached position that is acceptable to return.
|
||||
* Default: 0
|
||||
* Ignored on iOS
|
||||
*/
|
||||
maximumAge: number
|
||||
}
|
||||
|
||||
export async function watchPosition(
|
||||
options: PositionOptions,
|
||||
// TODO: This can receive errors too
|
||||
cb: (location: Position | string) => void
|
||||
cb: (location: Position | null, error?: string) => void
|
||||
): Promise<number> {
|
||||
const channel = new Channel<Position>()
|
||||
channel.onmessage = cb
|
||||
await commands.watchPosition(options, channel)
|
||||
const channel = new Channel<Position | string>()
|
||||
channel.onmessage = (message) => {
|
||||
if (typeof message === 'string') {
|
||||
cb(null, message)
|
||||
} else {
|
||||
cb(message)
|
||||
}
|
||||
}
|
||||
await invoke('plugin:geolocation|watch_position', {
|
||||
options,
|
||||
channel
|
||||
})
|
||||
return channel.id
|
||||
}
|
||||
|
||||
export const {
|
||||
getCurrentPosition,
|
||||
clearWatch,
|
||||
checkPermissions,
|
||||
requestPermissions
|
||||
} = commands
|
||||
export async function getCurrentPosition(
|
||||
options?: PositionOptions
|
||||
): Promise<Position> {
|
||||
return await invoke('plugin:geolocation|get_current_position', {
|
||||
options
|
||||
})
|
||||
}
|
||||
|
||||
export type {
|
||||
PermissionState,
|
||||
PermissionStatus,
|
||||
PermissionType,
|
||||
Position,
|
||||
PositionOptions,
|
||||
Coordinates
|
||||
} from './bindings'
|
||||
export async function clearWatch(channelId: number): Promise<void> {
|
||||
await invoke('plugin:geolocation|clear_watch', {
|
||||
channelId
|
||||
})
|
||||
}
|
||||
|
||||
// export { events };
|
||||
export async function checkPermissions(): Promise<PermissionStatus> {
|
||||
return await checkPluginPermissions('geolocation')
|
||||
}
|
||||
|
||||
export async function requestPermissions(
|
||||
permissions: PermissionType[] | null
|
||||
): Promise<PermissionStatus> {
|
||||
return await invoke('plugin:geolocation|request_permissions', {
|
||||
permissions
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-clear-positions"
|
||||
description = "Enables the clear_positions command without any pre-configured scope."
|
||||
commands.allow = ["clear_positions"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-clear-positions"
|
||||
description = "Denies the clear_positions command without any pre-configured scope."
|
||||
commands.deny = ["clear_positions"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-request-permissions"
|
||||
description = "Enables the request_permissions command without any pre-configured scope."
|
||||
commands.allow = ["request_permissions"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-request-permissions"
|
||||
description = "Denies the request_permissions command without any pre-configured scope."
|
||||
commands.deny = ["request_permissions"]
|
||||
@@ -63,32 +63,6 @@ Denies the clear_permissions command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:allow-clear-positions`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the clear_positions command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:deny-clear-positions`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the clear_positions command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:allow-clear-watch`
|
||||
|
||||
</td>
|
||||
@@ -141,6 +115,32 @@ Denies the get_current_position command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:allow-request-permissions`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the request_permissions command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:deny-request-permissions`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the request_permissions command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`geolocation:allow-watch-position`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -314,16 +314,6 @@
|
||||
"type": "string",
|
||||
"const": "deny-clear-permissions"
|
||||
},
|
||||
{
|
||||
"description": "Enables the clear_positions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-clear-positions"
|
||||
},
|
||||
{
|
||||
"description": "Denies the clear_positions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-clear-positions"
|
||||
},
|
||||
{
|
||||
"description": "Enables the clear_watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -344,6 +334,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-get-current-position"
|
||||
},
|
||||
{
|
||||
"description": "Enables the request_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-request-permissions"
|
||||
},
|
||||
{
|
||||
"description": "Denies the request_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-request-permissions"
|
||||
},
|
||||
{
|
||||
"description": "Enables the watch_position command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -98,7 +98,10 @@ impl<R: Runtime> Geolocation<R> {
|
||||
permissions: Option<Vec<PermissionType>>,
|
||||
) -> crate::Result<PermissionStatus> {
|
||||
self.0
|
||||
.run_mobile_plugin("requestPermissions", permissions)
|
||||
.run_mobile_plugin(
|
||||
"requestPermissions",
|
||||
serde_json::json!({ "permissions": permissions }),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user