From 0ac7adfd108fc1da62f2df87d84a304082190b0e Mon Sep 17 00:00:00 2001 From: Bradley Leatherwood Date: Sat, 5 Oct 2024 15:25:08 -0700 Subject: [PATCH] Enable background geolocation updates Enhanced geolocation plugin to support background updates. Added the `requestUpdatesInBackground` option to `watchPosition` and `requestPermissions` functions. Updated iOS handler to request appropriate location permissions based on this setting, and configured location manager for background updates. This change enables apps to continuously track location, improving functionality for applications requiring persistent geolocation data. Also introduced a default permissions configuration for the plugin. --- plugins/geolocation/guest-js/index.ts | 9 ++- .../ios/Sources/GeolocationPlugin.swift | 57 +++++++++++++++++-- plugins/geolocation/permissions/default.toml | 3 + plugins/geolocation/src/commands.rs | 6 +- plugins/geolocation/src/desktop.rs | 5 +- plugins/geolocation/src/mobile.rs | 19 ++++++- 6 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 plugins/geolocation/permissions/default.toml diff --git a/plugins/geolocation/guest-js/index.ts b/plugins/geolocation/guest-js/index.ts index 8ef5c533..b00ca664 100644 --- a/plugins/geolocation/guest-js/index.ts +++ b/plugins/geolocation/guest-js/index.ts @@ -93,7 +93,7 @@ export type PositionOptions = { } export async function watchPosition( - options: PositionOptions, + options: PositionOptions & {requestUpdatesInBackground?: boolean}, cb: (location: Position | null, error?: string) => void ): Promise { const channel = new Channel() @@ -106,6 +106,7 @@ export async function watchPosition( } await invoke('plugin:geolocation|watch_position', { options, + requestUpdatesInBackground: options.requestUpdatesInBackground ?? false, channel }) return channel.id @@ -130,9 +131,11 @@ export async function checkPermissions(): Promise { } export async function requestPermissions( - permissions: PermissionType[] | null + permissions: PermissionType[] | null, + requestUpdatesInBackground: boolean = false ): Promise { return await invoke('plugin:geolocation|request_permissions', { - permissions + permissions, + requestUpdatesInBackground }) } diff --git a/plugins/geolocation/ios/Sources/GeolocationPlugin.swift b/plugins/geolocation/ios/Sources/GeolocationPlugin.swift index 7a2b57a9..2595a996 100644 --- a/plugins/geolocation/ios/Sources/GeolocationPlugin.swift +++ b/plugins/geolocation/ios/Sources/GeolocationPlugin.swift @@ -14,6 +14,7 @@ class GetPositionArgs: Decodable { class WatchPositionArgs: Decodable { let options: GetPositionArgs + let request_updates_in_background: Bool let channel: Channel } @@ -21,9 +22,20 @@ class ClearWatchArgs: Decodable { let channelId: UInt32 } +enum PermissionType: String, Decodable { + case location + case coarseLocation +} + +class RequestPermissionsArgs: Decodable { + let permissions: [PermissionType] + let requestUpdatesInBackground: Bool +} + class GeolocationPlugin: Plugin, CLLocationManagerDelegate { private let locationManager = CLLocationManager() private var isUpdatingLocation: Bool = false + private var backgroundUpdatesRequested: Bool = false private var permissionRequests: [Invoke] = [] private var positionRequests: [Invoke] = [] private var watcherChannels: [Channel] = [] @@ -61,6 +73,8 @@ class GeolocationPlugin: Plugin, CLLocationManagerDelegate { @objc public func watchPosition(_ invoke: Invoke) throws { let args = try invoke.parseArgs(WatchPositionArgs.self) + self.backgroundUpdatesRequested = args.request_updates_in_background; + self.watcherChannels.append(args.channel) DispatchQueue.main.async { @@ -70,12 +84,28 @@ class GeolocationPlugin: Plugin, CLLocationManagerDelegate { self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer } + + // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. if CLLocationManager.authorizationStatus() == .notDetermined { - self.locationManager.requestWhenInUseAuthorization() + if self.backgroundUpdatesRequested { + self.locationManager.requestAlwaysAuthorization() + } else { + self.locationManager.requestWhenInUseAuthorization() + } } else { - self.locationManager.startUpdatingLocation() - self.isUpdatingLocation = true + if CLLocationManager.authorizationStatus() == .authorizedWhenInUse && self.backgroundUpdatesRequested { + self.locationManager.requestAlwaysAuthorization() + } else { + self.locationManager.startUpdatingLocation() + self.isUpdatingLocation = true + // Enable background updates if enabled + if self.backgroundUpdatesRequested { + self.locationManager.allowsBackgroundLocationUpdates = true + self.locationManager.pausesLocationUpdatesAutomatically = false + self.locationManager.showsBackgroundLocationIndicator = true + } + } } } @@ -125,9 +155,19 @@ class GeolocationPlugin: Plugin, CLLocationManagerDelegate { // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. if CLLocationManager.authorizationStatus() == .notDetermined { self.permissionRequests.append(invoke) - - DispatchQueue.main.async { - self.locationManager.requestWhenInUseAuthorization() + do { + let args = try invoke.parseArgs(RequestPermissionsArgs.self) + + DispatchQueue.main.async { + self.locationManager.requestWhenInUseAuthorization() + if args.requestUpdatesInBackground { + // Request background permission if requested + self.locationManager.requestAlwaysAuthorization() + self.backgroundUpdatesRequested = true + } + } + } catch { + invoke.reject(error.localizedDescription) } } else { checkPermissions(invoke) @@ -224,6 +264,11 @@ class GeolocationPlugin: Plugin, CLLocationManagerDelegate { private func stopUpdating() { self.locationManager.stopUpdatingLocation() self.isUpdatingLocation = false + if self.backgroundUpdatesRequested { + self.locationManager.allowsBackgroundLocationUpdates = false + self.locationManager.pausesLocationUpdatesAutomatically = true + self.locationManager.showsBackgroundLocationIndicator = false + } } private func convertLocation(_ location: CLLocation) -> JsonObject { diff --git a/plugins/geolocation/permissions/default.toml b/plugins/geolocation/permissions/default.toml new file mode 100644 index 00000000..c4f75baf --- /dev/null +++ b/plugins/geolocation/permissions/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-request-permissions", "allow-check-permissions", "allow-clear-watch", "allow-watch-position", "allow-get-current-position"] diff --git a/plugins/geolocation/src/commands.rs b/plugins/geolocation/src/commands.rs index 9f9b7aa0..a176214e 100644 --- a/plugins/geolocation/src/commands.rs +++ b/plugins/geolocation/src/commands.rs @@ -20,9 +20,10 @@ pub(crate) async fn get_current_position( pub(crate) async fn watch_position( app: AppHandle, options: PositionOptions, + request_updates_in_background: bool, channel: Channel, ) -> Result<()> { - app.geolocation().watch_position_inner(options, channel) + app.geolocation().watch_position_inner(options, channel, request_updates_in_background) } #[command] @@ -42,6 +43,7 @@ pub(crate) async fn check_permissions(app: AppHandle) -> Result

( app: AppHandle, permissions: Option>, + request_updates_in_background: bool, ) -> Result { - app.geolocation().request_permissions(permissions) + app.geolocation().request_permissions(permissions, request_updates_in_background) } diff --git a/plugins/geolocation/src/desktop.rs b/plugins/geolocation/src/desktop.rs index 00da1fad..5bebfd01 100644 --- a/plugins/geolocation/src/desktop.rs +++ b/plugins/geolocation/src/desktop.rs @@ -32,6 +32,7 @@ impl Geolocation { pub fn watch_position( &self, options: PositionOptions, + request_updates_in_background: bool, callback: F, ) -> crate::Result { let channel = Channel::new(move |event| { @@ -51,7 +52,7 @@ impl Geolocation { }); let id = channel.id(); - self.watch_position_inner(options, channel)?; + self.watch_position_inner(options, request_updates_in_background, channel)?; Ok(id) } @@ -59,6 +60,7 @@ impl Geolocation { pub(crate) fn watch_position_inner( &self, _options: PositionOptions, + _request_updates_in_background: bool, _callback_channel: Channel, ) -> crate::Result<()> { Ok(()) @@ -75,6 +77,7 @@ impl Geolocation { pub fn request_permissions( &self, _permissions: Option>, + _request_updates_in_background: bool, ) -> crate::Result { Ok(PermissionStatus::default()) } diff --git a/plugins/geolocation/src/mobile.rs b/plugins/geolocation/src/mobile.rs index 48a3f5de..a67047cf 100644 --- a/plugins/geolocation/src/mobile.rs +++ b/plugins/geolocation/src/mobile.rs @@ -47,6 +47,7 @@ impl Geolocation { pub fn watch_position( &self, options: PositionOptions, + request_updates_in_background: bool, callback: F, ) -> crate::Result { let channel = Channel::new(move |event| { @@ -66,7 +67,7 @@ impl Geolocation { }); let id = channel.id(); - self.watch_position_inner(options, channel)?; + self.watch_position_inner(options, request_updates_in_background, channel)?; Ok(id) } @@ -74,10 +75,18 @@ impl Geolocation { pub(crate) fn watch_position_inner( &self, options: PositionOptions, + request_updates_in_background: bool, channel: Channel, ) -> crate::Result<()> { self.0 - .run_mobile_plugin("watchPosition", WatchPayload { options, channel }) + .run_mobile_plugin( + "watchPosition", + WatchPayload { + options, + channel, + request_updates_in_background, + }, + ) .map_err(Into::into) } @@ -96,11 +105,15 @@ impl Geolocation { pub fn request_permissions( &self, permissions: Option>, + request_updates_in_background: bool, ) -> crate::Result { self.0 .run_mobile_plugin( "requestPermissions", - serde_json::json!({ "permissions": permissions }), + serde_json::json!({ + "permissions": permissions, + "requestUpdatesInBackground": request_updates_in_background, + }), ) .map_err(Into::into) }