diff --git a/web/public/static/sounds/beep.mp3 b/web/public/static/sounds/beep.mp3
new file mode 100644
index 0000000..d02e210
Binary files /dev/null and b/web/public/static/sounds/beep.mp3 differ
diff --git a/web/public/static/sounds/juntos.mp3 b/web/public/static/sounds/juntos.mp3
new file mode 100644
index 0000000..aeadbb8
Binary files /dev/null and b/web/public/static/sounds/juntos.mp3 differ
diff --git a/web/public/static/sounds/mixkit-correct-answer-tone.mp3 b/web/public/static/sounds/mixkit-correct-answer-tone.mp3
new file mode 100644
index 0000000..cdfd445
Binary files /dev/null and b/web/public/static/sounds/mixkit-correct-answer-tone.mp3 differ
diff --git a/web/public/static/sounds/mixkit-long-pop.mp3 b/web/public/static/sounds/mixkit-long-pop.mp3
new file mode 100644
index 0000000..e650bb2
Binary files /dev/null and b/web/public/static/sounds/mixkit-long-pop.mp3 differ
diff --git a/web/public/static/sounds/mixkit-message-pop-alert.mp3 b/web/public/static/sounds/mixkit-message-pop-alert.mp3
new file mode 100644
index 0000000..d8a83b7
Binary files /dev/null and b/web/public/static/sounds/mixkit-message-pop-alert.mp3 differ
diff --git a/web/public/static/sounds/mixkit-software-interface-start.mp3 b/web/public/static/sounds/mixkit-software-interface-start.mp3
new file mode 100644
index 0000000..759057b
Binary files /dev/null and b/web/public/static/sounds/mixkit-software-interface-start.mp3 differ
diff --git a/web/public/static/sounds/pristine.mp3 b/web/public/static/sounds/pristine.mp3
new file mode 100644
index 0000000..ed3e308
Binary files /dev/null and b/web/public/static/sounds/pristine.mp3 differ
diff --git a/web/src/app/NotificationManager.js b/web/src/app/Notifier.js
similarity index 69%
rename from web/src/app/NotificationManager.js
rename to web/src/app/Notifier.js
index 8834d2e..5db6f48 100644
--- a/web/src/app/NotificationManager.js
+++ b/web/src/app/Notifier.js
@@ -1,8 +1,8 @@
-import {formatMessage, formatTitleWithDefault, openUrl, topicShortUrl} from "./utils";
+import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils";
import prefs from "./Prefs";
import subscriptionManager from "./SubscriptionManager";
-class NotificationManager {
+class Notifier {
async notify(subscriptionId, notification, onClickFallback) {
const subscription = await subscriptionManager.get(subscriptionId);
const shouldNotify = await this.shouldNotify(subscription, notification);
@@ -13,7 +13,8 @@ class NotificationManager {
const message = formatMessage(notification);
const title = formatTitleWithDefault(notification, shortUrl);
- console.log(`[NotificationManager, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
+ // Show notification
+ console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
const n = new Notification(title, {
body: message,
icon: '/static/img/favicon.png'
@@ -23,6 +24,17 @@ class NotificationManager {
} else {
n.onclick = onClickFallback;
}
+
+ // Play sound
+ const sound = await prefs.sound();
+ if (sound && sound !== "none") {
+ try {
+ await playSound(sound);
+ } catch (e) {
+ console.log(`[Notifier, ${shortUrl}] Error playing audio`, e);
+ // FIXME show no sound allowed popup
+ }
+ }
}
granted() {
@@ -48,5 +60,5 @@ class NotificationManager {
}
}
-const notificationManager = new NotificationManager();
-export default notificationManager;
+const notifier = new Notifier();
+export default notifier;
diff --git a/web/src/app/Prefs.js b/web/src/app/Prefs.js
index 359fbf6..6acc8f9 100644
--- a/web/src/app/Prefs.js
+++ b/web/src/app/Prefs.js
@@ -1,13 +1,13 @@
import db from "./db";
class Prefs {
- async setSelectedSubscriptionId(selectedSubscriptionId) {
- db.prefs.put({key: 'selectedSubscriptionId', value: selectedSubscriptionId});
+ async setSound(sound) {
+ db.prefs.put({key: 'sound', value: sound.toString()});
}
- async selectedSubscriptionId() {
- const selectedSubscriptionId = await db.prefs.get('selectedSubscriptionId');
- return (selectedSubscriptionId) ? selectedSubscriptionId.value : "";
+ async sound() {
+ const sound = await db.prefs.get('sound');
+ return (sound) ? sound.value : "mixkit-correct-answer-tone";
}
async setMinPriority(minPriority) {
diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js
index 17edce4..a64c3bd 100644
--- a/web/src/app/SubscriptionManager.js
+++ b/web/src/app/SubscriptionManager.js
@@ -41,7 +41,7 @@ class SubscriptionManager {
if (exists) {
return false;
}
- await db.notifications.add({ ...notification, subscriptionId });
+ await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
await db.subscriptions.update(subscriptionId, {
last: notification.id
});
diff --git a/web/src/app/utils.js b/web/src/app/utils.js
index c047ff7..c9b5b4c 100644
--- a/web/src/app/utils.js
+++ b/web/src/app/utils.js
@@ -121,6 +121,11 @@ export const subscriptionRoute = (subscription) => {
return `/${subscription.topic}`;
}
+export const playSound = async (sound) => {
+ const audio = new Audio(`/static/sounds/${sound}.mp3`);
+ return audio.play();
+};
+
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
export async function* fetchLinesIterator(fileURL, headers) {
const utf8Decoder = new TextDecoder('utf-8');
diff --git a/web/src/components/App.js b/web/src/components/App.js
index 182df34..6db04aa 100644
--- a/web/src/components/App.js
+++ b/web/src/components/App.js
@@ -9,7 +9,7 @@ import theme from "./theme";
import connectionManager from "../app/ConnectionManager";
import Navigation from "./Navigation";
import ActionBar from "./ActionBar";
-import notificationManager from "../app/NotificationManager";
+import notifier from "../app/Notifier";
import NoTopics from "./NoTopics";
import Preferences from "./Preferences";
import {useLiveQuery} from "dexie-react-hooks";
@@ -26,6 +26,11 @@ import {subscriptionRoute} from "../app/utils";
// TODO sound
// TODO "copy url" toast
// TODO "copy link url" button
+// TODO races when two tabs are open
+// TODO sound mentions
+// https://notificationsounds.com/message-tones/pristine-609
+// https://notificationsounds.com/message-tones/juntos-607
+// https://notificationsounds.com/notification-sounds/beep-472
const App = () => {
return (
@@ -40,7 +45,7 @@ const App = () => {
const Root = () => {
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
- const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
+ const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
const navigate = useNavigate();
const location = useLocation();
const users = useLiveQuery(() => userManager.all());
@@ -54,7 +59,7 @@ const Root = () => {
};
const handleRequestPermission = () => {
- notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted));
+ notifier.maybeRequestPermission(granted => setNotificationsGranted(granted));
};
useEffect(() => {
@@ -68,7 +73,7 @@ const Root = () => {
const added = await subscriptionManager.addNotification(subscriptionId, notification);
if (added) {
const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
- await notificationManager.notify(subscriptionId, notification, defaultClickAction)
+ await notifier.notify(subscriptionId, notification, defaultClickAction)
}
} catch (e) {
console.error(`[App] Error handling notification`, e);
diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js
index 6f5ac0e..a9b4e87 100644
--- a/web/src/components/Preferences.js
+++ b/web/src/components/Preferences.js
@@ -19,6 +19,7 @@ import {Paragraph} from "./styles";
import EditIcon from '@mui/icons-material/Edit';
import CloseIcon from "@mui/icons-material/Close";
import IconButton from "@mui/material/IconButton";
+import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import Container from "@mui/material/Container";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
@@ -31,6 +32,7 @@ import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import userManager from "../app/UserManager";
+import {playSound} from "../app/utils";
const Preferences = () => {
return (
@@ -50,6 +52,7 @@ const Notifications = () => {
Notifications
+
@@ -57,8 +60,40 @@ const Notifications = () => {
);
};
+
+const Sound = () => {
+ const sound = useLiveQuery(async () => prefs.sound());
+ const handleChange = async (ev) => {
+ await prefs.setSound(ev.target.value);
+ }
+ if (!sound) {
+ return null; // While loading
+ }
+ return (
+
+
+
+
+
+
playSound(sound)} disabled={sound === "none"}>
+
+
+
+
+ )
+};
+
const MinPriority = () => {
- const minPriority = useLiveQuery(() => prefs.minPriority());
+ const minPriority = useLiveQuery(async () => prefs.minPriority());
const handleChange = async (ev) => {
await prefs.setMinPriority(ev.target.value);
}