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

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
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,8 +12,15 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {HTTP_INTERCEPTORS, HttpClient,
HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
import {
HTTP_INTERCEPTORS,
HttpClient,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {SCIndexResponse, SCSettingInputType, SCThingOriginType, SCThingType} from '@openstapps/core';
import {Observable, of} from 'rxjs';
@@ -71,20 +78,28 @@ const sampleIndexResponse: SCIndexResponse = {
},
translations: {
de: {
categories: ['Benutzer'],
description: 'Mit welcher Benutzergruppe soll die App verwendet werden?'
+ ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe',
values: [
'Student',
'Angestellter',
'Gast',
],
},
en: {
categories: ['User'],
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.',
name: 'Group',
values: [
'Student',
'Employee',
'Guest',
],
},
},
type: SCThingType.Setting,
uid: '',
uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['student', 'employee', 'guest'],
},
{
@@ -101,19 +116,25 @@ const sampleIndexResponse: SCIndexResponse = {
},
translations: {
de: {
categories: ['Benutzer'],
description: 'Die Sprache in der die App angezeigt werden soll',
description: 'Die Sprache in der die App angezeigt wird.',
name: 'Sprache',
values: [
'Deutsch',
'English',
],
},
en: {
categories: ['User'],
description: 'The language this app is going to use',
description: 'The language this app is going to use.',
name: 'Language',
values: [
'Deutsch',
'English',
],
},
},
type: SCThingType.Setting,
uid: '',
values: ['en', 'de'],
uid: 'dc9d6dec-6576-45ef-9e35-3598c0d6a662',
values: ['de', 'en'],
},
{
categories: ['privacy'],
@@ -129,31 +150,33 @@ const sampleIndexResponse: SCIndexResponse = {
},
translations: {
de: {
categories: ['Privatsphäre'],
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',
description: `Berechtigung für die Verwendung des Ortungsdienstes, für die Anzeige der aktuellen Position '
auf der Karte und zur Berechnung der Entfernung zu Gebäuden und Orten des Campus.`,
name: 'Position',
values: ['ja', 'nein'],
},
en: {
categories: ['Privacy'],
description: 'Allow the App to use the device location to provide additional informationsbased ' +
'on your actual location',
description: 'Allow the App to use the device location to provide additional information\'s based ' +
'on your actual location.',
name: 'Position',
values: ['yes', 'no'],
},
},
type: SCThingType.Setting,
uid: '',
uid: '0dbff2de-23b4-442b-9aa7-7bd2c707c199',
values: [true, false],
},
],
},
backend: {
SCVersion: '1.0.0',
externalRequestTimeout: 5000,
hiddenTypes: [
SCThingType.DateSeries,
SCThingType.Diff,
SCThingType.Floor,
],
mappingIgnoredTags: [],
maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024,
name: 'Technische Universität Berlin',
@@ -228,6 +251,12 @@ const sampleIndexResponse: SCIndexResponse = {
*/
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
/**
* Delay time of faked responses
*/
RESPONSE_DELAY = 1000;
/**
* TODO
*/
@@ -240,6 +269,7 @@ export class FakeBackendInterceptor implements HttpInterceptor {
/**
* TODO
*/
// tslint:disable-next-line:no-any
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (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 (request.body.filter.arguments.field === 'uid') {
return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value)
// tslint:disable-next-line:no-any
.pipe(map((sampleData: any) => {
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()
// tslint:disable-next-line:no-any
.pipe(map((sampleData: any) => {
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
*/
component: any;
component: unknown;
/**
* TODO
*/
@@ -44,7 +44,7 @@ export class AppComponent {
}>;
/**
*
*
* @param platform TODO
* @param statusBar TODO
* @param splashScreen TODO
@@ -97,7 +97,7 @@ export class AppComponent {
try {
// 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);
} catch (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 {StorageProvider} from '../storage/storage.provider';
import {ConfigProvider, STORAGE_KEY_CONFIG} from './config.provider';
import {
ConfigFetchError,
ConfigInitError,
SavedConfigNotAvailable,
WrongConfigVersionInStorage,
} from './errors';
import {ConfigFetchError, ConfigInitError, SavedConfigNotAvailable, WrongConfigVersionInStorage,} from './errors';
describe('ConfigProvider', () => {
let configProvider: ConfigProvider;
@@ -218,11 +213,9 @@ const sampleIndexResponse: SCIndexResponse = {
},
translations: {
de: {
categories: ['Anmeldedaten'],
name: 'Benutzername',
},
en: {
categories: ['Credentials'],
name: 'Username',
},
},
@@ -233,11 +226,13 @@ const sampleIndexResponse: SCIndexResponse = {
},
backend: {
SCVersion: '1.0.0',
externalRequestTimeout: 5000,
hiddenTypes: [
SCThingType.DateSeries,
SCThingType.Diff,
SCThingType.Floor,
],
mappingIgnoredTags: [],
maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024,
name: 'Technische Universität Berlin',

View File

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

View File

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

View File

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

View File

@@ -15,11 +15,11 @@
import {Component} from '@angular/core';
import {AlertController, ToastController} from '@ionic/angular';
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';
/**
* TODO
* Settings page component
*/
@Component({
selector: 'stapps-settings-page',
@@ -32,43 +32,43 @@ export class SettingsPageComponent {
categoriesOrder: string[];
/**
* Possible languages to be used for translation
*
*
* limit to languages that are available in StApps Core
*/
language: keyof SCTranslations<any>;
language: keyof SCTranslations<SCLanguage>;
/**
* Meta information about settings
*/
meta = SCSettingMeta;
/**
* TODO
* Mapping of Object.keys for Html usage
*/
objectKeys = Object.keys;
/**
* TODO
* Container to cache settings from provider
*/
settingsCache: SettingsCache;
/**
* TODO
* Core translator
*/
translator: SCThingTranslator;
/**
*
* @param alertController TODO
* @param settingsProvider TODO
* @param toastController TODO
* @param translateService TODO
*
* @param alertController AlertController
* @param settingsProvider SettingsProvider
* @param toastController ToastController
* @param translateService TranslateService
*/
constructor(private readonly alertController: AlertController,
private readonly settingsProvider: SettingsProvider,
private readonly toastController: ToastController,
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);
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.settingsCache = {};
@@ -142,4 +142,27 @@ export class SettingsPageComponent {
await this.loadSettings();
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-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>{{'settings.title' | translate}}</ion-title>
<ion-title>{{'settings.title' | translate | titlecase}}</ion-title>
</ion-toolbar>
</ion-header>
@@ -12,8 +12,7 @@
<ion-list *ngFor="let categoryKey of categoriesOrder ">
<div *ngIf="objectKeys(settingsCache).includes(categoryKey)">
<ion-item-divider>
<h5>{{ settingsCache[categoryKey].settings[objectKeys(settingsCache[categoryKey].settings)[0]].translations[language].categories[0]}}
</h5>
<h5>{{ translator.translate(settingsCache[categoryKey]?.settings[objectKeys(settingsCache[categoryKey]?.settings)[0]]).categories()[0] | titlecase}}</h5>
</ion-item-divider>
<stapps-settings-item *ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)" [setting]="settingsCache[categoryKey].settings[settingKeys]"></stapps-settings-item>
</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
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -14,10 +14,10 @@
*/
import {TestBed} from '@angular/core/testing';
import {SCSetting, SCThingOriginType, SCThingType, SCSettingInputType} from '@openstapps/core';
import {Geolocation} from '@ionic-native/geolocation/ngx';
import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider';
import {SettingsProvider, SettingValuesContainer, STORAGE_KEY_SETTING_VALUES} from './settings.provider';
import {Geolocation} from '@ionic-native/geolocation/ngx';
describe('SettingsProvider', () => {
let configProviderSpy: jasmine.SpyObj<ConfigProvider>;
@@ -203,11 +203,9 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Anmeldedaten'],
name: 'Benutzername',
},
en: {
categories: ['Credentials'],
name: 'Username',
},
},
@@ -228,11 +226,9 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Anmeldedaten'],
name: 'Passwort',
},
en: {
categories: ['Credentials'],
name: 'Password',
},
},
@@ -241,8 +237,8 @@ describe('SettingsProvider', () => {
},
{
categories: ['profile'],
description: '',
defaultValue: 0,
description: '',
inputType: SCSettingInputType.Number,
name: 'age',
order: 0,
@@ -253,11 +249,9 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Profil'],
name: 'Alter',
},
en: {
categories: ['Profile'],
name: 'Age',
},
},
@@ -278,13 +272,11 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Benutzer'],
description: 'Mit welcher Benutzergruppe soll die App verwendet werden?'
+ ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe',
},
en: {
categories: ['User'],
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.',
name: 'Group',
@@ -308,12 +300,10 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Benutzer'],
description: 'Die Sprache in der die App angezeigt werden soll',
name: 'Sprache',
},
en: {
categories: ['User'],
description: 'The language this app is going to use',
name: 'Language',
},
@@ -336,13 +326,11 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Privatsphäre'],
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',
name: 'Position',
},
en: {
categories: ['Privacy'],
description: 'Allow the App to use the device location to provide additional informationsbased ' +
'on your actual location',
name: 'Position',
@@ -354,8 +342,8 @@ describe('SettingsProvider', () => {
},
{
categories: ['others'],
description: '',
defaultValue: [],
description: '',
inputType: SCSettingInputType.MultipleChoice,
name: 'numbers',
order: 0,
@@ -366,12 +354,10 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
categories: ['Sonstiges'],
description: 'Test für multiple select Feld',
name: 'Nummern',
},
en: {
categories: ['Others'],
description: 'Test for multiple select field',
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
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -19,7 +19,7 @@ import {
SCSettingValue,
SCSettingValues,
} from '@openstapps/core';
import * as deepMerge from 'deepmerge';
import deepmerge from 'deepmerge';
import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider';
@@ -64,7 +64,6 @@ export interface SettingValueContainer {
/**
* Provider for app settings
*
*/
@Injectable()
export class SettingsProvider {
@@ -161,7 +160,7 @@ export class SettingsProvider {
}
/**
*
*
* @param storage TODO
* @param configProvider TODO
* @param geoLocation TODO
@@ -340,7 +339,7 @@ export class SettingsProvider {
*
* @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();
if (this.settingExists(category, name)) {
// return a copy of the settings value
@@ -396,7 +395,7 @@ export class SettingsProvider {
const savedSettingsValues: SettingValuesContainer =
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
const cacheSettingsValues = this.getSettingValuesFromCache();
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
const mergedSettingValues = deepmerge(savedSettingsValues, cacheSettingsValues);
await this.storage
.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
} else {
@@ -416,7 +415,7 @@ export class SettingsProvider {
* Sets a valid value of a setting and persists changes in storage
* @param category Category key name
* @param name Setting key name
* @param value Value to be set
* @param value Value to be set
*
* @throws Exception if setting is not provided or value not valid to the settings inputType
*/