diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js new file mode 100644 index 0000000000..704fdd17b8 --- /dev/null +++ b/src/RoomNotifs.js @@ -0,0 +1,137 @@ +/* +Copyright 2016 OpenMarket Ltd + +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 MatrixClientPeg from './MatrixClientPeg'; +import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; +import q from 'q'; + +export function getRoomNotifsState(roomId) { + // look through the override rules for a rule affecting this room: + // if one exists, it will take precedence. + const muteRule = findOverrideMuteRule(roomId); + if (muteRule && muteRule.enabled) { + return 'mute'; + } + + // for everything else, look at the room rule. + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + + // XXX: We have to assume the default is to notify for all messages + // (in particular this will be 'wrong' for one to one rooms because + // they will notify loudly for all messages) + if (!roomRule || !roomRule.enabled) return 'all_messages'; + + // a mute at the room level will still allow mentions + // to notify + if (isMuteRule(roomRule)) return 'mentions_only'; + + const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); + if (actionsObject.tweaks.sound) return 'all_messages_loud'; + + return null; +} + +export function setRoomNotifsState(roomId, newState) { + const cli = MatrixClientPeg.get(); + const promises = []; + + if (newState == 'mute') { + // delete the room rule + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } + + // add an override rule to squelch everything in this room + promises.push(cli.addPushRule('global', 'override', roomId, { + conditions: [ + { + kind: 'event_match', + key: 'room_id', + pattern: roomId, + } + ], + actions: [ + 'dont_notify', + ] + })); + } else { + const overrideMuteRule = findOverrideMuteRule(roomId); + if (overrideMuteRule) { + promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + } + + if (newState == 'all_messages') { + promises.push(cli.deletePushRule('global', 'room', roomId)); + } else if (newState == 'mentions_only') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'dont_notify', + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } else if ('all_messages_loud') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'notify', + { + set_tweak: 'sound', + value: 'default', + } + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } + } + + return q.all(promises); +} + +function findOverrideMuteRule(roomId) { + for (const rule of MatrixClientPeg.get().pushRules['global'].override) { + if (isRuleForRoom(roomId, rule)) { + if (isMuteRule(rule)) { + return rule; + } + } + } + return null; +} + +function isRuleForRoom(roomId, rule) { + if (rule.conditions.length !== 1) { + return false; + } + const cond = rule.conditions[0]; + if ( + cond.kind == 'event_match' && + cond.key == 'room_id' && + cond.pattern == roomId + ) { + return true; + } + return false; +} + +function isMuteRule(rule) { + return ( + rule.actions.length == 1 && + rule.actions[0] == 'dont_notify' + ); +} + diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index ae0ffafae5..7248ed7fcd 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -22,6 +22,7 @@ var dis = require("../../../dispatcher"); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); var ContextualMenu = require('../../structures/ContextualMenu'); +var RoomNotifs = require('../../../RoomNotifs'); module.exports = React.createClass({ displayName: 'RoomTile', @@ -43,43 +44,40 @@ module.exports = React.createClass({ }, getInitialState: function() { - var areNotifsMuted = false; - var cli = MatrixClientPeg.get(); - if (!cli.isGuest()) { - var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; - } - } - } - return({ hover : false, badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - areNotifsMuted: areNotifsMuted, + showBadge: this._shouldShowBadge(), }); }, - onAction: function(payload) { - switch (payload.action) { - case 'notification_change': - // Is the notification about this room? - if (payload.roomId === this.props.room.roomId) { - this.setState( { areNotifsMuted : payload.areNotifsMuted }); - } - break; + _shouldShowBadge: function() { + if (MatrixClientPeg.get().isGuest()) return true; + + const showBadgeInStates = ['all_messages', 'all_messages_loud']; + const currentState = RoomNotifs.getRoomNotifsState(this.props.room.roomId); + return showBadgeInStates.indexOf(currentState) > -1; + }, + + onAccountData: function(accountDataEvent) { + if (accountDataEvent.getType() == 'm.push_rules') { + this.setState({ + showBadge: this._shouldShowBadge(), + }); } }, - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); + componentWillMount: function() { + MatrixClientPeg.get().on("accountData", this.onAccountData); }, componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); + var cli = MatrixClientPeg.get(); + if (cli) { + MatrixClientPeg.get().removeListener("accountData", this.onAccountData); + } }, onClick: function() { @@ -183,11 +181,11 @@ module.exports = React.createClass({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && !this.state.areNotifsMuted, + 'mx_RoomTile_unreadNotify': notificationCount > 0 && this.state.showBadge, 'mx_RoomTile_highlight': this.props.highlight, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, - 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) + 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && this.state.showBadge)) }); var avatarClasses = classNames({ @@ -214,7 +212,7 @@ module.exports = React.createClass({ if (this.state.badgeHover || this.state.notificationTagMenu) { badgeContent = "\u00B7\u00B7\u00B7"; - } else if (this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) { + } else if (this.props.highlight || (notificationCount > 0 && this.state.showBadge)) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -230,7 +228,7 @@ module.exports = React.createClass({ var nameClasses = classNames({ 'mx_RoomTile_name': true, 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.notificationTagMenu, + 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && this.state.showBadge) || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) {