Notification sounds (#367)
authorginnyTheCat <ginnythecat@lelux.net>
Fri, 18 Mar 2022 03:37:11 +0000 (04:37 +0100)
committerGitHub <noreply@github.com>
Fri, 18 Mar 2022 03:37:11 +0000 (09:07 +0530)
* Basic notification sound support

* Add settings option for notification sounds

* Allow sound without desktop notifications

public/sound/notification.ogg [new file with mode: 0755]
src/app/organisms/settings/Settings.jsx
src/client/action/settings.js
src/client/state/Notifications.js
src/client/state/cons.js
src/client/state/settings.js
webpack.common.js

diff --git a/public/sound/notification.ogg b/public/sound/notification.ogg
new file mode 100755 (executable)
index 0000000..93b3a01
Binary files /dev/null and b/public/sound/notification.ogg differ
index f9cc63160a5960c0d1319165e49dd8776994deed..87f2766033d01d832eaeb65644ce92f8828edb35 100644 (file)
@@ -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={<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>
   );
 }
@@ -200,7 +210,7 @@ function AboutSection() {
       <div className="set-about__branding">
         <img width="60" height="60" src={CinnySVG} alt="Cinny logo" />
         <div>
-          <Text variant="h2" weight='medium'>
+          <Text variant="h2" weight="medium">
             Cinny
             <span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span>
           </Text>
@@ -223,6 +233,10 @@ function AboutSection() {
             {/* 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>
           </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>
       </div>
     </div>
index 11923418e50b3d1e9a57e18daafe595a6f54c3c1..7b539c8d741f95933ac5c1483c05c256b15be887 100644 (file)
@@ -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,
+  });
+}
index 11fd665e1798926eb5f5e37b6a29dcac8184c038..7bcd95061fd9b819b38b2df0bd5718e78cc1a722 100644 (file)
@@ -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 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 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() {
index 6ec6c27a79ad4b718f6b875df439068e68b721a5..862bf5cf7cd892ce91819ff72b471eae0e174141 100644 (file)
@@ -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',
     },
   },
 };
index 25d0b2d530f2605dfb4255c4ee3ed83a6e70ef1f..0f476ef8835380e6b1b61b3ceee3b633c8d6645a 100644 (file)
@@ -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]?.();
index dd45a0675ca905110c28f91ae76658acf5ffc545..9ff58daf588d4abc78ac358e3e2b1d78fff7e894 100644 (file)
@@ -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',
       },
       {