MOBILE-3889 settings: Add developer options page

main
Pau Ferrer Ocaña 2021-10-07 14:09:12 +02:00
parent fbf495ca1b
commit f9f60414bb
11 changed files with 305 additions and 12 deletions

View File

@ -2090,10 +2090,12 @@
"core.settings.debugdisplaydescription": "local_moodlemobileapp",
"core.settings.deletesitefiles": "local_moodlemobileapp",
"core.settings.deletesitefilestitle": "local_moodlemobileapp",
"core.settings.developeroptions": "local_moodlemobileapp",
"core.settings.deviceinfo": "local_moodlemobileapp",
"core.settings.deviceos": "local_moodlemobileapp",
"core.settings.disableall": "message",
"core.settings.disabled": "lesson",
"core.settings.disabledfeatures": "tool_mobile",
"core.settings.displayformat": "local_moodlemobileapp",
"core.settings.enabledownloadsection": "local_moodlemobileapp",
"core.settings.enablefirebaseanalytics": "local_moodlemobileapp",
@ -2109,6 +2111,7 @@
"core.settings.fontsize": "local_moodlemobileapp",
"core.settings.fontsizecharacter": "block_accessibility/char",
"core.settings.forcedsetting": "local_moodlemobileapp",
"core.settings.forcesafeareamargins": "local_moodlemobileapp",
"core.settings.general": "moodle",
"core.settings.helpusimprove": "local_moodlemobileapp",
"core.settings.ioscookies": "local_moodlemobileapp",
@ -2124,15 +2127,18 @@
"core.settings.navigatoruseragent": "local_moodlemobileapp",
"core.settings.networkstatus": "local_moodlemobileapp",
"core.settings.opensourcelicenses": "local_moodlemobileapp",
"core.settings.pluginstyles": "local_moodlemobileapp",
"core.settings.preferences": "moodle",
"core.settings.privacypolicy": "local_moodlemobileapp",
"core.settings.publisher": "local_moodlemobileapp",
"core.settings.pushid": "local_moodlemobileapp",
"core.settings.remotestyles": "local_moodlemobileapp",
"core.settings.reportinbackground": "local_moodlemobileapp",
"core.settings.screen": "local_moodlemobileapp",
"core.settings.settings": "moodle",
"core.settings.showdownloadoptions": "local_moodlemobileapp",
"core.settings.siteinfo": "local_moodlemobileapp",
"core.settings.siteplugins": "local_moodlemobileapp",
"core.settings.sites": "moodle",
"core.settings.spaceusage": "local_moodlemobileapp",
"core.settings.spaceusagehelp": "local_moodlemobileapp",
@ -2140,8 +2146,10 @@
"core.settings.synchronizenow": "local_moodlemobileapp",
"core.settings.synchronizenowhelp": "local_moodlemobileapp",
"core.settings.syncsettings": "local_moodlemobileapp",
"core.settings.textdirection": "local_moodlemobileapp",
"core.settings.total": "moodle",
"core.settings.wificonnection": "local_moodlemobileapp",
"core.settings.youradev": "local_moodlemobileapp",
"core.sharedfiles.chooseaccountstorefile": "local_moodlemobileapp",
"core.sharedfiles.chooseactionrepeatedfile": "local_moodlemobileapp",
"core.sharedfiles.errorreceivefilenosites": "local_moodlemobileapp",

View File

@ -22,10 +22,12 @@
"debugdisplaydescription": "If enabled, error modals will display more data about the error if possible.",
"deletesitefiles": "Are you sure that you want to delete the downloaded files and cached data from the site '{{sitename}}'? You won't be able to use the app in offline mode.",
"deletesitefilestitle": "Delete site files",
"developeroptions": "Developer options",
"deviceinfo": "Device info",
"deviceos": "Device OS",
"disableall": "Disable notifications",
"disabled": "Disabled",
"disabledfeatures": "Disabled features",
"displayformat": "Display format",
"enabledownloadsection": "Enable download sections",
"enablefirebaseanalytics": "Enable Firebase analytics",
@ -41,6 +43,7 @@
"fontsize": "Text size",
"fontsizecharacter": "A",
"forcedsetting": "This setting has been forced by your site configuration.",
"forcesafeareamargins": "Force safe area margins",
"general": "General",
"helpusimprove": "Help us improve this app",
"ioscookies": "Cross-Website Tracking",
@ -56,15 +59,18 @@
"navigatoruseragent": "Navigator userAgent",
"networkstatus": "Internet connection status",
"opensourcelicenses": "Open Source Licences",
"pluginstyles": "Enable site plugin styles",
"preferences": "Preferences",
"privacypolicy": "Privacy policy",
"publisher": "Publisher",
"pushid": "Push notifications ID",
"remotestyles": "Enable remote styles",
"reportinbackground": "Report errors automatically",
"screen": "Screen information",
"settings": "Settings",
"showdownloadoptions": "Show download options",
"siteinfo": "Site info",
"siteplugins": "Site plugins",
"sites": "Sites",
"spaceusage": "Space usage",
"spaceusagehelp": "Deleting the stored information of the site will remove all the site offline data. This information allows you to use the app when offline. ",
@ -72,6 +78,8 @@
"synchronizenow": "Synchronise now",
"synchronizenowhelp": "Synchronising a site will send pending changes and all offline activity stored in the device and will synchronise some data like messages and notifications.",
"syncsettings": "Synchronisation settings",
"textdirection": "Text direction",
"total": "Total",
"wificonnection": "Wi-Fi connection"
"wificonnection": "Wi-Fi connection",
"youradev": "You are now a developer"
}

View File

@ -11,12 +11,9 @@
<ion-content>
<ion-list>
<ion-item class="ion-text-wrap">
<ion-label><h2>{{ appName }} {{ versionName }}</h2></ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="openPage('deviceinfo')" detail="true">
<ion-item button class="ion-text-wrap" detail="false" (click)="openPage('deviceinfo')">
<ion-icon name="fas-mobile" slot="start" aria-hidden="true"></ion-icon>
<ion-label>{{ 'core.settings.deviceinfo' | translate }}</ion-label>
<ion-label><h2>{{ appName }} {{ versionName }}</h2></ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="openPage('licenses')" detail="true">
<ion-icon name="far-copyright" slot="start" aria-hidden="true"></ion-icon>

View File

@ -49,8 +49,6 @@ export class CoreSettingsAboutPage {
* @param page The component deeplink name you want to push onto the navigation stack.
*/
openPage(page: string): void {
// const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
// navCtrl.push(page);
CoreNavigator.navigate(page);
}

View File

@ -0,0 +1,61 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<h1>{{ 'core.settings.developeroptions' | translate }}</h1>
<ion-buttons slot="end" *ngIf="siteId">
<ion-button fill="clear" (click)="copyInfo()" [attr.aria-label]="'core.settings.copyinfo' | translate">
<ion-icon slot="icon-only" name="fas-clipboard" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'core.settings.textdirection' | translate }}</h2>
<p>{{ direction }}</p>
</ion-label>
<ion-toggle [(ngModel)]="rtl" (ionChange)="RTLChanged()"></ion-toggle>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'core.settings.forcesafeareamargins' | translate }}</h2>
</ion-label>
<ion-toggle [(ngModel)]="forceSafeAreaMargins" (ionChange)="safeAreaChanged()"></ion-toggle>
</ion-item>
<ng-container *ngIf="siteId">
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'core.settings.remotestyles' | translate }} <ion-badge>{{remoteStylesCount}}</ion-badge></h2>
</ion-label>
<ion-toggle [(ngModel)]="remoteStyles" (ionChange)="remoteStylesChanged()"></ion-toggle>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'core.settings.pluginstyles' | translate }} <ion-badge>{{pluginStylesCount}}</ion-badge></h2>
</ion-label>
<ion-toggle [(ngModel)]="pluginStyles" (ionChange)="pluginStylesChanged()"></ion-toggle>
</ion-item>
<ion-item-divider><ion-label><h2>{{ 'core.settings.disabledfeatures' | translate }}</h2></ion-label></ion-item-divider>
<ion-item class="ion-text-wrap" *ngFor="let feature of disabledFeatures">
<ion-label>
<h2>{{ feature }}</h2>
</ion-label>
</ion-item>
<ion-item-divider><ion-label><h2>{{ 'core.settings.siteplugins' | translate }}</h2></ion-label></ion-item-divider>
<ion-item class="ion-text-wrap" *ngFor="let plugin of sitePlugins">
<ion-label>
<h2>{{ plugin.addon }} ({{plugin.component}})</h2>
<p>{{plugin.version}}</p>
</ion-label>
</ion-item>
</ng-container>
</ion-list>
</ion-content>

View File

@ -0,0 +1,149 @@
// (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 { Component, OnInit } from '@angular/core';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { Platform } from '@singletons';
/**
* Page that displays the developer options.
*/
@Component({
selector: 'page-core-app-settings-dev',
templateUrl: 'dev.html',
})
export class CoreSettingsDevPage implements OnInit {
rtl = false;
forceSafeAreaMargins = false;
direction = 'ltr';
remoteStyles = true;
remoteStylesCount = 0;
pluginStyles = true;
pluginStylesCount = 0;
sitePlugins: CoreSitePluginsBasicInfo[] = [];
disabledFeatures: string[] = [];
siteId: string | undefined;
async ngOnInit(): Promise<void> {
this.rtl = Platform.isRTL;
this.RTLChanged();
this.forceSafeAreaMargins = document.documentElement.classList.contains('force-safe-area-margins');
this.safeAreaChanged();
this.siteId = CoreSites.getCurrentSite()?.getId();
if (!this.siteId) {
return;
}
this.remoteStyles = false;
this.remoteStylesCount = 0;
this.pluginStyles = false;
this.pluginStylesCount = 0;
document.head.querySelectorAll('style').forEach((style) => {
if (this.siteId && style.id.endsWith(this.siteId)) {
if (style.innerHTML.length > 0) {
this.remoteStylesCount++;
}
this.remoteStyles = this.remoteStyles || style.getAttribute('media') != 'disabled';
}
if (style.id.startsWith('siteplugin-')) {
if (style.innerHTML.length > 0) {
this.pluginStylesCount++;
}
this.pluginStyles = this.pluginStyles || style.getAttribute('media') != 'disabled';
}
});
this.sitePlugins = CoreSitePlugins.getCurrentSitePluginList().map((plugin) => ({
addon: plugin.addon,
component: plugin.component,
version: plugin.version,
}));
const disabledFeatures = (await CoreSites.getCurrentSite()?.getPublicConfig())?.tool_mobile_disabledfeatures;
this.disabledFeatures = disabledFeatures?.split(',') || [];
}
/**
* Called when the rtl is enabled or disabled.
*/
RTLChanged(): void {
this.direction = this.rtl ? 'rtl' : 'ltr';
document.dir = this.direction;
}
/**
* Called when safe area margins is enabled or disabled.
*/
safeAreaChanged(): void {
document.documentElement.classList.toggle('force-safe-area-margins', this.forceSafeAreaMargins);
}
/**
* Called when remote styles is enabled or disabled.
*/
remoteStylesChanged(): void {
document.head.querySelectorAll('style').forEach((style) => {
if (this.siteId && style.id.endsWith(this.siteId)) {
if (this.remoteStyles) {
style.removeAttribute('media');
} else {
style.setAttribute('media', 'disabled');
}
}
});
}
/**
* Called when remote styles is enabled or disabled.
*/
pluginStylesChanged(): void {
document.head.querySelectorAll('style').forEach((style) => {
if (style.id.startsWith('siteplugin-')) {
if (this.pluginStyles) {
style.removeAttribute('media');
} else {
style.setAttribute('media', 'disabled');
}
}
});
}
/**
* Copies site info.
*/
copyInfo(): void {
CoreUtils.copyToClipboard(JSON.stringify({ disabledFeatures: this.disabledFeatures, sitePlugins: this.sitePlugins }));
}
}
// Basic site plugin info.
type CoreSitePluginsBasicInfo = {
component: string;
addon: string;
version: string;
};

View File

@ -16,13 +16,19 @@
<ion-content>
<ion-list>
<ion-item *ngIf="showDevOptions" detail="true" (click)="gotoDevOptions()">
<ion-icon name="fas-terminal" slot="start" aria-hidden="true"></ion-icon>
<ion-label class="ion-text-wrap">
{{ 'core.settings.developeroptions' | translate }}
</ion-label>
</ion-item>
<ion-item (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.appversion' | translate}}</h2>
<p>{{ deviceInfo.versionName }} ({{ deviceInfo.versionCode }})</p>
</ion-label>
</ion-item>
<ion-item (longPress)="copyItemInfo($event)">
<ion-item (longPress)="copyItemInfo($event)" (click)="enableDevOptions()">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.compilationinfo' | translate }}</h2>
<p *ngIf="deviceInfo.compilationTime">{{ deviceInfo.compilationTime | coreFormatDate: "LLL Z": false }}</p>

View File

@ -17,12 +17,17 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreSettingsDeviceInfoPage } from './deviceinfo';
import { CoreSettingsDevPage } from '../dev/dev';
const routes: Routes = [
{
path: '',
component: CoreSettingsDeviceInfoPage,
},
{
path: 'dev',
component: CoreSettingsDevPage,
},
];
@NgModule({
@ -32,6 +37,7 @@ const routes: Routes = [
],
declarations: [
CoreSettingsDeviceInfoPage,
CoreSettingsDevPage,
],
exports: [RouterModule],
})

View File

@ -23,6 +23,9 @@ import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { Subscription } from 'rxjs';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreConfig } from '@services/config';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreNavigator } from '@services/navigator';
/**
* Device Info to be shown and copied to clipboard.
@ -69,6 +72,10 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
deviceOsTranslated?: string;
currentLangName?: string;
fsClickable = false;
showDevOptions = false;
protected devOptionsClickCounter = 0;
protected devOptionsForced = false;
protected devOptionsClickTimeout?: number;
protected onlineObserver?: Subscription;
@ -171,7 +178,7 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
this.onlineObserver = Network.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.run(() => {
this.deviceInfo!.networkStatus = appProvider.isOnline() ? 'online' : 'offline';
this.deviceInfo.networkStatus = appProvider.isOnline() ? 'online' : 'offline';
});
});
@ -193,6 +200,10 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
this.deviceInfo.fileSystemRoot = basepath;
this.fsClickable = fileProvider.usesHTMLAPI();
}
const showDevOptionsOnConfig = await CoreConfig.get('showDevOptions', 0);
this.devOptionsForced = CoreConstants.BUILD.isDevelopment || CoreConstants.BUILD.isTesting;
this.showDevOptions = this.devOptionsForced || showDevOptionsOnConfig == 1;
}
/**
@ -221,4 +232,44 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
this.onlineObserver && this.onlineObserver.unsubscribe();
}
/**
* 5 clicks will enable dev options.
*/
async enableDevOptions(): Promise<void> {
if (this.devOptionsForced) {
return;
}
clearTimeout(this.devOptionsClickTimeout);
this.devOptionsClickCounter++;
if (this.devOptionsClickCounter == 5) {
if (!this.showDevOptions) {
this.showDevOptions = true;
await CoreConfig.set('showDevOptions', 1);
CoreDomUtils.showToast('core.settings.youradev', true);
} else {
this.showDevOptions = false;
await CoreConfig.delete('showDevOptions');
}
this.devOptionsClickCounter = 0;
return;
}
this.devOptionsClickTimeout = window.setTimeout(() => {
this.devOptionsClickTimeout = undefined;
this.devOptionsClickCounter = 0;
}, 500);
}
/**
* Navigate to dev options.
*/
gotoDevOptions(): void {
CoreNavigator.navigate('dev');
}
}

View File

@ -314,6 +314,15 @@ export class CoreSitePluginsProvider {
return this.sitePlugins[name];
}
/**
* Get the current site plugin list.
*
* @return Plugin list ws info.
*/
getCurrentSitePluginList(): CoreSitePluginsWSPlugin[] {
return CoreUtils.objectToArray(this.sitePlugins).map((plugin) => plugin.plugin);
}
/**
* Invalidate all WS call to a certain method.
*

View File

@ -330,9 +330,9 @@ export class CoreStylesService {
(<any> element).disabled = !!disable; // eslint-disable-line @typescript-eslint/no-explicit-any
if (disable) {
element.setAttribute('disabled', 'true');
element.setAttribute('media', 'disabled');
} else {
element.removeAttribute('disabled');
element.removeAttribute('media');
}
}