MOBILE-3489 location: Expose geolocation service to plugins
parent
2c477ac93a
commit
af5f010239
|
@ -14,15 +14,10 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormBuilder } from '@angular/forms';
|
import { FormBuilder } from '@angular/forms';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { Platform } from 'ionic-angular';
|
|
||||||
import { Geolocation } from '@ionic-native/geolocation';
|
|
||||||
import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
|
import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
|
||||||
import { CoreApp, CoreAppProvider } from '@providers/app';
|
import { CoreApp, CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreGeolocation, CoreGeolocationError, CoreGeolocationErrorReason } from '@providers/geolocation';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { Diagnostic } from '@singletons/core.singletons';
|
|
||||||
import { CoreError } from '@classes/error';
|
|
||||||
|
|
||||||
class AccessLocationError extends CoreError {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to render data latlong field.
|
* Component to render data latlong field.
|
||||||
|
@ -37,13 +32,11 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
|
||||||
east: number;
|
east: number;
|
||||||
showGeolocation: boolean;
|
showGeolocation: boolean;
|
||||||
|
|
||||||
constructor(protected fb: FormBuilder,
|
constructor(
|
||||||
protected platform: Platform,
|
protected fb: FormBuilder,
|
||||||
protected geolocation: Geolocation,
|
|
||||||
protected domUtils: CoreDomUtilsProvider,
|
protected domUtils: CoreDomUtilsProvider,
|
||||||
protected sanitizer: DomSanitizer,
|
protected sanitizer: DomSanitizer,
|
||||||
appProvider: CoreAppProvider
|
appProvider: CoreAppProvider) {
|
||||||
) {
|
|
||||||
super(fb);
|
super(fb);
|
||||||
|
|
||||||
this.showGeolocation = !appProvider.isDesktop();
|
this.showGeolocation = !appProvider.isDesktop();
|
||||||
|
@ -126,107 +119,25 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
|
||||||
const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true);
|
const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.updateLocation();
|
const coordinates = await CoreGeolocation.instance.getCoordinates();
|
||||||
|
|
||||||
|
this.form.controls['f_' + this.field.id + '_0'].setValue(coordinates.latitude);
|
||||||
|
this.form.controls['f_' + this.field.id + '_1'].setValue(coordinates.longitude);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showErrorModal(error);
|
this.showLocationErrorModal(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update component location.
|
* Show the appropriate error modal for the given error getting the location.
|
||||||
*/
|
|
||||||
protected async updateLocation(): Promise<void> {
|
|
||||||
await this.authorizeLocation();
|
|
||||||
await this.enableLocation();
|
|
||||||
|
|
||||||
const result = await this.geolocation.getCurrentPosition({
|
|
||||||
enableHighAccuracy: true,
|
|
||||||
timeout: 30000,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.form.controls['f_' + this.field.id + '_0'].setValue(result.coords.latitude);
|
|
||||||
this.form.controls['f_' + this.field.id + '_1'].setValue(result.coords.longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure that using device location has been authorize and ask for permission if it hasn't.
|
|
||||||
*
|
*
|
||||||
* @param failOnDeniedOnce Throw an exception if the permission has been denied once.
|
* @param error Location error.
|
||||||
*/
|
*/
|
||||||
protected async authorizeLocation(failOnDeniedOnce: boolean = false): Promise<void> {
|
protected showLocationErrorModal(error: any): void {
|
||||||
const authorizationStatus = await Diagnostic.instance.getLocationAuthorizationStatus();
|
if (error instanceof CoreGeolocationError) {
|
||||||
|
this.domUtils.showErrorModal(this.getGeolocationErrorMessage(error), true);
|
||||||
switch (authorizationStatus) {
|
|
||||||
// This constant is hard-coded because it is not declared in @ionic-native/diagnostic v4.
|
|
||||||
case 'DENIED_ONCE':
|
|
||||||
if (failOnDeniedOnce) {
|
|
||||||
throw new AccessLocationError('addon.mod_data.locationpermissiondenied');
|
|
||||||
}
|
|
||||||
// Fall through.
|
|
||||||
case Diagnostic.instance.permissionStatus.NOT_REQUESTED:
|
|
||||||
await Diagnostic.instance.requestLocationAuthorization();
|
|
||||||
await CoreApp.instance.waitForResume(500);
|
|
||||||
await this.authorizeLocation(true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
case Diagnostic.instance.permissionStatus.GRANTED:
|
|
||||||
case Diagnostic.instance.permissionStatus.GRANTED_WHEN_IN_USE:
|
|
||||||
// Location is authorized.
|
|
||||||
return;
|
|
||||||
case Diagnostic.instance.permissionStatus.DENIED:
|
|
||||||
default:
|
|
||||||
throw new AccessLocationError('addon.mod_data.locationpermissiondenied');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure that location is enabled and switch to settings if it hasn't.
|
|
||||||
*/
|
|
||||||
protected async enableLocation(): Promise<void> {
|
|
||||||
let locationEnabled = await Diagnostic.instance.isLocationEnabled();
|
|
||||||
|
|
||||||
if (locationEnabled) {
|
|
||||||
// Location is enabled.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CoreApp.instance.isIOS()) {
|
|
||||||
await Diagnostic.instance.switchToLocationSettings();
|
|
||||||
await CoreApp.instance.waitForResume(30000);
|
|
||||||
|
|
||||||
locationEnabled = await Diagnostic.instance.isLocationEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!locationEnabled) {
|
|
||||||
throw new AccessLocationError('addon.mod_data.locationnotenabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an error was caused by a PERMISSION_DENIED.
|
|
||||||
*
|
|
||||||
* @param error Error.
|
|
||||||
*/
|
|
||||||
protected isPermissionDeniedError(error?: any): boolean {
|
|
||||||
return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the appropriate error modal for the given error.
|
|
||||||
*
|
|
||||||
* @param error Error.
|
|
||||||
*/
|
|
||||||
protected showErrorModal(error: any): void {
|
|
||||||
if (error instanceof AccessLocationError) {
|
|
||||||
this.domUtils.showErrorModal(error.message, true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isPermissionDeniedError(error)) {
|
|
||||||
this.domUtils.showErrorModal('addon.mod_data.locationpermissiondenied', true);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -234,4 +145,19 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
|
||||||
this.domUtils.showErrorModalDefault(error, 'Error getting location');
|
this.domUtils.showErrorModalDefault(error, 'Error getting location');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get error message from a geolocation error.
|
||||||
|
*
|
||||||
|
* @param error Geolocation error.
|
||||||
|
*/
|
||||||
|
protected getGeolocationErrorMessage(error: CoreGeolocationError): string {
|
||||||
|
// tslint:disable-next-line: switch-default
|
||||||
|
switch (error.reason) {
|
||||||
|
case CoreGeolocationErrorReason.PermissionDenied:
|
||||||
|
return 'addon.mod_data.locationpermissiondenied';
|
||||||
|
case CoreGeolocationErrorReason.LocationNotEnabled:
|
||||||
|
return 'addon.mod_data.locationnotenabled';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
|
|
||||||
import { Diagnostic } from '@ionic-native/diagnostic';
|
import { Diagnostic } from '@ionic-native/diagnostic';
|
||||||
|
import { Geolocation } from '@ionic-native/geolocation';
|
||||||
import { ScreenOrientation } from '@ionic-native/screen-orientation';
|
import { ScreenOrientation } from '@ionic-native/screen-orientation';
|
||||||
|
|
||||||
import { MoodleMobileApp } from './app.component';
|
import { MoodleMobileApp } from './app.component';
|
||||||
|
@ -60,6 +61,7 @@ import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
|
||||||
import { CoreSyncProvider } from '@providers/sync';
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
import { CoreFileHelperProvider } from '@providers/file-helper';
|
import { CoreFileHelperProvider } from '@providers/file-helper';
|
||||||
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
|
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
|
||||||
|
import { CoreGeolocationProvider } from '@providers/geolocation';
|
||||||
|
|
||||||
// Handlers.
|
// Handlers.
|
||||||
import { CoreSiteInfoCronHandler } from '@providers/handlers/site-info-cron-handler';
|
import { CoreSiteInfoCronHandler } from '@providers/handlers/site-info-cron-handler';
|
||||||
|
@ -195,7 +197,8 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
CorePluginFileDelegate,
|
CorePluginFileDelegate,
|
||||||
CoreSyncProvider,
|
CoreSyncProvider,
|
||||||
CoreFileHelperProvider,
|
CoreFileHelperProvider,
|
||||||
CoreCustomURLSchemesProvider
|
CoreCustomURLSchemesProvider,
|
||||||
|
CoreGeolocationProvider,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const WP_PROVIDER: any = null;
|
export const WP_PROVIDER: any = null;
|
||||||
|
@ -343,6 +346,7 @@ export const WP_PROVIDER: any = null;
|
||||||
CoreSyncProvider,
|
CoreSyncProvider,
|
||||||
CoreFileHelperProvider,
|
CoreFileHelperProvider,
|
||||||
CoreCustomURLSchemesProvider,
|
CoreCustomURLSchemesProvider,
|
||||||
|
CoreGeolocationProvider,
|
||||||
CoreSiteInfoCronHandler,
|
CoreSiteInfoCronHandler,
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
@ -350,6 +354,7 @@ export const WP_PROVIDER: any = null;
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
Diagnostic,
|
Diagnostic,
|
||||||
|
Geolocation,
|
||||||
ScreenOrientation,
|
ScreenOrientation,
|
||||||
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
|
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
|
||||||
{provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
|
{provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
|
||||||
|
|
|
@ -67,6 +67,7 @@ import { CoreContentLinksModuleGradeHandler } from '@core/contentlinks/classes/m
|
||||||
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||||
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
|
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
|
||||||
import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler';
|
import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler';
|
||||||
|
import { CoreGeolocationError, CoreGeolocationErrorReason } from '@providers/geolocation';
|
||||||
|
|
||||||
// Import all core modules that define components, directives and pipes.
|
// Import all core modules that define components, directives and pipes.
|
||||||
import { CoreComponentsModule } from '@components/components.module';
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
@ -294,6 +295,8 @@ export class CoreCompileProvider {
|
||||||
instance['CoreSitePluginsQuizAccessRuleComponent'] = CoreSitePluginsQuizAccessRuleComponent;
|
instance['CoreSitePluginsQuizAccessRuleComponent'] = CoreSitePluginsQuizAccessRuleComponent;
|
||||||
instance['CoreSitePluginsAssignFeedbackComponent'] = CoreSitePluginsAssignFeedbackComponent;
|
instance['CoreSitePluginsAssignFeedbackComponent'] = CoreSitePluginsAssignFeedbackComponent;
|
||||||
instance['CoreSitePluginsAssignSubmissionComponent'] = CoreSitePluginsAssignSubmissionComponent;
|
instance['CoreSitePluginsAssignSubmissionComponent'] = CoreSitePluginsAssignSubmissionComponent;
|
||||||
|
instance['CoreGeolocationError'] = CoreGeolocationError;
|
||||||
|
instance['CoreGeolocationErrorReason'] = CoreGeolocationErrorReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Coordinates } from '@ionic-native/geolocation';
|
||||||
|
import { CoreApp } from '@providers/app';
|
||||||
|
import { Geolocation, Diagnostic, makeSingleton } from '@singletons/core.singletons';
|
||||||
|
import { CoreError } from '@classes/error';
|
||||||
|
|
||||||
|
export enum CoreGeolocationErrorReason {
|
||||||
|
PermissionDenied = 'permission-denied',
|
||||||
|
LocationNotEnabled = 'location-not-enabled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreGeolocationError extends CoreError {
|
||||||
|
|
||||||
|
readonly reason: CoreGeolocationErrorReason;
|
||||||
|
|
||||||
|
constructor(reason: CoreGeolocationErrorReason) {
|
||||||
|
super(`GeolocationError: ${reason}`);
|
||||||
|
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CoreGeolocationProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user coordinates.
|
||||||
|
*
|
||||||
|
* @throws {CoreGeolocationError}
|
||||||
|
*/
|
||||||
|
async getCoordinates(): Promise<Coordinates> {
|
||||||
|
try {
|
||||||
|
await this.authorizeLocation();
|
||||||
|
await this.enableLocation();
|
||||||
|
|
||||||
|
const result = await Geolocation.instance.getCurrentPosition({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.coords;
|
||||||
|
} catch (error) {
|
||||||
|
if (this.isCordovaPermissionDeniedError(error)) {
|
||||||
|
throw new CoreGeolocationError(CoreGeolocationErrorReason.PermissionDenied);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that using device location has been authorized and ask for permission if it hasn't.
|
||||||
|
*
|
||||||
|
* @throws {CoreGeolocationError}
|
||||||
|
*/
|
||||||
|
async authorizeLocation(): Promise<void> {
|
||||||
|
await this.doAuthorizeLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that location is enabled and open settings to enable it if necessary.
|
||||||
|
*
|
||||||
|
* @throws {CoreGeolocationError}
|
||||||
|
*/
|
||||||
|
async enableLocation(): Promise<void> {
|
||||||
|
let locationEnabled = await Diagnostic.instance.isLocationEnabled();
|
||||||
|
|
||||||
|
if (locationEnabled) {
|
||||||
|
// Location is enabled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CoreApp.instance.isIOS()) {
|
||||||
|
await Diagnostic.instance.switchToLocationSettings();
|
||||||
|
await CoreApp.instance.waitForResume(30000);
|
||||||
|
|
||||||
|
locationEnabled = await Diagnostic.instance.isLocationEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locationEnabled) {
|
||||||
|
throw new CoreGeolocationError(CoreGeolocationErrorReason.LocationNotEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive implementation of authorizeLocation method, protected to avoid exposing the failOnDeniedOnce parameter.
|
||||||
|
*
|
||||||
|
* @param failOnDeniedOnce Throw an exception if the permission has been denied once.
|
||||||
|
* @throws {CoreGeolocationError}
|
||||||
|
*/
|
||||||
|
protected async doAuthorizeLocation(failOnDeniedOnce: boolean = false): Promise<void> {
|
||||||
|
const authorizationStatus = await Diagnostic.instance.getLocationAuthorizationStatus();
|
||||||
|
|
||||||
|
switch (authorizationStatus) {
|
||||||
|
// This constant is hard-coded because it is not declared in @ionic-native/diagnostic v4.
|
||||||
|
case 'DENIED_ONCE':
|
||||||
|
if (failOnDeniedOnce) {
|
||||||
|
throw new CoreGeolocationError(CoreGeolocationErrorReason.PermissionDenied);
|
||||||
|
}
|
||||||
|
// Fall through.
|
||||||
|
case Diagnostic.instance.permissionStatus.NOT_REQUESTED:
|
||||||
|
await Diagnostic.instance.requestLocationAuthorization();
|
||||||
|
await CoreApp.instance.waitForResume(500);
|
||||||
|
await this.doAuthorizeLocation(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
case Diagnostic.instance.permissionStatus.GRANTED:
|
||||||
|
case Diagnostic.instance.permissionStatus.GRANTED_WHEN_IN_USE:
|
||||||
|
// Location is authorized.
|
||||||
|
return;
|
||||||
|
case Diagnostic.instance.permissionStatus.DENIED:
|
||||||
|
default:
|
||||||
|
throw new CoreGeolocationError(CoreGeolocationErrorReason.PermissionDenied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an error was caused by a PERMISSION_DENIED from the cordova plugin.
|
||||||
|
*
|
||||||
|
* @param error Error.
|
||||||
|
*/
|
||||||
|
protected isCordovaPermissionDeniedError(error?: any): boolean {
|
||||||
|
return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreGeolocation extends makeSingleton(CoreGeolocationProvider) {}
|
|
@ -16,6 +16,7 @@ import { AlertController, App } from 'ionic-angular';
|
||||||
import { Injector } from '@angular/core';
|
import { Injector } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Geolocation as GeolocationService } from '@ionic-native/geolocation';
|
||||||
import { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic';
|
import { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic';
|
||||||
|
|
||||||
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
|
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
|
||||||
|
@ -49,4 +50,6 @@ export class Ionic extends makeSingleton(App) {}
|
||||||
|
|
||||||
export class Diagnostic extends makeSingleton(DiagnosticService) {}
|
export class Diagnostic extends makeSingleton(DiagnosticService) {}
|
||||||
|
|
||||||
|
export class Geolocation extends makeSingleton(GeolocationService) {}
|
||||||
|
|
||||||
export class Http extends makeSingleton(HttpClient) {}
|
export class Http extends makeSingleton(HttpClient) {}
|
||||||
|
|
Loading…
Reference in New Issue