From 8f626255065e3f650e3ba8a9fdf0cee8727a1e52 Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Thu, 24 Jun 2021 14:26:18 +0100
Subject: [PATCH] Settings: Link notification sounds to playSound. Fix: Restore
 hooks.ts patch, additionally use numbers.

---
 external/lang                                 |  2 +-
 src/assets/sounds/Audio.ts                    |  5 +-
 .../common/messaging/MessageBox.tsx           |  4 ++
 src/context/Settings.tsx                      | 29 ++++++++--
 src/context/Voice.tsx                         | 14 ++++-
 src/context/index.tsx                         | 23 ++++----
 src/context/revoltjs/Notifications.tsx        |  6 +-
 src/context/revoltjs/hooks.ts                 | 10 ++--
 src/pages/settings/panes/Notifications.tsx    | 58 +++++++++----------
 src/redux/reducers/settings.ts                | 15 ++++-
 10 files changed, 104 insertions(+), 62 deletions(-)

diff --git a/external/lang b/external/lang
index be021b3..5af0f9c 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit be021b37763b2b0f8f0367b49f9912add845aa21
+Subproject commit 5af0f9c8092382aa9608ec39bf5149194da9161c
diff --git a/src/assets/sounds/Audio.ts b/src/assets/sounds/Audio.ts
index 37da121..1168c6c 100644
--- a/src/assets/sounds/Audio.ts
+++ b/src/assets/sounds/Audio.ts
@@ -1,14 +1,17 @@
 import message from './message.mp3';
+import outbound from './outbound.mp3';
 import call_join from './call_join.mp3';
 import call_leave from './call_leave.mp3';
 
 const SoundMap: { [key in Sounds]: string } = {
     message,
+    outbound,
     call_join,
     call_leave
 }
 
-export type Sounds = 'message' | 'call_join' | 'call_leave';
+export type Sounds = 'message' | 'outbound' | 'call_join' | 'call_leave';
+export const SOUNDS_ARRAY: Sounds[] = [ 'message', 'outbound', 'call_join', 'call_leave' ];
 
 export function playSound(sound: Sounds) {
     let file = SoundMap[sound];
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index a3c98c1..86463d3 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -23,6 +23,7 @@ import { SingletonMessageRenderer, SMOOTH_SCROLL_ON_RECEIVE } from "../../../lib
 import ReplyBar from "./bars/ReplyBar";
 import FilePreview from './bars/FilePreview';
 import AutoComplete, { useAutoComplete } from "../AutoComplete";
+import { SoundContext } from "../../../context/Settings";
 
 type Props = WithDispatcher & {
     channel: Channel;
@@ -59,6 +60,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
     const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
     const [ typing, setTyping ] = useState<boolean | number>(false);
     const [ replies, setReplies ] = useState<Reply[]>([]);
+    const playSound = useContext(SoundContext);
     const { openScreen } = useIntermediate();
     const client = useContext(AppContext);
     const translate = useTranslation();
@@ -108,6 +110,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
         stopTyping();
         setMessage();
         setReplies([]);
+        playSound('outbound');
 
         const nonce = ulid();
         dispatcher({
@@ -208,6 +211,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
 
         setMessage();
         setReplies([]);
+        playSound('outbound');
 
         if (files.length > CAN_UPLOAD_AT_ONCE) {
             setUploadState({
diff --git a/src/context/Settings.tsx b/src/context/Settings.tsx
index f45005e..92737e4 100644
--- a/src/context/Settings.tsx
+++ b/src/context/Settings.tsx
@@ -4,28 +4,47 @@
 //
 // Replace references to SettingsContext with connectState in the future
 // if it does cause problems though.
+//
+// This now also supports Audio stuff.
 
-import { Settings } from "../redux/reducers/settings";
+import { DEFAULT_SOUNDS, Settings, SoundOptions } from "../redux/reducers/settings";
+import { playSound, Sounds } from "../assets/sounds/Audio";
 import { connectState } from "../redux/connector";
+import defaultsDeep from "lodash.defaultsdeep";
 import { Children } from "../types/Preact";
 import { createContext } from "preact";
+import { useMemo } from "preact/hooks";
 
 export const SettingsContext = createContext<Settings>({} as any);
+export const SoundContext = createContext<(sound: Sounds) => void>({} as any);
 
 interface Props {
     children?: Children,
     settings: Settings
 }
 
-function Settings(props: Props) {
+function Settings({ settings, children }: Props) {
+    console.info(settings.notification);
+    const play = useMemo(() => {
+        const enabled: SoundOptions = defaultsDeep(settings.notification ?? {}, DEFAULT_SOUNDS);
+        return (sound: Sounds) => {
+            console.info('check if we can play sound', enabled[sound]);
+            if (enabled[sound]) {
+                playSound(sound);
+            }
+        };
+    }, [ settings.notification ]);
+
     return (
-        <SettingsContext.Provider value={props.settings}>
-            { props.children }
+        <SettingsContext.Provider value={settings}>
+            <SoundContext.Provider value={play}>
+                { children }
+            </SoundContext.Provider>
         </SettingsContext.Provider>
     )
 }
 
-export default connectState(Settings, state => {
+export default connectState<Omit<Props, 'settings'>>(Settings, state => {
     return {
         settings: state.settings
     }
diff --git a/src/context/Voice.tsx b/src/context/Voice.tsx
index 30d6bbb..ac5c407 100644
--- a/src/context/Voice.tsx
+++ b/src/context/Voice.tsx
@@ -5,6 +5,7 @@ import { AppContext } from "./revoltjs/RevoltClient";
 import type VoiceClient from "../lib/vortex/VoiceClient";
 import type { ProduceType, VoiceUser } from "../lib/vortex/Types";
 import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
+import { SoundContext } from "./Settings";
 
 export enum VoiceStatus {
     LOADING = 0,
@@ -106,6 +107,7 @@ export default function Voice({ children }: Props) {
                 } catch (error) {
                     console.error(error);
                     setStatus(VoiceStatus.READY);
+                    return;
                 }
 
                 setStatus(VoiceStatus.CONNECTED);
@@ -154,6 +156,8 @@ export default function Voice({ children }: Props) {
     }, [ client ]);
 
     const { forceUpdate } = useForceUpdate();
+    const playSound = useContext(SoundContext);
+
     useEffect(() => {
         if (!client?.supported()) return;
 
@@ -164,8 +168,14 @@ export default function Voice({ children }: Props) {
         client.on("startProduce",  forceUpdate);
         client.on("stopProduce", forceUpdate);
 
-        client.on("userJoined", forceUpdate);
-        client.on("userLeft", forceUpdate);
+        client.on("userJoined", () => {
+            playSound('call_join');
+            forceUpdate();
+        });
+        client.on("userLeft", () => {
+            playSound('call_leave');
+            forceUpdate();
+        });
         client.on("userStartProduce", forceUpdate);
         client.on("userStopProduce", forceUpdate);
         client.on("close", forceUpdate);
diff --git a/src/context/index.tsx b/src/context/index.tsx
index f600e5b..6bb0de8 100644
--- a/src/context/index.tsx
+++ b/src/context/index.tsx
@@ -4,23 +4,26 @@ import { BrowserRouter as Router } from "react-router-dom";
 
 import Intermediate from './intermediate/Intermediate';
 import Client from './revoltjs/RevoltClient';
-import Voice from "./Voice";
+import Settings from "./Settings";
 import Locale from "./Locale";
+import Voice from "./Voice";
 import Theme from "./Theme";
 
 export default function Context({ children }: { children: Children }) {
     return (
         <Router>
             <State>
-                <Locale>
-                    <Intermediate>
-                        <Client>
-                            <Voice>
-                                <Theme>{children}</Theme>
-                            </Voice>
-                        </Client>
-                    </Intermediate>
-                </Locale>
+                <Settings>
+                    <Locale>
+                        <Intermediate>
+                            <Client>
+                                <Voice>
+                                    <Theme>{children}</Theme>
+                                </Voice>
+                            </Client>
+                        </Intermediate>
+                    </Locale>
+                </Settings>
             </State>
         </Router>
     );
diff --git a/src/context/revoltjs/Notifications.tsx b/src/context/revoltjs/Notifications.tsx
index 009ba05..7f7fe11 100644
--- a/src/context/revoltjs/Notifications.tsx
+++ b/src/context/revoltjs/Notifications.tsx
@@ -1,10 +1,10 @@
 import { decodeTime } from "ulid";
+import { SoundContext } from "../Settings";
 import { AppContext } from "./RevoltClient";
 import { useTranslation } from "../../lib/i18n";
 import { Users } from "revolt.js/dist/api/objects";
 import { useContext, useEffect } from "preact/hooks";
 import { connectState } from "../../redux/connector";
-import { playSound } from "../../assets/sounds/Audio";
 import { Message, SYSTEM_USER_ID, User } from "revolt.js";
 import { NotificationOptions } from "../../redux/reducers/settings";
 import { Route, Switch, useHistory, useParams } from "react-router-dom";
@@ -27,8 +27,6 @@ async function createNotification(title: string, options: globalThis.Notificatio
 function Notifier(props: Props) {
     const translate = useTranslation();
     const showNotification = props.options?.desktopEnabled ?? false;
-    // const playIncoming = props.options?.soundEnabled ?? true;
-    // const playOutgoing = props.options?.outgoingSoundEnabled ?? true;
 
     const client = useContext(AppContext);
     const { guild: guild_id, channel: channel_id } = useParams<{
@@ -36,13 +34,13 @@ function Notifier(props: Props) {
         channel: string;
     }>();
     const history = useHistory();
+    const playSound = useContext(SoundContext);
 
     async function message(msg: Message) {
         if (msg.author === client.user!._id) return;
         if (msg.channel === channel_id && document.hasFocus()) return;
         if (client.user?.status?.presence === Users.Presence.Busy) return;
 
-        // Sounds.playInbound();
         playSound('message');
         if (!showNotification) return;
 
diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts
index a6ca792..bb11295 100644
--- a/src/context/revoltjs/hooks.ts
+++ b/src/context/revoltjs/hooks.ts
@@ -11,8 +11,9 @@ export interface HookContext {
 export function useForceUpdate(context?: HookContext): HookContext {
     const client = useContext(AppContext);
     if (context) return context;
-    /*const H = useState(undefined);
-    var updateState: (_: undefined) => void;
+
+    const H = useState(0);
+    var updateState: (_: number) => void;
     if (Array.isArray(H)) {
         let [, u] = H;
         updateState = u;
@@ -20,9 +21,8 @@ export function useForceUpdate(context?: HookContext): HookContext {
         console.warn('Failed to construct using useState.');
         console.warn(H);
         updateState = ()=>{};
-    }*/
+    }
 
-    const [, updateState] = useState(0);
     return { client, forceUpdate: () => updateState(Math.random()) };
 }
 
@@ -99,7 +99,7 @@ export function useDMs(context?: HookContext) {
 
     return map
         .toArray()
-        .filter(x => x.channel_type === 'DirectMessage' || x.channel_type === 'Group' || x.channel_type === 'SavedMessages') as (Channels.GroupChannel | Channels.DirectMessageChannel | Channels.SavedMessagesChannel)[];
+        .filter(x => (x.channel_type === 'DirectMessage' && x.active) || x.channel_type === 'Group' || x.channel_type === 'SavedMessages') as (Channels.GroupChannel | Channels.DirectMessageChannel | Channels.SavedMessagesChannel)[];
 }
 
 export function useUserPermission(id: string, context?: HookContext) {
diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx
index 2ba745a..1dca0cd 100644
--- a/src/pages/settings/panes/Notifications.tsx
+++ b/src/pages/settings/panes/Notifications.tsx
@@ -1,19 +1,21 @@
 import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
+import defaultsDeep from "lodash.defaultsdeep";
 import Checkbox from "../../../components/ui/Checkbox";
 import { connectState } from "../../../redux/connector";
 import { WithDispatcher } from "../../../redux/reducers";
+import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { urlBase64ToUint8Array } from "../../../lib/conversion";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
-import { NotificationOptions } from "../../../redux/reducers/settings";
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
+import { DEFAULT_SOUNDS, NotificationOptions, SoundOptions } from "../../../redux/reducers/settings";
 
 interface Props {
     options?: NotificationOptions;
 }
 
-export function Component(props: Props & WithDispatcher) {
+export function Component({ options, dispatcher }: Props & WithDispatcher) {
     const client = useContext(AppContext);
     const { openScreen } = useIntermediate();
     const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
@@ -28,6 +30,7 @@ export function Component(props: Props & WithDispatcher) {
         });
     }, []);
 
+    const enabledSounds: SoundOptions = defaultsDeep(options?.sounds ?? {}, DEFAULT_SOUNDS);
     return (
         <div className={styles.notifications}>
             <h3>
@@ -35,7 +38,7 @@ export function Component(props: Props & WithDispatcher) {
             </h3>
             <Checkbox
                 disabled={!("Notification" in window)}
-                checked={props.options?.desktopEnabled ?? false}
+                checked={options?.desktopEnabled ?? false}
                 onChange={async desktopEnabled => {
                     if (desktopEnabled) {
                         let permission = await Notification.requestPermission();
@@ -47,7 +50,7 @@ export function Component(props: Props & WithDispatcher) {
                         }
                     }
 
-                    props.dispatcher({
+                    dispatcher({
                         type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
                         options: { desktopEnabled }
                     });
@@ -103,34 +106,25 @@ export function Component(props: Props & WithDispatcher) {
             <h3>
                 <Text id="app.settings.pages.notifications.sounds" />
             </h3>
-            <Checkbox
-                checked={props.options?.soundEnabled ?? true}
-                onChange={soundEnabled =>
-                    props.dispatcher({
-                        type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
-                        options: { soundEnabled }
-                    })
-                }
-            >
-                <Text id="app.settings.pages.notifications.enable_sound" />
-                <p>
-                    <Text id="app.settings.pages.notifications.descriptions.enable_sound" />
-                </p>
-            </Checkbox>
-            <Checkbox
-                checked={props.options?.outgoingSoundEnabled ?? true}
-                onChange={outgoingSoundEnabled =>
-                    props.dispatcher({
-                        type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
-                        options: { outgoingSoundEnabled }
-                    })
-                }
-            >
-                <Text id="app.settings.pages.notifications.enable_outgoing_sound" />
-                <p>
-                    <Text id="app.settings.pages.notifications.descriptions.enable_outgoing_sound" />
-                </p>
-            </Checkbox>
+            {
+                SOUNDS_ARRAY.map(key =>
+                    <Checkbox
+                        checked={enabledSounds[key] ? true : false}
+                        onChange={enabled =>
+                            dispatcher({
+                                type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
+                                options: {
+                                    sounds: {
+                                        ...options?.sounds,
+                                        [key]: enabled
+                                    }
+                                }
+                            })
+                        }>
+                        <Text id={`app.settings.pages.notifications.sound.${key}`} />
+                    </Checkbox>
+                )
+            }
         </div>
     );
 }
diff --git a/src/redux/reducers/settings.ts b/src/redux/reducers/settings.ts
index ac18438..bcab75c 100644
--- a/src/redux/reducers/settings.ts
+++ b/src/redux/reducers/settings.ts
@@ -1,11 +1,22 @@
 import { filter } from ".";
 import { SyncUpdateAction } from "./sync";
+import { Sounds } from "../../assets/sounds/Audio";
 import { Theme, ThemeOptions } from "../../context/Theme";
 
+export type SoundOptions = {
+    [key in Sounds]?: boolean
+}
+
+export const DEFAULT_SOUNDS: SoundOptions = {
+    message: true,
+    outbound: false,
+    call_join: true,
+    call_leave: true
+};
+
 export interface NotificationOptions {
     desktopEnabled?: boolean;
-    soundEnabled?: boolean;
-    outgoingSoundEnabled?: boolean;
+    sounds?: SoundOptions
 }
 
 export type EmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji";
-- 
GitLab