diff --git a/src/Notifier.js b/src/Notifier.js index 93ef192fe0..10cdf074bf 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -25,6 +25,7 @@ import dis from './dispatcher'; import sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; +import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; /* * Dispatches: @@ -138,10 +139,8 @@ const Notifier = { // make sure that we persist the current setting audio_enabled setting // before changing anything - if (global.localStorage) { - if (global.localStorage.getItem('audio_notifications_enabled') === null) { - this.setAudioEnabled(this.isEnabled()); - } + if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) { + SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, this.isEnabled()); } if (enable) { @@ -149,6 +148,7 @@ const Notifier = { plaf.requestNotificationPermission().done((result) => { if (result !== 'granted') { // The permission request was dismissed or denied + // TODO: Support alternative branding in messaging const description = result === 'denied' ? _t('Riot does not have permission to send you notifications - please check your browser settings') : _t('Riot was not given permission to send notifications - please try again'); @@ -160,10 +160,6 @@ const Notifier = { return; } - if (global.localStorage) { - global.localStorage.setItem('notifications_enabled', 'true'); - } - if (callback) callback(); dis.dispatch({ action: "notifier_enabled", @@ -174,8 +170,6 @@ const Notifier = { // disabled again in the future, we will show the banner again. this.setToolbarHidden(false); } else { - if (!global.localStorage) return; - global.localStorage.setItem('notifications_enabled', 'false'); dis.dispatch({ action: "notifier_enabled", value: false, @@ -184,44 +178,24 @@ const Notifier = { }, isEnabled: function() { + return this.isPossible() && SettingsStore.getValue("notificationsEnabled"); + }, + + isPossible: function() { const plaf = PlatformPeg.get(); if (!plaf) return false; if (!plaf.supportsNotifications()) return false; if (!plaf.maySendNotifications()) return false; - if (!global.localStorage) return true; - - const enabled = global.localStorage.getItem('notifications_enabled'); - if (enabled === null) return true; - return enabled === 'true'; - }, - - setBodyEnabled: function(enable) { - if (!global.localStorage) return; - global.localStorage.setItem('notifications_body_enabled', enable ? 'true' : 'false'); + return true; // possible, but not necessarily enabled }, isBodyEnabled: function() { - if (!global.localStorage) return true; - const enabled = global.localStorage.getItem('notifications_body_enabled'); - // default to true if the popups are enabled - if (enabled === null) return this.isEnabled(); - return enabled === 'true'; + return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled"); }, - setAudioEnabled: function(enable) { - if (!global.localStorage) return; - global.localStorage.setItem('audio_notifications_enabled', - enable ? 'true' : 'false'); - }, - - isAudioEnabled: function(enable) { - if (!global.localStorage) return true; - const enabled = global.localStorage.getItem( - 'audio_notifications_enabled'); - // default to true if the popups are enabled - if (enabled === null) return this.isEnabled(); - return enabled === 'true'; + isAudioEnabled: function() { + return this.isEnabled() && SettingsStore.getValue("audioNotificationsEnabled"); }, setToolbarHidden: function(hidden, persistent = true) { @@ -237,17 +211,15 @@ const Notifier = { }); // update the info to localStorage for persistent settings - if (persistent && global.localStorage) { - global.localStorage.setItem('notifications_hidden', hidden); + if (persistent && SettingsStore.isLevelSupported(SettingLevel.DEVICE)) { + return SettingsStore.setValue("notificationToolbarHidden", null, SettingLevel.DEVICE, hidden); } }, isToolbarHidden: function() { // Check localStorage for any such meta data - if (global.localStorage) { - if (global.localStorage.getItem('notifications_hidden') === 'true') { - return true; - } + if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) { + return SettingsStore.getValue("notificationToolbarHidden"); } return this.toolbarHidden; diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index b5c7d2ed00..30e44bc860 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -17,9 +17,6 @@ limitations under the License. import Promise from 'bluebird'; import MatrixClientPeg from './MatrixClientPeg'; -import Notifier from './Notifier'; -import { _t, _td } from './languageHandler'; -import SdkConfig from './SdkConfig'; /* * TODO: Make things use this. This is all WIP - see UserSettings.js for usage. @@ -48,42 +45,6 @@ export default { // TODO }, - // TODO: {Travis} Granular setting - getEnableNotifications: function() { - return Notifier.isEnabled(); - }, - - // TODO: {Travis} Granular setting - setEnableNotifications: function(enable) { - if (!Notifier.supportsDesktopNotifications()) { - return; - } - Notifier.setEnabled(enable); - }, - - // TODO: {Travis} Granular setting - getEnableNotificationBody: function() { - return Notifier.isBodyEnabled(); - }, - - // TODO: {Travis} Granular setting - setEnableNotificationBody: function(enable) { - if (!Notifier.supportsDesktopNotifications()) { - return; - } - Notifier.setBodyEnabled(enable); - }, - - // TODO: {Travis} Granular setting - getEnableAudioNotifications: function() { - return Notifier.isAudioEnabled(); - }, - - // TODO: {Travis} Granular setting - setEnableAudioNotifications: function(enable) { - Notifier.setAudioEnabled(enable); - }, - changePassword: function(oldPassword, newPassword) { const cli = MatrixClientPeg.get(); diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index b3d2e92155..d9b811aacd 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -415,10 +415,6 @@ module.exports = React.createClass({ dis.dispatch({action: 'password_changed'}); }, - onEnableNotificationsChange: function(event) { - UserSettingsStore.setEnableNotifications(event.target.checked); - }, - _onAddEmailEditFinished: function(value, shouldSubmit) { if (!shouldSubmit) return; this._addEmail(); diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 42d417ba07..a166e8e199 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -62,6 +62,9 @@ export const SETTINGS = { // default: { // your: "value", // }, + // + // // Optional settings controller. See SettingsController for more information. + // controller: new MySettingController(), // }, "feature_groups": { isFeature: true, @@ -213,4 +216,23 @@ export const SETTINGS = { secondary_color: null, // Hex string, eg: #000000 }, }, + "notificationsEnabled": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + //controller: new NotificationsEnabledController(), + }, + "notificationBodyEnabled": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + //controller: new NotificationBodyEnabledController(), + }, + "audioNotificationsEnabled": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + //controller: new AudioNotificationsEnabledController(), + }, + "notificationToolbarHidden": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + }, }; \ No newline at end of file diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index f31c9980db..a65943b927 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -15,17 +15,17 @@ limitations under the License. */ import Promise from 'bluebird'; -import DeviceSettingsHandler from "./DeviceSettingsHandler"; -import RoomDeviceSettingsHandler from "./RoomDeviceSettingsHandler"; -import DefaultSettingsHandler from "./DefaultSettingsHandler"; -import RoomAccountSettingsHandler from "./RoomAccountSettingsHandler"; -import AccountSettingsHandler from "./AccountSettingsHandler"; -import RoomSettingsHandler from "./RoomSettingsHandler"; -import ConfigSettingsHandler from "./ConfigSettingsHandler"; +import DeviceSettingsHandler from "./handlers/DeviceSettingsHandler"; +import RoomDeviceSettingsHandler from "./handlers/RoomDeviceSettingsHandler"; +import DefaultSettingsHandler from "./handlers/DefaultSettingsHandler"; +import RoomAccountSettingsHandler from "./handlers/RoomAccountSettingsHandler"; +import AccountSettingsHandler from "./handlers/AccountSettingsHandler"; +import RoomSettingsHandler from "./handlers/RoomSettingsHandler"; +import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler"; import {_t} from '../languageHandler'; import SdkConfig from "../SdkConfig"; import {SETTINGS} from "./Settings"; -import LocalEchoWrapper from "./LocalEchoWrapper"; +import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; /** * Represents the various setting levels supported by the SettingsStore. @@ -208,8 +208,9 @@ export default class SettingsStore { if (explicit) { let handler = handlers[level]; - if (!handler) return null; - return handler.getValue(settingName, roomId); + if (!handler) return SettingsStore._tryControllerOverride(settingName, level, roomId, null); + const value = handler.getValue(settingName, roomId); + return SettingsStore._tryControllerOverride(settingName, level, roomId, value); } for (let i = minIndex; i < LEVEL_ORDER.length; i++) { @@ -219,10 +220,19 @@ export default class SettingsStore { const value = handler.getValue(settingName, roomId); if (value === null || value === undefined) continue; - return value; + return SettingsStore._tryControllerOverride(settingName, level, roomId, value); } - return null; + return SettingsStore._tryControllerOverride(settingName, level, roomId, null); + } + + static _tryControllerOverride(settingName, level, roomId, calculatedValue) { + const controller = SETTINGS[settingName].controller; + if (!controller) return calculatedValue; + + const actualValue = controller.getValueOverride(level, roomId, calculatedValue); + if (actualValue !== undefined && actualValue !== null) return actualValue; + return calculatedValue; } /** @@ -251,7 +261,11 @@ export default class SettingsStore { throw new Error("User cannot set " + settingName + " at " + level + " in " + roomId); } - return handler.setValue(settingName, roomId, value); + return handler.setValue(settingName, roomId, value).finally((() => { + const controller = SETTINGS[settingName].controller; + if (!controller) return; + controller.onChange(level, roomId, value); + })); } /** diff --git a/src/settings/controllers/NotificationControllers.js b/src/settings/controllers/NotificationControllers.js new file mode 100644 index 0000000000..1c2c673136 --- /dev/null +++ b/src/settings/controllers/NotificationControllers.js @@ -0,0 +1,49 @@ +/* +Copyright 2017 Travis Ralston + +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 SettingController from "./SettingController"; + +// export class NotificationsEnabledController extends SettingController { +// getValueOverride(level, roomId, calculatedValue) { +// const Notifier = require('../../Notifier'); // avoids cyclical references +// +// return calculatedValue && Notifier.isPossible(); +// } +// +// onChange(level, roomId, newValue) { +// const Notifier = require('../../Notifier'); // avoids cyclical references +// +// if (Notifier.supportsDesktopNotifications()) { +// Notifier.setBodyEnabled(newValue); +// } +// } +// } +// +// export class NotificationBodyEnabledController extends SettingController { +// getValueOverride(level, roomId, calculatedValue) { +// const Notifier = require('../../Notifier'); // avoids cyclical references +// +// return calculatedValue && Notifier.isEnabled(); +// } +// } +// +// export class AudioNotificationsEnabledController extends SettingController { +// getValueOverride(level, roomId, calculatedValue) { +// const Notifier = require('../../Notifier'); // avoids cyclical references +// +// return calculatedValue && Notifier.isEnabled(); +// } +// } diff --git a/src/settings/controllers/SettingController.js b/src/settings/controllers/SettingController.js new file mode 100644 index 0000000000..194e7af93b --- /dev/null +++ b/src/settings/controllers/SettingController.js @@ -0,0 +1,49 @@ +/* +Copyright 2017 Travis Ralston + +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. +*/ + +/** + * Represents a controller for individual settings to alter the reading behaviour + * based upon environmental conditions, or to react to changes and therefore update + * the working environment. + * + * This is not intended to replace the functionality of a SettingsHandler, it is only + * intended to handle environmental factors for specific settings. + */ +export default class SettingController { + + /** + * Gets the overridden value for the setting, if any. This must return null if the + * value is not to be overridden, otherwise it must return the new value. + * @param {string} level The level at which the value was requested at. + * @param {String} roomId The room ID, may be null. + * @param {*} calculatedValue The value that the handlers think the setting should be, + * may be null. + * @return {*} The value that should be used, or null if no override is applicable. + */ + getValueOverride(level, roomId, calculatedValue) { + return null; // no override + } + + /** + * Called when the setting value has been changed. + * @param {string} level The level at which the setting has been modified. + * @param {String} roomId The room ID, may be null. + * @param {*} newValue The new value for the setting, may be null. + */ + onChange(level, roomId, newValue) { + // do nothing by default + } +} \ No newline at end of file diff --git a/src/settings/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js similarity index 97% rename from src/settings/AccountSettingsHandler.js rename to src/settings/handlers/AccountSettingsHandler.js index 5511851435..393410ae29 100644 --- a/src/settings/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -15,7 +15,7 @@ limitations under the License. */ import SettingsHandler from "./SettingsHandler"; -import MatrixClientPeg from '../MatrixClientPeg'; +import MatrixClientPeg from '../../MatrixClientPeg'; /** * Gets and sets settings at the "account" level for the current user. diff --git a/src/settings/ConfigSettingsHandler.js b/src/settings/handlers/ConfigSettingsHandler.js similarity index 97% rename from src/settings/ConfigSettingsHandler.js rename to src/settings/handlers/ConfigSettingsHandler.js index 29df0119a6..e28e12c02c 100644 --- a/src/settings/ConfigSettingsHandler.js +++ b/src/settings/handlers/ConfigSettingsHandler.js @@ -16,7 +16,7 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; -import SdkConfig from "../SdkConfig"; +import SdkConfig from "../../SdkConfig"; /** * Gets and sets settings at the "config" level. This handler does not make use of the diff --git a/src/settings/DefaultSettingsHandler.js b/src/settings/handlers/DefaultSettingsHandler.js similarity index 100% rename from src/settings/DefaultSettingsHandler.js rename to src/settings/handlers/DefaultSettingsHandler.js diff --git a/src/settings/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.js similarity index 65% rename from src/settings/DeviceSettingsHandler.js rename to src/settings/handlers/DeviceSettingsHandler.js index d4f5a5d727..cfac861ce4 100644 --- a/src/settings/DeviceSettingsHandler.js +++ b/src/settings/handlers/DeviceSettingsHandler.js @@ -16,7 +16,7 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; -import MatrixClientPeg from "../MatrixClientPeg"; +import MatrixClientPeg from "../../MatrixClientPeg"; /** * Gets and sets settings at the "device" level for the current device. @@ -38,6 +38,17 @@ export default class DeviceSettingsHandler extends SettingsHandler { return this._readFeature(settingName); } + // Special case notifications + if (settingName === "notificationsEnabled") { + return localStorage.getItem("notifications_enabled") === "true"; + } else if (settingName === "notificationBodyEnabled") { + return localStorage.getItem("notifications_body_enabled") === "true"; + } else if (settingName === "audioNotificationsEnabled") { + return localStorage.getItem("audio_notifications_enabled") === "true"; + } else if (settingName === "notificationToolbarHidden") { + return localStorage.getItem("notifications_hidden") === "true"; + } + return this._getSettings()[settingName]; } @@ -47,6 +58,21 @@ export default class DeviceSettingsHandler extends SettingsHandler { return Promise.resolve(); } + // Special case notifications + if (settingName === "notificationsEnabled") { + localStorage.setItem("notifications_enabled", newValue); + return Promise.resolve(); + } else if (settingName === "notificationBodyEnabled") { + localStorage.setItem("notifications_body_enabled", newValue); + return Promise.resolve(); + } else if (settingName === "audioNotificationsEnabled") { + localStorage.setItem("audio_notifications_enabled", newValue); + return Promise.resolve(); + } else if (settingName === "notificationToolbarHidden") { + localStorage.setItem("notifications_hidden", newValue); + return Promise.resolve(); + } + const settings = this._getSettings(); settings[settingName] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(settings)); diff --git a/src/settings/LocalEchoWrapper.js b/src/settings/handlers/LocalEchoWrapper.js similarity index 100% rename from src/settings/LocalEchoWrapper.js rename to src/settings/handlers/LocalEchoWrapper.js diff --git a/src/settings/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.js similarity index 98% rename from src/settings/RoomAccountSettingsHandler.js rename to src/settings/handlers/RoomAccountSettingsHandler.js index 0c9d15db68..503d5de6c4 100644 --- a/src/settings/RoomAccountSettingsHandler.js +++ b/src/settings/handlers/RoomAccountSettingsHandler.js @@ -15,7 +15,7 @@ limitations under the License. */ import SettingsHandler from "./SettingsHandler"; -import MatrixClientPeg from '../MatrixClientPeg'; +import MatrixClientPeg from '../../MatrixClientPeg'; /** * Gets and sets settings at the "room-account" level for the current user. diff --git a/src/settings/RoomDeviceSettingsHandler.js b/src/settings/handlers/RoomDeviceSettingsHandler.js similarity index 100% rename from src/settings/RoomDeviceSettingsHandler.js rename to src/settings/handlers/RoomDeviceSettingsHandler.js diff --git a/src/settings/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js similarity index 97% rename from src/settings/RoomSettingsHandler.js rename to src/settings/handlers/RoomSettingsHandler.js index 7aac561965..3aee0dd6eb 100644 --- a/src/settings/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -15,7 +15,7 @@ limitations under the License. */ import SettingsHandler from "./SettingsHandler"; -import MatrixClientPeg from '../MatrixClientPeg'; +import MatrixClientPeg from '../../MatrixClientPeg'; /** * Gets and sets settings at the "room" level. diff --git a/src/settings/SettingsHandler.js b/src/settings/handlers/SettingsHandler.js similarity index 100% rename from src/settings/SettingsHandler.js rename to src/settings/handlers/SettingsHandler.js