mattermost-mobile/app/screens/settings/notification_call/notification_call.tsx
Christopher Poile 92bdb2847b
[MM-57486] [MM-58008] Calls: Mobile ringing for incoming calls (#7984)
* notification ringing, settings screen, native code patch, ringing mp3s

* i18n

* play preview on first press

* prevent playing from background (only affects Android) to match iOS beh

* stop ringing/vibration on entering background

* ring when coming back from background and new incoming call is present

* no push notification sound when it's a call; improve ringing

* move sounds to asset folder; copy on postinstall for android bundling

* make Ringtone type a string enum

* make Android ring async + await ring and stop; changes from PR comments

* missing fields after merge

* release lock on an exception

* cancel sample ringing when turning notifications off

* copy sound files for android build

* typo

* update snapshots

* testing if the problem is copying the mp3 files

* fix android mp3 assets when building for non-release

* add sounds to .gitignore

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2024-07-03 10:22:46 -04:00

192 lines
8.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo, useState} from 'react';
import {defineMessages, useIntl} from 'react-intl';
import InCallManager from 'react-native-incall-manager';
import {updateMe} from '@actions/remote/user';
import SettingBlock from '@components/settings/block';
import SettingContainer from '@components/settings/container';
import SettingOption from '@components/settings/option';
import SettingSeparator from '@components/settings/separator';
import {Calls} from '@constants';
import {Ringtone} from '@constants/calls';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import useBackNavigation from '@hooks/navigate_back';
import {popTopScreen} from '@screens/navigation';
import {changeOpacity} from '@utils/theme';
import {getNotificationProps} from '@utils/user';
import type UserModel from '@typings/database/models/servers/user';
import type {AvailableScreens} from '@typings/screens/navigation';
type Props = {
componentId: AvailableScreens;
currentUser?: UserModel;
};
const {footerText} = defineMessages({
footerText: {
id: 'notification_settings.calls.callsInfo',
defaultMessage: 'Note: silent mode must be off to hear the ringtone preview.',
},
});
const NotificationCall = ({componentId, currentUser}: Props) => {
const serverUrl = useServerUrl();
const intl = useIntl();
const theme = useTheme();
const notifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser?.notifyProps]);
const initialCallsMobileSound = useMemo(() => Boolean(notifyProps?.calls_mobile_sound ? notifyProps.calls_mobile_sound === 'true' : notifyProps?.calls_desktop_sound === 'true'),
[/* dependency array should remain empty */]);
const [callsMobileSound, setCallsMobileSound] = useState(initialCallsMobileSound);
const initialCallsMobileNotificationSound = useMemo(() => {
let initialSound = notifyProps?.calls_mobile_notification_sound ? notifyProps.calls_mobile_notification_sound : notifyProps?.calls_notification_sound;
if (!initialSound) {
initialSound = Calls.RINGTONE_DEFAULT;
}
return initialSound;
}, [/* dependency array should remain empty */]);
const [callsMobileNotificationSound, setCallsMobileNotificationSound] = useState(initialCallsMobileNotificationSound);
const [playingRingtone, setPlayingRingtone] = useState(false);
const close = useCallback(() => {
InCallManager.stopRingtone();
popTopScreen(componentId);
}, [componentId]);
const selectOption = useCallback(async (value: string) => {
const tone = 'calls_' + value.toLowerCase();
if (value !== callsMobileNotificationSound) {
setCallsMobileNotificationSound(value);
await InCallManager.stopRingtone();
await InCallManager.startRingtone(tone, Calls.RINGTONE_VIBRATE_PATTERN);
setPlayingRingtone(true);
return;
}
if (playingRingtone) {
await InCallManager.stopRingtone();
setPlayingRingtone(false);
} else {
await InCallManager.startRingtone(tone, Calls.RINGTONE_VIBRATE_PATTERN);
setPlayingRingtone(true);
}
}, [callsMobileNotificationSound, playingRingtone]);
const selectNotificationOnOff = useCallback(async (on: boolean) => {
setCallsMobileSound(on);
if (!on) {
await InCallManager.stopRingtone();
}
}, []);
const canSaveSettings = useCallback(() => {
const cmsString = callsMobileSound ? 'true' : 'false';
const cms = cmsString !== notifyProps.calls_mobile_sound;
const cmns = callsMobileNotificationSound !== notifyProps.calls_mobile_notification_sound;
return cms || cmns;
}, [notifyProps, callsMobileSound, callsMobileNotificationSound]);
const saveNotificationSettings = useCallback(() => {
const canSave = canSaveSettings();
if (canSave) {
const cmsString = callsMobileSound ? 'true' : 'false';
const notify_props: UserNotifyProps = {
...notifyProps,
calls_mobile_sound: cmsString,
calls_mobile_notification_sound: callsMobileNotificationSound,
};
updateMe(serverUrl, {notify_props});
}
close();
}, [serverUrl, canSaveSettings, close, notifyProps, callsMobileSound, callsMobileNotificationSound, playingRingtone]);
useBackNavigation(saveNotificationSettings);
useAndroidHardwareBackHandler(componentId, saveNotificationSettings);
return (
<SettingContainer testID='call_notification_settings'>
<SettingOption
label={intl.formatMessage({
id: 'notification_settings.calls.enable_sound',
defaultMessage: 'Notification sound for incoming calls',
})}
action={selectNotificationOnOff}
testID='notification_settings.calls.enable_sound.option'
type='toggle'
selected={callsMobileSound}
/>
{callsMobileSound && (
<SettingBlock footerText={footerText}>
<SettingOption
action={selectOption}
label={intl.formatMessage({
id: 'notification_settings.calls.dynamic',
defaultMessage: 'Dynamic',
})}
selected={callsMobileNotificationSound === Ringtone.Dynamic}
testID='notification_settings.calls.dynamic.option'
type='select'
value={Ringtone.Dynamic}
icon='volume-high'
iconColor={changeOpacity(theme.centerChannelColor, 0.56)}
/>
<SettingSeparator/>
<SettingOption
action={selectOption}
label={intl.formatMessage({
id: 'notification_settings.calls.calm',
defaultMessage: 'Calm',
})}
selected={callsMobileNotificationSound === Ringtone.Calm}
testID='notification_settings.calls.calm.option'
type='select'
value={Ringtone.Calm}
icon='volume-high'
iconColor={changeOpacity(theme.centerChannelColor, 0.56)}
/>
<SettingSeparator/>
<SettingOption
action={selectOption}
label={intl.formatMessage({
id: 'notification_settings.calls.urgent',
defaultMessage: 'Urgent',
})}
selected={callsMobileNotificationSound === Ringtone.Urgent}
testID='notification_settings.calls.urgent.option'
type='select'
value={Ringtone.Urgent}
icon='volume-high'
iconColor={changeOpacity(theme.centerChannelColor, 0.56)}
/>
<SettingSeparator/>
<SettingOption
action={selectOption}
label={intl.formatMessage({
id: 'notification_settings.calls.cheerful',
defaultMessage: 'Cheerful',
})}
selected={callsMobileNotificationSound === Ringtone.Cheerful}
testID='notification_settings.calls.cheerful.option'
type='select'
value={Ringtone.Cheerful}
icon='volume-high'
iconColor={changeOpacity(theme.centerChannelColor, 0.56)}
/>
<SettingSeparator/>
</SettingBlock>
)}
</SettingContainer>
);
};
export default NotificationCall;