forked from Shiloh/remnantchat
* feat: [#6] show notification when message is received * feat: [#6] add setting for enabling/disabling notifications * refactor: [#6] decouple PeerNameDisplay from funAnimalName * feat: [#6] disable notifications setting when notifications are unavailable
This commit is contained in:
parent
492cfa58ce
commit
b4decae69c
@ -55,6 +55,7 @@ test('persists user settings if none were already persisted', async () => {
|
||||
colorMode: 'dark',
|
||||
userId: 'abc123',
|
||||
playSoundOnNewMessage: true,
|
||||
showNotificationOnNewMessage: true,
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -34,6 +34,7 @@ function Bootstrap({
|
||||
userId: getUuid(),
|
||||
colorMode: 'dark',
|
||||
playSoundOnNewMessage: true,
|
||||
showNotificationOnNewMessage: true,
|
||||
})
|
||||
const { userId } = userSettings
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Typography, { TypographyProps } from '@mui/material/Typography'
|
||||
import { funAnimalName } from 'fun-animal-names'
|
||||
|
||||
import { getPeerName } from './getPeerName'
|
||||
|
||||
interface PeerNameDisplayProps extends TypographyProps {
|
||||
children: string
|
||||
@ -11,7 +12,7 @@ export const PeerNameDisplay = ({
|
||||
}: PeerNameDisplayProps) => {
|
||||
return (
|
||||
<Typography component="span" {...rest}>
|
||||
{funAnimalName(children)}
|
||||
{getPeerName(children)}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
5
src/components/PeerNameDisplay/getPeerName.ts
Normal file
5
src/components/PeerNameDisplay/getPeerName.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { funAnimalName } from 'fun-animal-names'
|
||||
|
||||
export const getPeerName = (peerId: string) => {
|
||||
return funAnimalName(peerId)
|
||||
}
|
@ -1 +1,2 @@
|
||||
export * from './PeerNameDisplay'
|
||||
export * from './getPeerName'
|
||||
|
@ -12,6 +12,8 @@ import { PeerActions } from 'models/network'
|
||||
import { UnsentMessage, ReceivedMessage } from 'models/chat'
|
||||
import { MessageForm } from 'components/MessageForm'
|
||||
import { ChatTranscript } from 'components/ChatTranscript'
|
||||
import { getPeerName } from 'components/PeerNameDisplay'
|
||||
import { NotificationService } from 'services/Notification'
|
||||
|
||||
export interface RoomProps {
|
||||
appId?: string
|
||||
@ -45,6 +47,7 @@ export function Room({
|
||||
roomId
|
||||
)
|
||||
|
||||
// TODO: Move audio logic to a service
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
try {
|
||||
@ -117,9 +120,19 @@ export function Room({
|
||||
|
||||
receiveMessage(message => {
|
||||
const userSettings = settingsContext.getUserSettings()
|
||||
!shellContext.tabHasFocus &&
|
||||
userSettings.playSoundOnNewMessage &&
|
||||
playNewMessageSound()
|
||||
|
||||
if (!shellContext.tabHasFocus) {
|
||||
if (userSettings.playSoundOnNewMessage) {
|
||||
playNewMessageSound()
|
||||
}
|
||||
|
||||
if (userSettings.showNotificationOnNewMessage) {
|
||||
NotificationService.showNotification(
|
||||
`${getPeerName(message.authorId)}: ${message.text}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setMessageLog([...messageLog, { ...message, timeReceived: Date.now() }])
|
||||
})
|
||||
|
||||
|
@ -13,5 +13,6 @@ export const SettingsContext = createContext<SettingsContextProps>({
|
||||
userId: '',
|
||||
colorMode: 'dark',
|
||||
playSoundOnNewMessage: true,
|
||||
showNotificationOnNewMessage: true,
|
||||
}),
|
||||
})
|
||||
|
@ -2,4 +2,5 @@ export interface UserSettings {
|
||||
colorMode: 'dark' | 'light'
|
||||
userId: string
|
||||
playSoundOnNewMessage: boolean
|
||||
showNotificationOnNewMessage: boolean
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Room } from 'components/Room'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { NotificationService } from 'services/Notification'
|
||||
|
||||
interface PublicRoomProps {
|
||||
userId: string
|
||||
@ -12,6 +13,10 @@ export function PublicRoom({ userId }: PublicRoomProps) {
|
||||
const { roomId = '' } = useParams()
|
||||
const { setTitle } = useContext(ShellContext)
|
||||
|
||||
useEffect(() => {
|
||||
NotificationService.requestPermission()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(`Room: ${roomId}`)
|
||||
}, [roomId, setTitle])
|
||||
|
@ -3,8 +3,11 @@ import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import { Switch } from '@mui/material'
|
||||
import Switch from '@mui/material/Switch'
|
||||
import FormGroup from '@mui/material/FormGroup'
|
||||
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||
|
||||
import { NotificationService } from 'services/Notification'
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { StorageContext } from 'contexts/StorageContext'
|
||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||
@ -24,19 +27,38 @@ export const Settings = ({ userId }: SettingsProps) => {
|
||||
isDeleteSettingsConfirmDiaglogOpen,
|
||||
setIsDeleteSettingsConfirmDiaglogOpen,
|
||||
] = useState(false)
|
||||
const { playSoundOnNewMessage } = getUserSettings()
|
||||
const [, setIsNotificationPermissionDetermined] = useState(false)
|
||||
const { playSoundOnNewMessage, showNotificationOnNewMessage } =
|
||||
getUserSettings()
|
||||
|
||||
const persistedStorage = getPersistedStorage()
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
await NotificationService.requestPermission()
|
||||
|
||||
// This state needs to be set to cause a rerender so that
|
||||
// areNotificationsAvailable is up-to-date.
|
||||
setIsNotificationPermissionDetermined(true)
|
||||
})()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('Settings')
|
||||
}, [setTitle])
|
||||
|
||||
const handlePlaySoundOnNewMessageChange = (
|
||||
_event: ChangeEvent,
|
||||
value: boolean
|
||||
playSoundOnNewMessage: boolean
|
||||
) => {
|
||||
updateUserSettings({ playSoundOnNewMessage: value })
|
||||
updateUserSettings({ playSoundOnNewMessage })
|
||||
}
|
||||
|
||||
const handleShowNotificationOnNewMessageChange = (
|
||||
_event: ChangeEvent,
|
||||
showNotificationOnNewMessage: boolean
|
||||
) => {
|
||||
updateUserSettings({ showNotificationOnNewMessage })
|
||||
}
|
||||
|
||||
const handleDeleteSettingsClick = () => {
|
||||
@ -52,6 +74,8 @@ export const Settings = ({ userId }: SettingsProps) => {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const areNotificationsAvailable = NotificationService.permission === 'granted'
|
||||
|
||||
return (
|
||||
<Box className="max-w-3xl mx-auto p-4">
|
||||
<Typography
|
||||
@ -64,11 +88,30 @@ export const Settings = ({ userId }: SettingsProps) => {
|
||||
>
|
||||
Chat
|
||||
</Typography>
|
||||
<Switch
|
||||
checked={playSoundOnNewMessage}
|
||||
onChange={handlePlaySoundOnNewMessageChange}
|
||||
/>{' '}
|
||||
Play a sound when a new message is received
|
||||
<Typography>When a message is received in the background:</Typography>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={playSoundOnNewMessage}
|
||||
onChange={handlePlaySoundOnNewMessageChange}
|
||||
/>
|
||||
}
|
||||
label="Play a sound"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
areNotificationsAvailable && showNotificationOnNewMessage
|
||||
}
|
||||
onChange={handleShowNotificationOnNewMessageChange}
|
||||
disabled={!areNotificationsAvailable}
|
||||
/>
|
||||
}
|
||||
label="Show a notification"
|
||||
/>
|
||||
</FormGroup>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography
|
||||
variant="h2"
|
||||
|
18
src/services/Notification/Notification.tsx
Normal file
18
src/services/Notification/Notification.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export class NotificationService {
|
||||
static permission: NotificationPermission
|
||||
|
||||
static requestPermission = async () => {
|
||||
if (NotificationService.permission === 'granted') return
|
||||
|
||||
NotificationService.permission = await Notification.requestPermission()
|
||||
}
|
||||
|
||||
static showNotification = (
|
||||
message: string,
|
||||
options?: NotificationOptions
|
||||
) => {
|
||||
if (NotificationService.permission !== 'granted') return
|
||||
|
||||
new Notification(message, options)
|
||||
}
|
||||
}
|
1
src/services/Notification/index.ts
Normal file
1
src/services/Notification/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Notification'
|
Loading…
Reference in New Issue
Block a user