Skip to content

Commit

Permalink
[MM-57486] [MM-58008] Calls: Mobile ringing for incoming calls (#7984)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Jul 3, 2024
1 parent 331d313 commit 92bdb28
Show file tree
Hide file tree
Showing 35 changed files with 884 additions and 24 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,7 @@ launch.json
.metro-health-check*

libraries/**/**/build
libraries/**/**/.build
libraries/**/**/.build

# Android sounds
android/app/src/main/res/raw/*
16 changes: 16 additions & 0 deletions app/components/user_list/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
"locale": "",
"nickname": "",
"notify_props": {
"calls_desktop_sound": "true",
"calls_mobile_notification_sound": "",
"calls_mobile_sound": "",
"calls_notification_sound": "Calm",
"channel": "true",
"comments": "never",
"desktop": "mention",
Expand Down Expand Up @@ -584,6 +588,10 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
"locale": "",
"nickname": "",
"notify_props": {
"calls_desktop_sound": "true",
"calls_mobile_notification_sound": "",
"calls_mobile_sound": "",
"calls_notification_sound": "Calm",
"channel": "true",
"comments": "never",
"desktop": "mention",
Expand Down Expand Up @@ -899,6 +907,10 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
"locale": "",
"nickname": "",
"notify_props": {
"calls_desktop_sound": "true",
"calls_mobile_notification_sound": "",
"calls_mobile_sound": "",
"calls_notification_sound": "Calm",
"channel": "true",
"comments": "never",
"desktop": "mention",
Expand Down Expand Up @@ -933,6 +945,10 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
"locale": "",
"nickname": "",
"notify_props": {
"calls_desktop_sound": "true",
"calls_mobile_notification_sound": "",
"calls_mobile_sound": "",
"calls_notification_sound": "Calm",
"channel": "true",
"comments": "never",
"desktop": "mention",
Expand Down
9 changes: 9 additions & 0 deletions app/components/user_list/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React from 'react';
import {Image} from 'react-native';

import {Ringtone} from '@constants/calls';
import {renderWithEverything} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';

Expand Down Expand Up @@ -51,6 +52,10 @@ describe('components/channel_list_row', () => {
highlight_keys: '',
push: 'mention',
push_status: 'away',
calls_desktop_sound: 'true',
calls_mobile_sound: '',
calls_notification_sound: Ringtone.Calm,
calls_mobile_notification_sound: '',
},
};

Expand Down Expand Up @@ -80,6 +85,10 @@ describe('components/channel_list_row', () => {
highlight_keys: '',
push: 'mention',
push_status: 'away',
calls_desktop_sound: 'true',
calls_mobile_sound: '',
calls_notification_sound: Ringtone.Calm,
calls_mobile_notification_sound: '',
},
};

Expand Down
16 changes: 16 additions & 0 deletions app/constants/calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ const REACTION_TIMEOUT = 10000;
const REACTION_LIMIT = 20;
const CALL_QUALITY_RESET_MS = toMilliseconds({minutes: 1});
const CAPTION_TIMEOUT = 5000;
const RING_LENGTH = 30000;

export enum Ringtone {
Calm = 'Calm',
Dynamic = 'Dynamic',
Urgent = 'Urgent',
Cheerful = 'Cheerful',
}

const RINGTONE_DEFAULT = Ringtone.Calm;

// 30 seconds of vibration (there is no loop setting)
const RINGTONE_VIBRATE_PATTERN = [1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 1000, 500, 1000, 500, 1000];

export enum MessageBarType {
Microphone,
Expand All @@ -57,4 +70,7 @@ export default {
JOB_TYPE_RECORDING,
JOB_TYPE_TRANSCRIBING,
JOB_TYPE_CAPTIONING,
RING_LENGTH,
RINGTONE_DEFAULT,
RINGTONE_VIBRATE_PATTERN,
};
2 changes: 2 additions & 0 deletions app/constants/screens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const SETTINGS_NOTIFICATION_AUTO_RESPONDER = 'SettingsNotificationAutoRes
export const SETTINGS_NOTIFICATION_EMAIL = 'SettingsNotificationEmail';
export const SETTINGS_NOTIFICATION_MENTION = 'SettingsNotificationMention';
export const SETTINGS_NOTIFICATION_PUSH = 'SettingsNotificationPush';
export const SETTINGS_NOTIFICATION_CALL = 'SettingsNotificationCall';
export const SHARE_FEEDBACK = 'ShareFeedback';
export const SNACK_BAR = 'SnackBar';
export const SSO = 'SSO';
Expand Down Expand Up @@ -139,6 +140,7 @@ export default {
SETTINGS_NOTIFICATION_EMAIL,
SETTINGS_NOTIFICATION_MENTION,
SETTINGS_NOTIFICATION_PUSH,
SETTINGS_NOTIFICATION_CALL,
SHARE_FEEDBACK,
SNACK_BAR,
SSO,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {Ringtone} from '@constants/calls';
import DatabaseManager from '@database/manager';
import {buildPreferenceKey} from '@database/operator/server_data_operator/comparators';
import {shouldUpdateUserRecord} from '@database/operator/server_data_operator/comparators/user';
Expand Down Expand Up @@ -82,6 +83,10 @@ describe('*** Operator: User Handlers tests ***', () => {
comments: 'never',
desktop_notification_sound: 'Hello',
push_status: 'online',
calls_desktop_sound: 'true',
calls_mobile_sound: '',
calls_notification_sound: Ringtone.Calm,
calls_mobile_notification_sound: '',
},
last_picture_update: 1604686302260,
locale: 'en',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {Ringtone} from '@constants/calls';
import {OperationType} from '@constants/database';
import {transformPreferenceRecord, transformUserRecord} from '@database/operator/server_data_operator/transformers/user';
import {createTestConnection} from '@database/operator/utils/create_test_connection';
Expand Down Expand Up @@ -66,6 +67,10 @@ describe('*** USER Prepare Records Test ***', () => {
comments: 'never',
desktop_notification_sound: 'Hello',
push_status: 'online',
calls_desktop_sound: 'true',
calls_mobile_sound: '',
calls_notification_sound: Ringtone.Calm,
calls_mobile_notification_sound: '',
},
last_picture_update: 1604686302260,
locale: 'en',
Expand Down
2 changes: 2 additions & 0 deletions app/init/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {CallsManager} from '@calls/calls_manager';
import DatabaseManager from '@database/manager';
import {getAllServerCredentials} from '@init/credentials';
import {initialLaunch} from '@init/launch';
Expand Down Expand Up @@ -47,6 +48,7 @@ export async function initialize() {
GlobalEventHandler.init();
ManagedApp.init();
SessionManager.init();
CallsManager.initialize();
}
}

Expand Down
5 changes: 4 additions & 1 deletion app/init/push_notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,10 @@ class PushNotifications {

this.processNotification(notification);
}
completion({alert: false, sound: true, badge: true});

// Always play a sound, except when this is a foreground notification about a call
const sound = !(notification.foreground && isCallsStartedMessage(notification.payload));
completion({alert: false, sound, badge: true});
};

onRemoteNotificationsRegistered = async (event: Registered) => {
Expand Down
23 changes: 23 additions & 0 deletions app/products/calls/calls_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {AppState, Platform} from 'react-native';

import {callsOnAppStateChange} from '@calls/state';

const initialize = () => {
if (Platform.OS === 'android') {
AppState.addEventListener('blur', () => {
callsOnAppStateChange('inactive');
});
AppState.addEventListener('focus', () => {
callsOnAppStateChange('active');
});
} else {
AppState.addEventListener('change', callsOnAppStateChange);
}
};

export const CallsManager = {
initialize,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Pressable, Text, View} from 'react-native';
import {switchToChannelById} from '@actions/remote/channel';
import {fetchProfilesInChannel} from '@actions/remote/user';
import {dismissIncomingCall} from '@calls/actions/calls';
import {removeIncomingCall} from '@calls/state';
import {playIncomingCallsRinging, removeIncomingCall} from '@calls/state';
import {ChannelType, type IncomingCallNotification} from '@calls/types/calls';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
Expand All @@ -17,6 +17,7 @@ import {CALL_NOTIFICATION_BAR_HEIGHT} from '@constants/view';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import DatabaseManager from '@database/manager';
import {useAppState} from '@hooks/device';
import WebsocketManager from '@managers/websocket_manager';
import {getServerDisplayName} from '@queries/app/servers';
import ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
Expand Down Expand Up @@ -107,7 +108,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
type Props = {
servers: ServersModel[];
incomingCall: IncomingCallNotification;
currentUserId: string;
currentUserId?: string;
userStatus?: string;
teammateNameDisplay: string;
members?: ChannelMembershipModel[];
onCallsScreen?: boolean;
Expand All @@ -117,12 +119,14 @@ export const CallNotification = ({
servers,
incomingCall,
currentUserId,
userStatus,
teammateNameDisplay,
members,
onCallsScreen,
}: Props) => {
const intl = useIntl();
const serverUrl = useServerUrl();
const appState = useAppState();
const theme = useTheme();
const style = getStyleSheet(theme);
const [serverName, setServerName] = useState('');
Expand All @@ -135,6 +139,10 @@ export const CallNotification = ({
}
}, []);

useEffect(() => {
playIncomingCallsRinging(incomingCall.serverUrl, incomingCall.callID, userStatus || '');
}, [incomingCall.serverUrl, incomingCall.callID, appState]);

// We only need to getServerDisplayName once
useEffect(() => {
async function getName() {
Expand Down
15 changes: 11 additions & 4 deletions app/products/calls/components/call_notification/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {observeAllActiveServers} from '@app/queries/app/servers';
import {CallNotification} from '@calls/components/call_notification/call_notification';
import DatabaseManager from '@database/manager';
import {observeChannelMembers} from '@queries/servers/channel';
import {observeCurrentUserId} from '@queries/servers/system';
import {observeTeammateNameDisplay} from '@queries/servers/user';
import {observeCurrentUser, observeTeammateNameDisplay} from '@queries/servers/user';

import type {IncomingCallNotification} from '@calls/types/calls';

Expand All @@ -20,8 +19,15 @@ type OwnProps = {

const enhanced = withObservables(['incomingCall'], ({incomingCall}: OwnProps) => {
const database = of$(DatabaseManager.serverDatabases[incomingCall.serverUrl]?.database);
const currentUserId = database.pipe(
switchMap((db) => (db ? observeCurrentUserId(db) : of$(''))),
const currentUser = database.pipe(
switchMap((db) => (db ? observeCurrentUser(db) : of$(null))),
);
const currentUserId = currentUser.pipe(
switchMap((u) => of$(u?.id)),
distinctUntilChanged(),
);
const userStatus = currentUser.pipe(
switchMap((u) => of$(u?.status)),
distinctUntilChanged(),
);
const teammateNameDisplay = database.pipe(
Expand All @@ -36,6 +42,7 @@ const enhanced = withObservables(['incomingCall'], ({incomingCall}: OwnProps) =>
return {
servers: observeAllActiveServers(),
currentUserId,
userStatus,
teammateNameDisplay,
members,
};
Expand Down
7 changes: 5 additions & 2 deletions app/products/calls/state/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ import {
DefaultGlobalCallsState,
DefaultIncomingCalls,
type GlobalCallsState,
type IncomingCalls,
} from '@calls/types/calls';
import {License} from '@constants';
import Calls from '@constants/calls';
import DatabaseManager from '@database/manager';

import type {CallJobState, LiveCaptionData} from '@mattermost/calls/lib/types';
import type UserModel from '@typings/database/models/servers/user';

jest.mock('@calls/alerts');

Expand Down Expand Up @@ -1339,11 +1341,12 @@ describe('useCallsState', () => {
};
const initialCurrentCallState: CurrentCall | null = null;
const initialIncomingCalls = DefaultIncomingCalls;
const expectedIncomingCalls = {
const expectedIncomingCalls: IncomingCalls = {
...DefaultIncomingCalls,
incomingCalls: [{
callID: 'callDM',
callerID: 'user-5',
callerModel: {username: 'user-5'},
callerModel: {username: 'user-5'} as UserModel,
channelID: 'channel-private',
myUserId: 'myId',
serverUrl: 'server1',
Expand Down
Loading

0 comments on commit 92bdb28

Please sign in to comment.