Skip to content

Commit 0214bd8

Browse files
authored
Merge pull request #129 from httpsphl/feat/app-sounds
feat(app): soft sounds for a message sent, a reply, a file and a new bot
2 parents f6d62fa + 3678968 commit 0214bd8

15 files changed

Lines changed: 314 additions & 43 deletions

File tree

‎app/src/features/account/AlertsSettings.tsx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function AlertsSettings() {
1212
const needs = usePref(prefs.notifyNeeds);
1313
const done = usePref(prefs.notifyDone);
1414
const sound = usePref(prefs.sound);
15+
const appSounds = usePref(prefs.appSounds);
1516
const replies = usePref(prefs.markReplies);
1617
const tray = usePref(prefs.tray);
1718
const keep = usePref(prefs.whenClosed) === "keep";
@@ -40,6 +41,12 @@ export function AlertsSettings() {
4041
/>
4142
)}
4243
<Toggle label={s.sound} hint={s.soundHint} checked={sound} onChange={prefs.sound.set} />
44+
<Toggle
45+
label={s.appSounds}
46+
hint={s.appSoundsHint}
47+
checked={appSounds}
48+
onChange={prefs.appSounds.set}
49+
/>
4350
{!nearClock && <p className="text-muted text-xs leading-relaxed">{s.alertsNeedTray}</p>}
4451
</Section>
4552
);

‎app/src/features/chat/ChatComposer.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { errorText } from "../../lib/api";
2020
import { fileSize } from "../../lib/format";
2121
import { type Bot, FIELD_LIMITS } from "../../lib/protocol.gen";
2222
import { prefs, usePref } from "../../shell/prefs";
23+
import { playSound } from "../../shell/sounds";
2324
import { useApi } from "../../store/context";
2425
import { ContextMeter } from "./ContextMeter";
2526
import { draftOf, keepDraft } from "./drafts";
@@ -129,6 +130,7 @@ export const ChatComposer = memo(function ChatComposer({
129130
});
130131
setText("");
131132
files.clear();
133+
playSound("sent");
132134
onSent();
133135
} catch (failure) {
134136
files.setError(errorText(failure));

‎app/src/features/chat/ChatView.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { NoticeRow } from "./NoticeRow";
2626
import { chatRows } from "./rows";
2727
import { useChat } from "./useChat";
2828
import { useFiles } from "./useFiles";
29+
import { useSharedSound } from "./useSharedSound";
2930

3031
/** Distance from the bottom that still counts as "reading the latest". */
3132
const STICKY_PX = 80;
@@ -46,6 +47,7 @@ export const ChatView = memo(function ChatView({ bot, stopped }: { bot: Bot; sto
4647
// arrives while they look animates in.
4748
// biome-ignore lint/correctness/useExhaustiveDependencies: a new bot is a new chat
4849
const openedAt = useMemo(() => Date.now(), [bot.id]);
50+
useSharedSound(chat.items, openedAt);
4951
const last = chat.items.at(-1);
5052
/** Changes whenever the end of the chat does. */
5153
const end = `${chat.items.length}/${last?.id}/${last?.updatedAt}/${chat.draft.length}`;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// A soft sound when a bot shares a file in the chat the owner is looking at
2+
// (spec 15.1). Only for what arrives after the chat opened: older items,
3+
// and those loaded from further up, stay quiet.
4+
5+
import { useEffect, useRef } from "react";
6+
import type { ChatItem } from "../../lib/protocol.gen";
7+
import { playSound } from "../../shell/sounds";
8+
import { useHost } from "../../store/context";
9+
import { isSharedFiles } from "./shared";
10+
11+
export function useSharedSound(items: ChatItem[], since: number): void {
12+
const host = useHost();
13+
const played = useRef(new Set<string>());
14+
15+
useEffect(() => {
16+
const fresh = items.filter(
17+
(item) => item.updatedAt > since && !played.current.has(item.id) && isSharedFiles(item),
18+
);
19+
if (fresh.length === 0) {
20+
return;
21+
}
22+
for (const item of fresh) {
23+
played.current.add(item.id);
24+
}
25+
if (host.window.inFront()) {
26+
playSound("file");
27+
}
28+
}, [items, since, host]);
29+
}

‎app/src/i18n/en/account.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ export const account = {
7272
markRepliesHint: "A dot on the Botloft icon in the taskbar while a conversation is unread.",
7373
sound: "Play a sound",
7474
soundHint: "A short sound with each notification, also with Botloft in front.",
75+
appSounds: "App sounds",
76+
appSoundsHint:
77+
"Soft sounds while Botloft is in front: a message sent, a reply or a file in the open chat, a new bot or crew.",
7578
alertsNeedTray:
7679
"With the window closed, notifications only come with Botloft near the clock (in General).",
7780
keepAwake: "Keep the computer awake while bots work",

‎app/src/i18n/es/account.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export const account: Messages["account"] = {
7373
"Un punto en el icono de Botloft en la barra de tareas mientras haya una conversación sin leer.",
7474
sound: "Reproducir un sonido",
7575
soundHint: "Un sonido corto con cada aviso, también con Botloft al frente.",
76+
appSounds: "Sonidos de la app",
77+
appSoundsHint:
78+
"Sonidos suaves con Botloft al frente: mensaje enviado, respuesta o archivo en el chat abierto, bot o equipo nuevo.",
7679
alertsNeedTray:
7780
"Con la ventana cerrada, los avisos solo llegan con Botloft junto al reloj (en General).",
7881
keepAwake: "Mantener el equipo despierto mientras los bots trabajan",

‎app/src/i18n/pt-BR/account.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export const account: Messages["account"] = {
7373
"Um ponto no ícone do Botloft na barra de tarefas enquanto houver conversa que você não leu.",
7474
sound: "Tocar um som",
7575
soundHint: "Um som curto com cada aviso, também com o Botloft na frente.",
76+
appSounds: "Sons do app",
77+
appSoundsHint:
78+
"Sons suaves com o Botloft na frente: mensagem enviada, resposta ou arquivo no chat aberto, bot ou equipe nova.",
7679
alertsNeedTray:
7780
"Com a janela fechada, os avisos só chegam com o Botloft perto do relógio (em Geral).",
7881
keepAwake: "Não deixar o computador dormir enquanto os bots trabalham",

‎app/src/shell/Workspace.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useAttentionMark } from "./attention";
2121
import { useFolderNotices } from "./folders";
2222
import { SidebarSlot, SidebarToggle } from "./sidebarToggle";
2323
import { useOpenAtSignIn } from "./signIn";
24+
import { useAppSounds } from "./sounds";
2425
import { TitleBar } from "./TitleBar";
2526
import { useTray } from "./tray";
2627

@@ -34,6 +35,7 @@ export function Workspace() {
3435
useAttentionMark();
3536
useTray();
3637
useBotAlerts();
38+
useAppSounds();
3739
useFolderNotices();
3840
useOpenAtSignIn();
3941

‎app/src/shell/alerts.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { t } from "../i18n";
99
import type { Bot, BotId, BotState } from "../lib/protocol.gen";
1010
import { type AppState, activityOf } from "../store/app";
1111
import { useAppStore, useHost } from "../store/context";
12-
import { chime } from "./chime";
1312
import { prefs } from "./prefs";
13+
import { playSound } from "./sounds";
1414

1515
/** How long after a notification opening Botloft goes to its bot. */
1616
const FOLLOW_MS = 10 * 60 * 1000;
@@ -74,9 +74,7 @@ export function useBotAlerts(): void {
7474
}
7575
const sound = prefs.sound.get();
7676
if (host.window.inFront()) {
77-
if (sound) {
78-
chime(kind === "done" ? "done" : "needs");
79-
}
77+
playSound(kind === "done" ? "done" : "needs");
8078
continue;
8179
}
8280
last = { bot: bot.id, at: Date.now() };

‎app/src/shell/chime.ts‎

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)