fix: location flow on iOS devices

This commit is contained in:
Rainer Killinger
2023-02-15 18:27:15 +01:00
parent 3f5b77520c
commit 05a0bf53fa
5 changed files with 95 additions and 22 deletions

View File

@@ -13,7 +13,9 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {MapPosition} from '../../map/position.service';
import {SearchPageComponent} from './search-page.component'; import {SearchPageComponent} from './search-page.component';
import {Geolocation} from '@capacitor/geolocation';
/** /**
* Presents a list of places for eating/drinking * 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();
}
}
} }

View File

@@ -260,6 +260,11 @@ export class MapPageComponent {
* Subscribe to needed observables and get the location status when user is entering the page * Subscribe to needed observables and get the location status when user is entering the page
*/ */
async ionViewWillEnter() { 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.subscriptions.push(
this.dataRoutingService.itemSelectListener().subscribe(async item => { this.dataRoutingService.itemSelectListener().subscribe(async item => {
// in case the list item is clicked // in case the list item is clicked
@@ -269,17 +274,19 @@ export class MapPageComponent {
void this.router.navigate(['/data-detail', item.uid]); void this.router.navigate(['/data-detail', item.uid]);
} }
}), }),
this.positionService.watchCurrentLocation({maximumAge: 3000}).subscribe({ this.positionService
next: (position: MapPosition) => { .watchCurrentLocation(this.constructor.name, {enableHighAccuracy: true, maximumAge: 1000})
this.position = position; .subscribe({
this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32); next: (position: MapPosition) => {
}, this.position = position;
error: async _error => { this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32);
this.locationStatus = await Geolocation.checkPermissions(); },
// eslint-disable-next-line unicorn/no-null error: async _error => {
this.position = null; this.locationStatus = await Geolocation.checkPermissions();
}, // eslint-disable-next-line unicorn/no-null
}), this.position = null;
},
}),
); );
// get detailed location status (diagnostics only supports devices) // get detailed location status (diagnostics only supports devices)
@@ -290,6 +297,7 @@ export class MapPageComponent {
* Unsubscribe from all subscriptions when user leaves page * Unsubscribe from all subscriptions when user leaves page
*/ */
ionViewWillLeave() { ionViewWillLeave() {
void this.positionService.clearWatcher(this.constructor.name);
for (const sub of this.subscriptions) { for (const sub of this.subscriptions) {
sub.unsubscribe(); sub.unsubscribe();
} }

View File

@@ -96,12 +96,12 @@
<ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon> <ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon>
<ng-template #noLocationIcon> <ng-template #noLocationIcon>
<ion-icon <ion-icon
*ngIf="locationStatus.location !== 'denied'; else deniedLocationIcon" *ngIf="locationStatus && locationStatus.location === 'denied'; else pendingLocationIcon"
name="location_searching" name="location_disabled"
></ion-icon> ></ion-icon>
</ng-template> </ng-template>
<ng-template #deniedLocationIcon> <ng-template #pendingLocationIcon>
<ion-icon name="location_disabled"></ion-icon> <ion-icon name="location_searching"></ion-icon>
</ng-template> </ng-template>
</ion-button> </ion-button>
</div> </div>

View File

@@ -58,9 +58,30 @@ describe('PositionService', () => {
}); });
it('should continuously provide (watch) location of the device', done => { it('should continuously provide (watch) location of the device', done => {
positionService.watchCurrentLocation().subscribe(location => { positionService.watchCurrentLocation('testCaller').subscribe(location => {
expect(location).toBeDefined(); expect(location).toBeDefined();
done(); 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();
});
});
}); });

View File

@@ -45,6 +45,11 @@ export class PositionService {
*/ */
position?: MapPosition; position?: MapPosition;
/**
* Map of callers and their running watchers. Both by their ID
*/
watchers: Map<string, Promise<string>> = new Map();
/** /**
* Gets current coordinates information of the device * Gets current coordinates information of the device
* *
@@ -84,19 +89,19 @@ export class PositionService {
/** /**
* Watches (continuously gets) current coordinates information of the device * 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) * @param options Options which define which data should be provided (e.g. how accurate or how old)
*/ */
watchCurrentLocation(options: PositionOptions = {}): Observable<MapPosition> { watchCurrentLocation(caller: string, options: PositionOptions = {}): Observable<MapPosition> {
return new Observable(subscriber => { return new Observable(subscriber => {
void Geolocation.watchPosition(options, (position, error) => { const watcherID = Geolocation.watchPosition(options, (position, error) => {
if (error) { if (error) {
subscriber.error(position); subscriber.error(position);
} else { } else {
this.position = { this.position = {
heading: // TODO use native compass heading instead
Number.isNaN(position?.coords.heading) || position?.coords.heading == undefined // waiting for https://github.com/ionic-team/capacitor-plugins/issues/1192
? undefined heading: undefined,
: position.coords.heading,
latitude: position?.coords.latitude ?? 0, latitude: position?.coords.latitude ?? 0,
longitude: position?.coords.longitude ?? 0, // TODO: handle null position longitude: position?.coords.longitude ?? 0, // TODO: handle null position
}; };
@@ -104,6 +109,19 @@ export class PositionService {
subscriber.next(this.position); 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<void> {
const watcherID = await this.watchers.get(caller);
if (watcherID) {
Geolocation.clearWatch({id: watcherID});
}
}
} }