diff --git a/public/sound/notification.ogg b/public/sound/notification.ogg new file mode 100755 index 0000000..93b3a01 Binary files /dev/null and b/public/sound/notification.ogg differ diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index f9cc631..87f2766 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -7,7 +7,7 @@ import cons from '../../../client/state/cons'; import settings from '../../../client/state/settings'; import { toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents, - toggleNotifications, + toggleNotifications, toggleNotificationSounds, } from '../../../client/action/settings'; import logout from '../../../client/action/logout'; import { usePermission } from '../../hooks/usePermission'; @@ -158,6 +158,16 @@ function NotificationsSection() { options={renderOptions()} content={Show notifications when new messages arrive.} /> + { toggleNotificationSounds(); updateState({}); }} + /> + )} + content={Play a sound when new messages arrive.} + /> ); } @@ -200,7 +210,7 @@ function AboutSection() {
Cinny logo
- + Cinny {`v${cons.version}`} @@ -223,6 +233,10 @@ function AboutSection() { {/* eslint-disable-next-line react/jsx-one-expression-per-line */ } The Twemoji emoji art is © Twitter, Inc and other contributors used under the terms of CC-BY 4.0. +
  • + {/* eslint-disable-next-line react/jsx-one-expression-per-line */ } + The Material sound resources are © Google used under the terms of CC-BY 4.0. +
  • diff --git a/src/client/action/settings.js b/src/client/action/settings.js index 1192341..7b539c8 100644 --- a/src/client/action/settings.js +++ b/src/client/action/settings.js @@ -36,3 +36,9 @@ export function toggleNotifications() { type: cons.actions.settings.TOGGLE_NOTIFICATIONS, }); } + +export function toggleNotificationSounds() { + appDispatcher.dispatch({ + type: cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS, + }); +} diff --git a/src/client/state/Notifications.js b/src/client/state/Notifications.js index 11fd665..7bcd950 100644 --- a/src/client/state/Notifications.js +++ b/src/client/state/Notifications.js @@ -6,6 +6,8 @@ import cons from './cons'; import navigation from './navigation'; import settings from './settings'; +import NotificationSound from '../../../public/sound/notification.ogg'; + function isNotifEvent(mEvent) { const eType = mEvent.getType(); if (!cons.supportEventTypes.includes(eType)) return false; @@ -185,7 +187,7 @@ class Notifications extends EventEmitter { } async _displayPopupNoti(mEvent, room) { - if (!settings.showNotifications) return; + if (!settings.showNotifications && !settings.isNotificationSounds) return; const actions = this.matrixClient.getPushActionsForEvent(mEvent); if (!actions?.notify) return; @@ -196,28 +198,43 @@ class Notifications extends EventEmitter { await mEvent.attemptDecryption(this.matrixClient.crypto); } - let title; - if (!mEvent.sender || room.name === mEvent.sender.name) { - title = room.name; - } else if (mEvent.sender) { - title = `${mEvent.sender.name} (${room.name})`; + if (settings.showNotifications) { + let title; + if (!mEvent.sender || room.name === mEvent.sender.name) { + title = 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; - 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, - }); - noti.onclick = () => selectRoom(room.roomId, mEvent.getId()); + _playNotiSounds() { + if (!this._notiAudio) { + this._notiAudio = new Audio(NotificationSound); + } + this._notiAudio.play(); } _listenEvents() { diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 6ec6c27..862bf5c 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -67,6 +67,7 @@ const cons = { TOGGLE_MEMBERSHIP_EVENT: 'TOGGLE_MEMBERSHIP_EVENT', TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT', TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS', + TOGGLE_NOTIFICATION_SOUNDS: 'TOGGLE_NOTIFICATION_SOUNDS', }, }, events: { @@ -135,6 +136,7 @@ const cons = { MEMBERSHIP_EVENTS_TOGGLED: 'MEMBERSHIP_EVENTS_TOGGLED', NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED', NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED', + NOTIFICATION_SOUNDS_TOGGLED: 'NOTIFICATION_SOUNDS_TOGGLED', }, }, }; diff --git a/src/client/state/settings.js b/src/client/state/settings.js index 25d0b2d..0f476ef 100644 --- a/src/client/state/settings.js +++ b/src/client/state/settings.js @@ -29,6 +29,7 @@ class Settings extends EventEmitter { this.hideMembershipEvents = this.getHideMembershipEvents(); this.hideNickAvatarEvents = this.getHideNickAvatarEvents(); this._showNotifications = this.getShowNotifications(); + this.isNotificationSounds = this.getIsNotificationSounds(); this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); } @@ -125,6 +126,15 @@ class Settings extends EventEmitter { 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) { const actions = { [cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => { @@ -164,6 +174,11 @@ class Settings extends EventEmitter { setSettings('showNotifications', 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]?.(); diff --git a/webpack.common.js b/webpack.common.js index dd45a06..9ff58da 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -39,7 +39,7 @@ module.exports = { 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', }, {