Notification sounds (#367)

* Basic notification sound support

* Add settings option for notification sounds

* Allow sound without desktop notifications
This commit is contained in:
ginnyTheCat 2022-03-18 04:37:11 +01:00 committed by GitHub
parent a2655ee6a5
commit dc6e153b92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 24 deletions

BIN
public/sound/notification.ogg Executable file

Binary file not shown.

View file

@ -7,7 +7,7 @@ import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings'; import settings from '../../../client/state/settings';
import { import {
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents, toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
toggleNotifications, toggleNotifications, toggleNotificationSounds,
} from '../../../client/action/settings'; } from '../../../client/action/settings';
import logout from '../../../client/action/logout'; import logout from '../../../client/action/logout';
import { usePermission } from '../../hooks/usePermission'; import { usePermission } from '../../hooks/usePermission';
@ -158,6 +158,16 @@ function NotificationsSection() {
options={renderOptions()} options={renderOptions()}
content={<Text variant="b3">Show notifications when new messages arrive.</Text>} content={<Text variant="b3">Show notifications when new messages arrive.</Text>}
/> />
<SettingTile
title="Play notification sounds"
options={(
<Toggle
isActive={settings.isNotificationSounds}
onToggle={() => { toggleNotificationSounds(); updateState({}); }}
/>
)}
content={<Text variant="b3">Play a sound when new messages arrive.</Text>}
/>
</div> </div>
); );
} }
@ -200,7 +210,7 @@ function AboutSection() {
<div className="set-about__branding"> <div className="set-about__branding">
<img width="60" height="60" src={CinnySVG} alt="Cinny logo" /> <img width="60" height="60" src={CinnySVG} alt="Cinny logo" />
<div> <div>
<Text variant="h2" weight='medium'> <Text variant="h2" weight="medium">
Cinny Cinny
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span> <span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span>
</Text> </Text>
@ -223,6 +233,10 @@ function AboutSection() {
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ } {/* eslint-disable-next-line react/jsx-one-expression-per-line */ }
<Text>The <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twemoji</a> emoji art is © <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twitter, Inc and other contributors</a> used under the terms of <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer noopener">CC-BY 4.0</a>.</Text> <Text>The <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twemoji</a> emoji art is © <a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">Twitter, Inc and other contributors</a> used under the terms of <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer noopener">CC-BY 4.0</a>.</Text>
</li> </li>
<li>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */ }
<Text>The <a href="https://material.io/design/sound/sound-resources.html" target="_blank" rel="noreferrer noopener">Material sound resources</a> are © <a href="https://google.com" target="_blank" rel="noreferrer noopener">Google</a> used under the terms of <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer noopener">CC-BY 4.0</a>.</Text>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -36,3 +36,9 @@ export function toggleNotifications() {
type: cons.actions.settings.TOGGLE_NOTIFICATIONS, type: cons.actions.settings.TOGGLE_NOTIFICATIONS,
}); });
} }
export function toggleNotificationSounds() {
appDispatcher.dispatch({
type: cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS,
});
}

View file

@ -6,6 +6,8 @@ import cons from './cons';
import navigation from './navigation'; import navigation from './navigation';
import settings from './settings'; import settings from './settings';
import NotificationSound from '../../../public/sound/notification.ogg';
function isNotifEvent(mEvent) { function isNotifEvent(mEvent) {
const eType = mEvent.getType(); const eType = mEvent.getType();
if (!cons.supportEventTypes.includes(eType)) return false; if (!cons.supportEventTypes.includes(eType)) return false;
@ -185,7 +187,7 @@ class Notifications extends EventEmitter {
} }
async _displayPopupNoti(mEvent, room) { async _displayPopupNoti(mEvent, room) {
if (!settings.showNotifications) return; if (!settings.showNotifications && !settings.isNotificationSounds) return;
const actions = this.matrixClient.getPushActionsForEvent(mEvent); const actions = this.matrixClient.getPushActionsForEvent(mEvent);
if (!actions?.notify) return; if (!actions?.notify) return;
@ -196,28 +198,43 @@ class Notifications extends EventEmitter {
await mEvent.attemptDecryption(this.matrixClient.crypto); await mEvent.attemptDecryption(this.matrixClient.crypto);
} }
let title; if (settings.showNotifications) {
if (!mEvent.sender || room.name === mEvent.sender.name) { let title;
title = room.name; if (!mEvent.sender || room.name === mEvent.sender.name) {
} else if (mEvent.sender) { title = room.name;
title = `${mEvent.sender.name} (${room.name})`; } else if (mEvent.sender) {
title = `${mEvent.sender.name} (${room.name})`;
}
const iconSize = 36;
const icon = await renderAvatar({
text: mEvent.sender.name,
bgColor: cssColorMXID(mEvent.getSender()),
imageSrc: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, iconSize, iconSize, 'crop'),
size: iconSize,
borderRadius: 8,
scale: 8,
});
const noti = new window.Notification(title, {
body: mEvent.getContent().body,
icon,
silent: settings.isNotificationSounds,
});
if (settings.isNotificationSounds) {
noti.onshow = () => this._playNotiSounds();
}
noti.onclick = () => selectRoom(room.roomId, mEvent.getId());
} else {
this._playNotiSounds();
} }
}
const iconSize = 36; _playNotiSounds() {
const icon = await renderAvatar({ if (!this._notiAudio) {
text: mEvent.sender.name, this._notiAudio = new Audio(NotificationSound);
bgColor: cssColorMXID(mEvent.getSender()), }
imageSrc: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, iconSize, iconSize, 'crop'), this._notiAudio.play();
size: iconSize,
borderRadius: 8,
scale: 8,
});
const noti = new window.Notification(title, {
body: mEvent.getContent().body,
icon,
});
noti.onclick = () => selectRoom(room.roomId, mEvent.getId());
} }
_listenEvents() { _listenEvents() {

View file

@ -67,6 +67,7 @@ const cons = {
TOGGLE_MEMBERSHIP_EVENT: 'TOGGLE_MEMBERSHIP_EVENT', TOGGLE_MEMBERSHIP_EVENT: 'TOGGLE_MEMBERSHIP_EVENT',
TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT', TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT',
TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS', TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS',
TOGGLE_NOTIFICATION_SOUNDS: 'TOGGLE_NOTIFICATION_SOUNDS',
}, },
}, },
events: { events: {
@ -135,6 +136,7 @@ const cons = {
MEMBERSHIP_EVENTS_TOGGLED: 'MEMBERSHIP_EVENTS_TOGGLED', MEMBERSHIP_EVENTS_TOGGLED: 'MEMBERSHIP_EVENTS_TOGGLED',
NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED', NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED',
NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED', NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED',
NOTIFICATION_SOUNDS_TOGGLED: 'NOTIFICATION_SOUNDS_TOGGLED',
}, },
}, },
}; };

View file

@ -29,6 +29,7 @@ class Settings extends EventEmitter {
this.hideMembershipEvents = this.getHideMembershipEvents(); this.hideMembershipEvents = this.getHideMembershipEvents();
this.hideNickAvatarEvents = this.getHideNickAvatarEvents(); this.hideNickAvatarEvents = this.getHideNickAvatarEvents();
this._showNotifications = this.getShowNotifications(); this._showNotifications = this.getShowNotifications();
this.isNotificationSounds = this.getIsNotificationSounds();
this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
} }
@ -125,6 +126,15 @@ class Settings extends EventEmitter {
return settings.showNotifications; return settings.showNotifications;
} }
getIsNotificationSounds() {
if (typeof this.isNotificationSounds === 'boolean') return this.isNotificationSounds;
const settings = getSettings();
if (settings === null) return true;
if (typeof settings.isNotificationSounds === 'undefined') return true;
return settings.isNotificationSounds;
}
setter(action) { setter(action) {
const actions = { const actions = {
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => { [cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
@ -164,6 +174,11 @@ class Settings extends EventEmitter {
setSettings('showNotifications', this._showNotifications); setSettings('showNotifications', this._showNotifications);
this.emit(cons.events.settings.NOTIFICATIONS_TOGGLED, this._showNotifications); this.emit(cons.events.settings.NOTIFICATIONS_TOGGLED, this._showNotifications);
}, },
[cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS]: () => {
this.isNotificationSounds = !this.isNotificationSounds;
setSettings('isNotificationSounds', this.isNotificationSounds);
this.emit(cons.events.settings.NOTIFICATION_SOUNDS_TOGGLED, this.isNotificationSounds);
},
}; };
actions[action.type]?.(); actions[action.type]?.();

View file

@ -39,7 +39,7 @@ module.exports = {
use: ['html-loader'], use: ['html-loader'],
}, },
{ {
test: /\.(png|jpe?g|gif|otf|ttf|woff|woff2)$/, test: /\.(png|jpe?g|gif|otf|ttf|woff|woff2|ogg)$/,
type: 'asset/resource', type: 'asset/resource',
}, },
{ {