mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
refactor: initialise settings from config module and persist only the values
Closes #30, #59
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 StApps
|
||||
* Copyright (C) 2018, 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.
|
||||
@@ -13,7 +13,16 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SCSetting, SCSettingMultipleChoice, SCSettingSingleChoice, SCSettingValue} from '@openstapps/core';
|
||||
import {Geolocation} from '@ionic-native/geolocation/ngx';
|
||||
import {
|
||||
SCSetting,
|
||||
SCSettingMultipleChoice,
|
||||
SCSettingSingleChoice,
|
||||
SCSettingValue,
|
||||
} from '@openstapps/core';
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import * as deepMerge from 'deepmerge';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {StorageProvider} from '../storage/storage.provider';
|
||||
|
||||
export const STORAGE_KEY_SETTINGS = 'settings';
|
||||
@@ -35,14 +44,29 @@ export interface SettingsCache {
|
||||
[key: string]: CategoryWithSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure with categories and its setting valueContainers for persistence
|
||||
*/
|
||||
export interface SettingValuesContainer {
|
||||
[key: string]: SettingValueContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure of a setting and its value
|
||||
*/
|
||||
export interface SettingValueContainer {
|
||||
[key: string]: SCSettingValue | SCSettingValue[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for app settings
|
||||
*/
|
||||
@Injectable()
|
||||
export class SettingsProvider {
|
||||
private categoriesOrder: string[];
|
||||
private initialized = false;
|
||||
private settingsCache: SettingsCache;
|
||||
categoriesOrder: string[];
|
||||
initialized = false;
|
||||
logger = new Logger();
|
||||
settingsCache: SettingsCache;
|
||||
|
||||
/**
|
||||
* Return true if all given values are valid to possible values in given settingInput
|
||||
@@ -102,17 +126,56 @@ export class SettingsProvider {
|
||||
return isValueValid;
|
||||
}
|
||||
|
||||
constructor(public storage: StorageProvider) {
|
||||
constructor(private storage: StorageProvider,
|
||||
private configProvider: ConfigProvider,
|
||||
private geoLocation: Geolocation) {
|
||||
this.categoriesOrder = [];
|
||||
this.settingsCache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes settings cache from storage
|
||||
* Add an Setting to the Cache if not exist and set undefined value to defaultValue
|
||||
* @param setting
|
||||
*/
|
||||
private async addSetting(setting: SCSetting): Promise<void> {
|
||||
if (!this.categoryExists(setting.categories[0])) {
|
||||
await this.provideCategory(setting.categories[0]);
|
||||
}
|
||||
if (!this.settingExists(setting.categories[0], setting.name)) {
|
||||
if (setting.input.value === undefined) {
|
||||
setting.input.value = setting.input.defaultValue;
|
||||
}
|
||||
this.settingsCache[setting.categories[0]].settings[setting.name] = setting;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all setting values from settingsCache in a SettingsValueContainer
|
||||
*/
|
||||
private getSettingValuesFromCache(): SettingValuesContainer {
|
||||
const settingValuesContainer: SettingValuesContainer = {};
|
||||
// iterate through keys of categories
|
||||
for (const categoryKey of Object.keys(this.settingsCache)) {
|
||||
// iterate through keys of settingValueContainer
|
||||
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
|
||||
if (typeof settingValuesContainer[categoryKey] === 'undefined') {
|
||||
settingValuesContainer[categoryKey] = {};
|
||||
}
|
||||
settingValuesContainer[categoryKey][settingKey] =
|
||||
this.settingsCache[categoryKey].settings[settingKey].input.value;
|
||||
}
|
||||
}
|
||||
return settingValuesContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes settings from config and stored values if exist
|
||||
*/
|
||||
private async initSettings(): Promise<void> {
|
||||
try {
|
||||
this.settingsCache = await this.storage.get<SettingsCache>(STORAGE_KEY_SETTING_VALUES);
|
||||
const settings: SCSetting[] = (await this.configProvider.getValue('settings')) as SCSetting[];
|
||||
settings.forEach((setting) => this.addSetting(setting));
|
||||
|
||||
for (const category of Object.keys(this.settingsCache)) {
|
||||
if (!this.categoriesOrder.includes(category)) {
|
||||
this.categoriesOrder.push(category);
|
||||
@@ -121,7 +184,29 @@ export class SettingsProvider {
|
||||
} catch (error) {
|
||||
this.settingsCache = {};
|
||||
}
|
||||
await this.saveSettings();
|
||||
|
||||
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
|
||||
// get setting values from StorageProvider into settingsCache
|
||||
const valuesContainer: SettingValuesContainer =
|
||||
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
|
||||
// iterate through keys of categories
|
||||
for (const categoryKey of Object.keys(this.settingsCache)) {
|
||||
// iterate through setting keys of category
|
||||
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
|
||||
// if saved setting value exists set it, otherwise set to default value
|
||||
if (typeof valuesContainer[categoryKey] !== 'undefined'
|
||||
&& typeof valuesContainer[categoryKey][settingKey] !== 'undefined') {
|
||||
this.settingsCache[categoryKey].settings[settingKey].input.value =
|
||||
valuesContainer[categoryKey][settingKey];
|
||||
} else {
|
||||
this.settingsCache[categoryKey].settings[settingKey].input.value =
|
||||
this.settingsCache[categoryKey].settings[settingKey].input.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.saveSettingValues();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
@@ -150,16 +235,40 @@ export class SettingsProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all provided settings
|
||||
* Checks for user permission to use location
|
||||
*/
|
||||
public async clear(): Promise<void> {
|
||||
await this.init();
|
||||
this.settingsCache = {};
|
||||
await this.saveSettings();
|
||||
public async checkGeoLocationPermission(): Promise<boolean> {
|
||||
// request geoLocation to test the user permission
|
||||
try {
|
||||
// set enableHighAccuracy, otherwise android platform does not respond
|
||||
const options = {
|
||||
enableHighAccuracy: true,
|
||||
};
|
||||
await this.geoLocation.getCurrentPosition(options);
|
||||
} catch (error) {
|
||||
// if error code is 1 the user denied permission,
|
||||
// other errors like 'timeout' or 'no location' will be ignored here
|
||||
if (error.code === 1) {
|
||||
// ios has special error message for disabled location services, for the setting we ignore it
|
||||
if (error.message.toLowerCase() !== 'location services are disabled.') {
|
||||
// revert setting value
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array with the order of categories
|
||||
* Returns copy of cached settings
|
||||
*/
|
||||
public async getCache(): Promise<SettingsCache> {
|
||||
await this.init();
|
||||
return JSON.parse(JSON.stringify(this.settingsCache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the order of categories
|
||||
*/
|
||||
public getCategoriesOrder(): string[] {
|
||||
return this.categoriesOrder;
|
||||
@@ -175,33 +284,27 @@ export class SettingsProvider {
|
||||
public async getSetting(category: string, name: string): Promise<any> {
|
||||
await this.init();
|
||||
if (this.settingExists(category, name)) {
|
||||
// return a copy of the settings
|
||||
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name]));
|
||||
} else {
|
||||
throw new Error('Setting "' + name + '" not provided');
|
||||
throw new Error(`Setting "${name}" not provided`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns copy of cached settings
|
||||
*/
|
||||
public async getSettingsCache(): Promise<SettingsCache> {
|
||||
await this.init();
|
||||
return JSON.parse(JSON.stringify(this.settingsCache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns copy of a setting if exist
|
||||
* Returns copy of a settings value if exist
|
||||
* @param category the category of requested setting
|
||||
* @param name the name of requested setting
|
||||
*
|
||||
* @throws Exception if setting is not provided
|
||||
*/
|
||||
public async getSettingValue(category: string, name: string): Promise<any> {
|
||||
public async getValue(category: string, name: string): Promise<any> {
|
||||
await this.init();
|
||||
if (this.settingExists(category, name)) {
|
||||
// return a copy of the settings value
|
||||
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].input.value));
|
||||
} else {
|
||||
throw new Error('Setting "' + name + '" not provided');
|
||||
throw new Error(`Setting "${name}" not provided`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,37 +323,44 @@ export class SettingsProvider {
|
||||
*/
|
||||
public async provideSetting(setting: SCSetting): Promise<void> {
|
||||
await this.init();
|
||||
if (!this.categoryExists(setting.categories[0])) {
|
||||
await this.provideCategory(setting.categories[0]);
|
||||
}
|
||||
if (!this.settingExists(setting.categories[0], setting.name)) {
|
||||
// set value to default
|
||||
if (setting.input.value === undefined) {
|
||||
setting.input.value = setting.input.defaultValue;
|
||||
}
|
||||
this.settingsCache[setting.categories[0]].settings[setting.name] = setting;
|
||||
await this.saveSettings();
|
||||
}
|
||||
await this.addSetting(setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes saved values and reinitialising the settings
|
||||
*/
|
||||
public async reset(): Promise<void> {
|
||||
await this.storage.put(STORAGE_KEY_SETTING_VALUES, {});
|
||||
await this.initSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets values of all settings to defaultValue
|
||||
*/
|
||||
public async resetDefault(): Promise<void> {
|
||||
await this.init();
|
||||
async resetDefault(): Promise<void> {
|
||||
for (const catKey of Object.keys(this.settingsCache)) {
|
||||
for (const settingKey of Object.keys(this.settingsCache[catKey].settings)) {
|
||||
const settingInput = this.settingsCache[catKey].settings[settingKey].input;
|
||||
settingInput.value = settingInput.defaultValue;
|
||||
}
|
||||
}
|
||||
await this.saveSettingValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves cached settings in app storage
|
||||
*/
|
||||
public async saveSettings(): Promise<void> {
|
||||
await this.storage.put(STORAGE_KEY_SETTING_VALUES, this.settingsCache);
|
||||
public async saveSettingValues(): Promise<void> {
|
||||
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
|
||||
const savedSettingsValues: SettingValuesContainer =
|
||||
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
|
||||
const cacheSettingsValues = this.getSettingValuesFromCache();
|
||||
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
|
||||
await this.storage
|
||||
.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
|
||||
} else {
|
||||
await this.storage.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, this.getSettingValuesFromCache());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,20 +379,21 @@ export class SettingsProvider {
|
||||
*
|
||||
* @throws Exception if setting is not provided or value not valid to the settings inputType
|
||||
*/
|
||||
public async setSettingValue(category: string, name: string, value: any): Promise<void> {
|
||||
public async setSettingValue(category: string, name: string,
|
||||
value: any): Promise<void> {
|
||||
await this.init();
|
||||
if (this.settingExists(category, name)) {
|
||||
const setting: SCSetting = this.settingsCache[category].settings[name];
|
||||
const isValueValid = SettingsProvider.validateValue(setting, value);
|
||||
if (isValueValid) {
|
||||
this.settingsCache[category].settings[name].input.value = value;
|
||||
await this.saveSettings();
|
||||
await this.saveSettingValues();
|
||||
} else {
|
||||
throw new Error('Value "' + value + '" of type ' +
|
||||
typeof value + ' is not valid for ' + setting.input.inputType);
|
||||
}
|
||||
throw new Error(`Value "${value}" of type
|
||||
${typeof value} is not valid for ${setting.input.inputType}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('setting ' + name + ' is not provided');
|
||||
throw new Error(`setting ${name} is not provided`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user