MOBILE-2329 settings: Migrate preferences

main
Albert Gasset 2018-04-09 13:20:45 +02:00
parent ee7808643b
commit 99af917816
20 changed files with 976 additions and 25 deletions

View File

@ -270,7 +270,7 @@ export class SQLiteDB {
deleteRecords(table: string, conditions?: object): Promise<any> {
if (conditions === null || typeof conditions == 'undefined') {
// No conditions, delete the whole table.
return this.execute(`DELETE FROM TABLE ${table}`);
return this.execute(`DELETE FROM ${table}`);
}
const selectAndParams = this.whereClause(conditions);

View File

@ -30,6 +30,7 @@ export class CoreConstants {
static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor';
static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound';
static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi';
static SETTINGS_REPORT_IN_BACKGROUND = 'CoreSettingsReportInBackground';
// WS constants.
static WS_TIMEOUT = 30000;

View File

@ -279,7 +279,7 @@ export class FileMock extends File {
maxIterations = 10;
// More accurate. Factor is 1.1.
calculateByRequest(size, 1.1).then((size: number) => {
return size / 1024; // Return size in KB.
resolve(size / 1024); // Return size in KB.
});
});

View File

@ -45,9 +45,9 @@ export class CoreLoginSitesPage {
* View loaded.
*/
ionViewDidLoad(): void {
this.sitesProvider.getSites().then((sites) => {
this.sitesProvider.getSortedSites().then((sites) => {
// Remove protocol from the url to show more url text.
sites = sites.map((site) => {
this.sites = sites.map((site) => {
site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '');
site.badge = 0;
this.pushNotificationsProvider.getSiteCounter(site.id).then((counter) => {
@ -57,24 +57,6 @@ export class CoreLoginSitesPage {
return site;
});
// Sort sites by url and fullname.
this.sites = sites.sort((a, b) => {
// First compare by site url without the protocol.
let compareA = a.siteUrl.toLowerCase(),
compareB = b.siteUrl.toLowerCase();
const compare = compareA.localeCompare(compareB);
if (compare !== 0) {
return compare;
}
// If site url is the same, use fullname instead.
compareA = a.fullName.toLowerCase().trim();
compareB = b.fullName.toLowerCase().trim();
return compareA.localeCompare(compareB);
});
this.showDelete = false;
}).catch(() => {
// Shouldn't happen.

View File

@ -1,12 +1,49 @@
{
"about": "About",
"appready": "App ready",
"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.",
"cordovadevicemodel": "Cordova device model",
"cordovadeviceosversion": "Cordova device OS version",
"cordovadeviceplatform": "Cordova device platform",
"cordovadeviceuuid": "Cordova device UUID",
"cordovaversion": "Cordova version",
"currentlanguage": "Current language",
"deletesitefiles": "Are you sure that you want to delete the downloaded files from the site '{{sitename}}'?",
"deletesitefilestitle": "Delete site files",
"deviceinfo": "Device info",
"deviceos": "Device OS",
"devicewebworkers": "Device web workers supported",
"disableall": "Disable notifications",
"disabled": "Disabled",
"displayformat": "Display format",
"enablerichtexteditor": "Enable text editor",
"enablerichtexteditordescription": "If enabled, a text editor will be available when entering content.",
"enablesyncwifi": "Allow sync only when on Wi-Fi",
"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",
"general": "General",
"language": "Language",
"license": "License",
"localnotifavailable": "Local notifications available",
"locationhref": "Web view URL",
"loggedin": "Online",
"loggedoff": "Offline",
"navigatorlanguage": "Navigator language",
"navigatoruseragent": "Navigator userAgent",
"networkstatus": "Internet connection status",
"privacypolicy": "Privacy policy",
"reportinbackground": "Report errors automatically",
"settings": "Settings",
"sites": "Sites",
"spaceusage": "Space usage",
"synchronization": "Synchronisation"
"synchronization": "Synchronisation",
"synchronizenow": "Synchronise now",
"syncsettings": "Synchronisation settings",
"total": "Total",
"versioncode": "Version code",
"versionname": "Version name",
"wificonnection": "Wi-Fi connection"
}

View File

@ -0,0 +1,109 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.settings.about' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-item text-wrap>
<h2>{{ appName }} {{ versionName }}</h2>
</ion-item>
<ion-item-group>
<ion-item-divider text-wrap color="light">
{{ 'core.settings.license' | translate }}
</ion-item-divider>
<ion-item text-wrap>
<h2>Apache 2.0</h2>
<p><a href="http://www.apache.org/licenses/LICENSE-2.0" core-link auto-login="no">http://www.apache.org/licenses/LICENSE-2.0</a></p>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="privacyPolicy">
<ion-item-divider text-wrap color="light">
{{ 'core.settings.privacypolicy' | translate }}
</ion-item-divider>
<ion-item text-wrap>
<p><a [href]="privacyPolicy" core-link auto-login="no">{{ privacyPolicy }}</a></p>
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item-divider text-wrap color="light">
{{ 'core.settings.deviceinfo' | translate }}
</ion-item-divider>
<ion-item text-wrap *ngIf="versionName">
<h2>{{ 'core.settings.versionname' | translate}}</h2>
<p>{{ versionName }}</p>
</ion-item>
<ion-item text-wrap *ngIf="versionCode">
<h2>{{ 'core.settings.versioncode' | translate}}</h2>
<p>{{ versionCode }}</p>
</ion-item>
<ion-item text-wrap *ngIf="fileSystemRoot">
<h2>{{ 'core.settings.filesystemroot' | translate}}</h2>
<p><a *ngIf="fsClickable" [href]="fileSystemRoot" core-link auto-login="no">{{ filesystemroot }}</a></p>
<p *ngIf="!fsClickable">{{ fileSystemRoot }}</p>
</ion-item>
<ion-item text-wrap *ngIf="navigator && navigator.userAgent">
<h2>{{ 'core.settings.navigatoruseragent' | translate}}</h2>
<p>{{ navigator.userAgent }}</p>
</ion-item>
<ion-item text-wrap *ngIf="navigator && navigator.language">
<h2>{{ 'core.settings.navigatorlanguage' | translate}}</h2>
<p>{{ navigator.language }}</p>
</ion-item>
<ion-item text-wrap *ngIf="locationHref">
<h2>{{ 'core.settings.locationhref' | translate}}</h2>
<p>{{ locationHref }}</p>
</ion-item>
<ion-item text-wrap *ngIf="appReady">
<h2>{{ 'core.settings.appready' | translate}}</h2>
<p>{{ appReady | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="deviceType">
<h2>{{ 'core.settings.displayformat' | translate}}</h2>
<p>{{ deviceType | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="deviceOs">
<h2>{{ 'core.settings.deviceos' | translate}}</h2>
<p>{{ deviceOs | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="currentLanguage">
<h2>{{ 'core.settings.currentlanguage' | translate}}</h2>
<p>{{ currentLanguage }}</p>
</ion-item>
<ion-item text-wrap *ngIf="networkStatus">
<h2>{{ 'core.settings.networkstatus' | translate}}</h2>
<p>{{ networkStatus | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="wifiConnection">
<h2>{{ 'core.settings.wificonnection' | translate}}</h2>
<p>{{ wifiConnection | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="deviceWebWorkers">
<h2>{{ 'core.settings.devicewebworkers' | translate}}</h2>
<p>{{ deviceWebWorkers | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="device && device.cordova">
<h2>{{ 'core.settings.cordovaversion' | translate}}</h2>
<p>{{ device.cordova }}</p>
</ion-item>
<ion-item text-wrap *ngIf="device && device.platform">
<h2>{{ 'core.settings.cordovadeviceplatform' | translate}}</h2>
<p>{{ device.platform }}</p>
</ion-item>
<ion-item text-wrap *ngIf="device && device.version">
<h2>{{ 'core.settings.cordovadeviceosversion' | translate}}</h2>
<p>{{ device.version }}</p>
</ion-item>
<ion-item text-wrap *ngIf="device && device.model">
<h2>{{ 'core.settings.cordovadevicemodel' | translate}}</h2>
<p>{{ device.model }}</p>
</ion-item>
<ion-item text-wrap *ngIf="device && device.uuid">
<h2>{{ 'core.settings.cordovadeviceuuid' | translate}}</h2>
<p>{{ device.uuid }}</p>
</ion-item>
<ion-item text-wrap *ngIf="localNotifAvailable">
<h2>{{ 'core.settings.localnotifavailable' | translate}}</h2>
<p>{{ localNotifAvailable | translate }}</p>
</ion-item>
</ion-item-group>
</ion-content>

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreSettingsAboutPage } from './about';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
CoreSettingsAboutPage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(CoreSettingsAboutPage),
TranslateModule.forChild()
],
})
export class CoreSettingsAboutPageModule {}

View File

@ -0,0 +1,105 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, } from '@angular/core';
import { IonicPage, Platform } from 'ionic-angular';
import { Device } from '@ionic-native/device';
import { CoreAppProvider } from '@providers/app';
import { CoreFileProvider } from '@providers/file';
import { CoreInitDelegate } from '@providers/init';
import { CoreLangProvider } from '@providers/lang';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreConfigConstants } from '../../../../configconstants';
/**
* Page that displays the about settings.
*/
@IonicPage({segment: 'core-settings-about'})
@Component({
selector: 'page-core-settings-about',
templateUrl: 'about.html',
})
export class CoreSettingsAboutPage {
appName: string;
versionName: string;
versionCode: number;
privacyPolicy: string;
navigator: Navigator;
locationHref: string;
appReady: string;
deviceType: string;
deviceOs: string;
currentLanguage: string;
networkStatus: string;
wifiConnection: string;
deviceWebWorkers: string;
device: Device;
fileSystemRoot: string;
fsClickable: boolean;
storageType: string;
localNotifAvailable: string;
constructor(platform: Platform, device: Device, appProvider: CoreAppProvider, fileProvider: CoreFileProvider,
initDelegate: CoreInitDelegate, langProvider: CoreLangProvider,
localNotificationsProvider: CoreLocalNotificationsProvider) {
this.appName = appProvider.isDesktop() ? CoreConfigConstants.desktopappname : CoreConfigConstants.appname;
this.versionName = CoreConfigConstants.versionname;
this.versionCode = CoreConfigConstants.versioncode;
this.privacyPolicy = CoreConfigConstants.privacypolicy;
this.navigator = window.navigator;
if (window.location && window.location.href) {
const url = window.location.href;
this.locationHref = url.substr(0, url.indexOf('#'));
}
this.appReady = initDelegate.isReady() ? 'core.yes' : 'core.no';
this.deviceType = platform.is('tablet') ? 'core.tablet' : 'core.phone';
if (platform.is('android')) {
this.deviceOs = 'core.android';
} else if (platform.is('ios')) {
this.deviceOs = 'core.ios';
} else if (platform.is('windows')) {
this.deviceOs = 'core.windowsphone';
} else {
const matches = navigator.userAgent.match(/\(([^\)]*)\)/);
if (matches && matches.length > 1) {
this.deviceOs = matches[1];
} else {
this.deviceOs = 'core.unknown';
}
}
langProvider.getCurrentLanguage().then((lang) => {
this.currentLanguage = lang;
});
this.networkStatus = appProvider.isOnline() ? 'core.online' : 'core.offline';
this.wifiConnection = appProvider.isNetworkAccessLimited() ? 'core.no' : 'core.yes';
this.deviceWebWorkers = !!window['Worker'] && !!window['URL'] ? 'core.yes' : 'core.no';
this.device = device;
if (fileProvider.isAvailable()) {
fileProvider.getBasePath().then((basepath) => {
this.fileSystemRoot = basepath;
this.fsClickable = fileProvider.usesHTMLAPI();
});
}
this.localNotifAvailable = localNotificationsProvider.isAvailable() ? 'core.yes' : 'core.no';
}
}

View File

@ -0,0 +1,24 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.settings.general' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-item text-wrap>
<ion-label><h2>{{ 'core.settings.language' | translate }}</h2></ion-label>
<ion-select [(ngModel)]="selectedLanguage" (ngModelChange)="languageChanged()">
<ion-option *ngFor="let code of languageCodes" [value]="code">{{ languages[code] }}</ion-option>
</ion-select>
</ion-item>
<ion-item text-wrap *ngIf="rteSupported">
<ion-label>
<h2>{{ 'core.settings.enablerichtexteditor' | translate }}</h2>
<p>{{ 'core.settings.enablerichtexteditordescription' | translate }}</p>
</ion-label>
<ion-toggle [(ngModel)]="richTextEditor" (ngModelChange)="richTextEditorChanged()"></ion-toggle>
</ion-item>
<ion-item text-wrap *ngIf="showReport">
<ion-label><h2>{{ 'core.settings.reportinbackground' | translate }}</h2></ion-label>
<ion-toggle [(ngModel)]="reportInBackground" (ngModelChange)="reportInBackgroundChanged()"></ion-toggle>
</ion-item>
</ion-content>

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreSettingsGeneralPage } from './general';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
CoreSettingsGeneralPage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(CoreSettingsGeneralPage),
TranslateModule.forChild()
],
})
export class CoreSettingsGeneralPageModule {}

View File

@ -0,0 +1,93 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreConstants } from '@core/constants';
import { CoreConfigProvider } from '@providers/config';
import { CoreFileProvider } from '@providers/file';
import { CoreEventsProvider } from '@providers/events';
import { CoreLangProvider } from '@providers/lang';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreConfigConstants } from '../../../../configconstants';
/**
* Page that displays the general settings.
*/
@IonicPage({segment: 'core-settings-general'})
@Component({
selector: 'page-core-settings-general',
templateUrl: 'general.html',
})
export class CoreSettingsGeneralPage {
languages = {};
languageCodes = [];
selectedLanguage: string;
rteSupported: boolean;
richTextEditor: boolean;
showReport: boolean;
reportInBackground: boolean;
constructor(appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, fileProvider: CoreFileProvider,
private eventsProvider: CoreEventsProvider, private langProvider: CoreLangProvider,
private domUtils: CoreDomUtilsProvider,
localNotificationsProvider: CoreLocalNotificationsProvider) {
this.languages = CoreConfigConstants.languages;
this.languageCodes = Object.keys(this.languages);
langProvider.getCurrentLanguage().then((currentLanguage) => {
this.selectedLanguage = currentLanguage;
});
this.rteSupported = this.domUtils.isRichTextEditorSupported();
if (this.rteSupported) {
this.configProvider.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true).then((richTextEditorEnabled) => {
this.richTextEditor = richTextEditorEnabled;
});
}
if (localStorage && localStorage.getItem && localStorage.setItem) {
this.showReport = true;
this.reportInBackground = parseInt(localStorage.getItem(CoreConstants.SETTINGS_REPORT_IN_BACKGROUND), 10) === 1;
} else {
this.showReport = false;
}
}
/**
* Called when a new language is selected.
*/
languageChanged(): void {
this.langProvider.changeCurrentLanguage(this.selectedLanguage).finally(() => {
this.eventsProvider.trigger(CoreEventsProvider.LANGUAGE_CHANGED);
});
}
/**
* Called when the rich text editor is enabled or disabled.
*/
richTextEditorChanged(): void {
this.configProvider.set(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, this.richTextEditor);
}
/**
* Called when the report in background setting is enabled or disabled.
*/
reportInBackgroundChanged(): void {
localStorage.setItem(CoreConstants.SETTINGS_REPORT_IN_BACKGROUND, this.reportInBackground ? '1' : '0');
}
}

View File

@ -16,7 +16,7 @@
<ion-icon name="stats" item-start></ion-icon>
<p>{{ 'core.settings.spaceusage' | translate }}</p>
</ion-item>
<ion-item (click)="openHandler('CoreSettingSynchronizationPage')" [title]="'core.settings.synchronization' | translate" [class.core-split-item-selected]="'CoreSettingSynchronizationPage' == selectedPage" detail-push>
<ion-item (click)="openHandler('CoreSettingsSynchronizationPage')" [title]="'core.settings.synchronization' | translate" [class.core-split-item-selected]="'CoreSettingsSynchronizationPage' == selectedPage" detail-push>
<ion-icon name="sync" item-start></ion-icon>
<p>{{ 'core.settings.synchronization' | translate }}</p>
</ion-item>

View File

@ -0,0 +1,28 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.settings.spaceusage' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="usageLoaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="usageLoaded">
<ion-item *ngFor="let site of sites" [class.core-primary-item]="site.id == currentSiteId">
<h2><core-format-text [text]="site.siteName"></core-format-text></h2>
<p>{{ site.fullName }}</p>
<p item-end>{{ site.spaceUsage | coreBytesToSize }}</p>
<button ion-button icon-only clear color="danger" item-end (click)="deleteSiteFiles(site)" [hidden]="!site.spaceUsage > '0'" [attr.aria-label]="'core.settings.deletesitefilestitle' | translate">
<ion-icon name="trash"></ion-icon>
</button>
</ion-item>
<ion-item-divider color="light">
<p>{{ 'core.settings.total' | translate }}</p>
<p item-end>{{ totalUsage | coreBytesToSize }}</p>
</ion-item-divider>
<ion-item-divider color="light">
<p>{{ 'core.settings.estimatedfreespace' | translate }}</p>
<p item-end>{{ freeSpace | coreBytesToSize }}</p>
</ion-item-divider>
</core-loading>
</ion-content>

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreSettingsSpaceUsagePage } from './space-usage';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
@NgModule({
declarations: [
CoreSettingsSpaceUsagePage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(CoreSettingsSpaceUsagePage),
TranslateModule.forChild()
],
})
export class CoreSettingsSpaceUsagePageModule {}

View File

@ -0,0 +1,180 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreFileProvider } from '@providers/file';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
* Page that displays the space usage settings.
*/
@IonicPage({segment: 'core-settings-space-usage'})
@Component({
selector: 'page-core-settings-space-usage',
templateUrl: 'space-usage.html',
})
export class CoreSettingsSpaceUsagePage {
usageLoaded = false;
sites = [];
currentSiteId = '';
totalUsage = 0;
freeSpace = 0;
constructor(private fileProvider: CoreFileProvider, private filePoolProvider: CoreFilepoolProvider,
private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
private translate: TranslateService, private domUtils: CoreDomUtilsProvider) {
this.currentSiteId = this.sitesProvider.getCurrentSiteId();
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchData().finally(() => {
this.usageLoaded = true;
});
}
/**
* Convenience function to calculate each site's usage, and the total usage.
*
* @return {Promise<any>} Resolved when done.
*/
protected calculateSizeUsage(): Promise<any> {
return this.sitesProvider.getSortedSites().then((sites) => {
this.sites = sites;
// Get space usage.
const promises = this.sites.map((siteEntry) => {
return this.sitesProvider.getSite(siteEntry.id).then((site) => {
return site.getSpaceUsage().then((size) => {
siteEntry.spaceUsage = size;
});
});
});
return Promise.all(promises);
});
}
/**
* Convenience function to calculate total usage.
*/
protected calculateTotalUsage(): void {
let total = 0;
this.sites.forEach((site) => {
if (site.spaceUsage) {
total += parseInt(site.spaceUsage, 10);
}
});
this.totalUsage = total;
}
/**
* Convenience function to calculate free space in the device.
*
* @return {Promise<any>} Resolved when done.
*/
protected calculateFreeSpace(): Promise<any> {
if (this.fileProvider.isAvailable()) {
return this.fileProvider.calculateFreeSpace().then((freeSpace) => {
this.freeSpace = freeSpace;
}).catch(() => {
this.freeSpace = 0;
});
} else {
this.freeSpace = 0;
return Promise.resolve(null);
}
}
/**
* Convenience function to calculate space usage and free space in the device.
*
* @return {Promise<any>} Resolved when done.
*/
protected fetchData(): Promise<any> {
return Promise.all([
this.calculateSizeUsage().then(() => this.calculateTotalUsage()),
this.calculateFreeSpace(),
]);
}
/**
* Refresh the data.
*
* @param {any} refresher Refresher.
*/
refreshData(refresher: any): void {
this.fetchData().finally(() => {
refresher.complete();
});
}
/**
* Convenience function to update site size, along with total usage and free space.
*
* @param {any} site Site object with space usage.
* @param {number} newUsage New space usage of the site in bytes.
*/
protected updateSiteUsage(site: any, newUsage: number): void {
const oldUsage = site.spaceUsage;
site.spaceUsage = newUsage;
this.totalUsage -= oldUsage - newUsage;
this.freeSpace += oldUsage - newUsage;
}
/**
* Deletes files of a site.
*
* @param {any} siteData Site object with space usage.
*/
deleteSiteFiles(siteData: any): void {
this.textUtils.formatText(siteData.siteName).then((siteName) => {
const title = this.translate.instant('core.settings.deletesitefilestitle');
const message = this.translate.instant('core.settings.deletesitefiles', {sitename: siteName});
this.domUtils.showConfirm(message, title).then(() => {
return this.sitesProvider.getSite(siteData.id);
}).then((site) => {
site.deleteFolder().then(() => {
this.filePoolProvider.clearAllPackagesStatus(site.id);
this.filePoolProvider.clearFilepool(site.id);
this.updateSiteUsage(siteData, 0);
}).catch((error) => {
if (error && error.code === FileError.NOT_FOUND_ERR) {
// Not found, set size 0.
this.filePoolProvider.clearAllPackagesStatus(site.id);
this.updateSiteUsage(siteData, 0);
} else {
// Error, recalculate the site usage.
this.domUtils.showErrorModal('core.settings.errordeletesitefiles', true);
site.getSpaceUsage().then((size) => {
this.updateSiteUsage(siteData, size);
});
}
});
}).catch(() => {
// Ignore cancelled confirmation modal.
});
});
}
}

View File

@ -0,0 +1,29 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'core.settings.synchronization' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<core-loading [hideUntil]="sitesLoaded">
<ion-item-divider color="light">
<p>{{ 'core.settings.syncsettings' | translate }}</p>
</ion-item-divider>
<ion-item>
<ion-label>{{ 'core.settings.enablesyncwifi' | translate }}</ion-label>
<ion-toggle item-end [(ngModel)]="syncOnlyOnWifi" (ngModelChange)="syncOnlyOnWifiChanged()">
</ion-toggle>
</ion-item>
<ion-item-divider color="light">
<p>{{ 'core.settings.sites' | translate }}</p>
</ion-item-divider>
<ion-item *ngFor="let site of sites" [class.core-primary-item]="site.id == currentSiteId">
<h2><core-format-text [text]="site.siteName"></core-format-text></h2>
<p>{{ site.fullName }}</p>
<p>{{ site.siteUrl }}</p>
<button ion-button icon-only clear item-end *ngIf="!isSynchronizing(site.id)" (click)="synchronize(site.id)" [title]="site.siteName" [attr.aria-label]="'core.settings.synchronizenow' | translate">
<ion-icon name="sync"></ion-icon>
</button>
<ion-spinner item-end *ngIf="isSynchronizing(site.id)"></ion-spinner>
</ion-item>
</core-loading>
</ion-content>

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreSettingsSynchronizationPage } from './synchronization';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
@NgModule({
declarations: [
CoreSettingsSynchronizationPage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(CoreSettingsSynchronizationPage),
TranslateModule.forChild()
],
})
export class CoreSettingsSynchronizationPageModule {}

View File

@ -0,0 +1,113 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, OnDestroy } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { CoreConstants } from '@core/constants';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider, CoreSiteBasicInfo } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreConfigProvider } from '@providers/config';
import { CoreSettingsHelper } from '@core/settings/providers/helper';
/**
* Page that displays the synchronization settings.
*/
@IonicPage({segment: 'core-settings-synchronization'})
@Component({
selector: 'page-core-settings-synchronization',
templateUrl: 'synchronization.html',
})
export class CoreSettingsSynchronizationPage implements OnDestroy {
sites: CoreSiteBasicInfo[] = [];
sitesLoaded = false;
sitesObserver: any;
currentSiteId = '';
syncOnlyOnWifi = false;
isDestroyed = false;
constructor(private configProvider: CoreConfigProvider, private eventsProvider: CoreEventsProvider,
private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
private settingsHelper: CoreSettingsHelper) {
this.currentSiteId = this.sitesProvider.getCurrentSiteId();
this.sitesObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
this.sitesProvider.getSite(data.siteId).then((site) => {
const siteInfo = site.getInfo();
const siteEntry = this.sites.find((siteEntry) => siteEntry.id == site.id);
if (siteEntry) {
siteEntry.siteUrl = siteInfo.siteurl;
siteEntry.siteName = siteInfo.sitename;
siteEntry.fullName = siteInfo.fullname;
}
});
});
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.sitesProvider.getSortedSites().then((sites) => {
this.sites = sites;
}).finally(() => {
this.sitesLoaded = true;
});
this.configProvider.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true).then((syncOnlyOnWifi) => {
this.syncOnlyOnWifi = syncOnlyOnWifi;
});
}
/**
* Called when sync only on wifi setting is enabled or disabled.
*/
syncOnlyOnWifiChanged(): void {
this.configProvider.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.syncOnlyOnWifi);
}
/**
* Syncrhonizes a site.
*
* @param {string} siteId Site ID.
*/
synchronize(siteId: string): void {
this.settingsHelper.synchronizeSite(this.syncOnlyOnWifi, siteId).catch((error) => {
if (this.isDestroyed) {
return;
}
this.domUtils.showErrorModalDefault(error, 'core.settings.errorsyncsite', true);
});
}
/**
* Returns true if site is beeing synchronized.
*
* @param {string} siteId Site ID.
* @return {boolean} True if site is beeing synchronized, false otherwise.
*/
isSynchronizing(siteId: string): boolean {
return !!this.settingsHelper.getSiteSyncPromise(siteId);
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.isDestroyed = true;
this.sitesObserver && this.sitesObserver.off();
}
}

View File

@ -13,8 +13,14 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreAppProvider } from '@providers/app';
import { CoreCronDelegate } from '@providers/cron';
import { CoreEventsProvider } from '@providers/events';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { TranslateService } from '@ngx-translate/core';
/**
* Settings helper service.
@ -22,8 +28,11 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
@Injectable()
export class CoreSettingsHelper {
protected logger;
protected syncPromises = {};
constructor(loggerProvider: CoreLoggerProvider, private utils: CoreUtilsProvider) {
constructor(loggerProvider: CoreLoggerProvider, private appProvider: CoreAppProvider, private cronDelegate: CoreCronDelegate,
private eventsProvider: CoreEventsProvider, private filePoolProvider: CoreFilepoolProvider,
private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private translate: TranslateService) {
this.logger = loggerProvider.getInstance('CoreSettingsHelper');
}
@ -91,4 +100,79 @@ export class CoreSettingsHelper {
return result;
}
/**
* Get the synchronization promise of a site.
*
* @param {string} siteId ID of the site.
* @return {Promise<any> | null} Sync promise or null if site is not being syncrhonized.
*/
getSiteSyncPromise(siteId: string): Promise<any> {
if (this.syncPromises[siteId]) {
return this.syncPromises[siteId];
} else {
return null;
}
}
/**
* Synchronize a site.
*
* @param {boolean} syncOnlyOnWifi True to sync only on wifi, false otherwise.
* @param {string} siteId ID of the site to synchronize.
* @return {Promise<any>} Promise resolved when synchronized, rejected if failure.
*/
synchronizeSite(syncOnlyOnWifi: boolean, siteId: string): Promise<any> {
if (this.syncPromises[siteId]) {
// There's already a sync ongoing for this site, return the promise.
return this.syncPromises[siteId];
}
const promises = [];
const hasSyncHandlers = this.cronDelegate.hasManualSyncHandlers();
if (hasSyncHandlers && !this.appProvider.isOnline()) {
// We need connection to execute sync.
return Promise.reject(this.translate.instant('core.settings.cannotsyncoffline'));
} else if (hasSyncHandlers && syncOnlyOnWifi && this.appProvider.isNetworkAccessLimited()) {
return Promise.reject(this.translate.instant('core.settings.cannotsyncwithoutwifi'));
}
// Invalidate all the site files so they are re-downloaded.
promises.push(this.filePoolProvider.invalidateAllFiles(siteId).catch(() => {
// Ignore errors.
}));
// Get the site to invalidate data.
promises.push(this.sitesProvider.getSite(siteId).then((site) => {
// Invalidate the WS cache.
return site.invalidateWsCache().then(() => {
const subPromises = [];
// Check if local_mobile was installed in Moodle.
subPromises.push(site.checkIfLocalMobileInstalledAndNotUsed().then(() => {
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, siteId);
return Promise.reject(this.translate.instant('core.lostconnection'));
}, () => {
// Update site info.
return this.sitesProvider.updateSiteInfo(siteId);
}));
// Execute cron if needed.
subPromises.push(this.cronDelegate.forceSyncExecution(siteId));
return Promise.all(subPromises);
});
}));
const syncPromise = Promise.all(promises);
this.syncPromises[siteId] = syncPromise;
syncPromise.finally(() => {
delete this.syncPromises[siteId];
});
return syncPromise;
}
}

View File

@ -870,6 +870,36 @@ export class CoreSitesProvider {
});
}
/**
* Get the list of sites stored, sorted by URL and full name.
*
* @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites.
* @return {Promise<CoreSiteBasicInfo[]>} Promise resolved when the sites are retrieved.
*/
getSortedSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
return this.getSites(ids).then((sites) => {
// Sort sites by url and ful lname.
sites.sort((a, b) => {
// First compare by site url without the protocol.
let compareA = a.siteUrl.replace(/^https?:\/\//, '').toLowerCase(),
compareB = b.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
const compare = compareA.localeCompare(compareB);
if (compare !== 0) {
return compare;
}
// If site url is the same, use fullname instead.
compareA = a.fullName.toLowerCase().trim();
compareB = b.fullName.toLowerCase().trim();
return compareA.localeCompare(compareB);
});
return sites;
});
}
/**
* Get the list of IDs of sites stored.
*