From 05a0bf53fa1eda88ef7efced9394eef64dfbda8b Mon Sep 17 00:00:00 2001 From: Rainer Killinger Date: Wed, 15 Feb 2023 18:27:15 +0100 Subject: [PATCH] fix: location flow on iOS devices --- .../data/list/food-data-list.component.ts | 26 ++++++++++++++++ .../modules/map/page/map-page.component.ts | 30 ++++++++++++------- src/app/modules/map/page/map-page.html | 8 ++--- src/app/modules/map/position.service.spec.ts | 23 +++++++++++++- src/app/modules/map/position.service.ts | 30 +++++++++++++++---- 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/app/modules/data/list/food-data-list.component.ts b/src/app/modules/data/list/food-data-list.component.ts index 39995295..4f260d72 100644 --- a/src/app/modules/data/list/food-data-list.component.ts +++ b/src/app/modules/data/list/food-data-list.component.ts @@ -13,7 +13,9 @@ * this program. If not, see . */ import {Component} from '@angular/core'; +import {MapPosition} from '../../map/position.service'; import {SearchPageComponent} from './search-page.component'; +import {Geolocation} from '@capacitor/geolocation'; /** * Presents a list of places for eating/drinking @@ -91,4 +93,28 @@ export class FoodDataListComponent extends SearchPageComponent { ]; } } + + async ionViewWillEnter() { + await super.ionViewWillEnter(); + this.subscriptions.push( + this.positionService + .watchCurrentLocation(this.constructor.name, {enableHighAccuracy: false, maximumAge: 1000}) + .subscribe({ + next: (position: MapPosition) => { + this.positionService.position = position; + }, + error: async _error => { + this.positionService.position = undefined; + await Geolocation.checkPermissions(); + }, + }), + ); + } + + ionViewWillLeave() { + void this.positionService.clearWatcher(this.constructor.name); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } } diff --git a/src/app/modules/map/page/map-page.component.ts b/src/app/modules/map/page/map-page.component.ts index 32a15627..b2628d34 100644 --- a/src/app/modules/map/page/map-page.component.ts +++ b/src/app/modules/map/page/map-page.component.ts @@ -260,6 +260,11 @@ export class MapPageComponent { * Subscribe to needed observables and get the location status when user is entering the page */ async ionViewWillEnter() { + if (this.positionService.position) { + this.position = this.positionService.position; + this.positionMarker = MapProvider.getPositionMarker(this.position, 'stapps-device-location', 32); + } + this.subscriptions.push( this.dataRoutingService.itemSelectListener().subscribe(async item => { // in case the list item is clicked @@ -269,17 +274,19 @@ export class MapPageComponent { void this.router.navigate(['/data-detail', item.uid]); } }), - this.positionService.watchCurrentLocation({maximumAge: 3000}).subscribe({ - next: (position: MapPosition) => { - this.position = position; - this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32); - }, - error: async _error => { - this.locationStatus = await Geolocation.checkPermissions(); - // eslint-disable-next-line unicorn/no-null - this.position = null; - }, - }), + this.positionService + .watchCurrentLocation(this.constructor.name, {enableHighAccuracy: true, maximumAge: 1000}) + .subscribe({ + next: (position: MapPosition) => { + this.position = position; + this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32); + }, + error: async _error => { + this.locationStatus = await Geolocation.checkPermissions(); + // eslint-disable-next-line unicorn/no-null + this.position = null; + }, + }), ); // get detailed location status (diagnostics only supports devices) @@ -290,6 +297,7 @@ export class MapPageComponent { * Unsubscribe from all subscriptions when user leaves page */ ionViewWillLeave() { + void this.positionService.clearWatcher(this.constructor.name); for (const sub of this.subscriptions) { sub.unsubscribe(); } diff --git a/src/app/modules/map/page/map-page.html b/src/app/modules/map/page/map-page.html index 606e7340..52b9aceb 100644 --- a/src/app/modules/map/page/map-page.html +++ b/src/app/modules/map/page/map-page.html @@ -96,12 +96,12 @@ - - + + diff --git a/src/app/modules/map/position.service.spec.ts b/src/app/modules/map/position.service.spec.ts index 23c376d6..9a165eb9 100644 --- a/src/app/modules/map/position.service.spec.ts +++ b/src/app/modules/map/position.service.spec.ts @@ -58,9 +58,30 @@ describe('PositionService', () => { }); it('should continuously provide (watch) location of the device', done => { - positionService.watchCurrentLocation().subscribe(location => { + positionService.watchCurrentLocation('testCaller').subscribe(location => { expect(location).toBeDefined(); done(); }); }); + + it('should stop to continuously provide (watch) location of the device', done => { + positionService.watchers.set( + 'clearWatch', + new Promise(resolve => { + setTimeout(function () { + resolve(`watcherID123`); + }, 20); + }), + ); + positionService + .clearWatcher('clearWatch') + .then(result => { + expect(result).toBeUndefined(); + done(); + }) + .catch(error => { + expect(error).toBeUndefined(); + done(); + }); + }); }); diff --git a/src/app/modules/map/position.service.ts b/src/app/modules/map/position.service.ts index 35eabab2..1a3316f2 100644 --- a/src/app/modules/map/position.service.ts +++ b/src/app/modules/map/position.service.ts @@ -45,6 +45,11 @@ export class PositionService { */ position?: MapPosition; + /** + * Map of callers and their running watchers. Both by their ID + */ + watchers: Map> = new Map(); + /** * Gets current coordinates information of the device * @@ -84,19 +89,19 @@ export class PositionService { /** * Watches (continuously gets) current coordinates information of the device * + * @param caller Identifier for later reference. (I.e use of `clearWatcher`) * @param options Options which define which data should be provided (e.g. how accurate or how old) */ - watchCurrentLocation(options: PositionOptions = {}): Observable { + watchCurrentLocation(caller: string, options: PositionOptions = {}): Observable { return new Observable(subscriber => { - void Geolocation.watchPosition(options, (position, error) => { + const watcherID = Geolocation.watchPosition(options, (position, error) => { if (error) { subscriber.error(position); } else { this.position = { - heading: - Number.isNaN(position?.coords.heading) || position?.coords.heading == undefined - ? undefined - : position.coords.heading, + // TODO use native compass heading instead + // waiting for https://github.com/ionic-team/capacitor-plugins/issues/1192 + heading: undefined, latitude: position?.coords.latitude ?? 0, longitude: position?.coords.longitude ?? 0, // TODO: handle null position }; @@ -104,6 +109,19 @@ export class PositionService { subscriber.next(this.position); } }); + this.watchers.set(caller, watcherID); }); } + + /** + * Clears watcher for a certain caller + * + * @param caller Identifier of the caller wanting to clear the watcher + */ + async clearWatcher(caller: string): Promise { + const watcherID = await this.watchers.get(caller); + if (watcherID) { + Geolocation.clearWatch({id: watcherID}); + } + } }