Merge pull request #2533 from NoelDeMartin/MOBILE-3489

Mobile 3489
main
Juan Leyva 2020-09-22 12:57:05 +02:00 committed by GitHub
commit 46df6f2989
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 427 additions and 151 deletions

279
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -61,6 +61,7 @@
"@ionic-native/clipboard": "^4.20.0",
"@ionic-native/core": "^4.20.0",
"@ionic-native/device": "^4.20.0",
"@ionic-native/diagnostic": "^4.2.0",
"@ionic-native/file": "^4.20.0",
"@ionic-native/file-opener": "^4.20.0",
"@ionic-native/file-transfer": "^4.20.0",
@ -147,6 +148,7 @@
"@types/node": "^8.10.59",
"@types/promise.prototype.finally": "^2.0.4",
"acorn": "^5.7.4",
"cordova.plugins.diagnostic": "^5.0.2",
"electron-builder-lib": "^20.23.1",
"electron-rebuild": "^1.10.0",
"gulp": "4.0.2",
@ -223,7 +225,8 @@
"cordova-plugin-wkuserscript": {},
"cordova-plugin-media": {
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
}
},
"cordova.plugins.diagnostic": {}
}
},
"main": "desktop/electron.js",

View File

@ -14,10 +14,9 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Platform } from 'ionic-angular';
import { Geolocation, GeolocationOptions } from '@ionic-native/geolocation';
import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
import { CoreApp, CoreAppProvider } from '@providers/app';
import { CoreGeolocation, CoreGeolocationError, CoreGeolocationErrorReason } from '@providers/geolocation';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
@ -33,13 +32,11 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
east: number;
showGeolocation: boolean;
constructor(protected fb: FormBuilder,
protected platform: Platform,
protected geolocation: Geolocation,
constructor(
protected fb: FormBuilder,
protected domUtils: CoreDomUtilsProvider,
protected sanitizer: DomSanitizer,
appProvider: CoreAppProvider
) {
appProvider: CoreAppProvider) {
super(fb);
this.showGeolocation = !appProvider.isDesktop();
@ -116,33 +113,51 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
*
* @param $event The event.
*/
getLocation(event: Event): void {
async getLocation(event: Event): Promise<void> {
event.preventDefault();
const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true);
const options: GeolocationOptions = {
enableHighAccuracy: true,
timeout: 30000
};
try {
const coordinates = await CoreGeolocation.instance.getCoordinates();
this.geolocation.getCurrentPosition(options).then((result) => {
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);
this.form.controls['f_' + this.field.id + '_0'].setValue(coordinates.latitude);
this.form.controls['f_' + this.field.id + '_1'].setValue(coordinates.longitude);
} catch (error) {
this.showLocationErrorModal(error);
}
return;
}
this.domUtils.showErrorModalDefault(error, 'Error getting location');
}).finally(() => {
modal.dismiss();
});
modal.dismiss();
}
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 getting the location.
*
* @param error Location error.
*/
protected showLocationErrorModal(error: any): void {
if (error instanceof CoreGeolocationError) {
this.domUtils.showErrorModal(this.getGeolocationErrorMessage(error), true);
return;
}
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';
}
}
}

View File

@ -23,6 +23,7 @@
"gettinglocation": "Getting location",
"latlongboth": "Both latitude and longitude are required.",
"locationpermissiondenied": "Permission to access your location has been denied.",
"locationnotenabled": "Location is not enabled",
"menuchoose": "Choose...",
"modulenameplural": "Databases",
"more": "More",

View File

@ -25,6 +25,8 @@ import { MockLocationStrategy } from '@angular/common/testing';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { Diagnostic } from '@ionic-native/diagnostic';
import { Geolocation } from '@ionic-native/geolocation';
import { ScreenOrientation } from '@ionic-native/screen-orientation';
import { MoodleMobileApp } from './app.component';
@ -59,6 +61,7 @@ import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
import { CoreSyncProvider } from '@providers/sync';
import { CoreFileHelperProvider } from '@providers/file-helper';
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
import { CoreGeolocationProvider } from '@providers/geolocation';
// Handlers.
import { CoreSiteInfoCronHandler } from '@providers/handlers/site-info-cron-handler';
@ -194,7 +197,8 @@ export const CORE_PROVIDERS: any[] = [
CorePluginFileDelegate,
CoreSyncProvider,
CoreFileHelperProvider,
CoreCustomURLSchemesProvider
CoreCustomURLSchemesProvider,
CoreGeolocationProvider,
];
export const WP_PROVIDER: any = null;
@ -342,12 +346,15 @@ export const WP_PROVIDER: any = null;
CoreSyncProvider,
CoreFileHelperProvider,
CoreCustomURLSchemesProvider,
CoreGeolocationProvider,
CoreSiteInfoCronHandler,
{
provide: HTTP_INTERCEPTORS,
useClass: CoreInterceptor,
multi: true,
},
Diagnostic,
Geolocation,
ScreenOrientation,
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},

View File

@ -508,6 +508,7 @@
"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.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.menuchoose": "Choose...",
"addon.mod_data.modulenameplural": "Databases",

View File

@ -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);
}
}

View File

@ -69,6 +69,7 @@ import { CoreContentLinksModuleGradeHandler } from '@core/contentlinks/classes/m
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-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 { CoreComponentsModule } from '@components/components.module';
@ -299,6 +300,8 @@ export class CoreCompileProvider {
instance['CoreSitePluginsQuizAccessRuleComponent'] = CoreSitePluginsQuizAccessRuleComponent;
instance['CoreSitePluginsAssignFeedbackComponent'] = CoreSitePluginsAssignFeedbackComponent;
instance['CoreSitePluginsAssignSubmissionComponent'] = CoreSitePluginsAssignSubmissionComponent;
instance['CoreGeolocationError'] = CoreGeolocationError;
instance['CoreGeolocationErrorReason'] = CoreGeolocationErrorReason;
}
/**

View File

@ -654,6 +654,35 @@ export class CoreAppProvider {
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.
*

View File

@ -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) {}

View File

@ -16,6 +16,8 @@ import { AlertController, App } from 'ionic-angular';
import { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { Geolocation as GeolocationService } from '@ionic-native/geolocation';
import { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic';
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
@ -46,4 +48,8 @@ export class Alerts extends makeSingleton(AlertController) {}
export class Ionic extends makeSingleton(App) {}
export class Diagnostic extends makeSingleton(DiagnosticService) {}
export class Geolocation extends makeSingleton(GeolocationService) {}
export class Http extends makeSingleton(HttpClient) {}