Settings: Link notification sounds to playSound.

Fix: Restore hooks.ts patch, additionally use numbers.
This commit is contained in:
Paul 2021-06-24 14:26:18 +01:00
parent 352c0e880c
commit 8f62625506
10 changed files with 104 additions and 62 deletions

2
external/lang vendored

@ -1 +1 @@
Subproject commit be021b37763b2b0f8f0367b49f9912add845aa21 Subproject commit 5af0f9c8092382aa9608ec39bf5149194da9161c

View file

@ -1,14 +1,17 @@
import message from './message.mp3'; import message from './message.mp3';
import outbound from './outbound.mp3';
import call_join from './call_join.mp3'; import call_join from './call_join.mp3';
import call_leave from './call_leave.mp3'; import call_leave from './call_leave.mp3';
const SoundMap: { [key in Sounds]: string } = { const SoundMap: { [key in Sounds]: string } = {
message, message,
outbound,
call_join, call_join,
call_leave 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) { export function playSound(sound: Sounds) {
let file = SoundMap[sound]; let file = SoundMap[sound];

View file

@ -23,6 +23,7 @@ import { SingletonMessageRenderer, SMOOTH_SCROLL_ON_RECEIVE } from "../../../lib
import ReplyBar from "./bars/ReplyBar"; import ReplyBar from "./bars/ReplyBar";
import FilePreview from './bars/FilePreview'; import FilePreview from './bars/FilePreview';
import AutoComplete, { useAutoComplete } from "../AutoComplete"; import AutoComplete, { useAutoComplete } from "../AutoComplete";
import { SoundContext } from "../../../context/Settings";
type Props = WithDispatcher & { type Props = WithDispatcher & {
channel: Channel; channel: Channel;
@ -59,6 +60,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' }); const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
const [ typing, setTyping ] = useState<boolean | number>(false); const [ typing, setTyping ] = useState<boolean | number>(false);
const [ replies, setReplies ] = useState<Reply[]>([]); const [ replies, setReplies ] = useState<Reply[]>([]);
const playSound = useContext(SoundContext);
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const client = useContext(AppContext); const client = useContext(AppContext);
const translate = useTranslation(); const translate = useTranslation();
@ -108,6 +110,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
stopTyping(); stopTyping();
setMessage(); setMessage();
setReplies([]); setReplies([]);
playSound('outbound');
const nonce = ulid(); const nonce = ulid();
dispatcher({ dispatcher({
@ -208,6 +211,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
setMessage(); setMessage();
setReplies([]); setReplies([]);
playSound('outbound');
if (files.length > CAN_UPLOAD_AT_ONCE) { if (files.length > CAN_UPLOAD_AT_ONCE) {
setUploadState({ setUploadState({

View file

@ -4,28 +4,47 @@
// //
// Replace references to SettingsContext with connectState in the future // Replace references to SettingsContext with connectState in the future
// if it does cause problems though. // 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 { connectState } from "../redux/connector";
import defaultsDeep from "lodash.defaultsdeep";
import { Children } from "../types/Preact"; import { Children } from "../types/Preact";
import { createContext } from "preact"; import { createContext } from "preact";
import { useMemo } from "preact/hooks";
export const SettingsContext = createContext<Settings>({} as any); export const SettingsContext = createContext<Settings>({} as any);
export const SoundContext = createContext<(sound: Sounds) => void>({} as any);
interface Props { interface Props {
children?: Children, children?: Children,
settings: Settings 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 ( return (
<SettingsContext.Provider value={props.settings}> <SettingsContext.Provider value={settings}>
{ props.children } <SoundContext.Provider value={play}>
{ children }
</SoundContext.Provider>
</SettingsContext.Provider> </SettingsContext.Provider>
) )
} }
export default connectState(Settings, state => { export default connectState<Omit<Props, 'settings'>>(Settings, state => {
return { return {
settings: state.settings settings: state.settings
} }

View file

@ -5,6 +5,7 @@ import { AppContext } from "./revoltjs/RevoltClient";
import type VoiceClient from "../lib/vortex/VoiceClient"; import type VoiceClient from "../lib/vortex/VoiceClient";
import type { ProduceType, VoiceUser } from "../lib/vortex/Types"; import type { ProduceType, VoiceUser } from "../lib/vortex/Types";
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { SoundContext } from "./Settings";
export enum VoiceStatus { export enum VoiceStatus {
LOADING = 0, LOADING = 0,
@ -106,6 +107,7 @@ export default function Voice({ children }: Props) {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setStatus(VoiceStatus.READY); setStatus(VoiceStatus.READY);
return;
} }
setStatus(VoiceStatus.CONNECTED); setStatus(VoiceStatus.CONNECTED);
@ -154,6 +156,8 @@ export default function Voice({ children }: Props) {
}, [ client ]); }, [ client ]);
const { forceUpdate } = useForceUpdate(); const { forceUpdate } = useForceUpdate();
const playSound = useContext(SoundContext);
useEffect(() => { useEffect(() => {
if (!client?.supported()) return; if (!client?.supported()) return;
@ -164,8 +168,14 @@ export default function Voice({ children }: Props) {
client.on("startProduce", forceUpdate); client.on("startProduce", forceUpdate);
client.on("stopProduce", forceUpdate); client.on("stopProduce", forceUpdate);
client.on("userJoined", forceUpdate); client.on("userJoined", () => {
client.on("userLeft", forceUpdate); playSound('call_join');
forceUpdate();
});
client.on("userLeft", () => {
playSound('call_leave');
forceUpdate();
});
client.on("userStartProduce", forceUpdate); client.on("userStartProduce", forceUpdate);
client.on("userStopProduce", forceUpdate); client.on("userStopProduce", forceUpdate);
client.on("close", forceUpdate); client.on("close", forceUpdate);

View file

@ -4,23 +4,26 @@ import { BrowserRouter as Router } from "react-router-dom";
import Intermediate from './intermediate/Intermediate'; import Intermediate from './intermediate/Intermediate';
import Client from './revoltjs/RevoltClient'; import Client from './revoltjs/RevoltClient';
import Voice from "./Voice"; import Settings from "./Settings";
import Locale from "./Locale"; import Locale from "./Locale";
import Voice from "./Voice";
import Theme from "./Theme"; import Theme from "./Theme";
export default function Context({ children }: { children: Children }) { export default function Context({ children }: { children: Children }) {
return ( return (
<Router> <Router>
<State> <State>
<Locale> <Settings>
<Intermediate> <Locale>
<Client> <Intermediate>
<Voice> <Client>
<Theme>{children}</Theme> <Voice>
</Voice> <Theme>{children}</Theme>
</Client> </Voice>
</Intermediate> </Client>
</Locale> </Intermediate>
</Locale>
</Settings>
</State> </State>
</Router> </Router>
); );

View file

@ -1,10 +1,10 @@
import { decodeTime } from "ulid"; import { decodeTime } from "ulid";
import { SoundContext } from "../Settings";
import { AppContext } from "./RevoltClient"; import { AppContext } from "./RevoltClient";
import { useTranslation } from "../../lib/i18n"; import { useTranslation } from "../../lib/i18n";
import { Users } from "revolt.js/dist/api/objects"; import { Users } from "revolt.js/dist/api/objects";
import { useContext, useEffect } from "preact/hooks"; import { useContext, useEffect } from "preact/hooks";
import { connectState } from "../../redux/connector"; import { connectState } from "../../redux/connector";
import { playSound } from "../../assets/sounds/Audio";
import { Message, SYSTEM_USER_ID, User } from "revolt.js"; import { Message, SYSTEM_USER_ID, User } from "revolt.js";
import { NotificationOptions } from "../../redux/reducers/settings"; import { NotificationOptions } from "../../redux/reducers/settings";
import { Route, Switch, useHistory, useParams } from "react-router-dom"; 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) { function Notifier(props: Props) {
const translate = useTranslation(); const translate = useTranslation();
const showNotification = props.options?.desktopEnabled ?? false; const showNotification = props.options?.desktopEnabled ?? false;
// const playIncoming = props.options?.soundEnabled ?? true;
// const playOutgoing = props.options?.outgoingSoundEnabled ?? true;
const client = useContext(AppContext); const client = useContext(AppContext);
const { guild: guild_id, channel: channel_id } = useParams<{ const { guild: guild_id, channel: channel_id } = useParams<{
@ -36,13 +34,13 @@ function Notifier(props: Props) {
channel: string; channel: string;
}>(); }>();
const history = useHistory(); const history = useHistory();
const playSound = useContext(SoundContext);
async function message(msg: Message) { async function message(msg: Message) {
if (msg.author === client.user!._id) return; if (msg.author === client.user!._id) return;
if (msg.channel === channel_id && document.hasFocus()) return; if (msg.channel === channel_id && document.hasFocus()) return;
if (client.user?.status?.presence === Users.Presence.Busy) return; if (client.user?.status?.presence === Users.Presence.Busy) return;
// Sounds.playInbound();
playSound('message'); playSound('message');
if (!showNotification) return; if (!showNotification) return;

View file

@ -11,8 +11,9 @@ export interface HookContext {
export function useForceUpdate(context?: HookContext): HookContext { export function useForceUpdate(context?: HookContext): HookContext {
const client = useContext(AppContext); const client = useContext(AppContext);
if (context) return context; if (context) return context;
/*const H = useState(undefined);
var updateState: (_: undefined) => void; const H = useState(0);
var updateState: (_: number) => void;
if (Array.isArray(H)) { if (Array.isArray(H)) {
let [, u] = H; let [, u] = H;
updateState = u; updateState = u;
@ -20,9 +21,8 @@ export function useForceUpdate(context?: HookContext): HookContext {
console.warn('Failed to construct using useState.'); console.warn('Failed to construct using useState.');
console.warn(H); console.warn(H);
updateState = ()=>{}; updateState = ()=>{};
}*/ }
const [, updateState] = useState(0);
return { client, forceUpdate: () => updateState(Math.random()) }; return { client, forceUpdate: () => updateState(Math.random()) };
} }
@ -99,7 +99,7 @@ export function useDMs(context?: HookContext) {
return map return map
.toArray() .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) { export function useUserPermission(id: string, context?: HookContext) {

View file

@ -1,19 +1,21 @@
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import defaultsDeep from "lodash.defaultsdeep";
import Checkbox from "../../../components/ui/Checkbox"; import Checkbox from "../../../components/ui/Checkbox";
import { connectState } from "../../../redux/connector"; import { connectState } from "../../../redux/connector";
import { WithDispatcher } from "../../../redux/reducers"; import { WithDispatcher } from "../../../redux/reducers";
import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { urlBase64ToUint8Array } from "../../../lib/conversion"; import { urlBase64ToUint8Array } from "../../../lib/conversion";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { NotificationOptions } from "../../../redux/reducers/settings";
import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { DEFAULT_SOUNDS, NotificationOptions, SoundOptions } from "../../../redux/reducers/settings";
interface Props { interface Props {
options?: NotificationOptions; options?: NotificationOptions;
} }
export function Component(props: Props & WithDispatcher) { export function Component({ options, dispatcher }: Props & WithDispatcher) {
const client = useContext(AppContext); const client = useContext(AppContext);
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>( 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 ( return (
<div className={styles.notifications}> <div className={styles.notifications}>
<h3> <h3>
@ -35,7 +38,7 @@ export function Component(props: Props & WithDispatcher) {
</h3> </h3>
<Checkbox <Checkbox
disabled={!("Notification" in window)} disabled={!("Notification" in window)}
checked={props.options?.desktopEnabled ?? false} checked={options?.desktopEnabled ?? false}
onChange={async desktopEnabled => { onChange={async desktopEnabled => {
if (desktopEnabled) { if (desktopEnabled) {
let permission = await Notification.requestPermission(); let permission = await Notification.requestPermission();
@ -47,7 +50,7 @@ export function Component(props: Props & WithDispatcher) {
} }
} }
props.dispatcher({ dispatcher({
type: "SETTINGS_SET_NOTIFICATION_OPTIONS", type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
options: { desktopEnabled } options: { desktopEnabled }
}); });
@ -103,34 +106,25 @@ export function Component(props: Props & WithDispatcher) {
<h3> <h3>
<Text id="app.settings.pages.notifications.sounds" /> <Text id="app.settings.pages.notifications.sounds" />
</h3> </h3>
<Checkbox {
checked={props.options?.soundEnabled ?? true} SOUNDS_ARRAY.map(key =>
onChange={soundEnabled => <Checkbox
props.dispatcher({ checked={enabledSounds[key] ? true : false}
type: "SETTINGS_SET_NOTIFICATION_OPTIONS", onChange={enabled =>
options: { soundEnabled } dispatcher({
}) type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
} options: {
> sounds: {
<Text id="app.settings.pages.notifications.enable_sound" /> ...options?.sounds,
<p> [key]: enabled
<Text id="app.settings.pages.notifications.descriptions.enable_sound" /> }
</p> }
</Checkbox> })
<Checkbox }>
checked={props.options?.outgoingSoundEnabled ?? true} <Text id={`app.settings.pages.notifications.sound.${key}`} />
onChange={outgoingSoundEnabled => </Checkbox>
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>
</div> </div>
); );
} }

View file

@ -1,11 +1,22 @@
import { filter } from "."; import { filter } from ".";
import { SyncUpdateAction } from "./sync"; import { SyncUpdateAction } from "./sync";
import { Sounds } from "../../assets/sounds/Audio";
import { Theme, ThemeOptions } from "../../context/Theme"; 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 { export interface NotificationOptions {
desktopEnabled?: boolean; desktopEnabled?: boolean;
soundEnabled?: boolean; sounds?: SoundOptions
outgoingSoundEnabled?: boolean;
} }
export type EmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji"; export type EmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji";