When a group call starts, show an OS notification
This commit is contained in:
parent
68b711b360
commit
9aa0de5b6c
3 changed files with 105 additions and 28 deletions
|
@ -41,6 +41,7 @@ import {
|
|||
} from '../state/ducks/calling';
|
||||
import { getConversationCallMode } from '../state/ducks/conversations';
|
||||
import { isConversationTooBigToRing } from '../conversations/isConversationTooBigToRing';
|
||||
import { isMe } from '../util/whatTypeOfConversation';
|
||||
import {
|
||||
AudioDevice,
|
||||
AvailableIODevicesType,
|
||||
|
@ -79,7 +80,11 @@ import { callingMessageToProto } from '../util/callingMessageToProto';
|
|||
import { getSendOptions } from '../util/getSendOptions';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import dataInterface from '../sql/Client';
|
||||
import { notificationService } from './notifications';
|
||||
import {
|
||||
notificationService,
|
||||
NotificationSetting,
|
||||
FALLBACK_NOTIFICATION_TITLE,
|
||||
} from './notifications';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
const {
|
||||
|
@ -2015,6 +2020,58 @@ export class CallingClass {
|
|||
conversation.updateCallHistoryForGroupCall(peekInfo.eraId, creatorUuid);
|
||||
}
|
||||
|
||||
public notifyForGroupCall(
|
||||
conversationId: string,
|
||||
creatorBytes: undefined | Readonly<Uint8Array>
|
||||
): void {
|
||||
const creatorUuid = creatorBytes
|
||||
? arrayBufferToUuid(typedArrayToArrayBuffer(creatorBytes))
|
||||
: undefined;
|
||||
const creatorConversation = window.ConversationController.get(creatorUuid);
|
||||
if (creatorConversation && isMe(creatorConversation.attributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationTitle: string;
|
||||
let notificationMessage: string;
|
||||
|
||||
switch (notificationService.getNotificationSetting()) {
|
||||
case NotificationSetting.Off:
|
||||
return;
|
||||
case NotificationSetting.NoNameOrMessage:
|
||||
notificationTitle = FALLBACK_NOTIFICATION_TITLE;
|
||||
notificationMessage = window.i18n(
|
||||
'calling__call-notification__started-by-someone'
|
||||
);
|
||||
break;
|
||||
default: {
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
// These fallbacks exist just in case something unexpected goes wrong.
|
||||
notificationTitle =
|
||||
conversation?.getTitle() || FALLBACK_NOTIFICATION_TITLE;
|
||||
notificationMessage = creatorConversation
|
||||
? window.i18n('calling__call-notification__started', [
|
||||
creatorConversation.getTitle(),
|
||||
])
|
||||
: window.i18n('calling__call-notification__started-by-someone');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
notificationService.notify({
|
||||
icon: 'images/icons/v2/video-solid-24.svg',
|
||||
message: notificationMessage,
|
||||
onNotificationClick: () => {
|
||||
this.uxActions?.startCallingLobby({
|
||||
conversationId,
|
||||
isVideoCall: true,
|
||||
});
|
||||
},
|
||||
silent: false,
|
||||
title: notificationTitle,
|
||||
});
|
||||
}
|
||||
|
||||
private async cleanExpiredGroupCallRingsAndLoop(): Promise<void> {
|
||||
try {
|
||||
await cleanExpiredGroupCallRings();
|
||||
|
|
|
@ -254,6 +254,28 @@ export const getActiveCall = ({
|
|||
activeCallState &&
|
||||
getOwn(callsByConversation, activeCallState.conversationId);
|
||||
|
||||
// In theory, there could be multiple incoming calls, or an incoming call while there's
|
||||
// an active call. In practice, the UI is not ready for this, and RingRTC doesn't
|
||||
// support it for direct calls.
|
||||
export const getIncomingCall = (
|
||||
callsByConversation: Readonly<CallsByConversationType>,
|
||||
ourUuid: string
|
||||
): undefined | DirectCallStateType | GroupCallStateType =>
|
||||
Object.values(callsByConversation).find(call => {
|
||||
switch (call.callMode) {
|
||||
case CallMode.Direct:
|
||||
return call.isIncoming && call.callState === CallState.Ringing;
|
||||
case CallMode.Group:
|
||||
return (
|
||||
call.ringerUuid &&
|
||||
call.connectionState === GroupCallConnectionState.NotConnected &&
|
||||
isAnybodyElseInGroupCall(call.peekInfo, ourUuid)
|
||||
);
|
||||
default:
|
||||
throw missingCaseError(call);
|
||||
}
|
||||
});
|
||||
|
||||
export const isAnybodyElseInGroupCall = (
|
||||
{ uuids }: Readonly<GroupCallPeekInfoType>,
|
||||
ourUuid: string
|
||||
|
@ -837,6 +859,7 @@ function peekNotConnectedGroupCall(
|
|||
|
||||
queue.add(async () => {
|
||||
const state = getState();
|
||||
const { ourUuid } = state.user;
|
||||
|
||||
// We make sure we're not trying to peek at a connected (or connecting, or
|
||||
// reconnecting) call. Because this is asynchronous, it's possible that the call
|
||||
|
@ -872,14 +895,34 @@ function peekNotConnectedGroupCall(
|
|||
|
||||
calling.updateCallHistoryForGroupCall(conversationId, peekInfo);
|
||||
|
||||
const formattedPeekInfo = calling.formatGroupCallPeekInfoForRedux(
|
||||
peekInfo
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED,
|
||||
payload: {
|
||||
conversationId,
|
||||
peekInfo: calling.formatGroupCallPeekInfoForRedux(peekInfo),
|
||||
peekInfo: formattedPeekInfo,
|
||||
ourConversationId: state.user.ourConversationId,
|
||||
},
|
||||
});
|
||||
|
||||
// We want to show "Alice started a group call" only if a call isn't ringing or
|
||||
// active. We wait a moment to make sure that we don't accidentally show a ring
|
||||
// notification followed swiftly by a less urgent notification.
|
||||
if (!isAnybodyElseInGroupCall(formattedPeekInfo, ourUuid)) {
|
||||
return;
|
||||
}
|
||||
await sleep(1000);
|
||||
const newCallingState = getState().calling;
|
||||
if (
|
||||
getActiveCall(newCallingState)?.conversationId === conversationId ||
|
||||
getIncomingCall(newCallingState.callsByConversation, ourUuid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
calling.notifyForGroupCall(conversationId, peekInfo.creator);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,16 +9,10 @@ import {
|
|||
CallsByConversationType,
|
||||
DirectCallStateType,
|
||||
GroupCallStateType,
|
||||
isAnybodyElseInGroupCall,
|
||||
getIncomingCall as getIncomingCallHelper,
|
||||
} from '../ducks/calling';
|
||||
import {
|
||||
CallMode,
|
||||
CallState,
|
||||
GroupCallConnectionState,
|
||||
} from '../../types/Calling';
|
||||
import { getUserUuid } from './user';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
|
||||
export type CallStateType = DirectCallStateType | GroupCallStateType;
|
||||
|
||||
|
@ -62,29 +56,12 @@ export const isInCall = createSelector(
|
|||
(call: CallStateType | undefined): boolean => Boolean(call)
|
||||
);
|
||||
|
||||
// In theory, there could be multiple incoming calls, or an incoming call while there's
|
||||
// an active call. In practice, the UI is not ready for this, and RingRTC doesn't
|
||||
// support it for direct calls.
|
||||
export const getIncomingCall = createSelector(
|
||||
getCallsByConversation,
|
||||
getUserUuid,
|
||||
(
|
||||
callsByConversation: CallsByConversationType,
|
||||
ourUuid: string
|
||||
): undefined | DirectCallStateType | GroupCallStateType => {
|
||||
return Object.values(callsByConversation).find(call => {
|
||||
switch (call.callMode) {
|
||||
case CallMode.Direct:
|
||||
return call.isIncoming && call.callState === CallState.Ringing;
|
||||
case CallMode.Group:
|
||||
return (
|
||||
call.ringerUuid &&
|
||||
call.connectionState === GroupCallConnectionState.NotConnected &&
|
||||
isAnybodyElseInGroupCall(call.peekInfo, ourUuid)
|
||||
);
|
||||
default:
|
||||
throw missingCaseError(call);
|
||||
}
|
||||
});
|
||||
}
|
||||
): undefined | DirectCallStateType | GroupCallStateType =>
|
||||
getIncomingCallHelper(callsByConversation, ourUuid)
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue