refactor(setting): adjust setting module to new core translation

Closes #53
This commit is contained in:
Sebastian Lange
2019-02-26 08:22:49 +01:00
parent 24dbb42b34
commit 49b7c6d383
12 changed files with 9876 additions and 545 deletions

10067
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,9 +47,9 @@
"@ionic/storage": "2.2.0", "@ionic/storage": "2.2.0",
"@ngx-translate/core": "11.0.1", "@ngx-translate/core": "11.0.1",
"@ngx-translate/http-loader": "4.0.0", "@ngx-translate/http-loader": "4.0.0",
"@openstapps/api": "0.12.0", "@openstapps/api": "0.17.0",
"@openstapps/configuration": "0.21.0", "@openstapps/configuration": "0.22.0",
"@openstapps/core": "0.23.1", "@openstapps/core": "0.31.0",
"@openstapps/logger": "0.4.0", "@openstapps/logger": "0.4.0",
"cordova-android": "8.0.0", "cordova-android": "8.0.0",
"cordova-browser": "6.0.0", "cordova-browser": "6.0.0",
@@ -61,7 +61,7 @@
"cordova-plugin-splashscreen": "5.0.2", "cordova-plugin-splashscreen": "5.0.2",
"cordova-plugin-whitelist": "1.3.3", "cordova-plugin-whitelist": "1.3.3",
"core-js": "2.6.5", "core-js": "2.6.5",
"deepmerge": "3.2.0", "deepmerge": "3.3.0",
"form-data": "2.5.0", "form-data": "2.5.0",
"moment": "2.24.0", "moment": "2.24.0",
"ngx-markdown": "7.1.4", "ngx-markdown": "7.1.4",
@@ -80,6 +80,7 @@
"@angular/language-service": "7.2.14", "@angular/language-service": "7.2.14",
"@compodoc/compodoc": "1.1.10", "@compodoc/compodoc": "1.1.10",
"@ionic/angular-toolkit": "2.0.0", "@ionic/angular-toolkit": "2.0.0",
"@types/deepmerge": "2.2.0",
"@types/form-data": "2.5.0", "@types/form-data": "2.5.0",
"@types/jasmine": "3.3.12", "@types/jasmine": "3.3.12",
"@types/jasminewd2": "2.0.6", "@types/jasminewd2": "2.0.6",
@@ -87,13 +88,13 @@
"codelyzer": "5.0.0", "codelyzer": "5.0.0",
"conventional-changelog-cli": "2.0.12", "conventional-changelog-cli": "2.0.12",
"is-docker": "1.1.0", "is-docker": "1.1.0",
"jasmine-core": "3.4.0", "jasmine-core": "3.5.0",
"jasmine-spec-reporter": "4.2.1", "jasmine-spec-reporter": "4.2.1",
"karma": "4.0.1", "karma": "4.4.1",
"karma-chrome-launcher": "2.2.0", "karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "2.0.5", "karma-coverage-istanbul-reporter": "2.1.0",
"karma-jasmine": "2.0.1", "karma-jasmine": "2.0.1",
"karma-jasmine-html-reporter": "1.4.0", "karma-jasmine-html-reporter": "1.4.2",
"karma-mocha-reporter": "2.2.5", "karma-mocha-reporter": "2.2.5",
"protractor": "5.4.2", "protractor": "5.4.2",
"surge": "0.20.5", "surge": "0.20.5",

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018, 2019 StApps * Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -12,8 +12,15 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {HTTP_INTERCEPTORS, HttpClient, import {
HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; HTTP_INTERCEPTORS,
HttpClient,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {SCIndexResponse, SCSettingInputType, SCThingOriginType, SCThingType} from '@openstapps/core'; import {SCIndexResponse, SCSettingInputType, SCThingOriginType, SCThingType} from '@openstapps/core';
import {Observable, of} from 'rxjs'; import {Observable, of} from 'rxjs';
@@ -71,20 +78,28 @@ const sampleIndexResponse: SCIndexResponse = {
}, },
translations: { translations: {
de: { de: {
categories: ['Benutzer'],
description: 'Mit welcher Benutzergruppe soll die App verwendet werden?' description: 'Mit welcher Benutzergruppe soll die App verwendet werden?'
+ ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.', + ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe', name: 'Gruppe',
values: [
'Student',
'Angestellter',
'Gast',
],
}, },
en: { en: {
categories: ['User'],
description: 'The user group the app is going to be used.' description: 'The user group the app is going to be used.'
+ 'This settings for example is getting used for the predefined price category of mensa meals.', + 'This settings for example is getting used for the predefined price category of mensa meals.',
name: 'Group', name: 'Group',
values: [
'Student',
'Employee',
'Guest',
],
}, },
}, },
type: SCThingType.Setting, type: SCThingType.Setting,
uid: '', uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['student', 'employee', 'guest'], values: ['student', 'employee', 'guest'],
}, },
{ {
@@ -101,19 +116,25 @@ const sampleIndexResponse: SCIndexResponse = {
}, },
translations: { translations: {
de: { de: {
categories: ['Benutzer'], description: 'Die Sprache in der die App angezeigt wird.',
description: 'Die Sprache in der die App angezeigt werden soll',
name: 'Sprache', name: 'Sprache',
values: [
'Deutsch',
'English',
],
}, },
en: { en: {
categories: ['User'], description: 'The language this app is going to use.',
description: 'The language this app is going to use',
name: 'Language', name: 'Language',
values: [
'Deutsch',
'English',
],
}, },
}, },
type: SCThingType.Setting, type: SCThingType.Setting,
uid: '', uid: 'dc9d6dec-6576-45ef-9e35-3598c0d6a662',
values: ['en', 'de'], values: ['de', 'en'],
}, },
{ {
categories: ['privacy'], categories: ['privacy'],
@@ -129,31 +150,33 @@ const sampleIndexResponse: SCIndexResponse = {
}, },
translations: { translations: {
de: { de: {
categories: ['Privatsphäre'], description: `Berechtigung für die Verwendung des Ortungsdienstes, für die Anzeige der aktuellen Position '
description: 'Berechtigung für die Verwendung des Ortungsdienstes, für die Anzeige der aktuellen ' + auf der Karte und zur Berechnung der Entfernung zu Gebäuden und Orten des Campus.`,
'Position \'\n auf der Karte und zur Berechnung der Entfernung zu Gebäuden und Orten des Campus',
name: 'Position', name: 'Position',
values: ['ja', 'nein'],
}, },
en: { en: {
categories: ['Privacy'], description: 'Allow the App to use the device location to provide additional information\'s based ' +
description: 'Allow the App to use the device location to provide additional informationsbased ' + 'on your actual location.',
'on your actual location',
name: 'Position', name: 'Position',
values: ['yes', 'no'],
}, },
}, },
type: SCThingType.Setting, type: SCThingType.Setting,
uid: '', uid: '0dbff2de-23b4-442b-9aa7-7bd2c707c199',
values: [true, false], values: [true, false],
}, },
], ],
}, },
backend: { backend: {
SCVersion: '1.0.0', SCVersion: '1.0.0',
externalRequestTimeout: 5000,
hiddenTypes: [ hiddenTypes: [
SCThingType.DateSeries, SCThingType.DateSeries,
SCThingType.Diff, SCThingType.Diff,
SCThingType.Floor, SCThingType.Floor,
], ],
mappingIgnoredTags: [],
maxMultiSearchRouteQueries: 5, maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024, maxRequestBodySize: 512 * 1024,
name: 'Technische Universität Berlin', name: 'Technische Universität Berlin',
@@ -228,6 +251,12 @@ const sampleIndexResponse: SCIndexResponse = {
*/ */
@Injectable() @Injectable()
export class FakeBackendInterceptor implements HttpInterceptor { export class FakeBackendInterceptor implements HttpInterceptor {
/**
* Delay time of faked responses
*/
RESPONSE_DELAY = 1000;
/** /**
* TODO * TODO
*/ */
@@ -240,6 +269,7 @@ export class FakeBackendInterceptor implements HttpInterceptor {
/** /**
* TODO * TODO
*/ */
// tslint:disable-next-line:no-any
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.method === 'POST') { if (request.method === 'POST') {
if (request.url.endsWith('/') && request.method === 'POST') { if (request.url.endsWith('/') && request.method === 'POST') {
@@ -251,16 +281,18 @@ export class FakeBackendInterceptor implements HttpInterceptor {
if (typeof request.body.filter !== 'undefined' && typeof request.body.filter.arguments !== 'undefined') { if (typeof request.body.filter !== 'undefined' && typeof request.body.filter.arguments !== 'undefined') {
if (request.body.filter.arguments.field === 'uid') { if (request.body.filter.arguments.field === 'uid') {
return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value) return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value)
// tslint:disable-next-line:no-any
.pipe(map((sampleData: any) => { .pipe(map((sampleData: any) => {
return new HttpResponse({status: 200, body: {data: sampleData}}); return new HttpResponse({status: 200, body: {data: sampleData}});
}), delay(1000)); // add delay for skeleton screens to be seen (see !16) }), delay(this.RESPONSE_DELAY)); // add delay for skeleton screens to be seen (see !16)
} }
} }
return this.sampleFetcher.getSampleThings() return this.sampleFetcher.getSampleThings()
// tslint:disable-next-line:no-any
.pipe(map((sampleData: any) => { .pipe(map((sampleData: any) => {
return new HttpResponse({status: 200, body: {data: sampleData}}); return new HttpResponse({status: 200, body: {data: sampleData}});
}), delay(1000)); // add delay for skeleton screens to be seen (see !16) }), delay(this.RESPONSE_DELAY)); // add delay for skeleton screens to be seen (see !16)
} }
} }

View File

@@ -36,7 +36,7 @@ export class AppComponent {
/** /**
* TODO * TODO
*/ */
component: any; component: unknown;
/** /**
* TODO * TODO
*/ */
@@ -97,7 +97,7 @@ export class AppComponent {
try { try {
// set language from settings // set language from settings
const languageCode = await this.settingsProvider.getValue('profile', 'language'); const languageCode = (await this.settingsProvider.getValue('profile', 'language')) as string;
this.translateService.use(languageCode); this.translateService.use(languageCode);
} catch (error) { } catch (error) {
Logger.warn(error); Logger.warn(error);

View File

@@ -17,12 +17,7 @@ import {SCIndexResponse, SCThingOriginType, SCThingType, SCSettingInputType} fro
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {StorageProvider} from '../storage/storage.provider'; import {StorageProvider} from '../storage/storage.provider';
import {ConfigProvider, STORAGE_KEY_CONFIG} from './config.provider'; import {ConfigProvider, STORAGE_KEY_CONFIG} from './config.provider';
import { import {ConfigFetchError, ConfigInitError, SavedConfigNotAvailable, WrongConfigVersionInStorage,} from './errors';
ConfigFetchError,
ConfigInitError,
SavedConfigNotAvailable,
WrongConfigVersionInStorage,
} from './errors';
describe('ConfigProvider', () => { describe('ConfigProvider', () => {
let configProvider: ConfigProvider; let configProvider: ConfigProvider;
@@ -218,11 +213,9 @@ const sampleIndexResponse: SCIndexResponse = {
}, },
translations: { translations: {
de: { de: {
categories: ['Anmeldedaten'],
name: 'Benutzername', name: 'Benutzername',
}, },
en: { en: {
categories: ['Credentials'],
name: 'Username', name: 'Username',
}, },
}, },
@@ -233,11 +226,13 @@ const sampleIndexResponse: SCIndexResponse = {
}, },
backend: { backend: {
SCVersion: '1.0.0', SCVersion: '1.0.0',
externalRequestTimeout: 5000,
hiddenTypes: [ hiddenTypes: [
SCThingType.DateSeries, SCThingType.DateSeries,
SCThingType.Diff, SCThingType.Diff,
SCThingType.Floor, SCThingType.Floor,
], ],
mappingIgnoredTags: [],
maxMultiSearchRouteQueries: 5, maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024, maxRequestBodySize: 512 * 1024,
name: 'Technische Universität Berlin', name: 'Technische Universität Berlin',

View File

@@ -40,22 +40,23 @@ export const STORAGE_KEY_CONFIG = 'stapps.config';
@Injectable() @Injectable()
export class ConfigProvider { export class ConfigProvider {
/** /**
* TODO * Api client
*/ */
client: Client; client: Client;
/** /**
* TODO * App configuration as IndexResponse
*/ */
config: SCIndexResponse; config: SCIndexResponse;
/** /**
* TODO * Initialised status flag of config provider
*/ */
initialised = false; initialised = false;
/** /**
* Constructor, initialise api client
* *
* @param storageProvider TODO * @param storageProvider StorageProvider to load persistet configuration
* @param swHttpClient TODO * @param swHttpClient Api client
*/ */
constructor(private readonly storageProvider: StorageProvider, swHttpClient: StAppsWebHttpClient) { constructor(private readonly storageProvider: StorageProvider, swHttpClient: StAppsWebHttpClient) {
this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version); this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
@@ -108,7 +109,7 @@ export class ConfigProvider {
try { try {
this.config = await this.loadLocal(); this.config = await this.loadLocal();
this.initialised = true; this.initialised = true;
Logger.log(`initialised configuration from storage: ${JSON.stringify(this.config)}`); Logger.log(`initialised configuration from storage`);
if (this.config.backend.SCVersion !== environment.backend_version) { if (this.config.backend.SCVersion !== environment.backend_version) {
loadError = new WrongConfigVersionInStorage(environment.backend_version, this.config.backend.SCVersion); loadError = new WrongConfigVersionInStorage(environment.backend_version, this.config.backend.SCVersion);
Logger.warn(loadError); Logger.warn(loadError);
@@ -121,7 +122,7 @@ export class ConfigProvider {
const fetchedConfig: SCIndexResponse = await this.fetch(); const fetchedConfig: SCIndexResponse = await this.fetch();
await this.set(fetchedConfig); await this.set(fetchedConfig);
this.initialised = true; this.initialised = true;
Logger.log(`initialised configuration from remote: ${JSON.stringify(this.config)}`); Logger.log(`initialised configuration from remote`);
} catch (error) { } catch (error) {
fetchError = error; fetchError = error;
} }

View File

@@ -12,61 +12,68 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, Input} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {AlertController} from '@ionic/angular'; import {AlertController} from '@ionic/angular';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import { import {
SCLanguage,
SCSetting, SCSetting,
SCSettingValue, SCSettingValue,
SCSettingValues, SCSettingValues,
SCThingTranslator, SCThingTranslator,
SCTranslations, SCTranslations,
} from '@openstapps/core'; } from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import {SettingsProvider} from '../settings.provider'; import {SettingsProvider} from '../settings.provider';
/** /**
* TODO * Setting item component
*/ */
@Component({ @Component({
selector: 'stapps-settings-item', selector: 'stapps-settings-item',
templateUrl: 'settings-item.html', templateUrl: 'settings-item.html',
}) })
export class SettingsItemComponent { export class SettingsItemComponent implements OnInit {
/** /**
* TODO * Flag for workaround for selected 'select option' not updating translation
*/ */
isVisible = true; isVisible = true;
/** /**
* TODO * current language
*
* limit to languages that are available in StApps Core
*/ */
language: keyof SCTranslations<any>; language: keyof SCTranslations<SCLanguage>;
/** /**
* TODO * The setting to handle
*/ */
@Input() setting: SCSetting; @Input() setting: SCSetting;
/** /**
* TODO * Translated setting from SCThingTranslator
*/
// tslint:disable-next-line:no-any
translatedSetting: any;
/**
* Core translator
*/ */
translator: SCThingTranslator; translator: SCThingTranslator;
/** /**
* *
* @param alertCtrl TODO * @param alertCtrl AlertController
* @param translateService TODO * @param translateService TranslateService
* @param settingsProvider TODO * @param settingsProvider SettingProvider
*/ */
constructor(private readonly alertCtrl: AlertController, constructor(private readonly alertCtrl: AlertController,
private readonly translateService: TranslateService, private readonly translateService: TranslateService,
private readonly settingsProvider: SettingsProvider) { private readonly settingsProvider: SettingsProvider) {
this.language = translateService.currentLang as keyof SCTranslations<any>; this.language = translateService.currentLang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language); this.translator = new SCThingTranslator(this.language);
translateService.onLangChange.subscribe((event: LangChangeEvent) => { translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.isVisible = false; this.isVisible = false;
this.language = event.lang as keyof SCTranslations<any>; this.language = event.lang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language); this.translator.language = this.language;
// TODO: Issue #53 check workaround for selected 'select option' not updating translation // TODO: Issue #53 check workaround for selected 'select option' not updating translation
setTimeout(() => this.isVisible = true); setTimeout(() => this.isVisible = true);
}); });
@@ -96,11 +103,20 @@ export class SettingsItemComponent {
await this.presentAlert(title, message); await this.presentAlert(title, message);
} }
/**
* NgInit
*/
ngOnInit(): void {
Logger.log(JSON.stringify(this.setting));
this.translatedSetting = this.translator.translate(this.setting);
}
/** /**
* Shows alert with given title and message and a 'ok' button * Shows alert with given title and message and a 'ok' button
* *
* @param title title of the alert * @param title Title of the alert
* @param message message of the alert * @param message Message of the alert
*/ */
async presentAlert(title: string, message: string) { async presentAlert(title: string, message: string) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
@@ -123,7 +139,7 @@ export class SettingsItemComponent {
this.translateService.use(this.setting.value.toString()); this.translateService.use(this.setting.value.toString());
break; break;
case 'geoLocation': case 'geoLocation':
if (this.setting.value) { if (!!this.setting.value) {
await this.checkGeoLocationPermission(); await this.checkGeoLocationPermission();
} }
break; break;
@@ -140,10 +156,10 @@ export class SettingsItemComponent {
} }
/** /**
* TODO * Mapping of typeOf for Html usage
*/ */
// tslint:disable-next-line:prefer-function-over-method // tslint:disable-next-line:prefer-function-over-method
typeOf(val: any) { typeOf(val: unknown) {
return typeof (val); return typeof (val);
} }
} }

View File

@@ -1,9 +1,9 @@
<ion-card> <ion-card *ngIf="setting">
<ion-card-header> <ion-card-header>
<span>{{ translator.translate(setting).name() }}</span> <span>{{ translatedSetting.name() | titlecase }}</span>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<ion-note>{{ translator.translate(setting).description() }}</ion-note> <ion-note>{{ translatedSetting.description() }}</ion-note>
<div [ngSwitch]="setting.inputType" *ngIf="isVisible" > <div [ngSwitch]="setting.inputType" *ngIf="isVisible" >
<ion-item *ngSwitchCase="'number'"> <ion-item *ngSwitchCase="'number'">
@@ -21,22 +21,24 @@
<ion-input type="password" [(ngModel)]="setting.value" value={{setting.value}} (ionChange)="settingChanged()"></ion-input> <ion-input type="password" [(ngModel)]="setting.value" value={{setting.value}} (ionChange)="settingChanged()"></ion-input>
</ion-item> </ion-item>
<ion-item *ngSwitchCase="'singleChoice'"> <ion-item *ngSwitchCase="'single choice'">
<ion-label></ion-label> <ion-label></ion-label>
<!-- if values are boolean show as toggle -->
<ion-toggle *ngIf="typeOf(setting.defaultValue) === 'boolean'" [(ngModel)]="setting.value" (ionChange)="settingChanged()"></ion-toggle> <ion-toggle *ngIf="typeOf(setting.defaultValue) === 'boolean'" [(ngModel)]="setting.value" (ionChange)="settingChanged()"></ion-toggle>
<!-- else show select input -->
<ion-select *ngIf="typeOf(setting.defaultValue) !== 'boolean'" interface="popover" [(ngModel)]="setting.value" (ionChange)="settingChanged()"> <ion-select *ngIf="typeOf(setting.defaultValue) !== 'boolean'" interface="popover" [(ngModel)]="setting.value" (ionChange)="settingChanged()">
<ion-select-option *ngFor="let val of setting.values" [value]="val"> <ion-select-option *ngFor="let val of setting.values, index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">{{ val }}</div> <div *ngIf="typeOf(val) !== 'number'">{{ translatedSetting.values()[i] | titlecase }}</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div> <div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option> </ion-select-option>
</ion-select> </ion-select>
</ion-item> </ion-item>
<ion-item *ngSwitchCase="'multipleChoice'"> <ion-item *ngSwitchCase="'multiple choice'">
<ion-label></ion-label> <ion-label></ion-label>
<ion-select [(ngModel)]="setting.value" multiple="true" (ionChange)="settingChanged()"> <ion-select [(ngModel)]="setting.value" multiple="true" (ionChange)="settingChanged()">
<ion-select-option *ngFor="let val of setting.values" [value]="val"> <ion-select-option *ngFor="let val of setting.values, index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">{{ val }}</div> <div *ngIf="typeOf(val) !== 'number'">{{ translatedSetting.values()[i] }}</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div> <div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option> </ion-select-option>
</ion-select> </ion-select>

View File

@@ -15,11 +15,11 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {AlertController, ToastController} from '@ionic/angular'; import {AlertController, ToastController} from '@ionic/angular';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCSettingMeta, SCThingTranslator, SCTranslations} from '@openstapps/core'; import {SCLanguage, SCSettingMeta, SCThingTranslator, SCTranslations} from '@openstapps/core';
import {SettingsCache, SettingsProvider} from '../settings.provider'; import {SettingsCache, SettingsProvider} from '../settings.provider';
/** /**
* TODO * Settings page component
*/ */
@Component({ @Component({
selector: 'stapps-settings-page', selector: 'stapps-settings-page',
@@ -35,40 +35,40 @@ export class SettingsPageComponent {
* *
* limit to languages that are available in StApps Core * limit to languages that are available in StApps Core
*/ */
language: keyof SCTranslations<any>; language: keyof SCTranslations<SCLanguage>;
/** /**
* Meta information about settings * Meta information about settings
*/ */
meta = SCSettingMeta; meta = SCSettingMeta;
/** /**
* TODO * Mapping of Object.keys for Html usage
*/ */
objectKeys = Object.keys; objectKeys = Object.keys;
/** /**
* TODO * Container to cache settings from provider
*/ */
settingsCache: SettingsCache; settingsCache: SettingsCache;
/** /**
* TODO * Core translator
*/ */
translator: SCThingTranslator; translator: SCThingTranslator;
/** /**
* *
* @param alertController TODO * @param alertController AlertController
* @param settingsProvider TODO * @param settingsProvider SettingsProvider
* @param toastController TODO * @param toastController ToastController
* @param translateService TODO * @param translateService TranslateService
*/ */
constructor(private readonly alertController: AlertController, constructor(private readonly alertController: AlertController,
private readonly settingsProvider: SettingsProvider, private readonly settingsProvider: SettingsProvider,
private readonly toastController: ToastController, private readonly toastController: ToastController,
private readonly translateService: TranslateService) { private readonly translateService: TranslateService) {
this.language = translateService.currentLang as keyof SCTranslations<any>; this.language = translateService.currentLang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language); this.translator = new SCThingTranslator(this.language);
translateService.onLangChange.subscribe((event: LangChangeEvent) => { translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.language = event.lang as keyof SCTranslations<any>; this.language = event.lang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language); this.translator = new SCThingTranslator(this.language);
}); });
this.settingsCache = {}; this.settingsCache = {};
@@ -142,4 +142,27 @@ export class SettingsPageComponent {
await this.loadSettings(); await this.loadSettings();
await this.presentSettingsResetToast(); await this.presentSettingsResetToast();
} }
/**
* Shows alert to reset settings
*/
async showResetAlert() {
const alert = await this.alertController.create({
buttons: [
{
role: 'cancel',
text: this.translateService.instant('settings.resetAlert.buttonCancel'),
},
{
handler: async () => {
await this.resetSettings();
},
text: this.translateService.instant('settings.resetAlert.buttonYes'),
},
],
header: this.translateService.instant('settings.resetAlert.title'),
message: this.translateService.instant('settings.resetAlert.message'),
});
alert.present();
}
} }

View File

@@ -4,7 +4,7 @@
<ion-back-button></ion-back-button> <ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
</ion-buttons> </ion-buttons>
<ion-title>{{'settings.title' | translate}}</ion-title> <ion-title>{{'settings.title' | translate | titlecase}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -12,8 +12,7 @@
<ion-list *ngFor="let categoryKey of categoriesOrder "> <ion-list *ngFor="let categoryKey of categoriesOrder ">
<div *ngIf="objectKeys(settingsCache).includes(categoryKey)"> <div *ngIf="objectKeys(settingsCache).includes(categoryKey)">
<ion-item-divider> <ion-item-divider>
<h5>{{ settingsCache[categoryKey].settings[objectKeys(settingsCache[categoryKey].settings)[0]].translations[language].categories[0]}} <h5>{{ translator.translate(settingsCache[categoryKey]?.settings[objectKeys(settingsCache[categoryKey]?.settings)[0]]).categories()[0] | titlecase}}</h5>
</h5>
</ion-item-divider> </ion-item-divider>
<stapps-settings-item *ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)" [setting]="settingsCache[categoryKey].settings[settingKeys]"></stapps-settings-item> <stapps-settings-item *ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)" [setting]="settingsCache[categoryKey].settings[settingKeys]"></stapps-settings-item>
</div> </div>

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018, 2019 StApps * Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -14,10 +14,10 @@
*/ */
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {SCSetting, SCThingOriginType, SCThingType, SCSettingInputType} from '@openstapps/core'; import {SCSetting, SCThingOriginType, SCThingType, SCSettingInputType} from '@openstapps/core';
import {Geolocation} from '@ionic-native/geolocation/ngx';
import {ConfigProvider} from '../config/config.provider'; import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider'; import {StorageProvider} from '../storage/storage.provider';
import {SettingsProvider, SettingValuesContainer, STORAGE_KEY_SETTING_VALUES} from './settings.provider'; import {SettingsProvider, SettingValuesContainer, STORAGE_KEY_SETTING_VALUES} from './settings.provider';
import {Geolocation} from '@ionic-native/geolocation/ngx';
describe('SettingsProvider', () => { describe('SettingsProvider', () => {
let configProviderSpy: jasmine.SpyObj<ConfigProvider>; let configProviderSpy: jasmine.SpyObj<ConfigProvider>;
@@ -203,11 +203,9 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Anmeldedaten'],
name: 'Benutzername', name: 'Benutzername',
}, },
en: { en: {
categories: ['Credentials'],
name: 'Username', name: 'Username',
}, },
}, },
@@ -228,11 +226,9 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Anmeldedaten'],
name: 'Passwort', name: 'Passwort',
}, },
en: { en: {
categories: ['Credentials'],
name: 'Password', name: 'Password',
}, },
}, },
@@ -241,8 +237,8 @@ describe('SettingsProvider', () => {
}, },
{ {
categories: ['profile'], categories: ['profile'],
description: '',
defaultValue: 0, defaultValue: 0,
description: '',
inputType: SCSettingInputType.Number, inputType: SCSettingInputType.Number,
name: 'age', name: 'age',
order: 0, order: 0,
@@ -253,11 +249,9 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Profil'],
name: 'Alter', name: 'Alter',
}, },
en: { en: {
categories: ['Profile'],
name: 'Age', name: 'Age',
}, },
}, },
@@ -278,13 +272,11 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Benutzer'],
description: 'Mit welcher Benutzergruppe soll die App verwendet werden?' description: 'Mit welcher Benutzergruppe soll die App verwendet werden?'
+ ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.', + ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe', name: 'Gruppe',
}, },
en: { en: {
categories: ['User'],
description: 'The user group the app is going to be used.' description: 'The user group the app is going to be used.'
+ 'This settings for example is getting used for the predefined price category of mensa meals.', + 'This settings for example is getting used for the predefined price category of mensa meals.',
name: 'Group', name: 'Group',
@@ -308,12 +300,10 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Benutzer'],
description: 'Die Sprache in der die App angezeigt werden soll', description: 'Die Sprache in der die App angezeigt werden soll',
name: 'Sprache', name: 'Sprache',
}, },
en: { en: {
categories: ['User'],
description: 'The language this app is going to use', description: 'The language this app is going to use',
name: 'Language', name: 'Language',
}, },
@@ -336,13 +326,11 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Privatsphäre'],
description: 'Berechtigung für die Verwendung des Ortungsdienstes, für die Anzeige der aktuellen ' + description: 'Berechtigung für die Verwendung des Ortungsdienstes, für die Anzeige der aktuellen ' +
'Position \'\n auf der Karte und zur Berechnung der Entfernung zu Gebäuden und Orten des Campus', 'Position \'\n auf der Karte und zur Berechnung der Entfernung zu Gebäuden und Orten des Campus',
name: 'Position', name: 'Position',
}, },
en: { en: {
categories: ['Privacy'],
description: 'Allow the App to use the device location to provide additional informationsbased ' + description: 'Allow the App to use the device location to provide additional informationsbased ' +
'on your actual location', 'on your actual location',
name: 'Position', name: 'Position',
@@ -354,8 +342,8 @@ describe('SettingsProvider', () => {
}, },
{ {
categories: ['others'], categories: ['others'],
description: '',
defaultValue: [], defaultValue: [],
description: '',
inputType: SCSettingInputType.MultipleChoice, inputType: SCSettingInputType.MultipleChoice,
name: 'numbers', name: 'numbers',
order: 0, order: 0,
@@ -366,12 +354,10 @@ describe('SettingsProvider', () => {
}, },
translations: { translations: {
de: { de: {
categories: ['Sonstiges'],
description: 'Test für multiple select Feld', description: 'Test für multiple select Feld',
name: 'Nummern', name: 'Nummern',
}, },
en: { en: {
categories: ['Others'],
description: 'Test for multiple select field', description: 'Test for multiple select field',
name: 'Numbers', name: 'Numbers',
}, },

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018, 2019 StApps * Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -19,7 +19,7 @@ import {
SCSettingValue, SCSettingValue,
SCSettingValues, SCSettingValues,
} from '@openstapps/core'; } from '@openstapps/core';
import * as deepMerge from 'deepmerge'; import deepmerge from 'deepmerge';
import {ConfigProvider} from '../config/config.provider'; import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider'; import {StorageProvider} from '../storage/storage.provider';
@@ -64,7 +64,6 @@ export interface SettingValueContainer {
/** /**
* Provider for app settings * Provider for app settings
*
*/ */
@Injectable() @Injectable()
export class SettingsProvider { export class SettingsProvider {
@@ -340,7 +339,7 @@ export class SettingsProvider {
* *
* @throws Exception if setting is not provided * @throws Exception if setting is not provided
*/ */
public async getValue(category: string, name: string): Promise<any> { public async getValue(category: string, name: string): Promise<unknown> {
await this.init(); await this.init();
if (this.settingExists(category, name)) { if (this.settingExists(category, name)) {
// return a copy of the settings value // return a copy of the settings value
@@ -396,7 +395,7 @@ export class SettingsProvider {
const savedSettingsValues: SettingValuesContainer = const savedSettingsValues: SettingValuesContainer =
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES); await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
const cacheSettingsValues = this.getSettingValuesFromCache(); const cacheSettingsValues = this.getSettingValuesFromCache();
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues); const mergedSettingValues = deepmerge(savedSettingsValues, cacheSettingsValues);
await this.storage await this.storage
.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues); .put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
} else { } else {