[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 { getSubscriptionByRoomId } from '../../../../lib/database/services/Subscription';
|
||||||
import { useAppSelector } from '../../../../lib/hooks';
|
import { useAppSelector } from '../../../../lib/hooks';
|
||||||
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
||||||
import { videoConfStartAndJoin } from '../../../../lib/methods/videoConf';
|
|
||||||
import { useTheme } from '../../../../theme';
|
import { useTheme } from '../../../../theme';
|
||||||
import { useActionSheet } from '../../../ActionSheet';
|
import { useActionSheet } from '../../../ActionSheet';
|
||||||
import AvatarContainer from '../../../Avatar';
|
import AvatarContainer from '../../../Avatar';
|
||||||
|
@ -16,12 +15,12 @@ import { BUTTON_HIT_SLOP } from '../../../message/utils';
|
||||||
import StatusContainer from '../../../Status';
|
import StatusContainer from '../../../Status';
|
||||||
import useStyle from './styles';
|
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 style = useStyle();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '', rid: '' });
|
const [user, setUser] = useState({ username: '', avatar: '', uid: '' });
|
||||||
const [phone, setPhone] = useState(true);
|
const [mic, setMic] = useState(true);
|
||||||
const [camera, setCamera] = useState(false);
|
const [cam, setCam] = useState(false);
|
||||||
const username = useAppSelector(state => state.login.user.username);
|
const username = useAppSelector(state => state.login.user.username);
|
||||||
|
|
||||||
const { hideActionSheet } = useActionSheet();
|
const { hideActionSheet } = useActionSheet();
|
||||||
|
@ -31,7 +30,7 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
||||||
const room = await getSubscriptionByRoomId(rid);
|
const room = await getSubscriptionByRoomId(rid);
|
||||||
const uid = (await getUidDirectMessage(room)) as string;
|
const uid = (await getUidDirectMessage(room)) as string;
|
||||||
const avt = getRoomAvatar(room);
|
const avt = getRoomAvatar(room);
|
||||||
setUser({ uid, username: room?.name || '', avatar: avt, rid: room?.id || '' });
|
setUser({ uid, username: room?.name || '', avatar: avt });
|
||||||
})();
|
})();
|
||||||
}, [rid]);
|
}, [rid]);
|
||||||
|
|
||||||
|
@ -43,25 +42,27 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
||||||
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
||||||
<View style={style.actionSheetHeaderButtons}>
|
<View style={style.actionSheetHeaderButtons}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => setCamera(!camera)}
|
onPress={() => setCam(!cam)}
|
||||||
style={[style.iconCallContainer, camera && style.enabledBackground, { marginRight: 6 }]}
|
style={[style.iconCallContainer, cam && style.enabledBackground, { marginRight: 6 }]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
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>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => setPhone(!phone)}
|
onPress={() => setMic(!mic)}
|
||||||
style={[style.iconCallContainer, phone && style.enabledBackground]}
|
style={[style.iconCallContainer, mic && style.enabledBackground]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
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>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={style.actionSheetUsernameContainer}>
|
<View style={style.actionSheetUsernameContainer}>
|
||||||
<AvatarContainer text={user.avatar} size={36} />
|
<AvatarContainer text={user.avatar} size={36} />
|
||||||
<StatusContainer size={16} id={user.uid} style={{ marginLeft: 8, marginRight: 6 }} />
|
<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>
|
||||||
<View style={style.actionSheetPhotoContainer}>
|
<View style={style.actionSheetPhotoContainer}>
|
||||||
<AvatarContainer size={62} text={username} />
|
<AvatarContainer size={62} text={username} />
|
||||||
|
@ -70,7 +71,7 @@ export default function CallAgainActionSheet({ rid }: { rid: string }): React.Re
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
hideActionSheet();
|
hideActionSheet();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
videoConfStartAndJoin(user.rid, camera);
|
initCall({ cam, mic });
|
||||||
}, 100);
|
}, 100);
|
||||||
}}
|
}}
|
||||||
title={i18n.t('Call')}
|
title={i18n.t('Call')}
|
|
@ -3,17 +3,16 @@ import { Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
|
||||||
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
const { joinCall } = useVideoConf();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoConferenceBaseContainer variant='incoming'>
|
<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>
|
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
||||||
|
|
|
@ -6,9 +6,7 @@ import { IUser } from '../../../../definitions';
|
||||||
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { useAppSelector } from '../../../../lib/hooks';
|
import { useAppSelector } from '../../../../lib/hooks';
|
||||||
import { useSnaps } from '../../../../lib/hooks/useSnaps';
|
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||||
import { useActionSheet } from '../../../ActionSheet';
|
|
||||||
import CallAgainActionSheet from './CallAgainActionSheet';
|
|
||||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
@ -26,8 +24,7 @@ export default function VideoConferenceEnded({
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
const username = useAppSelector(state => state.login.user.username);
|
const username = useAppSelector(state => state.login.user.username);
|
||||||
const { showActionSheet } = useActionSheet();
|
const { showInitCallActionSheet } = useVideoConf(rid);
|
||||||
const snaps = useSnaps([1250]);
|
|
||||||
|
|
||||||
const onlyAuthorOnCall = users.length === 1 && users.some(user => user.username === createdBy.username);
|
const onlyAuthorOnCall = users.length === 1 && users.some(user => user.username === createdBy.username);
|
||||||
|
|
||||||
|
@ -35,15 +32,7 @@ export default function VideoConferenceEnded({
|
||||||
<VideoConferenceBaseContainer variant='ended'>
|
<VideoConferenceBaseContainer variant='ended'>
|
||||||
{type === 'direct' ? (
|
{type === 'direct' ? (
|
||||||
<>
|
<>
|
||||||
<Touchable
|
<Touchable style={style.callToActionCallBack} onPress={showInitCallActionSheet}>
|
||||||
style={style.callToActionCallBack}
|
|
||||||
onPress={() =>
|
|
||||||
showActionSheet({
|
|
||||||
children: <CallAgainActionSheet rid={rid} />,
|
|
||||||
snaps
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text style={style.callToActionCallBackText}>
|
<Text style={style.callToActionCallBackText}>
|
||||||
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -3,18 +3,17 @@ import { Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
|
||||||
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
const { joinCall } = useVideoConf();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoConferenceBaseContainer variant='outgoing'>
|
<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>
|
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<CallParticipants users={users} />
|
<CallParticipants users={users} />
|
||||||
|
|
|
@ -100,7 +100,8 @@ export default function useStyle() {
|
||||||
actionSheetUsername: {
|
actionSheetUsername: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...sharedStyles.textBold,
|
...sharedStyles.textBold,
|
||||||
color: colors.passcodePrimary
|
color: colors.passcodePrimary,
|
||||||
|
flexShrink: 1
|
||||||
},
|
},
|
||||||
enabledBackground: {
|
enabledBackground: {
|
||||||
backgroundColor: colors.conferenceCallEnabledIconBackground
|
backgroundColor: colors.conferenceCallEnabledIconBackground
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { BlockContext } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
|
|
||||||
import { useVideoConf } from '../../lib/hooks/useVideoConf';
|
import { videoConfJoin } from '../../lib/methods/videoConf';
|
||||||
import { IText } from './interfaces';
|
import { IText } from './interfaces';
|
||||||
|
|
||||||
export const textParser = ([{ text }]: IText[]) => text;
|
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 { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
||||||
const { value = initialValue } = values[actionId] || {};
|
const { value = initialValue } = values[actionId] || {};
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { joinCall } = useVideoConf();
|
|
||||||
|
|
||||||
const error = errors && actionId && errors[actionId];
|
const error = errors && actionId && errors[actionId];
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
||||||
try {
|
try {
|
||||||
if (appId === 'videoconf-core' && blockId) {
|
if (appId === 'videoconf-core' && blockId) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return joinCall(blockId);
|
return videoConfJoin(blockId);
|
||||||
}
|
}
|
||||||
await action({
|
await action({
|
||||||
blockId,
|
blockId,
|
||||||
|
|
|
@ -10,12 +10,12 @@ import { themes } from '../../lib/constants';
|
||||||
import { IMessageCallButton } from './interfaces';
|
import { IMessageCallButton } from './interfaces';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
|
const CallButton = React.memo(({ handleEnterCall }: IMessageCallButton) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={callJitsi}
|
onPress={handleEnterCall}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
|
|
@ -50,7 +50,7 @@ interface IMessageContainerProps {
|
||||||
showAttachment: (file: IAttachment) => void;
|
showAttachment: (file: IAttachment) => void;
|
||||||
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
||||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
callJitsi?: () => void;
|
handleEnterCall?: () => void;
|
||||||
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||||
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||||
threadBadgeColor?: string;
|
threadBadgeColor?: string;
|
||||||
|
@ -69,7 +69,6 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
getCustomEmoji: () => null,
|
getCustomEmoji: () => null,
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
callJitsi: () => {},
|
|
||||||
blockAction: () => {},
|
blockAction: () => {},
|
||||||
archived: false,
|
archived: false,
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
|
@ -338,7 +337,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
navToRoomInfo,
|
navToRoomInfo,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
isThreadRoom,
|
isThreadRoom,
|
||||||
callJitsi,
|
handleEnterCall,
|
||||||
blockAction,
|
blockAction,
|
||||||
rid,
|
rid,
|
||||||
threadBadgeColor,
|
threadBadgeColor,
|
||||||
|
@ -456,7 +455,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
showAttachment={showAttachment}
|
showAttachment={showAttachment}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
navToRoomInfo={navToRoomInfo}
|
navToRoomInfo={navToRoomInfo}
|
||||||
callJitsi={callJitsi}
|
handleEnterCall={handleEnterCall}
|
||||||
blockAction={blockAction}
|
blockAction={blockAction}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export interface IMessageBroadcast {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageCallButton {
|
export interface IMessageCallButton {
|
||||||
callJitsi?: () => void;
|
handleEnterCall?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
|
|
|
@ -4,37 +4,34 @@ import type { IRoom } from './IRoom';
|
||||||
import type { IUser } from './IUser';
|
import type { IUser } from './IUser';
|
||||||
import type { IMessage } from './IMessage';
|
import type { IMessage } from './IMessage';
|
||||||
|
|
||||||
export enum VideoConferenceStatus {
|
export declare enum VideoConferenceStatus {
|
||||||
CALLING = 0,
|
CALLING = 0,
|
||||||
STARTED = 1,
|
STARTED = 1,
|
||||||
EXPIRED = 2,
|
EXPIRED = 2,
|
||||||
ENDED = 3,
|
ENDED = 3,
|
||||||
DECLINED = 4
|
DECLINED = 4
|
||||||
}
|
}
|
||||||
|
export declare type DirectCallInstructions = {
|
||||||
export type DirectCallInstructions = {
|
|
||||||
type: 'direct';
|
type: 'direct';
|
||||||
callee: IUser['_id'];
|
calleeId: IUser['_id'];
|
||||||
callId: string;
|
callId: string;
|
||||||
};
|
};
|
||||||
|
export declare type ConferenceInstructions = {
|
||||||
export type ConferenceInstructions = {
|
|
||||||
type: 'videoconference';
|
type: 'videoconference';
|
||||||
callId: string;
|
callId: string;
|
||||||
rid: IRoom['_id'];
|
rid: IRoom['_id'];
|
||||||
};
|
};
|
||||||
|
export declare type LivechatInstructions = {
|
||||||
export type LivechatInstructions = {
|
|
||||||
type: 'livechat';
|
type: 'livechat';
|
||||||
callId: string;
|
callId: string;
|
||||||
};
|
};
|
||||||
|
export declare type VideoConferenceType =
|
||||||
export type VideoConferenceType = DirectCallInstructions['type'] | ConferenceInstructions['type'] | LivechatInstructions['type'];
|
| DirectCallInstructions['type']
|
||||||
|
| ConferenceInstructions['type']
|
||||||
|
| LivechatInstructions['type'];
|
||||||
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
||||||
ts: Date;
|
ts: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVideoConference extends IRocketChatRecord {
|
export interface IVideoConference extends IRocketChatRecord {
|
||||||
type: VideoConferenceType;
|
type: VideoConferenceType;
|
||||||
rid: string;
|
rid: string;
|
||||||
|
@ -45,51 +42,68 @@ export interface IVideoConference extends IRocketChatRecord {
|
||||||
ended?: IMessage['_id'];
|
ended?: IMessage['_id'];
|
||||||
};
|
};
|
||||||
url?: string;
|
url?: string;
|
||||||
|
createdBy: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||||
createdBy: Pick<IUser, '_id' | 'username' | 'name'>;
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
endedBy?: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||||
endedBy?: Pick<IUser, '_id' | 'username' | 'name'>;
|
|
||||||
endedAt?: Date;
|
endedAt?: Date;
|
||||||
|
|
||||||
providerName: string;
|
providerName: string;
|
||||||
providerData?: Record<string, any>;
|
providerData?: Record<string, any>;
|
||||||
|
|
||||||
ringing?: boolean;
|
ringing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDirectVideoConference extends IVideoConference {
|
export interface IDirectVideoConference extends IVideoConference {
|
||||||
type: 'direct';
|
type: 'direct';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGroupVideoConference extends IVideoConference {
|
export interface IGroupVideoConference extends IVideoConference {
|
||||||
type: 'videoconference';
|
type: 'videoconference';
|
||||||
anonymousUsers: number;
|
anonymousUsers: number;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILivechatVideoConference extends IVideoConference {
|
export interface ILivechatVideoConference extends IVideoConference {
|
||||||
type: 'livechat';
|
type: 'livechat';
|
||||||
}
|
}
|
||||||
|
export declare type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||||
export type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
export declare type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||||
|
export declare const isDirectVideoConference: (call: VideoConference | undefined | null) => call is IDirectVideoConference;
|
||||||
export type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
export declare const isGroupVideoConference: (call: VideoConference | undefined | null) => call is IGroupVideoConference;
|
||||||
|
export declare const isLivechatVideoConference: (call: VideoConference | undefined | null) => call is ILivechatVideoConference;
|
||||||
export const isDirectVideoConference = (call: VideoConference | undefined | null): call is IDirectVideoConference =>
|
declare type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & {
|
||||||
call?.type === 'direct';
|
createdBy: IUser['_id'];
|
||||||
|
};
|
||||||
export const isGroupVideoConference = (call: VideoConference | undefined | null): call is IGroupVideoConference =>
|
declare type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & {
|
||||||
call?.type === 'videoconference';
|
createdBy: IUser['_id'];
|
||||||
|
};
|
||||||
export const isLivechatVideoConference = (call: VideoConference | undefined | null): call is ILivechatVideoConference =>
|
declare type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & {
|
||||||
call?.type === 'livechat';
|
createdBy: IUser['_id'];
|
||||||
|
};
|
||||||
type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
export declare type VideoConferenceCreateData = AtLeast<
|
||||||
type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
|
||||||
type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
|
||||||
|
|
||||||
export type VideoConferenceCreateData = AtLeast<
|
|
||||||
DirectVideoConferenceCreateData | GroupVideoConferenceCreateData | LivechatVideoConferenceCreateData,
|
DirectVideoConferenceCreateData | GroupVideoConferenceCreateData | LivechatVideoConferenceCreateData,
|
||||||
'createdBy' | 'type' | 'rid' | 'providerName' | 'providerData'
|
'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 = {
|
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': {
|
'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': {
|
'video-conference.cancel': {
|
||||||
POST: (params: { callId: string }) => void;
|
POST: (params: VideoConfCancelProps) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
'video-conference.info': {
|
'video-conference.info': {
|
||||||
GET: (params: { callId: string }) => VideoConference & {
|
GET: (params: VideoConfInfoProps) => VideoConference & { capabilities: VideoConferenceCapabilities };
|
||||||
capabilities: {
|
|
||||||
mic?: boolean;
|
|
||||||
cam?: boolean;
|
|
||||||
title?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
'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",
|
"Reply_in_direct_message": "Reply in Direct Message",
|
||||||
"room_archived": "archived room",
|
"room_archived": "archived room",
|
||||||
"room_unarchived": "unarchived 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_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.",
|
"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"
|
"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 });
|
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function callJitsi(room: ISubscription, onlyAudio = false): Promise<void> {
|
export async function callJitsi({ room, cam = false }: { room: ISubscription; cam?: boolean }): Promise<void> {
|
||||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
logEvent(cam ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||||
const url = await jitsiURL({ room });
|
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];
|
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 {
|
try {
|
||||||
const result = await Services.videoConferenceJoin(callId, cam);
|
const result = await Services.videoConferenceJoin(callId, cam, mic);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const bltPermission = await handleBltPermission();
|
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 {
|
try {
|
||||||
const videoConfResponse: any = await Services.videoConferenceStart(rid);
|
const videoConfResponse = await Services.videoConferenceStart(rid);
|
||||||
if (videoConfResponse.success) {
|
if (videoConfResponse.success) {
|
||||||
videoConfJoin(videoConfResponse.data.callId, cam);
|
videoConfJoin(videoConfResponse.data.callId, cam, mic);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(i18n.t('error-init-video-conf'));
|
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 toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite });
|
||||||
|
|
||||||
export const videoConferenceJoin = (callId: string, cam: boolean) =>
|
export const videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) =>
|
||||||
sdk.post('video-conference.join', { callId, state: { cam } });
|
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 });
|
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 React from 'react';
|
||||||
import { BackHandler, NativeEventSubscription } from 'react-native';
|
import { BackHandler, NativeEventSubscription } from 'react-native';
|
||||||
import BackgroundTimer from 'react-native-background-timer';
|
|
||||||
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
|
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
|
||||||
import WebView from 'react-native-webview';
|
import WebView from 'react-native-webview';
|
||||||
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
|
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
|
||||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
|
||||||
|
|
||||||
import { IBaseScreen } from '../definitions';
|
import { IBaseScreen } from '../definitions';
|
||||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
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 { ChatsStackParamList } from '../stacks/types';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
|
||||||
|
@ -20,7 +19,6 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
private rid: string;
|
private rid: string;
|
||||||
private url: string;
|
private url: string;
|
||||||
private videoConf: boolean;
|
private videoConf: boolean;
|
||||||
private jitsiTimeout: number | null;
|
|
||||||
private backHandler!: NativeEventSubscription;
|
private backHandler!: NativeEventSubscription;
|
||||||
|
|
||||||
constructor(props: TJitsiMeetViewProps) {
|
constructor(props: TJitsiMeetViewProps) {
|
||||||
|
@ -28,7 +26,6 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params?.rid;
|
||||||
this.url = props.route.params?.url;
|
this.url = props.route.params?.url;
|
||||||
this.videoConf = !!props.route.params?.videoConf;
|
this.videoConf = !!props.route.params?.videoConf;
|
||||||
this.jitsiTimeout = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -50,10 +47,8 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||||
if (this.jitsiTimeout && !this.videoConf) {
|
if (!this.videoConf) {
|
||||||
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
endVideoConfTimer();
|
||||||
this.jitsiTimeout = null;
|
|
||||||
BackgroundTimer.stopBackgroundTimer();
|
|
||||||
}
|
}
|
||||||
this.backHandler.remove();
|
this.backHandler.remove();
|
||||||
deactivateKeepAwake();
|
deactivateKeepAwake();
|
||||||
|
@ -64,15 +59,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
onConferenceJoined = () => {
|
onConferenceJoined = () => {
|
||||||
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
||||||
if (this.rid && !this.videoConf) {
|
if (this.rid && !this.videoConf) {
|
||||||
Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
initVideoConfTimer(this.rid);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,7 +77,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<WebView
|
<WebView
|
||||||
source={{ uri: `${this.url}&config.disableDeepLinking=true` }}
|
source={{ uri: `${this.url}${this.url.includes('#config') ? '&' : '#'}config.disableDeepLinking=true` }}
|
||||||
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
||||||
onNavigationStateChange={this.onNavigationStateChange}
|
onNavigationStateChange={this.onNavigationStateChange}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useAppSelector } from '../lib/hooks';
|
||||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||||
import { getUserSelector } from '../selectors/login';
|
import { getUserSelector } from '../selectors/login';
|
||||||
import { ChatsStackParamList } from '../stacks/types';
|
import { ChatsStackParamList } from '../stacks/types';
|
||||||
|
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
|
||||||
|
|
||||||
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
||||||
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
|
`${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 JitsiMeetView = (): React.ReactElement => {
|
||||||
const { goBack } = useNavigation();
|
const { goBack } = useNavigation();
|
||||||
const {
|
const {
|
||||||
params: { url, onlyAudio, videoConf }
|
params: { url, onlyAudio, videoConf, rid }
|
||||||
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
|
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
|
||||||
const user = useAppSelector(state => getUserSelector(state));
|
const user = useAppSelector(state => getUserSelector(state));
|
||||||
const baseUrl = useAppSelector(state => state.server.server);
|
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);
|
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
||||||
|
if (!videoConf) initVideoConfTimer(rid);
|
||||||
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
|
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
|
||||||
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||||
|
if (!videoConf) endVideoConfTimer();
|
||||||
goBack();
|
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 styles from './styles';
|
||||||
import { ERoomType } from '../../definitions/ERoomType';
|
import { ERoomType } from '../../definitions/ERoomType';
|
||||||
import { E2E_ROOM_TYPES, SWITCH_TRACK_COLOR, themes } from '../../lib/constants';
|
import { E2E_ROOM_TYPES, SWITCH_TRACK_COLOR, themes } from '../../lib/constants';
|
||||||
import { callJitsi, getPermalinkChannel } from '../../lib/methods';
|
import { getPermalinkChannel } from '../../lib/methods';
|
||||||
import {
|
import {
|
||||||
canAutoTranslate as canAutoTranslateMethod,
|
canAutoTranslate as canAutoTranslateMethod,
|
||||||
getRoomAvatar,
|
getRoomAvatar,
|
||||||
|
@ -48,9 +48,9 @@ import { getSubscriptionByRoomId } from '../../lib/database/services/Subscriptio
|
||||||
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { closeLivechat } from '../../lib/methods/helpers/closeLivechat';
|
import { closeLivechat } from '../../lib/methods/helpers/closeLivechat';
|
||||||
import { videoConfStartAndJoin } from '../../lib/methods/videoConf';
|
|
||||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||||
import { ILivechatTag } from '../../definitions/ILivechatTag';
|
import { ILivechatTag } from '../../definitions/ILivechatTag';
|
||||||
|
import CallSection from './components/CallSection';
|
||||||
|
|
||||||
interface IOnPressTouch {
|
interface IOnPressTouch {
|
||||||
<T extends keyof ChatsStackParamList>(item: { route?: T; params?: ChatsStackParamList[T]; event?: Function }): void;
|
<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 = () => {
|
renderRoomInfo = () => {
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
const { rid, name, t, topic, source } = room;
|
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 = () => {
|
renderE2EEncryption = () => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { encryptionEnabled } = this.props;
|
const { encryptionEnabled } = this.props;
|
||||||
|
@ -1108,7 +1041,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<List.Container testID='room-actions-scrollview'>
|
<List.Container testID='room-actions-scrollview'>
|
||||||
{this.renderRoomInfo()}
|
{this.renderRoomInfo()}
|
||||||
{this.renderJitsi()}
|
<CallSection rid={rid} />
|
||||||
{this.renderE2EEncryption()}
|
{this.renderE2EEncryption()}
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -1299,13 +1232,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
userId: getUserSelector(state).id,
|
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,
|
encryptionEnabled: state.encryption.enabled,
|
||||||
serverVersion: state.server.version,
|
serverVersion: state.server.version,
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
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 React from 'react';
|
||||||
import { ScrollView, Text, View } from 'react-native';
|
import { ScrollView, Text, View } from 'react-native';
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||||
import { connect } from 'react-redux';
|
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 { 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 Avatar from '../../containers/Avatar';
|
||||||
import sharedStyles from '../Styles';
|
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
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 { MarkdownPreview } from '../../containers/markdown';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import EventEmitter from '../../lib/methods/helpers/events';
|
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
import Status from '../../containers/Status';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import Livechat from './Livechat';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import Channel from './Channel';
|
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||||
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 { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
||||||
import { callJitsi } from '../../lib/methods';
|
import I18n from '../../i18n';
|
||||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
import { themes } from '../../lib/constants';
|
||||||
import { Services } from '../../lib/services';
|
|
||||||
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
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 { 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 {
|
interface IGetRoomTitle {
|
||||||
room: ISubscription;
|
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) => {
|
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
|
||||||
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
||||||
try {
|
try {
|
||||||
|
@ -425,8 +420,7 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
};
|
};
|
||||||
|
|
||||||
renderButtons = () => {
|
renderButtons = () => {
|
||||||
const { roomFromRid, roomUser } = this.state;
|
const { roomFromRid, roomUser, room } = this.state;
|
||||||
const { jitsiEnabled } = this.props;
|
|
||||||
|
|
||||||
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
||||||
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
||||||
|
@ -442,9 +436,7 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
return (
|
return (
|
||||||
<View style={styles.roomButtonsContainer}>
|
<View style={styles.roomButtonsContainer}>
|
||||||
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
||||||
{jitsiEnabled && this.isDirect
|
<CallButton isDirect={this.isDirect} rid={room.rid} />
|
||||||
? this.renderButton(() => this.handleCreateDirectMessage(this.videoCall), 'camera', I18n.t('Video_call'))
|
|
||||||
: null}
|
|
||||||
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
||||||
? this.renderButton(
|
? this.renderButton(
|
||||||
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
||||||
|
|
|
@ -10,8 +10,7 @@ import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import { isTeamRoom } from '../../lib/methods/helpers/room';
|
import { IApplicationState, ISubscription, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
|
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
|
||||||
import i18n from '../../i18n';
|
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 { closeLivechat as closeLivechatService } from '../../lib/methods/helpers/closeLivechat';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||||
|
import HeaderCallButton from './components/HeaderCallButton';
|
||||||
|
|
||||||
interface IRightButtonsProps {
|
interface IRightButtonsProps extends Pick<ISubscription, 't'> {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
threadsEnabled: boolean;
|
threadsEnabled: boolean;
|
||||||
rid?: string;
|
|
||||||
t: string;
|
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
|
@ -43,6 +41,7 @@ interface IRightButtonsProps {
|
||||||
livechatRequestComment: boolean;
|
livechatRequestComment: boolean;
|
||||||
showActionSheet: Function;
|
showActionSheet: Function;
|
||||||
departmentId?: string;
|
departmentId?: string;
|
||||||
|
rid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRigthButtonsState {
|
interface IRigthButtonsState {
|
||||||
|
@ -338,7 +337,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
|
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 (t === 'l') {
|
||||||
if (!this.isOmnichannelPreview()) {
|
if (!this.isOmnichannelPreview()) {
|
||||||
|
@ -363,9 +362,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Container>
|
||||||
{isTeamRoom({ teamId, joined }) ? (
|
{rid ? <HeaderCallButton rid={rid} /> : null}
|
||||||
<HeaderButton.Item iconName='channel-public' onPress={this.goTeamChannels} testID='room-view-header-team-channels' />
|
|
||||||
) : null}
|
|
||||||
{threadsEnabled ? (
|
{threadsEnabled ? (
|
||||||
<HeaderButton.Item
|
<HeaderButton.Item
|
||||||
iconName='threads'
|
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}
|
joined={joined}
|
||||||
status={room.status}
|
status={room.status}
|
||||||
omnichannelPermissions={omnichannelPermissions}
|
omnichannelPermissions={omnichannelPermissions}
|
||||||
t={this.t || t}
|
t={(this.t || t) as SubscriptionType}
|
||||||
encrypted={encrypted}
|
encrypted={encrypted}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
toggleFollowThread={this.toggleFollowThread}
|
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;
|
const { room } = this.state;
|
||||||
if ('id' in room) {
|
if ('id' in room) {
|
||||||
const { jitsiTimeout } = room;
|
const { jitsiTimeout } = room;
|
||||||
if (jitsiTimeout && jitsiTimeout < new Date()) {
|
if (jitsiTimeout && jitsiTimeout < new Date()) {
|
||||||
showErrorAlert(I18n.t('Call_already_ended'));
|
showErrorAlert(I18n.t('Call_already_ended'));
|
||||||
} else {
|
} else {
|
||||||
callJitsi(room);
|
callJitsi({ room });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1382,7 +1383,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
||||||
navToRoomInfo={this.navToRoomInfo}
|
navToRoomInfo={this.navToRoomInfo}
|
||||||
getCustomEmoji={this.getCustomEmoji}
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
callJitsi={this.handleCallJitsi}
|
handleEnterCall={this.handleEnterCall}
|
||||||
blockAction={this.blockAction}
|
blockAction={this.blockAction}
|
||||||
threadBadgeColor={this.getBadgeColor(item?.id)}
|
threadBadgeColor={this.getBadgeColor(item?.id)}
|
||||||
toggleFollowThread={this.toggleFollowThread}
|
toggleFollowThread={this.toggleFollowThread}
|
||||||
|
|
Loading…
Reference in New Issue