Use thunks for calling action creators

This commit is contained in:
Evan Hahn 2020-11-04 11:47:50 -06:00 committed by Evan Hahn
parent 618c0fce1e
commit 7e23bb6246

View file

@ -1,9 +1,11 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { ThunkAction } from 'redux-thunk';
import { CallEndedReason } from 'ringrtc'; import { CallEndedReason } from 'ringrtc';
import { notify } from '../../services/notify'; import { notify } from '../../services/notify';
import { calling } from '../../services/calling'; import { calling } from '../../services/calling';
import { StateType as RootStateType } from '../reducer';
import { import {
CallingDeviceType, CallingDeviceType,
CallState, CallState,
@ -11,7 +13,6 @@ import {
MediaDeviceSettings, MediaDeviceSettings,
} from '../../types/Calling'; } from '../../types/Calling';
import { ColorType } from '../../types/Colors'; import { ColorType } from '../../types/Colors';
import { NoopActionType } from './noop';
import { callingTones } from '../../util/callingTones'; import { callingTones } from '../../util/callingTones';
import { requestCameraPermissions } from '../../util/callingPermissions'; import { requestCameraPermissions } from '../../util/callingPermissions';
import { import {
@ -116,12 +117,10 @@ export function isCallActive({
// Actions // Actions
const ACCEPT_CALL = 'calling/ACCEPT_CALL'; const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
const CANCEL_CALL = 'calling/CANCEL_CALL'; const CANCEL_CALL = 'calling/CANCEL_CALL';
const SHOW_CALL_LOBBY = 'calling/SHOW_CALL_LOBBY'; const SHOW_CALL_LOBBY = 'calling/SHOW_CALL_LOBBY';
const CALL_STATE_CHANGE = 'calling/CALL_STATE_CHANGE';
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED'; const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
const CHANGE_IO_DEVICE = 'calling/CHANGE_IO_DEVICE';
const CHANGE_IO_DEVICE_FULFILLED = 'calling/CHANGE_IO_DEVICE_FULFILLED'; const CHANGE_IO_DEVICE_FULFILLED = 'calling/CHANGE_IO_DEVICE_FULFILLED';
const CLOSE_NEED_PERMISSION_SCREEN = 'calling/CLOSE_NEED_PERMISSION_SCREEN'; const CLOSE_NEED_PERMISSION_SCREEN = 'calling/CLOSE_NEED_PERMISSION_SCREEN';
const DECLINE_CALL = 'calling/DECLINE_CALL'; const DECLINE_CALL = 'calling/DECLINE_CALL';
@ -131,15 +130,14 @@ const OUTGOING_CALL = 'calling/OUTGOING_CALL';
const REFRESH_IO_DEVICES = 'calling/REFRESH_IO_DEVICES'; const REFRESH_IO_DEVICES = 'calling/REFRESH_IO_DEVICES';
const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE'; const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
const SET_LOCAL_AUDIO = 'calling/SET_LOCAL_AUDIO'; const SET_LOCAL_AUDIO = 'calling/SET_LOCAL_AUDIO';
const SET_LOCAL_VIDEO = 'calling/SET_LOCAL_VIDEO';
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED'; const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
const START_CALL = 'calling/START_CALL'; const START_CALL = 'calling/START_CALL';
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS'; const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
const TOGGLE_PIP = 'calling/TOGGLE_PIP'; const TOGGLE_PIP = 'calling/TOGGLE_PIP';
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS'; const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
type AcceptCallActionType = { type AcceptCallPendingActionType = {
type: 'calling/ACCEPT_CALL'; type: 'calling/ACCEPT_CALL_PENDING';
payload: AcceptCallType; payload: AcceptCallType;
}; };
@ -152,21 +150,11 @@ type CallLobbyActionType = {
payload: OutgoingCallType; payload: OutgoingCallType;
}; };
type CallStateChangeActionType = {
type: 'calling/CALL_STATE_CHANGE';
payload: Promise<CallStateChangeType>;
};
type CallStateChangeFulfilledActionType = { type CallStateChangeFulfilledActionType = {
type: 'calling/CALL_STATE_CHANGE_FULFILLED'; type: 'calling/CALL_STATE_CHANGE_FULFILLED';
payload: CallStateChangeType; payload: CallStateChangeType;
}; };
type ChangeIODeviceActionType = {
type: 'calling/CHANGE_IO_DEVICE';
payload: Promise<ChangeIODevicePayloadType>;
};
type ChangeIODeviceFulfilledActionType = { type ChangeIODeviceFulfilledActionType = {
type: 'calling/CHANGE_IO_DEVICE_FULFILLED'; type: 'calling/CHANGE_IO_DEVICE_FULFILLED';
payload: ChangeIODevicePayloadType; payload: ChangeIODevicePayloadType;
@ -212,11 +200,6 @@ type SetLocalAudioActionType = {
payload: SetLocalAudioType; payload: SetLocalAudioType;
}; };
type SetLocalVideoActionType = {
type: 'calling/SET_LOCAL_VIDEO';
payload: Promise<SetLocalVideoType>;
};
type SetLocalVideoFulfilledActionType = { type SetLocalVideoFulfilledActionType = {
type: 'calling/SET_LOCAL_VIDEO_FULFILLED'; type: 'calling/SET_LOCAL_VIDEO_FULFILLED';
payload: SetLocalVideoType; payload: SetLocalVideoType;
@ -239,12 +222,10 @@ type ToggleSettingsActionType = {
}; };
export type CallingActionType = export type CallingActionType =
| AcceptCallActionType | AcceptCallPendingActionType
| CancelCallActionType | CancelCallActionType
| CallLobbyActionType | CallLobbyActionType
| CallStateChangeActionType
| CallStateChangeFulfilledActionType | CallStateChangeFulfilledActionType
| ChangeIODeviceActionType
| ChangeIODeviceFulfilledActionType | ChangeIODeviceFulfilledActionType
| CloseNeedPermissionScreenActionType | CloseNeedPermissionScreenActionType
| DeclineCallActionType | DeclineCallActionType
@ -254,7 +235,6 @@ export type CallingActionType =
| RefreshIODevicesActionType | RefreshIODevicesActionType
| RemoteVideoChangeActionType | RemoteVideoChangeActionType
| SetLocalAudioActionType | SetLocalAudioActionType
| SetLocalVideoActionType
| SetLocalVideoFulfilledActionType | SetLocalVideoFulfilledActionType
| StartCallActionType | StartCallActionType
| ToggleParticipantsActionType | ToggleParticipantsActionType
@ -265,73 +245,76 @@ export type CallingActionType =
function acceptCall( function acceptCall(
payload: AcceptCallType payload: AcceptCallType
): AcceptCallActionType | NoopActionType { ): ThunkAction<void, RootStateType, unknown, AcceptCallPendingActionType> {
(async () => { return async dispatch => {
dispatch({
type: ACCEPT_CALL_PENDING,
payload,
});
try { try {
await calling.accept(payload.callId, payload.asVideoCall); await calling.accept(payload.callId, payload.asVideoCall);
} catch (err) { } catch (err) {
window.log.error(`Failed to acceptCall: ${err.stack}`); window.log.error(`Failed to acceptCall: ${err.stack}`);
} }
})();
return {
type: ACCEPT_CALL,
payload,
}; };
} }
function callStateChange( function callStateChange(
payload: CallStateChangeType payload: CallStateChangeType
): CallStateChangeActionType { ): ThunkAction<
return { void,
type: CALL_STATE_CHANGE, RootStateType,
payload: doCallStateChange(payload), unknown,
CallStateChangeFulfilledActionType
> {
return async dispatch => {
const { callDetails, callState } = payload;
const { isIncoming } = callDetails;
if (callState === CallState.Ringing && isIncoming) {
await callingTones.playRingtone();
await showCallNotification(callDetails);
bounceAppIconStart();
}
if (callState !== CallState.Ringing) {
await callingTones.stopRingtone();
bounceAppIconStop();
}
if (callState === CallState.Ended) {
await callingTones.playEndCall();
}
dispatch({
type: CALL_STATE_CHANGE_FULFILLED,
payload,
});
}; };
} }
function changeIODevice( function changeIODevice(
payload: ChangeIODevicePayloadType payload: ChangeIODevicePayloadType
): ChangeIODeviceActionType { ): ThunkAction<
return { void,
type: CHANGE_IO_DEVICE, RootStateType,
payload: doChangeIODevice(payload), unknown,
ChangeIODeviceFulfilledActionType
> {
return async dispatch => {
// Only `setPreferredCamera` returns a Promise.
if (payload.type === CallingDeviceType.CAMERA) {
await calling.setPreferredCamera(payload.selectedDevice);
} else if (payload.type === CallingDeviceType.MICROPHONE) {
calling.setPreferredMicrophone(payload.selectedDevice);
} else if (payload.type === CallingDeviceType.SPEAKER) {
calling.setPreferredSpeaker(payload.selectedDevice);
}
dispatch({
type: CHANGE_IO_DEVICE_FULFILLED,
payload,
});
}; };
} }
async function doChangeIODevice(
payload: ChangeIODevicePayloadType
): Promise<ChangeIODevicePayloadType> {
if (payload.type === CallingDeviceType.CAMERA) {
await calling.setPreferredCamera(payload.selectedDevice);
} else if (payload.type === CallingDeviceType.MICROPHONE) {
calling.setPreferredMicrophone(payload.selectedDevice);
} else if (payload.type === CallingDeviceType.SPEAKER) {
calling.setPreferredSpeaker(payload.selectedDevice);
}
return payload;
}
async function doCallStateChange(
payload: CallStateChangeType
): Promise<CallStateChangeType> {
const { callDetails, callState } = payload;
const { isIncoming } = callDetails;
if (callState === CallState.Ringing && isIncoming) {
await callingTones.playRingtone();
await showCallNotification(callDetails);
bounceAppIconStart();
}
if (callState !== CallState.Ringing) {
await callingTones.stopRingtone();
bounceAppIconStop();
}
if (callState === CallState.Ended) {
await callingTones.playEndCall();
}
return payload;
}
async function showCallNotification(callDetails: CallDetailsType) { async function showCallNotification(callDetails: CallDetailsType) {
const canNotify = await window.getCallSystemNotification(); const canNotify = await window.getCallSystemNotification();
if (!canNotify) { if (!canNotify) {
@ -420,21 +403,19 @@ function remoteVideoChange(
}; };
} }
function setLocalPreview(payload: SetLocalPreviewType): NoopActionType { function setLocalPreview(
calling.videoCapturer.setLocalPreview(payload.element); payload: SetLocalPreviewType
): ThunkAction<void, RootStateType, unknown, never> {
return { return () => {
type: 'NOOP', calling.videoCapturer.setLocalPreview(payload.element);
payload: null,
}; };
} }
function setRendererCanvas(payload: SetRendererCanvasType): NoopActionType { function setRendererCanvas(
calling.videoRenderer.setCanvas(payload.element); payload: SetRendererCanvasType
): ThunkAction<void, RootStateType, unknown, never> {
return { return () => {
type: 'NOOP', calling.videoRenderer.setCanvas(payload.element);
payload: null,
}; };
} }
@ -449,10 +430,31 @@ function setLocalAudio(payload: SetLocalAudioType): SetLocalAudioActionType {
}; };
} }
function setLocalVideo(payload: SetLocalVideoType): SetLocalVideoActionType { function setLocalVideo(
return { payload: SetLocalVideoType
type: SET_LOCAL_VIDEO, ): ThunkAction<void, RootStateType, unknown, SetLocalVideoFulfilledActionType> {
payload: doSetLocalVideo(payload), return async dispatch => {
let enabled: boolean;
if (await requestCameraPermissions()) {
if (payload.callId) {
calling.setOutgoingVideo(payload.callId, payload.enabled);
} else if (payload.enabled) {
calling.enableLocalCamera();
} else {
calling.disableLocalCamera();
}
({ enabled } = payload);
} else {
enabled = false;
}
dispatch({
type: SET_LOCAL_VIDEO_FULFILLED,
payload: {
...payload,
enabled,
},
});
}; };
} }
@ -493,26 +495,6 @@ function toggleSettings(): ToggleSettingsActionType {
}; };
} }
async function doSetLocalVideo(
payload: SetLocalVideoType
): Promise<SetLocalVideoType> {
if (await requestCameraPermissions()) {
if (payload.callId) {
calling.setOutgoingVideo(payload.callId, payload.enabled);
} else if (payload.enabled) {
calling.enableLocalCamera();
} else {
calling.disableLocalCamera();
}
return payload;
}
return {
...payload,
enabled: false,
};
}
export const actions = { export const actions = {
acceptCall, acceptCall,
cancelCall, cancelCall,
@ -581,7 +563,7 @@ export function reducer(
}; };
} }
if (action.type === ACCEPT_CALL) { if (action.type === ACCEPT_CALL_PENDING) {
return { return {
...state, ...state,
hasLocalAudio: true, hasLocalAudio: true,