Initial support for notification settings

Signed-off-by: Travis Ralston <travpc@gmail.com>
This commit is contained in:
Travis Ralston 2017-11-04 21:47:18 -07:00
parent 8351ec7e73
commit 7ce4316cc8
16 changed files with 194 additions and 105 deletions

View file

@ -25,6 +25,7 @@ import dis from './dispatcher';
import sdk from './index'; import sdk from './index';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import Modal from './Modal'; import Modal from './Modal';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
/* /*
* Dispatches: * Dispatches:
@ -138,10 +139,8 @@ const Notifier = {
// make sure that we persist the current setting audio_enabled setting // make sure that we persist the current setting audio_enabled setting
// before changing anything // before changing anything
if (global.localStorage) { if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) {
if (global.localStorage.getItem('audio_notifications_enabled') === null) { SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, this.isEnabled());
this.setAudioEnabled(this.isEnabled());
}
} }
if (enable) { if (enable) {
@ -149,6 +148,7 @@ const Notifier = {
plaf.requestNotificationPermission().done((result) => { plaf.requestNotificationPermission().done((result) => {
if (result !== 'granted') { if (result !== 'granted') {
// The permission request was dismissed or denied // The permission request was dismissed or denied
// TODO: Support alternative branding in messaging
const description = result === 'denied' const description = result === 'denied'
? _t('Riot does not have permission to send you notifications - please check your browser settings') ? _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'); : _t('Riot was not given permission to send notifications - please try again');
@ -160,10 +160,6 @@ const Notifier = {
return; return;
} }
if (global.localStorage) {
global.localStorage.setItem('notifications_enabled', 'true');
}
if (callback) callback(); if (callback) callback();
dis.dispatch({ dis.dispatch({
action: "notifier_enabled", action: "notifier_enabled",
@ -174,8 +170,6 @@ const Notifier = {
// disabled again in the future, we will show the banner again. // disabled again in the future, we will show the banner again.
this.setToolbarHidden(false); this.setToolbarHidden(false);
} else { } else {
if (!global.localStorage) return;
global.localStorage.setItem('notifications_enabled', 'false');
dis.dispatch({ dis.dispatch({
action: "notifier_enabled", action: "notifier_enabled",
value: false, value: false,
@ -184,44 +178,24 @@ const Notifier = {
}, },
isEnabled: function() { isEnabled: function() {
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
},
isPossible: function() {
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
if (!plaf) return false; if (!plaf) return false;
if (!plaf.supportsNotifications()) return false; if (!plaf.supportsNotifications()) return false;
if (!plaf.maySendNotifications()) return false; if (!plaf.maySendNotifications()) return false;
if (!global.localStorage) return true; return true; // possible, but not necessarily enabled
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');
}, },
isBodyEnabled: function() { isBodyEnabled: function() {
if (!global.localStorage) return true; return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
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';
}, },
setAudioEnabled: function(enable) { isAudioEnabled: function() {
if (!global.localStorage) return; return this.isEnabled() && SettingsStore.getValue("audioNotificationsEnabled");
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';
}, },
setToolbarHidden: function(hidden, persistent = true) { setToolbarHidden: function(hidden, persistent = true) {
@ -237,17 +211,15 @@ const Notifier = {
}); });
// update the info to localStorage for persistent settings // update the info to localStorage for persistent settings
if (persistent && global.localStorage) { if (persistent && SettingsStore.isLevelSupported(SettingLevel.DEVICE)) {
global.localStorage.setItem('notifications_hidden', hidden); return SettingsStore.setValue("notificationToolbarHidden", null, SettingLevel.DEVICE, hidden);
} }
}, },
isToolbarHidden: function() { isToolbarHidden: function() {
// Check localStorage for any such meta data // Check localStorage for any such meta data
if (global.localStorage) { if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) {
if (global.localStorage.getItem('notifications_hidden') === 'true') { return SettingsStore.getValue("notificationToolbarHidden");
return true;
}
} }
return this.toolbarHidden; return this.toolbarHidden;

View file

@ -17,9 +17,6 @@ limitations under the License.
import Promise from 'bluebird'; import Promise from 'bluebird';
import MatrixClientPeg from './MatrixClientPeg'; 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. * TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
@ -48,42 +45,6 @@ export default {
// TODO // 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) { changePassword: function(oldPassword, newPassword) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();

View file

@ -415,10 +415,6 @@ module.exports = React.createClass({
dis.dispatch({action: 'password_changed'}); dis.dispatch({action: 'password_changed'});
}, },
onEnableNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked);
},
_onAddEmailEditFinished: function(value, shouldSubmit) { _onAddEmailEditFinished: function(value, shouldSubmit) {
if (!shouldSubmit) return; if (!shouldSubmit) return;
this._addEmail(); this._addEmail();

View file

@ -62,6 +62,9 @@ export const SETTINGS = {
// default: { // default: {
// your: "value", // your: "value",
// }, // },
//
// // Optional settings controller. See SettingsController for more information.
// controller: new MySettingController(),
// }, // },
"feature_groups": { "feature_groups": {
isFeature: true, isFeature: true,
@ -213,4 +216,23 @@ export const SETTINGS = {
secondary_color: null, // Hex string, eg: #000000 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,
},
}; };

View file

@ -15,17 +15,17 @@ limitations under the License.
*/ */
import Promise from 'bluebird'; import Promise from 'bluebird';
import DeviceSettingsHandler from "./DeviceSettingsHandler"; import DeviceSettingsHandler from "./handlers/DeviceSettingsHandler";
import RoomDeviceSettingsHandler from "./RoomDeviceSettingsHandler"; import RoomDeviceSettingsHandler from "./handlers/RoomDeviceSettingsHandler";
import DefaultSettingsHandler from "./DefaultSettingsHandler"; import DefaultSettingsHandler from "./handlers/DefaultSettingsHandler";
import RoomAccountSettingsHandler from "./RoomAccountSettingsHandler"; import RoomAccountSettingsHandler from "./handlers/RoomAccountSettingsHandler";
import AccountSettingsHandler from "./AccountSettingsHandler"; import AccountSettingsHandler from "./handlers/AccountSettingsHandler";
import RoomSettingsHandler from "./RoomSettingsHandler"; import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
import ConfigSettingsHandler from "./ConfigSettingsHandler"; import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
import {_t} from '../languageHandler'; import {_t} from '../languageHandler';
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import {SETTINGS} from "./Settings"; import {SETTINGS} from "./Settings";
import LocalEchoWrapper from "./LocalEchoWrapper"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
/** /**
* Represents the various setting levels supported by the SettingsStore. * Represents the various setting levels supported by the SettingsStore.
@ -208,8 +208,9 @@ export default class SettingsStore {
if (explicit) { if (explicit) {
let handler = handlers[level]; let handler = handlers[level];
if (!handler) return null; if (!handler) return SettingsStore._tryControllerOverride(settingName, level, roomId, null);
return handler.getValue(settingName, roomId); const value = handler.getValue(settingName, roomId);
return SettingsStore._tryControllerOverride(settingName, level, roomId, value);
} }
for (let i = minIndex; i < LEVEL_ORDER.length; i++) { for (let i = minIndex; i < LEVEL_ORDER.length; i++) {
@ -219,10 +220,19 @@ export default class SettingsStore {
const value = handler.getValue(settingName, roomId); const value = handler.getValue(settingName, roomId);
if (value === null || value === undefined) continue; 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); 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);
}));
} }
/** /**

View file

@ -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();
// }
// }

View file

@ -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
}
}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import SettingsHandler from "./SettingsHandler"; import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from '../MatrixClientPeg'; import MatrixClientPeg from '../../MatrixClientPeg';
/** /**
* Gets and sets settings at the "account" level for the current user. * Gets and sets settings at the "account" level for the current user.

View file

@ -16,7 +16,7 @@ limitations under the License.
import Promise from 'bluebird'; import Promise from 'bluebird';
import SettingsHandler from "./SettingsHandler"; 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 * Gets and sets settings at the "config" level. This handler does not make use of the

View file

@ -16,7 +16,7 @@ limitations under the License.
import Promise from 'bluebird'; import Promise from 'bluebird';
import SettingsHandler from "./SettingsHandler"; import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from "../MatrixClientPeg"; import MatrixClientPeg from "../../MatrixClientPeg";
/** /**
* Gets and sets settings at the "device" level for the current device. * 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); 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]; return this._getSettings()[settingName];
} }
@ -47,6 +58,21 @@ export default class DeviceSettingsHandler extends SettingsHandler {
return Promise.resolve(); 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(); const settings = this._getSettings();
settings[settingName] = newValue; settings[settingName] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(settings)); localStorage.setItem("mx_local_settings", JSON.stringify(settings));

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import SettingsHandler from "./SettingsHandler"; 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. * Gets and sets settings at the "room-account" level for the current user.

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import SettingsHandler from "./SettingsHandler"; import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from '../MatrixClientPeg'; import MatrixClientPeg from '../../MatrixClientPeg';
/** /**
* Gets and sets settings at the "room" level. * Gets and sets settings at the "room" level.