mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-10-05 17:22:12 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
863bdb3d6d |
@@ -12,28 +12,28 @@ include ':capacitor-app'
|
||||
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/app/android')
|
||||
|
||||
include ':capacitor-browser'
|
||||
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@8.0.2_@capacitor+core@8.2.0/node_modules/@capacitor/browser/android')
|
||||
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@8.0.4_@capacitor+core@8.2.0/node_modules/@capacitor/browser/android')
|
||||
|
||||
include ':capacitor-clipboard'
|
||||
project(':capacitor-clipboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/clipboard/android')
|
||||
|
||||
include ':capacitor-device'
|
||||
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/device/android')
|
||||
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@8.0.3_@capacitor+core@8.2.0/node_modules/@capacitor/device/android')
|
||||
|
||||
include ':capacitor-dialog'
|
||||
project(':capacitor-dialog').projectDir = new File('../../../node_modules/.pnpm/@capacitor+dialog@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/dialog/android')
|
||||
|
||||
include ':capacitor-filesystem'
|
||||
project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@8.1.2_@capacitor+core@8.2.0/node_modules/@capacitor/filesystem/android')
|
||||
project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@8.1.3_@capacitor+core@8.2.0/node_modules/@capacitor/filesystem/android')
|
||||
|
||||
include ':capacitor-geolocation'
|
||||
project(':capacitor-geolocation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+geolocation@8.1.0_@capacitor+core@8.2.0/node_modules/@capacitor/geolocation/android')
|
||||
|
||||
include ':capacitor-haptics'
|
||||
project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/haptics/android')
|
||||
project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@8.0.2_@capacitor+core@8.2.0/node_modules/@capacitor/haptics/android')
|
||||
|
||||
include ':capacitor-keyboard'
|
||||
project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/keyboard/android')
|
||||
project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@8.0.5_@capacitor+core@8.2.0/node_modules/@capacitor/keyboard/android')
|
||||
|
||||
include ':capacitor-local-notifications'
|
||||
project(':capacitor-local-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+local-notifications@8.0.2_@capacitor+core@8.2.0/node_modules/@capacitor/local-notifications/android')
|
||||
@@ -51,10 +51,10 @@ include ':capacitor-share'
|
||||
project(':capacitor-share').projectDir = new File('../../../node_modules/.pnpm/@capacitor+share@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/share/android')
|
||||
|
||||
include ':capacitor-splash-screen'
|
||||
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/splash-screen/android')
|
||||
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.2_@capacitor+core@8.2.0/node_modules/@capacitor/splash-screen/android')
|
||||
|
||||
include ':transistorsoft-capacitor-background-fetch'
|
||||
project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@8.0.0_@capacitor+core@8.2.0/node_modules/@transistorsoft/capacitor-background-fetch/android')
|
||||
|
||||
include ':capacitor-secure-storage-plugin'
|
||||
project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.12.0_@capacitor+core@8.2.0/node_modules/capacitor-secure-storage-plugin/android')
|
||||
project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.13.0_@capacitor+core@8.2.0/node_modules/capacitor-secure-storage-plugin/android')
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
*/
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {PositionService} from '../../map/position.service';
|
||||
import {Geolocation} from '@capacitor/geolocation';
|
||||
import {BehaviorSubject, catchError} from 'rxjs';
|
||||
import {BehaviorSubject, distinctUntilChanged, map} from 'rxjs';
|
||||
import {pauseWhen} from '../../../util/rxjs/pause-when';
|
||||
import {SCSearchFilter} from '@openstapps/core';
|
||||
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||
@@ -68,43 +67,42 @@ export class FoodDataListComponent {
|
||||
type: 'boolean',
|
||||
};
|
||||
|
||||
private positionService = inject(PositionService);
|
||||
|
||||
constructor() {
|
||||
const positionService = inject(PositionService);
|
||||
const positionService = this.positionService;
|
||||
const contextMenuService = inject(ContextMenuService);
|
||||
|
||||
positionService
|
||||
.watchCurrentLocation({enableHighAccuracy: false, maximumAge: 1000})
|
||||
// Uses the app-wide shared location watch instead of starting its own
|
||||
positionService.geoLocation
|
||||
.pipe(
|
||||
map(({coords}) => ({latitude: coords.latitude, longitude: coords.longitude})),
|
||||
// only re-sort (= new search) after moving roughly 100 m, not on every GPS update
|
||||
distinctUntilChanged(
|
||||
(a, b) => Math.abs(a.latitude - b.latitude) < 0.001 && Math.abs(a.longitude - b.longitude) < 0.0015,
|
||||
),
|
||||
pauseWhen(this.isNotInView$),
|
||||
takeUntilDestroyed(),
|
||||
catchError(async _error => {
|
||||
await Geolocation.checkPermissions();
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(position) {
|
||||
if (!position) return;
|
||||
positionService.position = position;
|
||||
contextMenuService.sortQuery.next([
|
||||
{
|
||||
type: 'distance',
|
||||
order: 'asc',
|
||||
arguments: {
|
||||
field: 'geo',
|
||||
position: [position.longitude, position.latitude],
|
||||
},
|
||||
.subscribe(position => {
|
||||
positionService.position = position;
|
||||
contextMenuService.sortQuery.next([
|
||||
{
|
||||
type: 'distance',
|
||||
order: 'asc',
|
||||
arguments: {
|
||||
field: 'geo',
|
||||
position: [position.longitude, position.latitude],
|
||||
},
|
||||
]);
|
||||
},
|
||||
async error() {
|
||||
positionService.position = undefined;
|
||||
await Geolocation.checkPermissions();
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.isNotInView$.next(false);
|
||||
// the shared watch never asks by itself; on this page, location is the point, so ask here
|
||||
void this.positionService.requestPermission();
|
||||
}
|
||||
|
||||
ionViewWillLeave() {
|
||||
|
||||
@@ -13,16 +13,86 @@ import {IonFabButton} from '@ionic/angular/standalone';
|
||||
import {MapService} from '@maplibre/ngx-maplibre-gl';
|
||||
import {FitBoundsOptions, GeolocateControl, GeolocateControlOptions} from 'maplibre-gl';
|
||||
import {Map as MapLibre} from 'maplibre-gl';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {BehaviorSubject, Subscription} from 'rxjs';
|
||||
import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
|
||||
import {PERMISSION_DENIED, PositionService} from '../position.service';
|
||||
|
||||
type WatchState = InstanceType<typeof GeolocateControl>['_watchState'];
|
||||
|
||||
type GeolocationApi = Pick<Geolocation, 'watchPosition' | 'clearWatch' | 'getCurrentPosition'>;
|
||||
|
||||
/**
|
||||
* A `navigator.geolocation` look-alike that serves positions from the app's PositionService.
|
||||
* MapLibre has no option for a custom position source and always calls `navigator.geolocation`,
|
||||
* so this lets the map share the app's single location watch instead of starting its own.
|
||||
*/
|
||||
class PositionServiceGeolocation implements GeolocationApi {
|
||||
private nextId = 1;
|
||||
|
||||
private readonly watches = new Map<number, Subscription>();
|
||||
|
||||
constructor(private readonly positionService: PositionService) {}
|
||||
|
||||
watchPosition(success: PositionCallback, error?: PositionErrorCallback | null): number {
|
||||
const id = this.nextId++;
|
||||
const watch = new Subscription();
|
||||
this.watches.set(id, watch);
|
||||
void this.positionService.requestPermission().then(permission => {
|
||||
if (watch.closed) return;
|
||||
if (permission === PERMISSION_DENIED) {
|
||||
error?.({code: 1, message: 'User denied Geolocation'} as GeolocationPositionError);
|
||||
return;
|
||||
}
|
||||
// Capacitor's Position has the same shape as the browser's GeolocationPosition
|
||||
watch.add(
|
||||
// precise: while the user tracks their location on the map, the shared watch uses GPS
|
||||
this.positionService.preciseLocation.subscribe(position =>
|
||||
success(position as unknown as GeolocationPosition),
|
||||
),
|
||||
);
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
clearWatch(id: number): void {
|
||||
this.watches.get(id)?.unsubscribe();
|
||||
this.watches.delete(id);
|
||||
}
|
||||
|
||||
getCurrentPosition(success: PositionCallback, error?: PositionErrorCallback | null): void {
|
||||
const id = this.watchPosition(
|
||||
position => {
|
||||
this.clearWatch(id);
|
||||
success(position);
|
||||
},
|
||||
positionError => {
|
||||
this.clearWatch(id);
|
||||
error?.(positionError);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `run` while `navigator.geolocation` is replaced by `geolocation`.
|
||||
* MapLibre only uses it synchronously inside the wrapped calls, so no other code sees the swap.
|
||||
*/
|
||||
function withGeolocation<T>(geolocation: GeolocationApi, run: () => T): T {
|
||||
Object.defineProperty(globalThis.navigator, 'geolocation', {value: geolocation, configurable: true});
|
||||
try {
|
||||
return run();
|
||||
} finally {
|
||||
// removing the own property brings back the browser's original
|
||||
delete (globalThis.navigator as {geolocation?: unknown}).geolocation;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomGeolocateControl extends GeolocateControl {
|
||||
constructor(
|
||||
public _container: HTMLElement,
|
||||
watchState: BehaviorSubject<WatchState>,
|
||||
options: GeolocateControlOptions,
|
||||
private readonly geolocation: GeolocationApi,
|
||||
) {
|
||||
super(options);
|
||||
Object.defineProperty(this, '_watchState', {
|
||||
@@ -45,7 +115,27 @@ class CustomGeolocateControl extends GeolocateControl {
|
||||
return this._container;
|
||||
}
|
||||
|
||||
override onRemove() {}
|
||||
// MapLibre uses navigator.geolocation only in these three methods: point it to the PositionService
|
||||
|
||||
override trigger(): boolean {
|
||||
return withGeolocation(this.geolocation, () => super.trigger());
|
||||
}
|
||||
|
||||
override _clearWatch(): void {
|
||||
withGeolocation(this.geolocation, () => super._clearWatch());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run MapLibre's cleanup (stops the watch, removes the dot, resets its global watch counter),
|
||||
* but don't let it remove our Angular-owned container
|
||||
*/
|
||||
override onRemove() {
|
||||
if (!this._map) return;
|
||||
const container = this._container;
|
||||
this._container = document.createElement('div');
|
||||
withGeolocation(this.geolocation, () => super.onRemove());
|
||||
this._container = container;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -58,6 +148,8 @@ class CustomGeolocateControl extends GeolocateControl {
|
||||
export class GeolocateControlComponent implements AfterContentInit, OnDestroy {
|
||||
private mapService = inject(MapService);
|
||||
|
||||
private positionService = inject(PositionService);
|
||||
|
||||
@Input() position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
|
||||
@Input() positionOptions?: PositionOptions;
|
||||
@@ -75,12 +167,17 @@ export class GeolocateControlComponent implements AfterContentInit, OnDestroy {
|
||||
control: CustomGeolocateControl;
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.control = new CustomGeolocateControl(this.content.nativeElement, this.watchState, {
|
||||
positionOptions: this.positionOptions,
|
||||
fitBoundsOptions: this.fitBoundsOptions,
|
||||
trackUserLocation: this.trackUserLocation,
|
||||
showUserLocation: this.showUserLocation,
|
||||
});
|
||||
this.control = new CustomGeolocateControl(
|
||||
this.content.nativeElement,
|
||||
this.watchState,
|
||||
{
|
||||
positionOptions: this.positionOptions,
|
||||
fitBoundsOptions: this.fitBoundsOptions,
|
||||
trackUserLocation: this.trackUserLocation,
|
||||
showUserLocation: this.showUserLocation,
|
||||
},
|
||||
new PositionServiceGeolocation(this.positionService),
|
||||
);
|
||||
this.mapService.mapCreated$.subscribe(() => {
|
||||
this.mapService.addControl(this.control, this.position);
|
||||
});
|
||||
|
||||
@@ -14,8 +14,92 @@
|
||||
*/
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Point} from 'geojson';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Geolocation, Position} from '@capacitor/geolocation';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
catchError,
|
||||
defer,
|
||||
distinctUntilChanged,
|
||||
finalize,
|
||||
first,
|
||||
from,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
of,
|
||||
ReplaySubject,
|
||||
retry,
|
||||
share,
|
||||
Subject,
|
||||
switchMap,
|
||||
timer,
|
||||
} from 'rxjs';
|
||||
import {Geolocation, PermissionStatus, Position} from '@capacitor/geolocation';
|
||||
import {App} from '@capacitor/app';
|
||||
import {Capacitor, PermissionState} from '@capacitor/core';
|
||||
|
||||
/**
|
||||
* Coarse (network / Wi-Fi) location: enough for distances in lists, fast indoors, saves battery
|
||||
*/
|
||||
const COARSE_OPTIONS: PositionOptions = {enableHighAccuracy: false, maximumAge: 30_000, timeout: 30_000};
|
||||
|
||||
/**
|
||||
* Precise (GPS) location: for following the user on the map
|
||||
*/
|
||||
const PRECISE_OPTIONS: PositionOptions = {enableHighAccuracy: true, maximumAge: 0, timeout: 30_000};
|
||||
|
||||
/**
|
||||
* Permission states as defined by Capacitor (`PermissionState` is a type only, it has no runtime constants)
|
||||
*/
|
||||
export const PERMISSION_GRANTED = 'granted' satisfies PermissionState;
|
||||
export const PERMISSION_DENIED = 'denied' satisfies PermissionState;
|
||||
|
||||
/**
|
||||
* Location may be used. On Android, `location` is only granted for *precise* location;
|
||||
* if the user chose "approximate", only `coarseLocation` is granted, which is enough for us
|
||||
* (Android then just ignores enableHighAccuracy).
|
||||
*/
|
||||
function isGranted(status: PermissionStatus): boolean {
|
||||
return status.location === PERMISSION_GRANTED || status.coarseLocation === PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user refused both precise and approximate location
|
||||
*/
|
||||
function isDenied(status: PermissionStatus): boolean {
|
||||
return status.location === PERMISSION_DENIED && status.coarseLocation === PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits when the app comes back to the foreground (e.g. from the system settings)
|
||||
*/
|
||||
const appResumed$ = new Observable<void>(subscriber => {
|
||||
const listener = App.addListener('resume', () => subscriber.next());
|
||||
return () => void listener.then(handle => handle.remove());
|
||||
});
|
||||
|
||||
/**
|
||||
* Wraps a Capacitor position watch in an Observable.
|
||||
* Passes the real error on, and only clears the watch if it is still alive
|
||||
* (the plugin removes a watch by itself after an error).
|
||||
*/
|
||||
function nativeWatch(options: PositionOptions): Observable<Position> {
|
||||
return new Observable<Position>(subscriber => {
|
||||
let active = true;
|
||||
const watcherID = Geolocation.watchPosition(options, (position, error) => {
|
||||
if (error) {
|
||||
active = false;
|
||||
subscriber.error(error);
|
||||
} else if (position) {
|
||||
subscriber.next(position);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
const wasActive = active;
|
||||
active = false;
|
||||
void watcherID.then(id => (wasActive ? Geolocation.clearWatch({id}) : undefined)).catch(() => {});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface Coordinates {
|
||||
/**
|
||||
@@ -39,29 +123,87 @@ export interface MapPosition extends Coordinates {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PositionService {
|
||||
geoLocation = new Observable<Position>(subscriber => {
|
||||
const watcherID = Geolocation.checkPermissions().then(permissions => {
|
||||
if (permissions.location === 'granted') {
|
||||
return Geolocation.watchPosition({}, (position, error) => {
|
||||
if (error) {
|
||||
subscriber.error(position);
|
||||
} else if (position) {
|
||||
subscriber.next(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
return {
|
||||
unsubscribe() {
|
||||
watcherID.then(id => {
|
||||
if (id) {
|
||||
Geolocation.clearWatch({id});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* How many subscribers currently need precise (GPS) location, see preciseLocation
|
||||
*/
|
||||
private readonly preciseSubscribers$ = new BehaviorSubject(0);
|
||||
|
||||
/**
|
||||
* Emits after requestPermission() showed the permission prompt
|
||||
*/
|
||||
private readonly permissionRequested$ = new Subject<void>();
|
||||
|
||||
/**
|
||||
* When to (re-)check whether location permission has been granted.
|
||||
* Web: every 2 s, browsers don't tell us when the user clicks "Allow".
|
||||
* Device: permission only changes via our prompt or the system settings (and coming back
|
||||
* from there resumes the app), so no polling is needed.
|
||||
*/
|
||||
private readonly permissionChecks$: Observable<unknown> = Capacitor.isNativePlatform()
|
||||
? merge(of(true), this.permissionRequested$, appResumed$)
|
||||
: timer(0, 2000);
|
||||
|
||||
/**
|
||||
* The device position, shared by everything that needs it (list distances, map, ...).
|
||||
*
|
||||
* - Only one native watch, however many subscribers (before: one per list item)
|
||||
* - Waits until location permission is granted, also if that happens later (e.g. via the prompt)
|
||||
* - New subscribers immediately get the last position
|
||||
* - Keeps running for 30 s without subscribers, so a list re-render doesn't restart it
|
||||
* - Retries after errors (e.g. timeouts indoors) instead of breaking all subscribers
|
||||
* - Coarse by default; switches the same watch to GPS while someone uses preciseLocation
|
||||
*/
|
||||
readonly geoLocation: Observable<Position> = defer(() =>
|
||||
this.permissionChecks$.pipe(
|
||||
switchMap(() =>
|
||||
from(Geolocation.checkPermissions()).pipe(
|
||||
// no Permissions API (some browsers): just start, watchPosition prompts / fails by itself
|
||||
catchError(() =>
|
||||
of<PermissionStatus>({location: PERMISSION_GRANTED, coarseLocation: PERMISSION_GRANTED}),
|
||||
),
|
||||
),
|
||||
),
|
||||
first(isGranted),
|
||||
// restart the one watch with other options whenever the need for precision changes
|
||||
switchMap(() =>
|
||||
this.preciseSubscribers$.pipe(
|
||||
map(count => count > 0),
|
||||
distinctUntilChanged(),
|
||||
switchMap(precise => nativeWatch(precise ? PRECISE_OPTIONS : COARSE_OPTIONS)),
|
||||
),
|
||||
),
|
||||
retry({delay: () => timer(10_000)}),
|
||||
),
|
||||
).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject<Position>(1),
|
||||
resetOnRefCountZero: () => timer(30_000),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Same positions as geoLocation, but while subscribed, the shared watch uses GPS.
|
||||
* For actively following the user (map); falls back to coarse when the last one unsubscribes.
|
||||
*/
|
||||
readonly preciseLocation: Observable<Position> = defer(() => {
|
||||
this.preciseSubscribers$.next(this.preciseSubscribers$.value + 1);
|
||||
return this.geoLocation;
|
||||
}).pipe(finalize(() => this.preciseSubscribers$.next(this.preciseSubscribers$.value - 1)));
|
||||
|
||||
/**
|
||||
* Shows the permission prompt if permission hasn't been decided yet
|
||||
* @returns whether location may be used
|
||||
*/
|
||||
async requestPermission(): Promise<typeof PERMISSION_GRANTED | typeof PERMISSION_DENIED> {
|
||||
const status = await Geolocation.checkPermissions().catch(() => {});
|
||||
if (status && isGranted(status)) return PERMISSION_GRANTED;
|
||||
if (status && isDenied(status)) return PERMISSION_DENIED;
|
||||
// getCurrentPosition shows the prompt on web, Android and iOS
|
||||
await Geolocation.getCurrentPosition(COARSE_OPTIONS).catch(() => {});
|
||||
this.permissionRequested$.next();
|
||||
const after = await Geolocation.checkPermissions().catch(() => {});
|
||||
return after && isDenied(after) ? PERMISSION_DENIED : PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current position
|
||||
@@ -108,29 +250,17 @@ export class PositionService {
|
||||
* @param options Options which define which data should be provided (e.g., how accurate or how old)
|
||||
*/
|
||||
watchCurrentLocation(options: PositionOptions = {}): Observable<MapPosition> {
|
||||
return new Observable(subscriber => {
|
||||
const watcherID = Geolocation.watchPosition(options, (position, error) => {
|
||||
if (error) {
|
||||
subscriber.error(position);
|
||||
} else {
|
||||
this.position = {
|
||||
// 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
|
||||
};
|
||||
|
||||
subscriber.next(this.position);
|
||||
}
|
||||
});
|
||||
return {
|
||||
unsubscribe() {
|
||||
watcherID.then(id => {
|
||||
void Geolocation.clearWatch({id});
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
return nativeWatch(options).pipe(
|
||||
map(position => {
|
||||
this.position = {
|
||||
// TODO use native compass heading instead
|
||||
// waiting for https://github.com/ionic-team/capacitor-plugins/issues/1192
|
||||
heading: undefined,
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude,
|
||||
};
|
||||
return this.position;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user