/* * Copyright (C) 2018 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. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {Injectable} from '@angular/core'; import {SCSetting, SCSettingMultipleChoice, SCSettingSingleChoice, SCSettingValue} from '@openstapps/core'; import {StorageProvider} from '../storage/storage.provider'; export const STORAGE_KEY_SETTINGS = 'settings'; export const STORAGE_KEY_SETTINGS_SEPARATOR = '.'; export const STORAGE_KEY_SETTING_VALUES = STORAGE_KEY_SETTINGS + STORAGE_KEY_SETTINGS_SEPARATOR + 'values'; /** * Category structure of settings cache */ export interface CategoryWithSettings { category: string; settings: { [key: string]: SCSetting }; } /** * Structure of SettingsCache */ export interface SettingsCache { [key: string]: CategoryWithSettings; } /** * Provider for app settings */ @Injectable() export class SettingsProvider { private categoriesOrder: string[]; private initialized = false; private settingsCache: SettingsCache; /** * Return true if all given values are valid to possible values in given settingInput * @param settingInput * @param values */ public static checkMultipleChoiceValue(settingInput: SCSettingMultipleChoice, values: SCSettingValue[]): boolean { for (const value of values) { if (!settingInput.values.includes(value)) { return false; } } return true; } /** * Returns true if given value is valid to possible values in given settingInput * @param settingInput * @param value */ public static checkSingleChoiceValue(settingInput: SCSettingSingleChoice, value: SCSettingValue): boolean { return settingInput.values !== undefined && settingInput.values.includes(value); } /** * Validates value for given settings inputType. Returns true if value is valid. * @param setting setting to check value against * @param value value to validate */ public static validateValue(setting: SCSetting, value: any): boolean { let isValueValid: boolean = false; switch (setting.input.inputType) { case 'number': if (typeof value === 'number') { isValueValid = true; } break; case 'multipleChoice': if (!value.isArray) { isValueValid = false; } else { isValueValid = SettingsProvider.checkMultipleChoiceValue(setting.input, value); } break; case 'password': case 'text': if (typeof value === 'string') { isValueValid = true; } break; case 'singleChoice': isValueValid = SettingsProvider.checkSingleChoiceValue(setting.input, value); break; default: } return isValueValid; } constructor(public storage: StorageProvider) { this.categoriesOrder = []; this.settingsCache = {}; } /** * Initializes settings cache from storage */ private async initSettings(): Promise { try { this.settingsCache = await this.storage.get(STORAGE_KEY_SETTING_VALUES); for (const category of Object.keys(this.settingsCache)) { if (!this.categoriesOrder.includes(category)) { this.categoriesOrder.push(category); } } } catch (error) { this.settingsCache = {}; } await this.saveSettings(); this.initialized = true; } /** * Add category if not exists * @param category the category to provide */ private async provideCategory(category: string): Promise { if (!this.categoryExists(category)) { if (!this.categoriesOrder.includes(category)) { this.categoriesOrder.push(category); } this.settingsCache[category] = { category: category, settings: {}, }; } } /** * Returns true if category exists * @param category */ public categoryExists(category: string): boolean { return this.settingsCache[category] !== undefined; } /** * Removes all provided settings */ public async clear(): Promise { await this.init(); this.settingsCache = {}; await this.saveSettings(); } /** * returns an array with the order of categories */ public getCategoriesOrder(): string[] { return this.categoriesOrder; } /** * Returns copy of a setting 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 getSetting(category: string, name: string): Promise { await this.init(); if (this.settingExists(category, name)) { return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name])); } else { throw new Error('Setting "' + name + '" not provided'); } } /** * Returns copy of cached settings */ public async getSettingsCache(): Promise { await this.init(); return JSON.parse(JSON.stringify(this.settingsCache)); } /** * Returns copy of a setting 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 { await this.init(); if (this.settingExists(category, name)) { return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].input.value)); } else { throw new Error('Setting "' + name + '" not provided'); } } /** * initializes settingsProvider */ public async init(): Promise { if (!this.initialized) { await this.initSettings(); } } /** * Adds given setting and its category if not exist * @param setting the setting to add */ public async provideSetting(setting: SCSetting): Promise { 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(); } } /** * Sets values of all settings to defaultValue */ public async resetDefault(): Promise { await this.init(); 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; } } } /** * Saves cached settings in app storage */ public async saveSettings(): Promise { await this.storage.put(STORAGE_KEY_SETTING_VALUES, this.settingsCache); } /** * Sets the order the given categories showup in the settings page * @param categoryNames the order of the categories */ public setCategoriesOrder(categoryNames: string[]) { this.categoriesOrder = categoryNames; } /** * Sets a valid value of a setting and persists changes in storage * @param category * @param name * @param value * * @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 { 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(); } else { 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'); } } /** * Returns true if setting in category exists * @param category * @param setting */ public settingExists(category: string, setting: string): boolean { return this.categoryExists(category) && this.settingsCache[category].settings[setting] !== undefined; } }