Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 4d055e0

Browse files
committed
Allow to reuse sounds for custom notifications
Signed-off-by: Florian Schunk <florian.schunk@karrieretutor.de>
1 parent c2ae6c2 commit 4d055e0

File tree

3 files changed

+128
-58
lines changed

3 files changed

+128
-58
lines changed

src/components/views/settings/tabs/room/NotificationSettingsTab.js

Lines changed: 119 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import Notifier from "../../../../../Notifier";
2323
import SettingsStore from '../../../../../settings/SettingsStore';
2424
import {SettingLevel} from "../../../../../settings/SettingLevel";
2525
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
26+
import Field from "../../../elements/Field";
27+
import Modal from '../../../../../Modal';
28+
import * as sdk from '../../../../../index';
2629

2730
@replaceableComponent("views.settings.tabs.room.NotificationsSettingsTab")
2831
export default class NotificationsSettingsTab extends React.Component {
@@ -37,38 +40,94 @@ export default class NotificationsSettingsTab extends React.Component {
3740

3841
this.state = {
3942
currentSound: "default",
40-
uploadedFile: null,
43+
currentSoundReplaced: false,
44+
selected: null,
45+
soundLibrary: {},
4146
};
4247
}
4348

4449
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
4550
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
46-
const soundData = Notifier.getSoundForRoom(this.props.roomId);
47-
if (!soundData) {
48-
return;
49-
}
50-
this.setState({currentSound: soundData.name || soundData.url});
51+
const soundData = SettingsStore.getValue("notificationSound", this.props.roomId);
52+
const soundLibrary = SettingsStore.getValue("soundLibrary", null);
53+
console.log(soundLibrary);
54+
const selected = (soundData === null) ? "default" : soundData.name;
55+
this.setState({
56+
currentSound: soundData.name || soundData.url,
57+
selected: selected,
58+
soundLibrary: soundLibrary,
59+
});
5160
}
5261

53-
async _triggerUploader(e) {
54-
e.stopPropagation();
55-
e.preventDefault();
56-
62+
async _triggerUploader() {
5763
this._soundUpload.current.click();
5864
}
5965

6066
async _onSoundUploadChanged(e) {
6167
if (!e.target.files || !e.target.files.length) {
62-
this.setState({
63-
uploadedFile: null,
64-
});
6568
return;
6669
}
6770

6871
const file = e.target.files[0];
72+
let soundLibrary = this.state.soundLibrary;
73+
74+
if (file.name in soundLibrary) {
75+
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
76+
Modal.createDialog(QuestionDialog, {
77+
title: _t("Replace File"),
78+
description: _t("There already exists a file with this name. " +
79+
"Are you sure, you want to replace it?"),
80+
button: _t("Replace"),
81+
onFinished: () => {
82+
this.setState({currentSoundReplaced: true});
83+
this._uploadSound(file);
84+
},
85+
});
86+
87+
return;
88+
}
89+
this._uploadSound(file);
90+
91+
}
92+
93+
async _uploadSound(file) {
94+
95+
let type = file.type;
96+
if (type === "video/ogg") {
97+
// XXX: I've observed browsers allowing users to pick a audio/ogg files,
98+
// and then calling it a video/ogg. This is a lame hack, but man browsers
99+
// suck at detecting mimetypes.
100+
type = "audio/ogg";
101+
}
102+
103+
const url = await MatrixClientPeg.get().uploadContent(
104+
file, {
105+
type,
106+
},
107+
);
108+
109+
const soundJSON = {
110+
name: file.name,
111+
type: type,
112+
size: file.size,
113+
url,
114+
};
115+
116+
let soundLibrary = this.state.soundLibrary;
117+
soundLibrary[soundJSON.name] = soundJSON;
118+
119+
await SettingsStore.setValue(
120+
"soundLibrary",
121+
null,
122+
SettingLevel.ACCOUNT,
123+
soundLibrary,
124+
);
125+
69126
this.setState({
70-
uploadedFile: file,
127+
soundLibrary: soundLibrary,
128+
selected: soundJSON.name,
71129
});
130+
72131
}
73132

74133
async _onClickSaveSound(e) {
@@ -86,45 +145,30 @@ export default class NotificationsSettingsTab extends React.Component {
86145
}
87146

88147
async _saveSound() {
89-
if (!this.state.uploadedFile) {
148+
if (!this.state.selected) {
90149
return;
91150
}
92151

93-
let type = this.state.uploadedFile.type;
94-
if (type === "video/ogg") {
95-
// XXX: I've observed browsers allowing users to pick a audio/ogg files,
96-
// and then calling it a video/ogg. This is a lame hack, but man browsers
97-
// suck at detecting mimetypes.
98-
type = "audio/ogg";
152+
if (this.state.selected == "default") {
153+
this._clearSound();
154+
return;
99155
}
100156

101-
const url = await MatrixClientPeg.get().uploadContent(
102-
this.state.uploadedFile, {
103-
type,
104-
},
105-
);
157+
const soundJSON = this.state.soundLibrary[this.state.selected];
106158

107159
await SettingsStore.setValue(
108160
"notificationSound",
109161
this.props.roomId,
110162
SettingLevel.ROOM_ACCOUNT,
111-
{
112-
name: this.state.uploadedFile.name,
113-
type: type,
114-
size: this.state.uploadedFile.size,
115-
url,
116-
},
163+
soundJSON,
117164
);
118165

119166
this.setState({
120-
uploadedFile: null,
121-
currentSound: this.state.uploadedFile.name,
167+
currentSound: soundJSON.name,
122168
});
123169
}
124170

125-
_clearSound(e) {
126-
e.stopPropagation();
127-
e.preventDefault();
171+
_clearSound() {
128172
SettingsStore.setValue(
129173
"notificationSound",
130174
this.props.roomId,
@@ -137,40 +181,60 @@ export default class NotificationsSettingsTab extends React.Component {
137181
});
138182
}
139183

140-
render() {
141-
let currentUploadedFile = null;
142-
if (this.state.uploadedFile) {
143-
currentUploadedFile = (
144-
<div>
145-
<span>{_t("Uploaded sound")}: <code>{this.state.uploadedFile.name}</code></span>
146-
</div>
147-
);
184+
_onReset() {
185+
this.setState({
186+
selected: this.state.currentSound,
187+
});
188+
}
189+
190+
_onChangeSelection(e) {
191+
e.stopPropagation();
192+
e.preventDefault();
193+
194+
if (e.target.value === "upload") {
195+
this._triggerUploader();
196+
return;
148197
}
149198

199+
this.setState({
200+
selected: e.target.value,
201+
});
202+
}
203+
204+
205+
render() {
206+
207+
const notChanged = this.state.currentSound == this.state.selected && !this.state.currentSoundReplaced;
208+
150209
return (
151210
<div className="mx_SettingsTab">
152211
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
153212
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
154213
<span className='mx_SettingsTab_subheading'>{_t("Sounds")}</span>
155214
<div>
156215
<span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br />
157-
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary">
158-
{_t("Reset")}
159-
</AccessibleButton>
160216
</div>
161217
<div>
162-
<h3>{_t("Set a new custom sound")}</h3>
218+
<h3>{_t("Select a custom sound")}</h3>
219+
<Field
220+
id="soundLibrary"
221+
element="select"
222+
label="custom sound"
223+
value={this.state.selected}
224+
onChange={this._onChangeSelection.bind(this)}
225+
>
226+
<option key="default" value="default">{_t("Default")}</option>
227+
{Object.keys(this.state.soundLibrary).map((sound, i) => <option key={i} value={sound}>{sound}</option>)}
228+
<option key="uplod" value="upload">{_t("upload")}</option>
229+
</Field>
163230
<form autoComplete="off" noValidate={true}>
164231
<input ref={this._soundUpload} className="mx_NotificationSound_soundUpload" type="file" onChange={this._onSoundUploadChanged.bind(this)} accept="audio/*" />
165232
</form>
166233

167-
{currentUploadedFile}
168-
169-
<AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary">
170-
{_t("Browse")}
234+
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={notChanged} onClick={this._onReset.bind(this)} kind="primary">
235+
{_t("Reset")}
171236
</AccessibleButton>
172-
173-
<AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary">
237+
<AccessibleButton className="mx_NotificationSound_save" disabled={notChanged} onClick={this._onClickSaveSound.bind(this)} kind="primary">
174238
{_t("Save")}
175239
</AccessibleButton>
176240
<br />

src/i18n/strings/en_EN.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,11 +1352,13 @@
13521352
"Bridges": "Bridges",
13531353
"URL Previews": "URL Previews",
13541354
"Room Addresses": "Room Addresses",
1355-
"Uploaded sound": "Uploaded sound",
1355+
"Replace File": "Replace File",
1356+
"There already exists a file with this name. Are you sure, you want to replace it?": "There already exists a file with this name. Are you sure, you want to replace it?",
1357+
"Replace": "Replace",
13561358
"Sounds": "Sounds",
13571359
"Notification sound": "Notification sound",
1358-
"Set a new custom sound": "Set a new custom sound",
1359-
"Browse": "Browse",
1360+
"Select a custom sound": "Select a custom sound",
1361+
"upload": "upload",
13601362
"Change room avatar": "Change room avatar",
13611363
"Change room name": "Change room name",
13621364
"Change main address for the room": "Change main address for the room",

src/settings/Settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
561561
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
562562
default: true,
563563
},
564+
"soundLibrary": {
565+
supportedLevels: [SettingLevel.ACCOUNT],
566+
default: {},
567+
},
564568
"enableWidgetScreenshots": {
565569
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
566570
displayName: _td('Enable widget screenshots on supported widgets'),

0 commit comments

Comments
 (0)