Support sounds
parent
09b128f27a
commit
dc7ca6e405
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,8 +1,8 @@
|
||||||
import {formatMessage, formatTitleWithDefault, openUrl, topicShortUrl} from "./utils";
|
import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils";
|
||||||
import prefs from "./Prefs";
|
import prefs from "./Prefs";
|
||||||
import subscriptionManager from "./SubscriptionManager";
|
import subscriptionManager from "./SubscriptionManager";
|
||||||
|
|
||||||
class NotificationManager {
|
class Notifier {
|
||||||
async notify(subscriptionId, notification, onClickFallback) {
|
async notify(subscriptionId, notification, onClickFallback) {
|
||||||
const subscription = await subscriptionManager.get(subscriptionId);
|
const subscription = await subscriptionManager.get(subscriptionId);
|
||||||
const shouldNotify = await this.shouldNotify(subscription, notification);
|
const shouldNotify = await this.shouldNotify(subscription, notification);
|
||||||
|
@ -13,7 +13,8 @@ class NotificationManager {
|
||||||
const message = formatMessage(notification);
|
const message = formatMessage(notification);
|
||||||
const title = formatTitleWithDefault(notification, shortUrl);
|
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, {
|
const n = new Notification(title, {
|
||||||
body: message,
|
body: message,
|
||||||
icon: '/static/img/favicon.png'
|
icon: '/static/img/favicon.png'
|
||||||
|
@ -23,6 +24,17 @@ class NotificationManager {
|
||||||
} else {
|
} else {
|
||||||
n.onclick = onClickFallback;
|
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() {
|
granted() {
|
||||||
|
@ -48,5 +60,5 @@ class NotificationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationManager = new NotificationManager();
|
const notifier = new Notifier();
|
||||||
export default notificationManager;
|
export default notifier;
|
|
@ -1,13 +1,13 @@
|
||||||
import db from "./db";
|
import db from "./db";
|
||||||
|
|
||||||
class Prefs {
|
class Prefs {
|
||||||
async setSelectedSubscriptionId(selectedSubscriptionId) {
|
async setSound(sound) {
|
||||||
db.prefs.put({key: 'selectedSubscriptionId', value: selectedSubscriptionId});
|
db.prefs.put({key: 'sound', value: sound.toString()});
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectedSubscriptionId() {
|
async sound() {
|
||||||
const selectedSubscriptionId = await db.prefs.get('selectedSubscriptionId');
|
const sound = await db.prefs.get('sound');
|
||||||
return (selectedSubscriptionId) ? selectedSubscriptionId.value : "";
|
return (sound) ? sound.value : "mixkit-correct-answer-tone";
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMinPriority(minPriority) {
|
async setMinPriority(minPriority) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SubscriptionManager {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return false;
|
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, {
|
await db.subscriptions.update(subscriptionId, {
|
||||||
last: notification.id
|
last: notification.id
|
||||||
});
|
});
|
||||||
|
|
|
@ -121,6 +121,11 @@ export const subscriptionRoute = (subscription) => {
|
||||||
return `/${subscription.topic}`;
|
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
|
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
||||||
export async function* fetchLinesIterator(fileURL, headers) {
|
export async function* fetchLinesIterator(fileURL, headers) {
|
||||||
const utf8Decoder = new TextDecoder('utf-8');
|
const utf8Decoder = new TextDecoder('utf-8');
|
||||||
|
|
|
@ -9,7 +9,7 @@ import theme from "./theme";
|
||||||
import connectionManager from "../app/ConnectionManager";
|
import connectionManager from "../app/ConnectionManager";
|
||||||
import Navigation from "./Navigation";
|
import Navigation from "./Navigation";
|
||||||
import ActionBar from "./ActionBar";
|
import ActionBar from "./ActionBar";
|
||||||
import notificationManager from "../app/NotificationManager";
|
import notifier from "../app/Notifier";
|
||||||
import NoTopics from "./NoTopics";
|
import NoTopics from "./NoTopics";
|
||||||
import Preferences from "./Preferences";
|
import Preferences from "./Preferences";
|
||||||
import {useLiveQuery} from "dexie-react-hooks";
|
import {useLiveQuery} from "dexie-react-hooks";
|
||||||
|
@ -26,6 +26,11 @@ import {subscriptionRoute} from "../app/utils";
|
||||||
// TODO sound
|
// TODO sound
|
||||||
// TODO "copy url" toast
|
// TODO "copy url" toast
|
||||||
// TODO "copy link url" button
|
// 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 = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -40,7 +45,7 @@ const App = () => {
|
||||||
|
|
||||||
const Root = () => {
|
const Root = () => {
|
||||||
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
||||||
const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
|
const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const users = useLiveQuery(() => userManager.all());
|
const users = useLiveQuery(() => userManager.all());
|
||||||
|
@ -54,7 +59,7 @@ const Root = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRequestPermission = () => {
|
const handleRequestPermission = () => {
|
||||||
notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted));
|
notifier.maybeRequestPermission(granted => setNotificationsGranted(granted));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -68,7 +73,7 @@ const Root = () => {
|
||||||
const added = await subscriptionManager.addNotification(subscriptionId, notification);
|
const added = await subscriptionManager.addNotification(subscriptionId, notification);
|
||||||
if (added) {
|
if (added) {
|
||||||
const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
|
const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
|
||||||
await notificationManager.notify(subscriptionId, notification, defaultClickAction)
|
await notifier.notify(subscriptionId, notification, defaultClickAction)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[App] Error handling notification`, e);
|
console.error(`[App] Error handling notification`, e);
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {Paragraph} from "./styles";
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
@ -31,6 +32,7 @@ import DialogTitle from "@mui/material/DialogTitle";
|
||||||
import DialogContent from "@mui/material/DialogContent";
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogActions from "@mui/material/DialogActions";
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
import userManager from "../app/UserManager";
|
import userManager from "../app/UserManager";
|
||||||
|
import {playSound} from "../app/utils";
|
||||||
|
|
||||||
const Preferences = () => {
|
const Preferences = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -50,6 +52,7 @@ const Notifications = () => {
|
||||||
Notifications
|
Notifications
|
||||||
</Typography>
|
</Typography>
|
||||||
<PrefGroup>
|
<PrefGroup>
|
||||||
|
<Sound/>
|
||||||
<MinPriority/>
|
<MinPriority/>
|
||||||
<DeleteAfter/>
|
<DeleteAfter/>
|
||||||
</PrefGroup>
|
</PrefGroup>
|
||||||
|
@ -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 (
|
||||||
|
<Pref title="Notification sound">
|
||||||
|
<div style={{ display: 'flex', width: '100%' }}>
|
||||||
|
<FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
|
||||||
|
<Select value={sound} onChange={handleChange}>
|
||||||
|
<MenuItem value={"none"}>No sound</MenuItem>
|
||||||
|
<MenuItem value={"mixkit-correct-answer-tone"}>Ding</MenuItem>
|
||||||
|
<MenuItem value={"juntos"}>Juntos</MenuItem>
|
||||||
|
<MenuItem value={"pristine"}>Pristine</MenuItem>
|
||||||
|
<MenuItem value={"mixkit-software-interface-start"}>Dadum</MenuItem>
|
||||||
|
<MenuItem value={"mixkit-message-pop-alert"}>Pop</MenuItem>
|
||||||
|
<MenuItem value={"mixkit-long-pop"}>Pop swoosh</MenuItem>
|
||||||
|
<MenuItem value={"beep"}>Beep</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<IconButton onClick={() => playSound(sound)} disabled={sound === "none"}>
|
||||||
|
<PlayArrowIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Pref>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
const MinPriority = () => {
|
const MinPriority = () => {
|
||||||
const minPriority = useLiveQuery(() => prefs.minPriority());
|
const minPriority = useLiveQuery(async () => prefs.minPriority());
|
||||||
const handleChange = async (ev) => {
|
const handleChange = async (ev) => {
|
||||||
await prefs.setMinPriority(ev.target.value);
|
await prefs.setMinPriority(ev.target.value);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue