MOBILE-3489 location: Use diagnostic plugin to enable location
parent
59d4e189c8
commit
2c477ac93a
File diff suppressed because it is too large
Load Diff
|
@ -61,6 +61,7 @@
|
||||||
"@ionic-native/clipboard": "^4.20.0",
|
"@ionic-native/clipboard": "^4.20.0",
|
||||||
"@ionic-native/core": "^4.20.0",
|
"@ionic-native/core": "^4.20.0",
|
||||||
"@ionic-native/device": "^4.20.0",
|
"@ionic-native/device": "^4.20.0",
|
||||||
|
"@ionic-native/diagnostic": "^4.2.0",
|
||||||
"@ionic-native/file": "^4.20.0",
|
"@ionic-native/file": "^4.20.0",
|
||||||
"@ionic-native/file-opener": "^4.20.0",
|
"@ionic-native/file-opener": "^4.20.0",
|
||||||
"@ionic-native/file-transfer": "^4.20.0",
|
"@ionic-native/file-transfer": "^4.20.0",
|
||||||
|
@ -147,6 +148,7 @@
|
||||||
"@types/node": "^8.10.59",
|
"@types/node": "^8.10.59",
|
||||||
"@types/promise.prototype.finally": "^2.0.4",
|
"@types/promise.prototype.finally": "^2.0.4",
|
||||||
"acorn": "^5.7.4",
|
"acorn": "^5.7.4",
|
||||||
|
"cordova.plugins.diagnostic": "^5.0.2",
|
||||||
"electron-builder-lib": "^20.23.1",
|
"electron-builder-lib": "^20.23.1",
|
||||||
"electron-rebuild": "^1.10.0",
|
"electron-rebuild": "^1.10.0",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
|
@ -223,7 +225,8 @@
|
||||||
"cordova-plugin-wkuserscript": {},
|
"cordova-plugin-wkuserscript": {},
|
||||||
"cordova-plugin-media": {
|
"cordova-plugin-media": {
|
||||||
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
|
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
|
||||||
}
|
},
|
||||||
|
"cordova.plugins.diagnostic": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"main": "desktop/electron.js",
|
"main": "desktop/electron.js",
|
||||||
|
|
|
@ -15,10 +15,14 @@ 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 { Platform } from 'ionic-angular';
|
||||||
import { Geolocation, GeolocationOptions } from '@ionic-native/geolocation';
|
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 { 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.
|
||||||
|
@ -116,33 +120,118 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
|
||||||
*
|
*
|
||||||
* @param $event The event.
|
* @param $event The event.
|
||||||
*/
|
*/
|
||||||
getLocation(event: Event): void {
|
async getLocation(event: Event): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true);
|
const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true);
|
||||||
|
|
||||||
const options: GeolocationOptions = {
|
try {
|
||||||
enableHighAccuracy: true,
|
await this.updateLocation();
|
||||||
timeout: 30000
|
} catch (error) {
|
||||||
};
|
this.showErrorModal(error);
|
||||||
|
}
|
||||||
|
|
||||||
this.geolocation.getCurrentPosition(options).then((result) => {
|
modal.dismiss();
|
||||||
this.form.controls['f_' + this.field.id + '_0'].setValue(result.coords.latitude);
|
|
||||||
this.form.controls['f_' + this.field.id + '_1'].setValue(result.coords.longitude);
|
|
||||||
}).catch((error) => {
|
|
||||||
if (this.isPermissionDeniedError(error)) {
|
|
||||||
this.domUtils.showErrorModal('addon.mod_data.locationpermissiondenied', true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.domUtils.showErrorModalDefault(error, 'Error getting location');
|
|
||||||
}).finally(() => {
|
|
||||||
modal.dismiss();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update component 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.
|
||||||
|
*/
|
||||||
|
protected async authorizeLocation(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 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 {
|
protected isPermissionDeniedError(error?: any): boolean {
|
||||||
return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'Error getting location');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"gettinglocation": "Getting location",
|
"gettinglocation": "Getting location",
|
||||||
"latlongboth": "Both latitude and longitude are required.",
|
"latlongboth": "Both latitude and longitude are required.",
|
||||||
"locationpermissiondenied": "Permission to access your location has been denied.",
|
"locationpermissiondenied": "Permission to access your location has been denied.",
|
||||||
|
"locationnotenabled": "Location is not enabled",
|
||||||
"menuchoose": "Choose...",
|
"menuchoose": "Choose...",
|
||||||
"modulenameplural": "Databases",
|
"modulenameplural": "Databases",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { MockLocationStrategy } from '@angular/common/testing';
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
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 { ScreenOrientation } from '@ionic-native/screen-orientation';
|
import { ScreenOrientation } from '@ionic-native/screen-orientation';
|
||||||
|
|
||||||
import { MoodleMobileApp } from './app.component';
|
import { MoodleMobileApp } from './app.component';
|
||||||
|
@ -348,6 +349,7 @@ export const WP_PROVIDER: any = null;
|
||||||
useClass: CoreInterceptor,
|
useClass: CoreInterceptor,
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
|
Diagnostic,
|
||||||
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]},
|
||||||
|
|
|
@ -508,6 +508,7 @@
|
||||||
"addon.mod_data.foundrecords": "Found records: {{$a.num}}/{{$a.max}} (<a href=\"{{$a.reseturl}}\">Reset filters</a>)",
|
"addon.mod_data.foundrecords": "Found records: {{$a.num}}/{{$a.max}} (<a href=\"{{$a.reseturl}}\">Reset filters</a>)",
|
||||||
"addon.mod_data.gettinglocation": "Getting location",
|
"addon.mod_data.gettinglocation": "Getting location",
|
||||||
"addon.mod_data.latlongboth": "Both latitude and longitude are required.",
|
"addon.mod_data.latlongboth": "Both latitude and longitude are required.",
|
||||||
|
"addon.mod_data.locationnotenabled": "Location is not enabled",
|
||||||
"addon.mod_data.locationpermissiondenied": "Permission to access your location has been denied.",
|
"addon.mod_data.locationpermissiondenied": "Permission to access your location has been denied.",
|
||||||
"addon.mod_data.menuchoose": "Choose...",
|
"addon.mod_data.menuchoose": "Choose...",
|
||||||
"addon.mod_data.modulenameplural": "Databases",
|
"addon.mod_data.modulenameplural": "Databases",
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Error class.
|
||||||
|
*
|
||||||
|
* The native Error class cannot be extended in Typescript without restoring the prototype chain, extend this
|
||||||
|
* class instead.
|
||||||
|
*
|
||||||
|
* @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class
|
||||||
|
*/
|
||||||
|
export class CoreError extends Error {
|
||||||
|
|
||||||
|
constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
// Fix prototype chain: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
||||||
|
this.name = new.target.name;
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -654,6 +654,35 @@ export class CoreAppProvider {
|
||||||
return this.ssoAuthenticationPromise || Promise.resolve();
|
return this.ssoAuthenticationPromise || Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until the application is resumed.
|
||||||
|
*
|
||||||
|
* @param timeout Maximum time to wait, use null to wait forever.
|
||||||
|
*/
|
||||||
|
async waitForResume(timeout: number | null = null): Promise<void> {
|
||||||
|
let resolve: Function;
|
||||||
|
let resumeSubscription: any;
|
||||||
|
let timeoutId: NodeJS.Timer | false;
|
||||||
|
|
||||||
|
const promise = new Promise((r): any => resolve = r);
|
||||||
|
const stopWaiting = (): any => {
|
||||||
|
if (!resolve) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
resumeSubscription.unsubscribe();
|
||||||
|
timeoutId && clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
resolve = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
resumeSubscription = this.platform.resume.subscribe(stopWaiting);
|
||||||
|
timeoutId = timeout ? setTimeout(stopWaiting, timeout) : false;
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve redirect data.
|
* Retrieve redirect data.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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 { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic';
|
||||||
|
|
||||||
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
|
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
|
||||||
|
|
||||||
|
@ -46,4 +47,6 @@ export class Alerts extends makeSingleton(AlertController) {}
|
||||||
|
|
||||||
export class Ionic extends makeSingleton(App) {}
|
export class Ionic extends makeSingleton(App) {}
|
||||||
|
|
||||||
|
export class Diagnostic extends makeSingleton(DiagnosticService) {}
|
||||||
|
|
||||||
export class Http extends makeSingleton(HttpClient) {}
|
export class Http extends makeSingleton(HttpClient) {}
|
||||||
|
|
Loading…
Reference in New Issue