diff --git a/src/Notifier.js b/src/Notifier.js
index e8d0c27a31..041b91f4b2 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -96,11 +96,19 @@ const Notifier = {
}
},
- _getSoundForRoom: async function(room) {
+ setRoomSound: function(room, soundData) {
+ return MatrixClientPeg.get().setRoomAccountData(room.roomId, "uk.half-shot.notification.sound", soundData);
+ },
+
+ clearRoomSound: function(room) {
+ return room.setAccountData("uk.half-shot.notification.sound", null);
+ },
+
+ getSoundForRoom: async function(room) {
// We do no caching here because the SDK caches the event content
// and the browser will cache the sound.
let ev = await room.getAccountData("uk.half-shot.notification.sound");
- if (!ev) {
+ if (!ev || !ev.getContent()) {
// Check the account data.
ev = await MatrixClientPeg.get().getAccountData("uk.half-shot.notification.sound");
if (!ev) {
@@ -112,15 +120,18 @@ const Notifier = {
console.warn(`${room.roomId} has custom notification sound event, but no url key`);
return null;
}
+
return {
url: MatrixClientPeg.get().mxcUrlToHttp(content.url),
+ name: content.name,
type: content.type,
+ size: content.size,
};
},
_playAudioNotification: function(ev, room) {
- this._getSoundForRoom(room).then((sound) => {
- console.log(`Got sound ${sound || "default"} for ${room.roomId}`);
+ this.getSoundForRoom(room).then((sound) => {
+ console.log(`Got sound ${sound.name || "default"} for ${room.roomId}`);
// XXX: How do we ensure this is a sound file and not
// going to be exploited?
const selector = document.querySelector(sound ? `audio[src='${sound.url}']` : "#messageAudio");
diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js
index 05ed262078..733c5002f5 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.js
+++ b/src/components/views/dialogs/RoomSettingsDialog.js
@@ -22,7 +22,8 @@ import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsT
import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab";
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
-import sdk from "../../../index";
+import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
+import sdk from "../../../index";RolesRoomSettingsTab
import MatrixClientPeg from "../../../MatrixClientPeg";
export default class RoomSettingsDialog extends React.Component {
@@ -49,6 +50,11 @@ export default class RoomSettingsDialog extends React.Component {
"mx_RoomSettingsDialog_rolesIcon",
,
));
+ tabs.push(new Tab(
+ _td("Notifications"),
+ "mx_RoomSettingsDialog_rolesIcon",
+ ,
+ ))
tabs.push(new Tab(
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js
new file mode 100644
index 0000000000..35a223a1d8
--- /dev/null
+++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js
@@ -0,0 +1,139 @@
+/*
+Copyright 2019 New Vector 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 React from 'react';
+import PropTypes from 'prop-types';
+import {_t} from "../../../../../languageHandler";
+import MatrixClientPeg from "../../../../../MatrixClientPeg";
+import sdk from "../../../../..";
+import AccessibleButton from "../../../elements/AccessibleButton";
+import Modal from "../../../../../Modal";
+import dis from "../../../../../dispatcher";
+import Notifier from "../../../../../Notifier";
+
+export default class NotificationsSettingsTab extends React.Component {
+ static propTypes = {
+ roomId: PropTypes.string.isRequired,
+ closeSettingsFn: PropTypes.func.isRequired,
+ };
+
+ constructor() {
+ super();
+
+ this.state = {
+ currentSound: "default",
+ uploadedFile: null,
+ };
+ }
+
+ componentWillMount() {
+ const room = MatrixClientPeg.get().getRoom(this.props.roomId);
+ Notifier.getSoundForRoom(room).then((soundData) => {
+ if (!soundData) {
+ return;
+ }
+ this.setState({currentSound: soundData.name || soundData.url})
+ })
+ }
+
+ _onSoundUploadChanged(e) {
+ if (!e.target.files || !e.target.files.length) {
+ this.setState({
+ uploadedFile: null,
+ });
+ return;
+ }
+
+ const file = e.target.files[0];
+ this.setState({
+ uploadedFile: file,
+ });
+ }
+
+ async _saveSound (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (!this.state.uploadedFile) {
+ return;
+ }
+ let type = this.state.uploadedFile.type;
+ if (type === "video/ogg") {
+ // XXX: I've observed browsers allowing users to pick a audio/ogg files,
+ // and then calling it a video/ogg. This is a lame hack, but man browsers
+ // suck at detecting mimetypes.
+ type = "audio/ogg";
+ }
+ const url = await MatrixClientPeg.get().uploadContent(
+ this.state.uploadedFile, {
+ type,
+ },
+ );
+
+ const room = MatrixClientPeg.get().getRoom(this.props.roomId);
+
+ await Notifier.setRoomSound(room, {
+ name: this.state.uploadedFile.name,
+ type: type,
+ size: this.state.uploadedFile.size,
+ url,
+ });
+
+ this.setState({
+ uploadedFile: null,
+ uploadedFileUrl: null,
+ currentSound: this.state.uploadedFile.name,
+ });
+ }
+
+ _clearSound (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ const room = client.getRoom(this.props.roomId);
+ Notifier.clearRoomSound(room);
+
+ this.setState({
+ currentSound: "default",
+ });
+ }
+
+ render() {
+ const client = MatrixClientPeg.get();
+
+ return (
+
+
{_t("Notifications")}
+
+
{_t("Sounds")}
+
+ {_t("Notification sound")}: {this.state.currentSound}
+
+
+
{_t("Set a new custom sound")}
+
+
+ {_t("Reset to default sound")}
+
+
+
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 2b50fd9ad3..7f94bdc9cd 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1614,5 +1614,9 @@
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
"Failed to set direct chat tag": "Failed to set direct chat tag",
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
- "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
+ "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room",
+ "Sounds": "Sounds",
+ "Notification sound": "Notification sound",
+ "Set a new custom sound": "Set a new custom sound",
+ "Reset to default sound": "Reset to default sound"
}