Merge pull request #2572 from crazyserver/MOBILE-3565

Mobile 3565
main
Dani Palou 2020-10-22 14:48:51 +02:00 committed by GitHub
commit df4dfa6eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 951 additions and 48 deletions

View File

@ -25,6 +25,10 @@ const routes: Routes = [
path: 'login',
loadChildren: () => import('./core/login/login.module').then( m => m.CoreLoginModule),
},
{
path: 'settings',
loadChildren: () => import('./core/settings/settings.module').then( m => m.CoreAppSettingsPageModule),
},
];
@NgModule({

View File

@ -19,6 +19,8 @@
:host {
&.fa {
font-size: 24px;
contain: none;
text-align: center;
}
// Center font awesome icons

View File

@ -71,6 +71,8 @@ export class CoreIconComponent implements OnChanges, OnDestroy {
if (this.fixedWidth) {
this.newElement.classList.add('fa-fw');
}
} else {
this.newElement.setAttribute('name', this.name);
}
!this.label && this.newElement.setAttribute('aria-hidden', 'true');

View File

@ -19,8 +19,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@/app/components/components.module';
import { CoreDirectivesModule } from '@/app/directives/directives.module';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreLoginRoutingModule } from './login-routing.module';
import { CoreLoginCredentialsPage } from './pages/credentials/credentials.page';
@ -34,6 +34,7 @@ import { CoreLoginHelperProvider } from './services/helper';
CommonModule,
IonicModule,
CoreLoginRoutingModule,
CoreComponentsModule,
TranslateModule.forChild(),
FormsModule,
ReactiveFormsModule,

View File

@ -22,9 +22,9 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { CoreLoginHelper, CoreLoginHelperProvider } from '@core/login/services/helper';
import { CoreConstants } from '@/app/core/constants';
import { CoreConstants } from '@core/constants';
import { Translate } from '@singletons/core.singletons';
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@/app/classes/site';
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
import { CoreEvents } from '@singletons/events';
/**

View File

@ -1,13 +1,16 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'core.login.connecttomoodle' | translate }}</ion-title>
<ion-buttons slot="end">
<!-- @todo: Settings button. -->
<ion-button router-direction="forward" routerLink="/settings/app"
[attr.aria-label]="'core.settings.appsettings' | translate">
<core-icon slot="icon-only" name="fa-cog"></core-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

View File

@ -24,10 +24,10 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreLoginHelper, CoreLoginHelperProvider } from '@core/login/services/helper';
import { CoreSite } from '@classes/site';
import { CoreError } from '@classes/errors/error';
import { CoreConstants } from '@/app/core/constants';
import { CoreConstants } from '@core/constants';
import { Translate } from '@singletons/core.singletons';
import { CoreUrl } from '@singletons/url';
import { CoreUrlUtils } from '@/app/services/utils/url';
import { CoreUrlUtils } from '@services/utils/url';
import { NavController } from '@ionic/angular';
/**

View File

@ -1,37 +1,43 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'core.settings.sites' | translate }}</ion-title>
<ion-buttons slot="end">
<!-- @todo: Settings button. -->
<ion-button *ngIf="sites && sites.length > 0" icon-only (click)="toggleDelete()" [attr.aria-label]="'core.delete' | translate">
<ion-icon name="create" ios="md-create"></ion-icon>
<ion-button *ngIf="sites && sites.length > 0" (click)="toggleDelete()" [attr.aria-label]="'core.delete' | translate">
<core-icon slot="icon-only" name="fa-pencil"></core-icon>
</ion-button>
<ion-button router-direction="forward" routerLink="/settings/app"
[attr.aria-label]="'core.settings.appsettings' | translate">
<core-icon slot="icon-only" name="fa-cog"></core-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index" detail-none>
<ion-avatar item-start>
<ion-item (click)="login(site.id)" *ngFor="let site of sites">
<ion-avatar slot="start">
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<ion-label>
<h2>{{site.fullName}}</h2>
<p><core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text></p>
<p>{{site.siteUrl}}</p>
<ion-badge item-end *ngIf="!showDelete && site.badge">{{site.badge}}</ion-badge>
<ion-button *ngIf="showDelete" item-end icon-only clear color="danger" (click)="deleteSite($event, idx)" [attr.aria-label]="'core.delete' | translate">
<ion-icon name="trash"></ion-icon>
</ion-label>
<ion-badge slot="end" *ngIf="!showDelete && site.badge">{{site.badge}}</ion-badge>
<ion-button *ngIf="showDelete" slot="end" fill="clear" color="danger" (click)="deleteSite($event, site)"
[attr.aria-label]="'core.delete' | translate">
<core-icon name="fa-trash" slot="icon-only"></core-icon>
</ion-button>
</ion-item>
</ion-list>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end">
</ion-list>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end">
<ion-fab-button (click)="add()" [attr.aria-label]="'core.add' | translate">
<ion-icon name="add"></ion-icon>
<core-icon name="fa-plus"></core-icon>
</ion-fab-button>
</ion-fab>
</ion-fab>
</ion-content>

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreDomUtils } from '@/app/services/utils/dom';
import { CoreUtils } from '@/app/services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { Component, OnInit } from '@angular/core';
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
@ -26,7 +26,6 @@ import { CoreLoginHelper } from '../../services/helper';
@Component({
selector: 'page-core-login-sites',
templateUrl: 'sites.html',
styleUrls: ['sites.scss'],
})
export class CoreLoginSitesPage implements OnInit {
@ -76,13 +75,12 @@ export class CoreLoginSitesPage implements OnInit {
* Delete a site.
*
* @param e Click event.
* @param index Position of the site.
* @param site Site to delete.
* @return Promise resolved when done.
*/
async deleteSite(e: Event, index: number): Promise<void> {
async deleteSite(e: Event, site: CoreSiteBasicInfo): Promise<void> {
e.stopPropagation();
const site = this.sites[index];
const siteName = site.siteName || '';
// @todo: Format text: siteName.
@ -97,7 +95,8 @@ export class CoreLoginSitesPage implements OnInit {
try {
await CoreSites.instance.deleteSite(site.id);
this.sites.splice(index, 1);
const index = this.sites.findIndex((listedSite) => listedSite.id == site.id);
index >= 0 && this.sites.splice(index, 1);
this.showDelete = false;
// If there are no sites left, go to add site.

View File

@ -1,3 +0,0 @@
.item-ios .item-button[icon-only] ion-icon {
font-size: 2.1em;
}

View File

@ -0,0 +1,73 @@
{
"about": "About",
"appsettings": "App settings",
"appversion": "App version",
"cannotsyncloggedout": "This site cannot be synchronised because you've logged out. Please try again when you're logged in the site again.",
"cannotsyncoffline": "Cannot synchronise offline.",
"cannotsyncwithoutwifi": "Cannot synchronise because the current settings only allow to synchronise when connected to Wi-Fi. Please connect to a Wi-Fi network.",
"colorscheme": "Color Scheme",
"colorscheme-auto": "Auto (based on system settings)",
"colorscheme-dark": "Dark",
"colorscheme-light": "Light",
"compilationinfo": "Compilation info",
"copyinfo": "Copy device info on the clipboard",
"cordovadevicemodel": "Cordova device model",
"cordovadeviceosversion": "Cordova device OS version",
"cordovadeviceplatform": "Cordova device platform",
"cordovadeviceuuid": "Cordova device UUID",
"cordovaversion": "Cordova version",
"currentlanguage": "Current language",
"debugdisplay": "Display debug messages",
"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",
"deviceinfo": "Device info",
"deviceos": "Device OS",
"disableall": "Disable notifications",
"disabled": "Disabled",
"displayformat": "Display format",
"enabledownloadsection": "Enable download sections",
"enablefirebaseanalytics": "Enable Firebase analytics",
"enablefirebaseanalyticsdescription": "If enabled, the app will collect anonymous data usage.",
"enablerichtexteditor": "Enable text editor",
"enablerichtexteditordescription": "If enabled, a text editor will be available when entering content.",
"enablesyncwifi": "Allow sync only when on Wi-Fi",
"entriesincache": "{{$a}} entries in cache",
"errordeletesitefiles": "Error deleting site files.",
"errorsyncsite": "Error synchronising site data. Please check your Internet connection and try again.",
"estimatedfreespace": "Estimated free space",
"filesystemroot": "File system root",
"fontsize": "Text size",
"fontsizecharacter": "A",
"forcedsetting": "This setting has been forced by your site configuration.",
"general": "General",
"language": "Language",
"license": "Licence",
"localnotifavailable": "Local notifications available",
"locationhref": "Web view URL",
"locked": "Locked",
"loggedin": "Online",
"loggedoff": "Offline",
"navigatorlanguage": "Navigator language",
"navigatoruseragent": "Navigator userAgent",
"networkstatus": "Internet connection status",
"opensourcelicenses": "Open Source Licences",
"preferences": "Preferences",
"privacypolicy": "Privacy policy",
"publisher": "Publisher",
"pushid": "Push notifications ID",
"reportinbackground": "Report errors automatically",
"screen": "Screen information",
"settings": "Settings",
"showdownloadoptions": "Show download options",
"siteinfo": "Site info",
"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. ",
"synchronization": "Synchronisation",
"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",
"total": "Total",
"wificonnection": "Wi-Fi connection"
}

View File

@ -0,0 +1,29 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
{{ 'core.settings.about' | translate}}
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item text-wrap>
<ion-label><h2>{{ appName }} {{ versionName }}</h2></ion-label>
</ion-item>
<ion-item text-wrap (click)="openPage('licenses')" detail>
<core-icon name="fa-copyright" slot="start"></core-icon>
<ion-label>{{ 'core.settings.opensourcelicenses' | translate }}</ion-label>
</ion-item>
<ion-item text-wrap *ngIf="privacyPolicy" [href]="privacyPolicy" core-link auto-login="no" detail>
<core-icon name="fa-user-secret" slot="start"></core-icon>
<ion-label>{{ 'core.settings.privacypolicy' | translate }}</ion-label>
</ion-item>
<ion-item text-wrap (click)="openPage('deviceinfo')" detail>
<core-icon name="fa-mobile" slot="start"></core-icon>
<ion-label>{{ 'core.settings.deviceinfo' | translate }}</ion-label>
</ion-item>
</ion-content>

View File

@ -0,0 +1,55 @@
// (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 { CoreSites } from '@services/sites';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { CoreConstants } from '@core/constants';
import { CoreApp } from '@services/app';
@Component({
selector: 'settings-about',
templateUrl: 'about.html',
})
export class CoreSettingsAboutPage {
appName: string;
versionName: string;
privacyPolicy: string;
constructor(
protected router: Router,
) {
const currentSite = CoreSites.instance.getCurrentSite();
this.appName = CoreApp.instance.isDesktop() ? CoreConstants.CONFIG.desktopappname : CoreConstants.CONFIG.appname;
this.versionName = CoreConstants.CONFIG.versionname;
// Calculate the privacy policy to use.
this.privacyPolicy = (currentSite && (currentSite.getStoredConfig('tool_mobile_apppolicy') ||
currentSite.getStoredConfig('sitepolicy'))) || CoreConstants.CONFIG.privacypolicy;
}
/**
* Opens a page.
*
* @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);
this.router.navigate(['/settings/' + page]);
}
}

View File

@ -0,0 +1,33 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
{{ 'core.settings.appsettings' | translate}}
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item (click)="openSettings('general')" [class.core-split-item-selected]="'general' == selectedPage" detail>
<core-icon name="fa-wrench" slot="start"></core-icon>
<ion-label>{{ 'core.settings.general' | translate }}</ion-label>
</ion-item>
<ion-item (click)="openSettings('spaceusage')" [class.core-split-item-selected]="'spaceusage' == selectedPage" detail>
<core-icon name="fa-tasks" slot="start"></core-icon>
<ion-label>{{ 'core.settings.spaceusage' | translate }}</ion-label>
</ion-item>
<ion-item (click)="openSettings('sync')" [class.core-split-item-selected]="'sync' == selectedPage" detail>
<core-icon name="fa-refresh" slot="start"></core-icon>
<ion-label>{{ 'core.settings.synchronization' | translate }}</ion-label>
</ion-item>
<ion-item *ngIf="isIOS" (click)="openSettings('sharedfiles', {manage: true})"
[class.core-split-item-selected]="'sharedfiles' == selectedPage" detail>
<core-icon name="fa-folder" slot="start"></core-icon>
<ion-label>{{ 'core.sharedfiles.sharedfiles' | translate }}</ion-label>
</ion-item>
<ion-item (click)="openSettings('about')" [class.core-split-item-selected]="'about' == selectedPage" detail>
<core-icon name="fa-id-card" slot="start"></core-icon>
<ion-label>{{ 'core.settings.about' | translate }}</ion-label>
</ion-item>
</ion-content>

View File

@ -0,0 +1,65 @@
// (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 { CoreApp } from '@services/app';
import { Component } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
@Component({
selector: 'app-settings',
templateUrl: 'app.html',
})
export class CoreAppSettingsPage {
// @ViewChild(CoreSplitViewComponent) splitviewCtrl?: CoreSplitViewComponent;
isIOS: boolean;
selectedPage?: string;
constructor(
protected route: ActivatedRoute,
protected router: Router, // Will be removed when splitview is implemented
) {
this.isIOS = CoreApp.instance.isIOS();
this.selectedPage = route.snapshot.paramMap.get('page') || undefined;
if (this.selectedPage) {
this.openSettings(this.selectedPage);
}
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
if (this.selectedPage) {
this.openSettings(this.selectedPage);
} /* else if (this.splitviewCtrl!.isOn()) {
this.openSettings('general');
}*/
}
/**
* Open a settings page.
*
* @param page Page to open.
* @param params Params of the page to open.
*/
openSettings(page: string, params?: Params): void {
this.selectedPage = page;
// this.splitviewCtrl!.push(page, params);
this.router.navigate(['../'+page], { relativeTo: this.route, queryParams: params });
}
}

View File

@ -0,0 +1,142 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
{{ 'core.settings.deviceinfo' | translate }}
</ion-title>
<ion-buttons slot="end">
<ion-button (click)="copyInfo()" [attr.aria-label]="'core.settings.copyinfo' | translate">
<core-icon slot="icon-only" name="fa-clipboard" color="light"></core-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<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-label class="ion-text-wrap">
<h2>{{ 'core.settings.compilationinfo' | translate }}</h2>
<p *ngIf="deviceInfo.compilationTime">{{ deviceInfo.compilationTime | coreFormatDate: "LLL Z": false }}</p>
<p *ngIf="deviceInfo.lastCommit">{{ deviceInfo.lastCommit }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.siteUrl" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.siteinfo' | translate }}<ng-container *ngIf="deviceInfo.isPrefixedUrl"> *</ng-container></h2>
<p><a [href]="deviceInfo.siteUrl" core-link auto-login="yes">{{ deviceInfo.siteUrl }}</a></p>
<p *ngIf="deviceInfo.siteVersion">{{ deviceInfo.siteVersion }}</p>
<p *ngIf="deviceInfo.siteId">{{ deviceInfo.siteId }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.fileSystemRoot" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.filesystemroot' | translate }}</h2>
<p><a *ngIf="fsClickable" [href]="deviceInfo.fileSystemRoot" core-link auto-login="no">{{ deviceInfo.fileSystemRoot }}</a></p>
<p *ngIf="!fsClickable">{{ deviceInfo.fileSystemRoot }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.userAgent" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.navigatoruseragent' | translate }}</h2>
<p>{{ deviceInfo.userAgent }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.browserLanguage" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.navigatorlanguage' | translate }}</h2>
<p>{{ deviceInfo.browserLanguage }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.currentLanguage" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.currentlanguage' | translate }}</h2>
<p>{{ currentLangName }} ({{ deviceInfo.currentLanguage }})</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.locationHref" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.locationhref' | translate }}</h2>
<p>{{ deviceInfo.locationHref }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.deviceType" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.displayformat' | translate }}</h2>
<p>{{ 'core.' + deviceInfo.deviceType | translate }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.deviceOs && deviceOsTranslated" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.deviceos' | translate}}</h2>
<p>{{ deviceOsTranslated }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.screen" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.screen' | translate }}</h2>
<p>{{ deviceInfo.screen }}</p>
</ion-label>
</ion-item>
<ion-item (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.networkstatus' | translate}}</h2>
<p>{{ 'core.' + deviceInfo.networkStatus | translate }}</p>
</ion-label>
</ion-item>
<ion-item (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.wificonnection' | translate}}</h2>
<p>{{ 'core.' + deviceInfo.wifiConnection | translate }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.cordovaVersion" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.cordovaversion' | translate }}</h2>
<p>{{ deviceInfo.cordovaVersion }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.platform" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.cordovadeviceplatform' | translate }}</h2>
<p>{{ deviceInfo.platform }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.osVersion" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.cordovadeviceosversion' | translate }}</h2>
<p>{{ deviceInfo.osVersion }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.model" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.cordovadevicemodel' | translate}}</h2>
<p>{{ deviceInfo.model }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.uuid" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.cordovadeviceuuid'}}</h2>
<p>{{ deviceInfo.uuid }}</p>
</ion-label>
</ion-item>
<ion-item *ngIf="deviceInfo.pushId" (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.pushid' | translate }}</h2>
<p>{{ deviceInfo.pushId }}</p>
</ion-label>
</ion-item>
<ion-item (longPress)="copyItemInfo($event)">
<ion-label class="ion-text-wrap">
<h2>{{ 'core.settings.localnotifavailable' | translate }}</h2>
<p>{{ 'core.' + deviceInfo.localNotifAvailable | translate }}</p>
</ion-label>
</ion-item>
</ion-content>

View File

@ -0,0 +1,232 @@
// (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 { CoreApp } from '@services/app';
import { Component, OnDestroy } from '@angular/core';
import { CoreConstants } from '@core/constants';
import { CoreLocalNotifications } from '@services/local-notifications';
import { Device, Platform, Translate, Network, NgZone } from '@singletons/core.singletons';
import { CoreLang } from '@services/lang';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { Subscription } from 'rxjs';
/**
* Device Info to be shown and copied to clipboard.
*/
interface CoreSettingsDeviceInfo {
versionName: string;
versionCode: number;
compilationTime: number;
lastCommit: string;
siteUrl?: string;
isPrefixedUrl?: boolean;
siteId?: string;
siteVersion?: string;
fileSystemRoot?: string;
userAgent?: string;
deviceOs?: string;
browserLanguage?: string;
currentLanguage?: string;
locationHref?: string;
deviceType: string;
screen?: string;
networkStatus: string;
wifiConnection: string;
cordovaVersion?: string;
platform?: string;
osVersion?: string;
model?: string;
uuid?: string;
pushId: string;
localNotifAvailable: string;
}
@Component({
selector: 'settings-deviceinfo',
templateUrl: 'deviceinfo.html',
styleUrls: ['deviceinfo.scss'],
})
export class CoreSettingsDeviceInfoPage implements OnDestroy {
deviceInfo: CoreSettingsDeviceInfo;
deviceOsTranslated?: string;
currentLangName?: string;
fsClickable = false;
protected onlineObserver?: Subscription;
constructor() {
const appProvider = CoreApp.instance;
const sitesProvider = CoreSites.instance;
const device = Device.instance;
const translate = Translate.instance;
const navigator = window.navigator;
this.deviceInfo = {
versionName: CoreConstants.CONFIG.versionname,
versionCode: CoreConstants.CONFIG.versioncode,
compilationTime: CoreConstants.BUILD.compilationTime || 0,
lastCommit: CoreConstants.BUILD.lastCommitHash || '',
networkStatus: appProvider.isOnline() ? 'online' : 'offline',
wifiConnection: appProvider.isWifi() ? 'yes' : 'no',
localNotifAvailable: CoreLocalNotifications.instance.isAvailable() ? 'yes' : 'no',
pushId: '',// TODO pushNotificationsProvider.getPushId(),
deviceType: '',
};
if (window.location && window.location.href) {
const url = window.location.href;
this.deviceInfo.locationHref = url.indexOf('#') > 0 ? url.substr(0, url.indexOf('#')) : url;
}
if (window.screen) {
this.deviceInfo.screen = window.innerWidth + 'x' + window.innerHeight +
' (' + window.screen.width + 'x' + window.screen.height + ')';
}
if (appProvider.isMobile()) {
this.deviceInfo.deviceType = Platform.instance.is('tablet') ? 'tablet' : 'phone';
if (appProvider.isAndroid()) {
this.deviceInfo.deviceOs = 'android';
this.deviceOsTranslated = 'Android';
} else if (appProvider.isIOS()) {
this.deviceInfo.deviceOs = 'ios';
this.deviceOsTranslated = 'iOS';
} else {
const matches = navigator.userAgent.match(/\(([^)]*)\)/);
if (matches && matches.length > 1) {
this.deviceInfo.deviceOs = matches[1];
this.deviceOsTranslated = matches[1];
} else {
this.deviceInfo.deviceOs = 'unknown';
this.deviceOsTranslated = translate.instant('core.unknown');
}
}
} else {
this.deviceInfo.deviceType = appProvider.isDesktop() ? 'desktop' : 'browser';
if (appProvider.isLinux()) {
this.deviceInfo.deviceOs = 'linux';
this.deviceOsTranslated = 'Linux';
} else if (appProvider.isMac()) {
this.deviceInfo.deviceOs = 'mac';
this.deviceOsTranslated = 'MacOS';
} else if (appProvider.isWindows()) {
this.deviceInfo.deviceOs = 'windows';
this.deviceOsTranslated = 'Windows';
} else {
const matches = navigator.userAgent.match(/\(([^)]*)\)/);
if (matches && matches.length > 1) {
this.deviceInfo.deviceOs = matches[1];
this.deviceOsTranslated = matches[1];
} else {
this.deviceInfo.deviceOs = 'unknown';
this.deviceOsTranslated = translate.instant('core.unknown');
}
}
}
if (navigator) {
if (navigator.userAgent) {
this.deviceInfo.userAgent = navigator.userAgent;
}
if (navigator.language) {
this.deviceInfo.browserLanguage = navigator.language;
}
}
if (device) {
if (device.cordova) {
this.deviceInfo.cordovaVersion = device.cordova;
}
if (device.platform) {
this.deviceInfo.platform = device.platform;
}
if (device.version) {
this.deviceInfo.osVersion = device.version;
}
if (device.model) {
this.deviceInfo.model = device.model;
}
if (device.uuid) {
this.deviceInfo.uuid = device.uuid;
}
}
const currentSite = sitesProvider.getCurrentSite();
this.deviceInfo.siteUrl = (currentSite?.getURL()) ||
(typeof CoreConstants.CONFIG.siteurl == 'string' && CoreConstants.CONFIG.siteurl) || undefined;
this.deviceInfo.isPrefixedUrl = !!CoreConstants.CONFIG.siteurl;
this.deviceInfo.siteId = currentSite?.getId();
this.deviceInfo.siteVersion = currentSite?.getInfo()?.release;
// Refresh online status when changes.
this.onlineObserver = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.deviceInfo!.networkStatus = appProvider.isOnline() ? 'online' : 'offline';
});
});
this.asyncInit();
}
/**
* Async part of the constructor.
*/
protected async asyncInit(): Promise<void> {
const fileProvider = CoreFile.instance;
const lang = await CoreLang.instance.getCurrentLanguage();
this.deviceInfo.currentLanguage = lang;
this.currentLangName = CoreConstants.CONFIG.languages[lang];
if (fileProvider.isAvailable()) {
const basepath = await fileProvider.getBasePath();
this.deviceInfo.fileSystemRoot = basepath;
this.fsClickable = fileProvider.usesHTMLAPI();
}
}
/**
* Copies device info into the clipboard.
*/
copyInfo(): void {
CoreUtils.instance.copyToClipboard(JSON.stringify(this.deviceInfo));
}
/**
* Copies device info item into the clipboard.
*
* @param e Event.
*/
copyItemInfo(e: Event): void {
const el = <Element>e.target;
const text = el?.closest('ion-item')?.textContent?.trim();
text && CoreUtils.instance.copyToClipboard(text);
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.onlineObserver && this.onlineObserver.unsubscribe();
}
}

View File

@ -0,0 +1,4 @@
.item {
user-select: text;
cursor: text;
}

View File

@ -0,0 +1,45 @@
// (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 { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreAppSettingsPage } from './pages/app/app.page';
import { CoreSettingsAboutPage } from './pages/about/about.page';
import { CoreSettingsDeviceInfoPage } from './pages/deviceinfo/deviceinfo.page';
const routes: Routes = [
{
path: 'about',
component: CoreSettingsAboutPage,
},
{
path: 'deviceinfo',
component: CoreSettingsDeviceInfoPage,
},
{
path: 'app',
component: CoreAppSettingsPage,
},
{
path: '',
redirectTo: 'app',
pathMatch: 'full',
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class CoreAppSettingsRoutingModule {}

View File

@ -0,0 +1,46 @@
// (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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreAppSettingsRoutingModule } from './settings-routing.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreAppSettingsPage } from './pages/app/app.page';
import { CoreSettingsAboutPage } from './pages/about/about.page';
import { CoreSettingsDeviceInfoPage } from './pages/deviceinfo/deviceinfo.page';
@NgModule({
imports: [
CommonModule,
IonicModule,
CoreAppSettingsRoutingModule,
CorePipesModule,
CoreComponentsModule,
CoreDirectivesModule,
TranslateModule.forChild(),
],
declarations: [
CoreAppSettingsPage,
CoreSettingsAboutPage,
CoreSettingsDeviceInfoPage,
],
})
export class CoreAppSettingsPageModule {}

View File

@ -17,8 +17,9 @@ import { NgModule } from '@angular/core';
import { CoreAutoFocusDirective } from './auto-focus';
import { CoreExternalContentDirective } from './external-content';
import { CoreFormatTextDirective } from './format-text';
import { CoreLongPressDirective } from './long-press.directive';
import { CoreLongPressDirective } from './long-press';
import { CoreSupressEventsDirective } from './supress-events';
import { CoreFabDirective } from './fab';
@NgModule({
declarations: [
@ -27,6 +28,7 @@ import { CoreSupressEventsDirective } from './supress-events';
CoreFormatTextDirective,
CoreLongPressDirective,
CoreSupressEventsDirective,
CoreFabDirective,
],
imports: [],
exports: [
@ -35,6 +37,7 @@ import { CoreSupressEventsDirective } from './supress-events';
CoreFormatTextDirective,
CoreLongPressDirective,
CoreSupressEventsDirective,
CoreFabDirective,
],
})
export class CoreDirectivesModule {}

View File

@ -0,0 +1,63 @@
// (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 { Directive, OnDestroy } from '@angular/core';
import { IonContent } from '@ionic/angular';
/**
* Directive to move ion-fab components as direct children of the nearest ion-content.
*
* Example usage:
*
* <ion-fab core-fab>
*/
@Directive({
selector: 'ion-fab[core-fab]',
})
export class CoreFabDirective implements OnDestroy {
protected static readonly PADDINGBOTTOM = 56;
protected element?: HTMLElement;
protected done = false;
constructor(protected content: IonContent) {
this.asyncInit();
}
/**
* Initialize Component.
*/
async asyncInit(): Promise<void> {
if (this.content) {
this.element = await this.content.getScrollElement();
if (!this.done) {
const bottom = parseInt(this.element.style.paddingBottom, 10) || 0;
this.element.style.paddingBottom = (bottom + CoreFabDirective.PADDINGBOTTOM) + 'px';
this.done = true;
}
}
}
/**
* Destroy component.
*/
ngOnDestroy(): void {
if (this.done && this.element) {
const bottom = parseInt(this.element.style.paddingBottom, 10) || 0;
this.element.style.paddingBottom = (bottom + CoreFabDirective.PADDINGBOTTOM) + 'px';
}
}
}

View File

@ -0,0 +1,11 @@
{
"back": "Back",
"browser": "Browser",
"copiedtoclipboard": "Text copied to clipboard",
"no": "No",
"offline": "Offline",
"ok": "OK",
"online": "Online",
"unknown": "Unknown",
"yes": "Yes"
}

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreLang } from '@services/lang';
import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '@/app/core/constants';
import { CoreConstants } from '@core/constants';
import { makeSingleton } from '@singletons/core.singletons';
import { CoreUrl } from '@singletons/url';

View File

@ -300,5 +300,85 @@
"assets.mimetypes.text/rtf": "RTF document",
"assets.mimetypes.text/vtt": "Web Video Text Track",
"assets.mimetypes.video": "Video file ({{$a.EXT}})",
"core.login.yourenteredsite": "Connect to your site"
"core.back": "Back",
"core.browser": "Browser",
"core.copiedtoclipboard": "Text copied to clipboard",
"core.login.yourenteredsite": "Connect to your site",
"core.no": "No",
"core.offline": "Offline",
"core.ok": "OK",
"core.online": "Online",
"core.settings.about": "About",
"core.settings.appsettings": "App settings",
"core.settings.appversion": "App version",
"core.settings.cannotsyncloggedout": "This site cannot be synchronised because you've logged out. Please try again when you're logged in the site again.",
"core.settings.cannotsyncoffline": "Cannot synchronise offline.",
"core.settings.cannotsyncwithoutwifi": "Cannot synchronise because the current settings only allow to synchronise when connected to Wi-Fi. Please connect to a Wi-Fi network.",
"core.settings.colorscheme": "Color Scheme",
"core.settings.colorscheme-auto": "Auto (based on system settings)",
"core.settings.colorscheme-dark": "Dark",
"core.settings.colorscheme-light": "Light",
"core.settings.compilationinfo": "Compilation info",
"core.settings.copyinfo": "Copy device info on the clipboard",
"core.settings.cordovadevicemodel": "Cordova device model",
"core.settings.cordovadeviceosversion": "Cordova device OS version",
"core.settings.cordovadeviceplatform": "Cordova device platform",
"core.settings.cordovadeviceuuid": "Cordova device UUID",
"core.settings.cordovaversion": "Cordova version",
"core.settings.currentlanguage": "Current language",
"core.settings.debugdisplay": "Display debug messages",
"core.settings.debugdisplaydescription": "If enabled, error modals will display more data about the error if possible.",
"core.settings.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.",
"core.settings.deletesitefilestitle": "Delete site files",
"core.settings.deviceinfo": "Device info",
"core.settings.deviceos": "Device OS",
"core.settings.disableall": "Disable notifications",
"core.settings.disabled": "Disabled",
"core.settings.displayformat": "Display format",
"core.settings.enabledownloadsection": "Enable download sections",
"core.settings.enablefirebaseanalytics": "Enable Firebase analytics",
"core.settings.enablefirebaseanalyticsdescription": "If enabled, the app will collect anonymous data usage.",
"core.settings.enablerichtexteditor": "Enable text editor",
"core.settings.enablerichtexteditordescription": "If enabled, a text editor will be available when entering content.",
"core.settings.enablesyncwifi": "Allow sync only when on Wi-Fi",
"core.settings.entriesincache": "{{$a}} entries in cache",
"core.settings.errordeletesitefiles": "Error deleting site files.",
"core.settings.errorsyncsite": "Error synchronising site data. Please check your Internet connection and try again.",
"core.settings.estimatedfreespace": "Estimated free space",
"core.settings.filesystemroot": "File system root",
"core.settings.fontsize": "Text size",
"core.settings.fontsizecharacter": "A",
"core.settings.forcedsetting": "This setting has been forced by your site configuration.",
"core.settings.general": "General",
"core.settings.language": "Language",
"core.settings.license": "Licence",
"core.settings.localnotifavailable": "Local notifications available",
"core.settings.locationhref": "Web view URL",
"core.settings.locked": "Locked",
"core.settings.loggedin": "Online",
"core.settings.loggedoff": "Offline",
"core.settings.navigatorlanguage": "Navigator language",
"core.settings.navigatoruseragent": "Navigator userAgent",
"core.settings.networkstatus": "Internet connection status",
"core.settings.opensourcelicenses": "Open Source Licences",
"core.settings.preferences": "Preferences",
"core.settings.privacypolicy": "Privacy policy",
"core.settings.publisher": "Publisher",
"core.settings.pushid": "Push notifications ID",
"core.settings.reportinbackground": "Report errors automatically",
"core.settings.screen": "Screen information",
"core.settings.settings": "Settings",
"core.settings.showdownloadoptions": "Show download options",
"core.settings.siteinfo": "Site info",
"core.settings.sites": "Sites",
"core.settings.spaceusage": "Space usage",
"core.settings.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. ",
"core.settings.synchronization": "Synchronisation",
"core.settings.synchronizenow": "Synchronise now",
"core.settings.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.",
"core.settings.syncsettings": "Synchronisation settings",
"core.settings.total": "Total",
"core.settings.wificonnection": "Wi-Fi connection",
"core.unknown": "Unknown",
"core.yes": "Yes"
}

View File

@ -0,0 +1,6 @@
// Add here base app styles.
ion-toolbar ion-back-button,
ion-toolbar .in-toolbar.button-clear {
--color: var(--ion-color-primary-contrast);
}

View File

@ -134,3 +134,5 @@
--background: var(--ion-background-color);
}
}
@import "app.scss";

View File

@ -14,7 +14,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { CoreSitesDemoSiteData } from '@/app/services/sites';
import { CoreSitesDemoSiteData } from '@services/sites';
declare global {