[IMPROVE] Brings the operation of the video call closer to the web (#4883)
* rename CallAgainActionSheet to StartACallActionSheet * remove useVideoConf and use videoConfJoin directly * consider phone on calls * fix text shrink * fix mic audio * change the behavior of call icon on header and RoomInfo * update types * update types and variables names * revert old type * fix issue on old servers * rename to a correct naming * fix translation * revamp call icon * add error handling to videoconf capabilities * lint * fix role logic * change const name * rename comp * remove commented code * fix types and apply correct logic * fix naming * correct the import * update icon size * create timer function for videoConf bellow 5.0 * add subscription to useVideoConf hook --------- Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
4d5ff8aba1
commit
3fbb7b5720
|
@ -6,7 +6,6 @@ import i18n from '../../../../i18n';
|
|||
import { getSubscriptionByRoomId } from '../../../../lib/database/services/Subscription';
|
||||
import { useAppSelector } from '../../../../lib/hooks';
|
||||
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
||||
import { videoConfStartAndJoin } from '../../../../lib/methods/videoConf';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { useActionSheet } from '../../../ActionSheet';
|
||||
import AvatarContainer from '../../../Avatar';
|
||||
|
@ -16,12 +15,12 @@ import { BUTTON_HIT_SLOP } from '../../../message/utils';
|
|||
import StatusContainer from '../../../Status';
|
||||
import useStyle from './styles';
|
||||
|
||||
export default function CallAgainActionSheet({ rid }: { rid: string }): React.ReactElement {
|
||||
export default function StartACallActionSheet({ rid, initCall }: { rid: string; initCall: Function }): React.ReactElement {
|
||||
const style = useStyle();
|
||||
const { colors } = useTheme();
|
||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '', rid: '' });
|
||||
const [phone, setPhone] = useState(true);
|
||||
const [camera, setCamera] = useState(false);
|
||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '' });
|
||||
const [mic, setMic] = useState(true);
|
||||
const [cam, setCam] = useState(false);
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
|
||||
const { hideActionSheet } = useActionSheet();
|
||||
|
@ -31,7 +30,7 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
|||
const room = await getSubscriptionByRoomId(rid);
|
||||
const uid = (await getUidDirectMessage(room)) as string;
|
||||
const avt = getRoomAvatar(room);
|
||||
setUser({ uid, username: room?.name || '', avatar: avt, rid: room?.id || '' });
|
||||
setUser({ uid, username: room?.name || '', avatar: avt });
|
||||
})();
|
||||
}, [rid]);
|
||||
|
||||
|
@ -43,25 +42,27 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
|||
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
||||
<View style={style.actionSheetHeaderButtons}>
|
||||
<Touchable
|
||||
onPress={() => setCamera(!camera)}
|
||||
style={[style.iconCallContainer, camera && style.enabledBackground, { marginRight: 6 }]}
|
||||
onPress={() => setCam(!cam)}
|
||||
style={[style.iconCallContainer, cam && style.enabledBackground, { marginRight: 6 }]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
>
|
||||
<CustomIcon name={camera ? 'camera' : 'camera-disabled'} size={16} color={handleColor(camera)} />
|
||||
<CustomIcon name={cam ? 'camera' : 'camera-disabled'} size={20} color={handleColor(cam)} />
|
||||
</Touchable>
|
||||
<Touchable
|
||||
onPress={() => setPhone(!phone)}
|
||||
style={[style.iconCallContainer, phone && style.enabledBackground]}
|
||||
onPress={() => setMic(!mic)}
|
||||
style={[style.iconCallContainer, mic && style.enabledBackground]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
>
|
||||
<CustomIcon name={phone ? 'microphone' : 'microphone-disabled'} size={16} color={handleColor(phone)} />
|
||||
<CustomIcon name={mic ? 'microphone' : 'microphone-disabled'} size={20} color={handleColor(mic)} />
|
||||
</Touchable>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.actionSheetUsernameContainer}>
|
||||
<AvatarContainer text={user.avatar} size={36} />
|
||||
<StatusContainer size={16} id={user.uid} style={{ marginLeft: 8, marginRight: 6 }} />
|
||||
<Text style={style.actionSheetUsername}>{user.username}</Text>
|
||||
<Text style={style.actionSheetUsername} numberOfLines={1}>
|
||||
{user.username}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={style.actionSheetPhotoContainer}>
|
||||
<AvatarContainer size={62} text={username} />
|
||||
|
@ -70,7 +71,7 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
|||
onPress={() => {
|
||||
hideActionSheet();
|
||||
setTimeout(() => {
|
||||
videoConfStartAndJoin(user.rid, camera);
|
||||
initCall({ cam, mic });
|
||||
}, 100);
|
||||
}}
|
||||
title={i18n.t('Call')}
|
|
@ -3,17 +3,16 @@ import { Text } from 'react-native';
|
|||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import i18n from '../../../../i18n';
|
||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||
import useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
||||
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
||||
const style = useStyle();
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
return (
|
||||
<VideoConferenceBaseContainer variant='incoming'>
|
||||
<Touchable style={style.callToActionButton} onPress={() => joinCall(blockId)}>
|
||||
<Touchable style={style.callToActionButton} onPress={() => videoConfJoin(blockId)}>
|
||||
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||
</Touchable>
|
||||
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
||||
|
|
|
@ -6,9 +6,7 @@ import { IUser } from '../../../../definitions';
|
|||
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
||||
import i18n from '../../../../i18n';
|
||||
import { useAppSelector } from '../../../../lib/hooks';
|
||||
import { useSnaps } from '../../../../lib/hooks/useSnaps';
|
||||
import { useActionSheet } from '../../../ActionSheet';
|
||||
import CallAgainActionSheet from './CallAgainActionSheet';
|
||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||
import useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
@ -26,8 +24,7 @@ export default function VideoConferenceEnded({
|
|||
}): React.ReactElement {
|
||||
const style = useStyle();
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
const { showActionSheet } = useActionSheet();
|
||||
const snaps = useSnaps([1250]);
|
||||
const { showInitCallActionSheet } = useVideoConf(rid);
|
||||
|
||||
const onlyAuthorOnCall = users.length === 1 && users.some(user => user.username === createdBy.username);
|
||||
|
||||
|
@ -35,15 +32,7 @@ export default function VideoConferenceEnded({
|
|||
<VideoConferenceBaseContainer variant='ended'>
|
||||
{type === 'direct' ? (
|
||||
<>
|
||||
<Touchable
|
||||
style={style.callToActionCallBack}
|
||||
onPress={() =>
|
||||
showActionSheet({
|
||||
children: <CallAgainActionSheet rid={rid} />,
|
||||
snaps
|
||||
})
|
||||
}
|
||||
>
|
||||
<Touchable style={style.callToActionCallBack} onPress={showInitCallActionSheet}>
|
||||
<Text style={style.callToActionCallBackText}>
|
||||
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
||||
</Text>
|
||||
|
|
|
@ -3,18 +3,17 @@ import { Text } from 'react-native';
|
|||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import i18n from '../../../../i18n';
|
||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||
import useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
||||
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
||||
const style = useStyle();
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
return (
|
||||
<VideoConferenceBaseContainer variant='outgoing'>
|
||||
<Touchable style={style.callToActionButton} onPress={() => joinCall(blockId)}>
|
||||
<Touchable style={style.callToActionButton} onPress={() => videoConfJoin(blockId)}>
|
||||
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||
</Touchable>
|
||||
<CallParticipants users={users} />
|
||||
|
|
|
@ -100,7 +100,8 @@ export default function useStyle() {
|
|||
actionSheetUsername: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textBold,
|
||||
color: colors.passcodePrimary
|
||||
color: colors.passcodePrimary,
|
||||
flexShrink: 1
|
||||
},
|
||||
enabledBackground: {
|
||||
backgroundColor: colors.conferenceCallEnabledIconBackground
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
import { useVideoConf } from '../../lib/hooks/useVideoConf';
|
||||
import { videoConfJoin } from '../../lib/methods/videoConf';
|
||||
import { IText } from './interfaces';
|
||||
|
||||
export const textParser = ([{ text }]: IText[]) => text;
|
||||
|
@ -40,7 +40,6 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
|||
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
||||
const { value = initialValue } = values[actionId] || {};
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
const error = errors && actionId && errors[actionId];
|
||||
|
||||
|
@ -58,7 +57,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
|||
try {
|
||||
if (appId === 'videoconf-core' && blockId) {
|
||||
setLoading(false);
|
||||
return joinCall(blockId);
|
||||
return videoConfJoin(blockId);
|
||||
}
|
||||
await action({
|
||||
blockId,
|
||||
|
|
|
@ -10,12 +10,12 @@ import { themes } from '../../lib/constants';
|
|||
import { IMessageCallButton } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
|
||||
const CallButton = React.memo(({ handleEnterCall }: IMessageCallButton) => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View style={styles.buttonContainer}>
|
||||
<Touchable
|
||||
onPress={callJitsi}
|
||||
onPress={handleEnterCall}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
|
|
|
@ -50,7 +50,7 @@ interface IMessageContainerProps {
|
|||
showAttachment: (file: IAttachment) => void;
|
||||
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||
callJitsi?: () => void;
|
||||
handleEnterCall?: () => void;
|
||||
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||
threadBadgeColor?: string;
|
||||
|
@ -69,7 +69,6 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
static defaultProps = {
|
||||
getCustomEmoji: () => null,
|
||||
onLongPress: () => {},
|
||||
callJitsi: () => {},
|
||||
blockAction: () => {},
|
||||
archived: false,
|
||||
broadcast: false,
|
||||
|
@ -338,7 +337,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
isThreadRoom,
|
||||
callJitsi,
|
||||
handleEnterCall,
|
||||
blockAction,
|
||||
rid,
|
||||
threadBadgeColor,
|
||||
|
@ -456,7 +455,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
callJitsi={callJitsi}
|
||||
handleEnterCall={handleEnterCall}
|
||||
blockAction={blockAction}
|
||||
highlighted={highlighted}
|
||||
comment={comment}
|
||||
|
|
|
@ -40,7 +40,7 @@ export interface IMessageBroadcast {
|
|||
}
|
||||
|
||||
export interface IMessageCallButton {
|
||||
callJitsi?: () => void;
|
||||
handleEnterCall?: () => void;
|
||||
}
|
||||
|
||||
export interface IMessageContent {
|
||||
|
|
|
@ -4,37 +4,34 @@ import type { IRoom } from './IRoom';
|
|||
import type { IUser } from './IUser';
|
||||
import type { IMessage } from './IMessage';
|
||||
|
||||
export enum VideoConferenceStatus {
|
||||
export declare enum VideoConferenceStatus {
|
||||
CALLING = 0,
|
||||
STARTED = 1,
|
||||
EXPIRED = 2,
|
||||
ENDED = 3,
|
||||
DECLINED = 4
|
||||
}
|
||||
|
||||
export type DirectCallInstructions = {
|
||||
export declare type DirectCallInstructions = {
|
||||
type: 'direct';
|
||||
callee: IUser['_id'];
|
||||
calleeId: IUser['_id'];
|
||||
callId: string;
|
||||
};
|
||||
|
||||
export type ConferenceInstructions = {
|
||||
export declare type ConferenceInstructions = {
|
||||
type: 'videoconference';
|
||||
callId: string;
|
||||
rid: IRoom['_id'];
|
||||
};
|
||||
|
||||
export type LivechatInstructions = {
|
||||
export declare type LivechatInstructions = {
|
||||
type: 'livechat';
|
||||
callId: string;
|
||||
};
|
||||
|
||||
export type VideoConferenceType = DirectCallInstructions['type'] | ConferenceInstructions['type'] | LivechatInstructions['type'];
|
||||
|
||||
export declare type VideoConferenceType =
|
||||
| DirectCallInstructions['type']
|
||||
| ConferenceInstructions['type']
|
||||
| LivechatInstructions['type'];
|
||||
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
||||
ts: Date;
|
||||
}
|
||||
|
||||
export interface IVideoConference extends IRocketChatRecord {
|
||||
type: VideoConferenceType;
|
||||
rid: string;
|
||||
|
@ -45,51 +42,68 @@ export interface IVideoConference extends IRocketChatRecord {
|
|||
ended?: IMessage['_id'];
|
||||
};
|
||||
url?: string;
|
||||
|
||||
createdBy: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
createdBy: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||
createdAt: Date;
|
||||
|
||||
endedBy?: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
endedBy?: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||
endedAt?: Date;
|
||||
|
||||
providerName: string;
|
||||
providerData?: Record<string, any>;
|
||||
|
||||
ringing?: boolean;
|
||||
}
|
||||
|
||||
export interface IDirectVideoConference extends IVideoConference {
|
||||
type: 'direct';
|
||||
}
|
||||
|
||||
export interface IGroupVideoConference extends IVideoConference {
|
||||
type: 'videoconference';
|
||||
anonymousUsers: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ILivechatVideoConference extends IVideoConference {
|
||||
type: 'livechat';
|
||||
}
|
||||
|
||||
export type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||
|
||||
export type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||
|
||||
export const isDirectVideoConference = (call: VideoConference | undefined | null): call is IDirectVideoConference =>
|
||||
call?.type === 'direct';
|
||||
|
||||
export const isGroupVideoConference = (call: VideoConference | undefined | null): call is IGroupVideoConference =>
|
||||
call?.type === 'videoconference';
|
||||
|
||||
export const isLivechatVideoConference = (call: VideoConference | undefined | null): call is ILivechatVideoConference =>
|
||||
call?.type === 'livechat';
|
||||
|
||||
type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||
type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||
type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||
|
||||
export type VideoConferenceCreateData = AtLeast<
|
||||
export declare type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||
export declare type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||
export declare const isDirectVideoConference: (call: VideoConference | undefined | null) => call is IDirectVideoConference;
|
||||
export declare const isGroupVideoConference: (call: VideoConference | undefined | null) => call is IGroupVideoConference;
|
||||
export declare const isLivechatVideoConference: (call: VideoConference | undefined | null) => call is ILivechatVideoConference;
|
||||
declare type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
declare type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
declare type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
export declare type VideoConferenceCreateData = AtLeast<
|
||||
DirectVideoConferenceCreateData | GroupVideoConferenceCreateData | LivechatVideoConferenceCreateData,
|
||||
'createdBy' | 'type' | 'rid' | 'providerName' | 'providerData'
|
||||
>;
|
||||
|
||||
export type VideoConferenceCapabilities = {
|
||||
mic?: boolean;
|
||||
cam?: boolean;
|
||||
title?: boolean;
|
||||
};
|
||||
|
||||
export type VideoConfStartProps = { roomId: string; title?: string; allowRinging?: boolean };
|
||||
|
||||
export type VideoConfJoinProps = {
|
||||
callId: string;
|
||||
state?: {
|
||||
mic?: boolean;
|
||||
cam?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type VideoConfCancelProps = {
|
||||
callId: string;
|
||||
};
|
||||
|
||||
export type VideoConfListProps = {
|
||||
roomId: string;
|
||||
count?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type VideoConfInfoProps = { callId: string };
|
||||
|
|
|
@ -1,27 +1,45 @@
|
|||
import { VideoConference } from '../../IVideoConference';
|
||||
import {
|
||||
VideoConfCancelProps,
|
||||
VideoConference,
|
||||
VideoConferenceCapabilities,
|
||||
VideoConferenceInstructions,
|
||||
VideoConfInfoProps,
|
||||
VideoConfJoinProps,
|
||||
VideoConfListProps,
|
||||
VideoConfStartProps
|
||||
} from '../../IVideoConference';
|
||||
import { PaginatedResult } from '../helpers/PaginatedResult';
|
||||
|
||||
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 };
|
||||
POST: (params: VideoConfStartProps) => { data: VideoConferenceInstructions & { providerName: string } };
|
||||
};
|
||||
|
||||
'video-conference.join': {
|
||||
POST: (params: VideoConfJoinProps) => { url: string; providerName: string };
|
||||
};
|
||||
|
||||
'video-conference.cancel': {
|
||||
POST: (params: { callId: string }) => void;
|
||||
POST: (params: VideoConfCancelProps) => void;
|
||||
};
|
||||
|
||||
'video-conference.info': {
|
||||
GET: (params: { callId: string }) => VideoConference & {
|
||||
capabilities: {
|
||||
mic?: boolean;
|
||||
cam?: boolean;
|
||||
title?: boolean;
|
||||
};
|
||||
};
|
||||
GET: (params: VideoConfInfoProps) => VideoConference & { capabilities: VideoConferenceCapabilities };
|
||||
};
|
||||
|
||||
'video-conference.list': {
|
||||
GET: (params: VideoConfListProps) => PaginatedResult<{ data: VideoConference[] }>;
|
||||
};
|
||||
|
||||
'video-conference.capabilities': {
|
||||
GET: () => { providerName: string; capabilities: VideoConferenceCapabilities };
|
||||
};
|
||||
|
||||
'video-conference.providers': {
|
||||
GET: () => { data: { key: string; label: string }[] };
|
||||
};
|
||||
|
||||
'video-conference/jitsi.update-timeout': {
|
||||
POST: (params: { roomId: string }) => void;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -877,6 +877,18 @@
|
|||
"Reply_in_direct_message": "Reply in Direct Message",
|
||||
"room_archived": "archived room",
|
||||
"room_unarchived": "unarchived room",
|
||||
"no-videoconf-provider-app-header": "Conference call not available",
|
||||
"no-videoconf-provider-app-body": "Conference call apps can be installed in the Rocket.Chat marketplace by a workspace admin.",
|
||||
"admin-no-videoconf-provider-app-header": "Conference call not enabled",
|
||||
"admin-no-videoconf-provider-app-body": "Conference call apps are available in the Rocket.Chat marketplace.",
|
||||
"no-active-video-conf-provider-header": "Conference call not enabled",
|
||||
"no-active-video-conf-provider-body": "A workspace admin needs to enable the conference call feature first.",
|
||||
"admin-no-active-video-conf-provider-header": "Conference call not enabled",
|
||||
"admin-no-active-video-conf-provider-body": "Configure conference calls in order to make it available on this workspace.",
|
||||
"video-conf-provider-not-configured-header": "Conference call not enabled",
|
||||
"video-conf-provider-not-configured-body": "A workspace admin needs to enable the conference calls feature first.",
|
||||
"admin-video-conf-provider-not-configured-header": "Conference call not enabled",
|
||||
"admin-video-conf-provider-not-configured-body": "Configure conference calls in order to make it available on this workspace.",
|
||||
"Presence_Cap_Warning_Title": "User status temporarily disabled",
|
||||
"Presence_Cap_Warning_Description": "Active connections have reached the limit for the workspace, thus the service that handles user status is disabled. It can be re-enabled manually in workspace settings.",
|
||||
"Learn_more": "Learn more"
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
|
||||
import i18n from '../../i18n';
|
||||
import { videoConfJoin } from '../methods/videoConf';
|
||||
|
||||
export const useVideoConf = (): { joinCall: (blockId: string) => void } => {
|
||||
const { showActionSheet } = useActionSheet();
|
||||
|
||||
const joinCall = useCallback(blockId => {
|
||||
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 { joinCall };
|
||||
};
|
|
@ -0,0 +1,113 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import { useActionSheet } from '../../containers/ActionSheet';
|
||||
import StartACallActionSheet from '../../containers/UIKit/VideoConferenceBlock/components/StartACallActionSheet';
|
||||
import { ISubscription, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import i18n from '../../i18n';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import database from '../database';
|
||||
import { getSubscriptionByRoomId } from '../database/services/Subscription';
|
||||
import { callJitsi } from '../methods';
|
||||
import { compareServerVersion, showErrorAlert } from '../methods/helpers';
|
||||
import { videoConfStartAndJoin } from '../methods/videoConf';
|
||||
import { Services } from '../services';
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
import { useSnaps } from './useSnaps';
|
||||
|
||||
const availabilityErrors = {
|
||||
NOT_CONFIGURED: 'video-conf-provider-not-configured',
|
||||
NOT_ACTIVE: 'no-active-video-conf-provider',
|
||||
NO_APP: 'no-videoconf-provider-app'
|
||||
} as const;
|
||||
|
||||
const handleErrors = (isAdmin: boolean, error: typeof availabilityErrors[keyof typeof availabilityErrors]) => {
|
||||
if (isAdmin) return showErrorAlert(i18n.t(`admin-${error}-body`), i18n.t(`admin-${error}-header`));
|
||||
return showErrorAlert(i18n.t(`${error}-body`), i18n.t(`${error}-header`));
|
||||
};
|
||||
|
||||
export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Promise<void>; showCallOption: boolean } => {
|
||||
const [showCallOption, setShowCallOption] = useState(false);
|
||||
|
||||
const serverVersion = useAppSelector(state => state.server.version);
|
||||
const jitsiEnabled = useAppSelector(state => state.settings.Jitsi_Enabled);
|
||||
const jitsiEnableTeams = useAppSelector(state => state.settings.Jitsi_Enable_Teams);
|
||||
const jitsiEnableChannels = useAppSelector(state => state.settings.Jitsi_Enable_Channels);
|
||||
const user = useAppSelector(state => getUserSelector(state));
|
||||
|
||||
const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
|
||||
|
||||
const { showActionSheet } = useActionSheet();
|
||||
const snaps = useSnaps([1250]);
|
||||
|
||||
const handleShowCallOption = (room: TSubscriptionModel) => {
|
||||
if (isServer5OrNewer) return setShowCallOption(true);
|
||||
const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams;
|
||||
const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels;
|
||||
|
||||
if (room.t === SubscriptionType.DIRECT) return setShowCallOption(!!jitsiEnabled);
|
||||
if (room.t === SubscriptionType.CHANNEL) return setShowCallOption(!isJitsiDisabledForChannels);
|
||||
if (room.t === SubscriptionType.GROUP) return setShowCallOption(!isJitsiDisabledForTeams);
|
||||
|
||||
return setShowCallOption(false);
|
||||
};
|
||||
|
||||
const canInitAnCall = async () => {
|
||||
if (isServer5OrNewer) {
|
||||
try {
|
||||
await Services.videoConferenceGetCapabilities();
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
const isAdmin = !!['admin'].find(role => user.roles?.includes(role));
|
||||
switch (error?.error) {
|
||||
case availabilityErrors.NOT_CONFIGURED:
|
||||
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
|
||||
case availabilityErrors.NOT_ACTIVE:
|
||||
return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE);
|
||||
case availabilityErrors.NO_APP:
|
||||
return handleErrors(isAdmin, availabilityErrors.NO_APP);
|
||||
default:
|
||||
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const initCall = async ({ cam, mic }: { cam: boolean; mic: boolean }) => {
|
||||
if (isServer5OrNewer) return videoConfStartAndJoin({ rid, cam, mic });
|
||||
const room = (await getSubscriptionByRoomId(rid)) as ISubscription;
|
||||
callJitsi({ room, cam });
|
||||
};
|
||||
|
||||
const showInitCallActionSheet = async () => {
|
||||
const canInit = await canInitAnCall();
|
||||
if (canInit) {
|
||||
showActionSheet({
|
||||
children: <StartACallActionSheet rid={rid} initCall={initCall} />,
|
||||
snaps
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const initSubscription = () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const observeSubCollection = db.get('subscriptions').query(Q.where('rid', rid)).observe();
|
||||
const subObserveQuery = observeSubCollection.subscribe(data => {
|
||||
if (data[0]) {
|
||||
handleShowCallOption(data[0]);
|
||||
subObserveQuery.unsubscribe();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("observeSubscriptions: Can't find subscription to observe");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initSubscription();
|
||||
}, []);
|
||||
|
||||
return { showInitCallActionSheet, showCallOption };
|
||||
};
|
|
@ -46,8 +46,8 @@ export function callJitsiWithoutServer(path: string): void {
|
|||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
||||
}
|
||||
|
||||
export async function callJitsi(room: ISubscription, onlyAudio = false): Promise<void> {
|
||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
export async function callJitsi({ room, cam = false }: { room: ISubscription; cam?: boolean }): Promise<void> {
|
||||
logEvent(cam ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
const url = await jitsiURL({ room });
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: cam, rid: room?.rid });
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ const handleBltPermission = async (): Promise<Permission[]> => {
|
|||
return [PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION];
|
||||
};
|
||||
|
||||
export const videoConfJoin = async (callId: string, cam: boolean) => {
|
||||
export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean): Promise<void> => {
|
||||
try {
|
||||
const result = await Services.videoConferenceJoin(callId, cam);
|
||||
const result = await Services.videoConferenceJoin(callId, cam, mic);
|
||||
if (result.success) {
|
||||
if (isAndroid) {
|
||||
const bltPermission = await handleBltPermission();
|
||||
|
@ -44,11 +44,11 @@ export const videoConfJoin = async (callId: string, cam: boolean) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const videoConfStartAndJoin = async (rid: string, cam: boolean) => {
|
||||
export const videoConfStartAndJoin = async ({ rid, cam, mic }: { rid: string; cam?: boolean; mic?: boolean }): Promise<void> => {
|
||||
try {
|
||||
const videoConfResponse: any = await Services.videoConferenceStart(rid);
|
||||
const videoConfResponse = await Services.videoConferenceStart(rid);
|
||||
if (videoConfResponse.success) {
|
||||
videoConfJoin(videoConfResponse.data.callId, cam);
|
||||
videoConfJoin(videoConfResponse.data.callId, cam, mic);
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorAlert(i18n.t('error-init-video-conf'));
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import BackgroundTimer from 'react-native-background-timer';
|
||||
|
||||
import { Services } from '../services';
|
||||
|
||||
let interval: number | null = null;
|
||||
|
||||
export const initVideoConfTimer = (rid: string): void => {
|
||||
if (rid) {
|
||||
Services.updateJitsiTimeout(rid).catch((e: unknown) => console.log(e));
|
||||
if (interval) {
|
||||
BackgroundTimer.clearInterval(interval);
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
interval = null;
|
||||
}
|
||||
interval = BackgroundTimer.setInterval(() => {
|
||||
Services.updateJitsiTimeout(rid).catch((e: unknown) => console.log(e));
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
export const endVideoConfTimer = (): void => {
|
||||
if (interval) {
|
||||
BackgroundTimer.clearInterval(interval);
|
||||
interval = null;
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
}
|
||||
};
|
|
@ -936,8 +936,10 @@ 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 videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) =>
|
||||
sdk.post('video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } });
|
||||
|
||||
export const videoConferenceGetCapabilities = () => sdk.get('video-conference.capabilities');
|
||||
|
||||
export const videoConferenceStart = (roomId: string) => sdk.post('video-conference.start', { roomId });
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
import React from 'react';
|
||||
import { BackHandler, NativeEventSubscription } from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
|
||||
import WebView from 'react-native-webview';
|
||||
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
|
||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
|
||||
import { IBaseScreen } from '../definitions';
|
||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||
import { Services } from '../lib/services';
|
||||
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { withTheme } from '../theme';
|
||||
|
||||
|
@ -20,7 +19,6 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
|||
private rid: string;
|
||||
private url: string;
|
||||
private videoConf: boolean;
|
||||
private jitsiTimeout: number | null;
|
||||
private backHandler!: NativeEventSubscription;
|
||||
|
||||
constructor(props: TJitsiMeetViewProps) {
|
||||
|
@ -28,7 +26,6 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
|||
this.rid = props.route.params?.rid;
|
||||
this.url = props.route.params?.url;
|
||||
this.videoConf = !!props.route.params?.videoConf;
|
||||
this.jitsiTimeout = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -50,10 +47,8 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
|||
|
||||
componentWillUnmount() {
|
||||
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||
if (this.jitsiTimeout && !this.videoConf) {
|
||||
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||
this.jitsiTimeout = null;
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
if (!this.videoConf) {
|
||||
endVideoConfTimer();
|
||||
}
|
||||
this.backHandler.remove();
|
||||
deactivateKeepAwake();
|
||||
|
@ -64,15 +59,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
|||
onConferenceJoined = () => {
|
||||
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);
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
this.jitsiTimeout = null;
|
||||
}
|
||||
this.jitsiTimeout = BackgroundTimer.setInterval(() => {
|
||||
Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||
}, 10000);
|
||||
initVideoConfTimer(this.rid);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -90,7 +77,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
|||
render() {
|
||||
return (
|
||||
<WebView
|
||||
source={{ uri: `${this.url}&config.disableDeepLinking=true` }}
|
||||
source={{ uri: `${this.url}${this.url.includes('#config') ? '&' : '#'}config.disableDeepLinking=true` }}
|
||||
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
style={{ flex: 1 }}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { useAppSelector } from '../lib/hooks';
|
|||
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
|
||||
|
||||
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
||||
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
|
||||
|
@ -16,7 +17,7 @@ const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLF
|
|||
const JitsiMeetView = (): React.ReactElement => {
|
||||
const { goBack } = useNavigation();
|
||||
const {
|
||||
params: { url, onlyAudio, videoConf }
|
||||
params: { url, onlyAudio, videoConf, rid }
|
||||
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
|
||||
const user = useAppSelector(state => getUserSelector(state));
|
||||
const baseUrl = useAppSelector(state => state.server.server);
|
||||
|
@ -60,8 +61,10 @@ const JitsiMeetView = (): React.ReactElement => {
|
|||
}
|
||||
};
|
||||
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
||||
if (!videoConf) initVideoConfTimer(rid);
|
||||
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
|
||||
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||
if (!videoConf) endVideoConfTimer();
|
||||
goBack();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as List from '../../../containers/List';
|
||||
import i18n from '../../../i18n';
|
||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
||||
|
||||
export default function CallSection({ rid }: { rid: string }): React.ReactElement | null {
|
||||
const { showCallOption, showInitCallActionSheet } = useVideoConf(rid);
|
||||
|
||||
if (showCallOption)
|
||||
return (
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title={i18n.t('Call')}
|
||||
onPress={showInitCallActionSheet}
|
||||
testID='room-actions-call'
|
||||
left={() => <List.Icon name='phone' />}
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
);
|
||||
return null;
|
||||
}
|
|
@ -33,7 +33,7 @@ import sharedStyles from '../Styles';
|
|||
import styles from './styles';
|
||||
import { ERoomType } from '../../definitions/ERoomType';
|
||||
import { E2E_ROOM_TYPES, SWITCH_TRACK_COLOR, themes } from '../../lib/constants';
|
||||
import { callJitsi, getPermalinkChannel } from '../../lib/methods';
|
||||
import { getPermalinkChannel } from '../../lib/methods';
|
||||
import {
|
||||
canAutoTranslate as canAutoTranslateMethod,
|
||||
getRoomAvatar,
|
||||
|
@ -48,9 +48,9 @@ import { getSubscriptionByRoomId } from '../../lib/database/services/Subscriptio
|
|||
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { closeLivechat } from '../../lib/methods/helpers/closeLivechat';
|
||||
import { videoConfStartAndJoin } from '../../lib/methods/videoConf';
|
||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||
import { ILivechatTag } from '../../definitions/ILivechatTag';
|
||||
import CallSection from './components/CallSection';
|
||||
|
||||
interface IOnPressTouch {
|
||||
<T extends keyof ChatsStackParamList>(item: { route?: T; params?: ChatsStackParamList[T]; event?: Function }): void;
|
||||
|
@ -730,16 +730,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
|||
}
|
||||
};
|
||||
|
||||
startVideoConf = ({ video }: { video: boolean }): void => {
|
||||
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;
|
||||
|
@ -815,63 +805,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
|||
);
|
||||
};
|
||||
|
||||
renderJitsi = () => {
|
||||
const { room } = this.state;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Voice_call'
|
||||
onPress={() => this.startVideoConf({ video: false })}
|
||||
testID='room-actions-voice'
|
||||
left={() => <List.Icon name='phone' />}
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Video_call'
|
||||
onPress={() => this.startVideoConf({ video: true })}
|
||||
testID='room-actions-video'
|
||||
left={() => <List.Icon name='camera' />}
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
);
|
||||
};
|
||||
|
||||
renderE2EEncryption = () => {
|
||||
const { room } = this.state;
|
||||
const { encryptionEnabled } = this.props;
|
||||
|
@ -1108,7 +1041,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
|||
<StatusBar />
|
||||
<List.Container testID='room-actions-scrollview'>
|
||||
{this.renderRoomInfo()}
|
||||
{this.renderJitsi()}
|
||||
<CallSection rid={rid} />
|
||||
{this.renderE2EEncryption()}
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
|
@ -1299,13 +1232,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
|||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
userId: getUserSelector(state).id,
|
||||
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,
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
|
||||
import { CustomIcon, TIconsName } from '../../../containers/CustomIcon';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
||||
import i18n from '../../../i18n';
|
||||
import { useAppSelector } from '../../../lib/hooks';
|
||||
import { compareServerVersion } from '../../../lib/methods/helpers';
|
||||
|
||||
// TODO: change other icons on future
|
||||
function UserInfoButton({
|
||||
danger,
|
||||
iconName,
|
||||
onPress,
|
||||
label,
|
||||
showIcon
|
||||
}: {
|
||||
danger?: boolean;
|
||||
iconName: TIconsName;
|
||||
onPress?: (prop: any) => void;
|
||||
label: string;
|
||||
showIcon?: boolean;
|
||||
}): React.ReactElement | null {
|
||||
const { colors } = useTheme();
|
||||
const color = danger ? colors.dangerColor : colors.actionTintColor;
|
||||
|
||||
if (showIcon)
|
||||
return (
|
||||
<BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
|
||||
<CustomIcon name={iconName} size={30} color={color} />
|
||||
<Text style={[styles.roomButtonText, { color }]}>{label}</Text>
|
||||
</BorderlessButton>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CallButton({ rid, isDirect }: { rid: string; isDirect: boolean }): React.ReactElement | null {
|
||||
const { showCallOption, showInitCallActionSheet } = useVideoConf(rid);
|
||||
const serverVersion = useAppSelector(state => state.server.version);
|
||||
const greaterThanFive = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
|
||||
|
||||
const showIcon = greaterThanFive ? showCallOption : showCallOption && isDirect;
|
||||
|
||||
return <UserInfoButton onPress={showInitCallActionSheet} iconName='phone' label={i18n.t('Call')} showIcon={showIcon} />;
|
||||
}
|
|
@ -1,43 +1,43 @@
|
|||
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import React from 'react';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import { connect } from 'react-redux';
|
||||
import UAParser from 'ua-parser-js';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import UAParser from 'ua-parser-js';
|
||||
|
||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||
import Status from '../../containers/Status';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import sharedStyles from '../Styles';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import { MarkdownPreview } from '../../containers/markdown';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
import Livechat from './Livechat';
|
||||
import Channel from './Channel';
|
||||
import Direct from './Direct';
|
||||
import styles from './styles';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { SubscriptionType, TSubscriptionModel, ISubscription, IUser, IApplicationState } from '../../definitions';
|
||||
import Status from '../../containers/Status';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
||||
import { callJitsi } from '../../lib/methods';
|
||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
import I18n from '../../i18n';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
import { Services } from '../../lib/services';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import sharedStyles from '../Styles';
|
||||
import Channel from './Channel';
|
||||
import { CallButton } from './components/UserInfoButton';
|
||||
import Direct from './Direct';
|
||||
import Livechat from './Livechat';
|
||||
import styles from './styles';
|
||||
|
||||
interface IGetRoomTitle {
|
||||
room: ISubscription;
|
||||
|
@ -386,11 +386,6 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
}
|
||||
};
|
||||
|
||||
videoCall = () => {
|
||||
const { room } = this.state;
|
||||
callJitsi(room);
|
||||
};
|
||||
|
||||
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
|
||||
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
||||
try {
|
||||
|
@ -425,8 +420,7 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
};
|
||||
|
||||
renderButtons = () => {
|
||||
const { roomFromRid, roomUser } = this.state;
|
||||
const { jitsiEnabled } = this.props;
|
||||
const { roomFromRid, roomUser, room } = this.state;
|
||||
|
||||
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
||||
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
||||
|
@ -442,9 +436,7 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
return (
|
||||
<View style={styles.roomButtonsContainer}>
|
||||
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
||||
{jitsiEnabled && this.isDirect
|
||||
? this.renderButton(() => this.handleCreateDirectMessage(this.videoCall), 'camera', I18n.t('Video_call'))
|
||||
: null}
|
||||
<CallButton isDirect={this.isDirect} rid={room.rid} />
|
||||
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
||||
? this.renderButton(
|
||||
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
||||
|
|
|
@ -10,8 +10,7 @@ import * as HeaderButton from '../../containers/HeaderButton';
|
|||
import database from '../../lib/database';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import { isTeamRoom } from '../../lib/methods/helpers/room';
|
||||
import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, ISubscription, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
|
||||
import i18n from '../../i18n';
|
||||
|
@ -20,12 +19,11 @@ import { onHoldLivechat, returnLivechat } from '../../lib/services/restApi';
|
|||
import { closeLivechat as closeLivechatService } from '../../lib/methods/helpers/closeLivechat';
|
||||
import { Services } from '../../lib/services';
|
||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||
import HeaderCallButton from './components/HeaderCallButton';
|
||||
|
||||
interface IRightButtonsProps {
|
||||
interface IRightButtonsProps extends Pick<ISubscription, 't'> {
|
||||
userId?: string;
|
||||
threadsEnabled: boolean;
|
||||
rid?: string;
|
||||
t: string;
|
||||
tmid?: string;
|
||||
teamId?: string;
|
||||
isMasterDetail: boolean;
|
||||
|
@ -43,6 +41,7 @@ interface IRightButtonsProps {
|
|||
livechatRequestComment: boolean;
|
||||
showActionSheet: Function;
|
||||
departmentId?: string;
|
||||
rid?: string;
|
||||
}
|
||||
|
||||
interface IRigthButtonsState {
|
||||
|
@ -338,7 +337,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
|||
|
||||
render() {
|
||||
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
|
||||
const { t, tmid, threadsEnabled, teamId, joined } = this.props;
|
||||
const { t, tmid, threadsEnabled, rid } = this.props;
|
||||
|
||||
if (t === 'l') {
|
||||
if (!this.isOmnichannelPreview()) {
|
||||
|
@ -363,9 +362,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
|||
}
|
||||
return (
|
||||
<HeaderButton.Container>
|
||||
{isTeamRoom({ teamId, joined }) ? (
|
||||
<HeaderButton.Item iconName='channel-public' onPress={this.goTeamChannels} testID='room-view-header-team-channels' />
|
||||
) : null}
|
||||
{rid ? <HeaderCallButton rid={rid} /> : null}
|
||||
{threadsEnabled ? (
|
||||
<HeaderButton.Item
|
||||
iconName='threads'
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as HeaderButton from '../../../containers/HeaderButton';
|
||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
||||
|
||||
export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null {
|
||||
const { showInitCallActionSheet, showCallOption } = useVideoConf(rid);
|
||||
|
||||
if (showCallOption)
|
||||
return <HeaderButton.Item iconName='phone' onPress={showInitCallActionSheet} testID='room-view-header-call' />;
|
||||
return null;
|
||||
}
|
|
@ -627,7 +627,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
joined={joined}
|
||||
status={room.status}
|
||||
omnichannelPermissions={omnichannelPermissions}
|
||||
t={this.t || t}
|
||||
t={(this.t || t) as SubscriptionType}
|
||||
encrypted={encrypted}
|
||||
navigation={navigation}
|
||||
toggleFollowThread={this.toggleFollowThread}
|
||||
|
@ -1229,14 +1229,15 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
});
|
||||
};
|
||||
|
||||
handleCallJitsi = () => {
|
||||
// OLD METHOD - support versions before 5.0.0
|
||||
handleEnterCall = () => {
|
||||
const { room } = this.state;
|
||||
if ('id' in room) {
|
||||
const { jitsiTimeout } = room;
|
||||
if (jitsiTimeout && jitsiTimeout < new Date()) {
|
||||
showErrorAlert(I18n.t('Call_already_ended'));
|
||||
} else {
|
||||
callJitsi(room);
|
||||
callJitsi({ room });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1382,7 +1383,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
||||
navToRoomInfo={this.navToRoomInfo}
|
||||
getCustomEmoji={this.getCustomEmoji}
|
||||
callJitsi={this.handleCallJitsi}
|
||||
handleEnterCall={this.handleEnterCall}
|
||||
blockAction={this.blockAction}
|
||||
threadBadgeColor={this.getBadgeColor(item?.id)}
|
||||
toggleFollowThread={this.toggleFollowThread}
|
||||
|
|
Loading…
Reference in New Issue