diff --git a/app/containers/UIKit/utils.ts b/app/containers/UIKit/utils.ts index 0336e6cf..86839d86 100644 --- a/app/containers/UIKit/utils.ts +++ b/app/containers/UIKit/utils.ts @@ -3,6 +3,9 @@ import React, { useContext, useState } from 'react'; import { BlockContext } from '@rocket.chat/ui-kit'; import { IText } from './interfaces'; +import { videoConfJoin } from '../../lib/methods/videoConf'; +import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet'; +import i18n from '../../i18n'; export const textParser = ([{ text }]: IText[]) => text; @@ -32,12 +35,14 @@ interface IUseBlockContext { actionId: string; appId?: string; initialValue?: string; + url?: string; } export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUseBlockContext, context: BlockContext): TReturn => { const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext); const { value = initialValue } = values[actionId] || {}; const [loading, setLoading] = useState(false); + const { showActionSheet } = useActionSheet(); const error = errors && actionId && errors[actionId]; @@ -53,6 +58,23 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse async ({ value }: any) => { setLoading(true); try { + if (appId === 'videoconf-core' && blockId) { + setLoading(false); + const options: TActionSheetOptionsItem[] = [ + { + title: i18n.t('Video_call'), + icon: 'camera', + onPress: () => videoConfJoin(blockId, true) + }, + { + title: i18n.t('Voice_call'), + icon: 'microphone', + onPress: () => videoConfJoin(blockId, false) + } + ]; + showActionSheet({ options }); + return; + } await action({ blockId, appId: appId || appIdFromContext, diff --git a/app/definitions/rest/v1/videoConference.ts b/app/definitions/rest/v1/videoConference.ts index a7a1e3f7..e919c9bd 100644 --- a/app/definitions/rest/v1/videoConference.ts +++ b/app/definitions/rest/v1/videoConference.ts @@ -2,4 +2,10 @@ export type VideoConferenceEndpoints = { 'video-conference/jitsi.update-timeout': { POST: (params: { roomId: string }) => void; }; + 'video-conference.join': { + POST: (params: { callId: string; state: { cam: boolean } }) => { url: string; providerName: string }; + }; + 'video-conference.start': { + POST: (params: { roomId: string }) => { url: string }; + }; }; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 977e9be9..fd5b8569 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -839,5 +839,6 @@ "Mark_as_unread_Info": "Display room as unread when there are unread messages", "Show_badge_for_mentions": "Show badge for mentions", "Show_badge_for_mentions_Info": "Display badge for direct mentions only", + "error-init-video-conf": "Error starting video call", "totp-invalid": "Code or password invalid" } \ No newline at end of file diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index a064932e..60c556e4 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -759,6 +759,7 @@ "Unsupported_format": "Formato não suportado", "Downloaded_file": "Arquivo baixado", "Error_Download_file": "Erro ao baixar o arquivo", + "error-init-video-conf": "Erro ao iniciar chamada de video" "added__roomName__to_team": "#{{roomName}} adicionada a esta equipe", "Added__username__to_team": "@{{user_added}} adicionado a esta equipe", "Converted__roomName__to_team": "#{{roomName}} convertida em equipe", diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index 3edb1070..90b928d9 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -212,6 +212,18 @@ export const defaultSettings = { Accounts_AvatarExternalProviderUrl: { type: 'valueAsString' }, + VideoConf_Enable_DMs: { + type: 'valueAsBoolean' + }, + VideoConf_Enable_Channels: { + type: 'valueAsBoolean' + }, + VideoConf_Enable_Groups: { + type: 'valueAsBoolean' + }, + VideoConf_Enable_Teams: { + type: 'valueAsBoolean' + }, Accounts_AllowDeleteOwnAccount: { type: 'valueAsBoolean' } diff --git a/app/lib/methods/helpers/log/events.ts b/app/lib/methods/helpers/log/events.ts index 51cb8cf1..ea4e57f4 100644 --- a/app/lib/methods/helpers/log/events.ts +++ b/app/lib/methods/helpers/log/events.ts @@ -351,6 +351,10 @@ export default { TC_TOGGLE_AUTOJOIN: 'tc_toggle_autojoin', TC_TOGGLE_AUTOJOIN_F: 'tc_toggle_autojoin_f', + // LIVECHAT VIDEOCONF + LIVECHAT_VIDEOCONF_JOIN: 'livechat_videoconf_join', + LIVECHAT_VIDEOCONF_TERMINATE: 'livechat_videoconf_terminate', + // DELETE OWN ACCOUNT ACCOUNT DELETE_OWN_ACCOUNT: 'delete_own_account', DELETE_OWN_ACCOUNT_F: 'delete_own_account_f' diff --git a/app/lib/methods/videoConf.ts b/app/lib/methods/videoConf.ts new file mode 100644 index 00000000..f10b2ad1 --- /dev/null +++ b/app/lib/methods/videoConf.ts @@ -0,0 +1,35 @@ +import navigation from '../navigation/appNavigation'; +import openLink from './helpers/openLink'; +import { Services } from '../services'; +import log from './helpers/log'; +import { showErrorAlert } from './helpers'; +import i18n from '../../i18n'; + +export const videoConfJoin = async (callId: string, cam: boolean) => { + try { + const result = await Services.videoConferenceJoin(callId, cam); + if (result.success) { + const { url, providerName } = result; + if (providerName === 'jitsi') { + navigation.navigate('JitsiMeetView', { url, onlyAudio: !cam, videoConf: true }); + } else { + openLink(url); + } + } + } catch (e) { + showErrorAlert(i18n.t('error-init-video-conf')); + log(e); + } +}; + +export const videoConfStartAndJoin = async (rid: string, cam: boolean) => { + try { + const videoConfResponse: any = await Services.videoConferenceStart(rid); + if (videoConfResponse.success) { + videoConfJoin(videoConfResponse.data.callId, cam); + } + } catch (e) { + showErrorAlert(i18n.t('error-init-video-conf')); + log(e); + } +}; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 5d7007c5..566c55c6 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -918,6 +918,12 @@ export function getUserInfo(userId: string) { export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite }); + +export const videoConferenceJoin = (callId: string, cam: boolean) => + sdk.post('video-conference.join', { callId, state: { cam } }); + +export const videoConferenceStart = (roomId: string) => sdk.post('video-conference.start', { roomId }); + export const saveUserProfileMethod = ( params: IProfileParams, customFields = {}, @@ -930,3 +936,4 @@ export const saveUserProfileMethod = ( export const deleteOwnAccount = (password: string, confirmRelinquish = false): any => // RC 0.67.0 sdk.post('users.deleteOwnAccount', { password, confirmRelinquish }); + diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index e5130374..6a525d57 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -136,6 +136,7 @@ const ChatsStackNavigator = () => { + ); }; @@ -325,7 +326,6 @@ const InsideStackNavigator = () => { - ); }; diff --git a/app/stacks/types.ts b/app/stacks/types.ts index aa96c982..6265b3aa 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -163,6 +163,12 @@ export type ChatsStackParamList = { cannedResponse: ICannedResponse; room: ISubscription; }; + JitsiMeetView: { + rid: string; + url: string; + onlyAudio?: boolean; + videoConf?: boolean; + }; }; export type ProfileStackParamList = { @@ -259,11 +265,6 @@ export type InsideStackParamList = { ModalBlockView: { data: any; // TODO: Change; }; - JitsiMeetView: { - rid: string; - url: string; - onlyAudio?: boolean; - }; }; export type OutsideParamList = { diff --git a/app/views/JitsiMeetView.tsx b/app/views/JitsiMeetView.tsx index 0a87f1bf..c2e8d2d9 100644 --- a/app/views/JitsiMeetView.tsx +++ b/app/views/JitsiMeetView.tsx @@ -9,7 +9,7 @@ import ActivityIndicator from '../containers/ActivityIndicator'; import { events, logEvent } from '../lib/methods/helpers/log'; import { isAndroid, isIOS } from '../lib/methods/helpers'; import { withTheme } from '../theme'; -import { InsideStackParamList } from '../stacks/types'; +import { ChatsStackParamList } from '../stacks/types'; import { IApplicationState, IUser, IBaseScreen } from '../definitions'; import { Services } from '../lib/services'; @@ -24,7 +24,7 @@ interface IJitsiMeetViewState { loading: boolean; } -interface IJitsiMeetViewProps extends IBaseScreen { +interface IJitsiMeetViewProps extends IBaseScreen { baseUrl: string; user: IUser; } @@ -32,12 +32,14 @@ interface IJitsiMeetViewProps extends IBaseScreen { private rid: string; private url: string; + private videoConf: boolean; private jitsiTimeout: number | null; constructor(props: IJitsiMeetViewProps) { super(props); this.rid = props.route.params?.rid; this.url = props.route.params?.url; + this.videoConf = !!props.route.params?.videoConf; this.jitsiTimeout = null; const { user, baseUrl } = props; @@ -71,7 +73,7 @@ class JitsiMeetView extends React.Component { - logEvent(events.JM_CONFERENCE_JOIN); - if (this.rid) { + logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN); + if (this.rid && !this.videoConf) { Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e)); if (this.jitsiTimeout) { BackgroundTimer.clearInterval(this.jitsiTimeout); @@ -101,7 +103,7 @@ class JitsiMeetView extends React.Component { - logEvent(events.JM_CONFERENCE_TERMINATE); + logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE); const { navigation } = this.props; navigation.pop(); }; diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 2f93d636..5d088422 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -44,6 +44,7 @@ import { } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription'; +import { videoConfStartAndJoin } from '../../lib/methods/videoConf'; interface IOnPressTouch { (item: { route?: T; params?: ChatsStackParamList[T]; event?: Function }): void; @@ -68,6 +69,10 @@ interface IRoomActionsViewProps extends IBaseScreen { + const { room } = this.state; + const { serverVersion } = this.props; + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0')) { + videoConfStartAndJoin(room.rid, video); + } else { + callJitsi(room, !video); + } + }; + renderRoomInfo = () => { const { room, member } = this.state; const { rid, name, t, topic, source } = room; @@ -810,12 +825,35 @@ class RoomActionsView extends React.Component { const { room } = this.state; - const { jitsiEnabled, jitsiEnableTeams, jitsiEnableChannels } = this.props; + const { + jitsiEnabled, + jitsiEnableTeams, + jitsiEnableChannels, + serverVersion, + videoConf_Enable_DMs, + videoConf_Enable_Channels, + videoConf_Enable_Groups, + videoConf_Enable_Teams + } = this.props; const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams; const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels; - if (!jitsiEnabled || isJitsiDisabledForTeams || isJitsiDisabledForChannels) { + const isVideoConfDisabledForTeams = !!room.teamMain && !videoConf_Enable_Teams; + const isVideoConfDisabledForChannels = !room.teamMain && room.t === 'c' && !videoConf_Enable_Channels; + const isVideoConfDisabledForGroups = !room.teamMain && room.t === 'p' && !videoConf_Enable_Groups; + const isVideoConfDisabledForDirect = !room.teamMain && room.t === 'd' && !videoConf_Enable_DMs; + + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0')) { + if ( + isVideoConfDisabledForTeams || + isVideoConfDisabledForChannels || + isVideoConfDisabledForGroups || + isVideoConfDisabledForDirect + ) { + return null; + } + } else if (!jitsiEnabled || isJitsiDisabledForTeams || isJitsiDisabledForChannels) { return null; } @@ -824,7 +862,7 @@ class RoomActionsView extends React.Component callJitsi(room, true)} + onPress={() => this.startVideoConf({ video: false })} testID='room-actions-voice' left={() => } showActionIndicator @@ -832,7 +870,7 @@ class RoomActionsView extends React.Component callJitsi(room)} + onPress={() => this.startVideoConf({ video: true })} testID='room-actions-video' left={() => } showActionIndicator @@ -1309,6 +1347,10 @@ const mapStateToProps = (state: IApplicationState) => ({ jitsiEnabled: (state.settings.Jitsi_Enabled || false) as boolean, jitsiEnableTeams: (state.settings.Jitsi_Enable_Teams || false) as boolean, jitsiEnableChannels: (state.settings.Jitsi_Enable_Channels || false) as boolean, + videoConf_Enable_DMs: (state.settings.VideoConf_Enable_DMs ?? true) as boolean, + videoConf_Enable_Channels: (state.settings.VideoConf_Enable_Channels ?? true) as boolean, + videoConf_Enable_Groups: (state.settings.VideoConf_Enable_Groups ?? true) as boolean, + videoConf_Enable_Teams: (state.settings.VideoConf_Enable_Teams ?? true) as boolean, encryptionEnabled: state.encryption.enabled, serverVersion: state.server.version, isMasterDetail: state.app.isMasterDetail,