From cca1b4dabeef8ca23883f67da1d64b06787f7a1d Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 8 Nov 2021 15:30:31 +1100 Subject: [PATCH] add a button to start a video call --- _locales/en/messages.json | 4 +- .../conversation/ConversationHeader.tsx | 56 +++++++++++++++---- ts/components/session/calling/CallButtons.tsx | 8 +-- ts/components/session/icon/Icons.tsx | 7 +++ ts/components/session/menu/Menu.tsx | 31 +++------- ts/interactions/conversationInteractions.ts | 23 +++++++- ts/state/selectors/conversations.ts | 14 +++++ 7 files changed, 101 insertions(+), 42 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 14c7f8fc9..0946dc78f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -447,11 +447,11 @@ "unableToCallTitle": "Cannot start new call", "callMissed": "Missed call from $name$", "callMissedTitle": "Call missed", - "startVideoCall": "Start Video Call", "noCameraFound": "No camera found", "noAudioInputFound": "No audio input found", "callMediaPermissionsTitle": "Voice and video calls", "callMissedCausePermission": "Call missed from '$name$' because you need to enable the 'Voice and video calls' permission in the Privacy Settings.", "callMediaPermissionsDescription": "Allows access to accept voice and video calls from other users", - "callMediaPermissionsDialogContent": "The current implementation of voice/video calls will expose your IP address to the Oxen Foundation servers and the calling/called user." + "callMediaPermissionsDialogContent": "The current implementation of voice/video calls will expose your IP address to the Oxen Foundation servers and the calling/called user.", + "menuCall": "Call" } diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 8b610ac29..808f81cf0 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -14,6 +14,10 @@ import { getConversationHeaderProps, getConversationHeaderTitleProps, getCurrentNotificationSettingText, + getHasIncomingCall, + getHasOngoingCall, + getIsSelectedNoteToSelf, + getIsSelectedPrivate, getSelectedConversation, getSelectedConversationIsPublic, getSelectedConversationKey, @@ -35,6 +39,7 @@ import { openRightPanel, resetSelectedMessageIds, } from '../../state/ducks/conversations'; +import { callRecipient } from '../../interactions/conversationInteractions'; export interface TimerOption { name: string; @@ -202,6 +207,32 @@ const BackButton = (props: { onGoBack: () => void; showBackButton: boolean }) => ); }; +const CallButton = () => { + const isPrivate = useSelector(getIsSelectedPrivate); + const isMe = useSelector(getIsSelectedNoteToSelf); + const selectedConvoKey = useSelector(getSelectedConversationKey); + + const hasIncomingCall = useSelector(getHasIncomingCall); + const hasOngoingCall = useSelector(getHasOngoingCall); + const canCall = !(hasIncomingCall || hasOngoingCall); + + if (!isPrivate || isMe || !selectedConvoKey) { + return null; + } + + return ( + { + void callRecipient(selectedConvoKey, canCall); + }} + /> + ); +}; + export const StyledSubtitleContainer = styled.div` display: flex; flex-direction: row; @@ -362,17 +393,20 @@ export const ConversationHeaderWithDetails = () => { {!isKickedFromGroup && } {!isSelectionMode && ( - { - dispatch(openRightPanel()); - }} - pubkey={conversationKey} - showBackButton={isMessageDetailOpened} - avatarPath={avatarPath} - memberAvatars={memberDetails} - name={name} - profileName={profileName} - /> + <> + + { + dispatch(openRightPanel()); + }} + pubkey={conversationKey} + showBackButton={isMessageDetailOpened} + avatarPath={avatarPath} + memberAvatars={memberDetails} + name={name} + profileName={profileName} + /> + )} { +const ShowInFullScreenButton = ({ isFullScreen }: { isFullScreen: boolean }) => { const dispatch = useDispatch(); const showInFullScreen = () => { @@ -270,7 +270,8 @@ export const CallWindowControls = ({ }) => { return ( - + {!remoteStreamVideoIsMuted && } + - - {!remoteStreamVideoIsMuted && } + ); }; diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx index c2a70eb6c..bad644f41 100644 --- a/ts/components/session/icon/Icons.tsx +++ b/ts/components/session/icon/Icons.tsx @@ -34,6 +34,7 @@ export type SessionIconType = | 'oxen' | 'pause' | 'pencil' + | 'phone' | 'pin' | 'play' | 'plus' @@ -281,6 +282,12 @@ export const icons = { viewBox: '1 1 21 21', ratio: 1, }, + phone: { + path: + 'M2.8,180.875c46.6,134,144.7,286.2,282.9,424.399c138.2,138.2,290.4,236.301,424.4,282.9c18.2,6.3,38.3,1.8,52-11.8 l92.7-92.7l21.6-21.6c19.5-19.5,19.5-51.2,0-70.7l-143.5-143.4c-19.5-19.5-51.2-19.5-70.7,0l-38.899,38.9 c-20.2,20.2-52.4,22.2-75,4.6c-44.7-34.8-89-73.899-131.9-116.8c-42.9-42.9-82-87.2-116.8-131.9c-17.601-22.6-15.601-54.7,4.6-75 l38.9-38.9c19.5-19.5,19.5-51.2,0-70.7l-143.5-143.5c-19.5-19.5-51.2-19.5-70.7,0l-21.6,21.6l-92.7,92.7 C1,142.575-3.5,162.675,2.8,180.875z', + viewBox: '0 0 891.024 891.024', + ratio: 1, + }, pin: { path: 'M83.88.451L122.427 39c.603.601.603 1.585 0 2.188l-13.128 13.125c-.602.604-1.586.604-2.187 0l-3.732-3.73-17.303 17.3c3.882 14.621.095 30.857-11.37 42.32-.266.268-.535.529-.808.787-1.004.955-.843.949-1.813-.021L47.597 86.48 0 122.867l36.399-47.584L11.874 50.76c-.978-.98-.896-.826.066-1.837.24-.251.485-.503.734-.753C24.137 36.707 40.376 32.917 54.996 36.8l17.301-17.3-3.733-3.732c-.601-.601-.601-1.585 0-2.188L81.691.451c.604-.601 1.588-.601 2.189 0z', diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 4f9635404..5195b77cf 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -21,6 +21,7 @@ import { SectionType } from '../../../state/ducks/section'; import { getConversationController } from '../../../session/conversations'; import { blockConvoById, + callRecipient, clearNickNameByConvoId, copyPublicKeyByConvoId, deleteAllMessagesByConvoIdWithConfirmation, @@ -36,8 +37,7 @@ import { } from '../../../interactions/conversationInteractions'; import { SessionButtonColor } from '../SessionButton'; import { getTimerOptions } from '../../../state/selectors/timerOptions'; -import { CallManager, ToastUtils } from '../../../session/utils'; -import { getCallMediaPermissionsSettings } from '../settings/SessionSettings'; +import { ToastUtils } from '../../../session/utils'; const maxNumberOfPinnedConversations = 5; @@ -357,34 +357,17 @@ export function getStartCallMenuItem(conversationId: string): JSX.Element | null const hasIncomingCall = useSelector(getHasIncomingCall); const hasOngoingCall = useSelector(getHasOngoingCall); const canCall = !(hasIncomingCall || hasOngoingCall); - if (!convoOut?.isPrivate()) { + if (!convoOut?.isPrivate() || convoOut.isMe()) { return null; } + return ( { - // TODO: either pass param to callRecipient or call different call methods based on item selected. - // TODO: one time redux-persisted permission modal? - const convo = getConversationController().get(conversationId); - - if (!canCall) { - ToastUtils.pushUnableToCall(); - return; - } - - if (!getCallMediaPermissionsSettings()) { - ToastUtils.pushVideoCallPermissionNeeded(); - return; - } - - if (convo) { - convo.callState = 'offering'; - await convo.commit(); - await CallManager.USER_callRecipient(convo.id); - } + onClick={() => { + void callRecipient(conversationId, canCall); }} > - {'Video Call'} + {window.i18n('menuCall')} ); } diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index ff6583857..16615e1dd 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -4,7 +4,7 @@ import { openGroupV2ConversationIdRegex, } from '../opengroup/utils/OpenGroupUtils'; import { getV2OpenGroupRoom } from '../data/opengroups'; -import { SyncUtils, ToastUtils, UserUtils } from '../session/utils'; +import { CallManager, SyncUtils, ToastUtils, UserUtils } from '../session/utils'; import { ConversationNotificationSettingType, ConversationTypeEnum } from '../models/conversation'; import _ from 'lodash'; @@ -35,6 +35,7 @@ import { FSv2 } from '../fileserver'; import { fromHexToArray, toHex } from '../session/utils/String'; import { SessionButtonColor } from '../components/session/SessionButton'; import { perfEnd, perfStart } from '../session/utils/Performance'; +import { getCallMediaPermissionsSettings } from '../components/session/settings/SessionSettings'; export const getCompleteUrlForV2ConvoId = async (convoId: string) => { if (convoId.match(openGroupV2ConversationIdRegex)) { @@ -431,3 +432,23 @@ function isURL(str: string) { const url = new RegExp(urlRegex, 'i'); return str.length < 2083 && url.test(str); } + +export async function callRecipient(pubkey: string, canCall: boolean) { + const convo = getConversationController().get(pubkey); + + if (!canCall) { + ToastUtils.pushUnableToCall(); + return; + } + + if (!getCallMediaPermissionsSettings()) { + ToastUtils.pushVideoCallPermissionNeeded(); + return; + } + + if (convo && convo.isPrivate() && !convo.isMe()) { + convo.callState = 'offering'; + await convo.commit(); + await CallManager.USER_callRecipient(convo.id); + } +} diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 87ca4b523..301ae73d4 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -590,6 +590,20 @@ export const getConversationHeaderProps = createSelector(getSelectedConversation }; }); +export const getIsSelectedPrivate = createSelector( + getConversationHeaderProps, + (headerProps): boolean => { + return headerProps?.isPrivate || false; + } +); + +export const getIsSelectedNoteToSelf = createSelector( + getConversationHeaderProps, + (headerProps): boolean => { + return headerProps?.isMe || false; + } +); + export const getNumberOfPinnedConversations = createSelector(getConversations, (state): number => { const values = Object.values(state.conversationLookup); return values.filter(conversation => conversation.isPinned).length;