Skip to content

Commit 223550d

Browse files
authored
feat: Add caller and ringer to video conf calls (#5046)
* add expo camera and use camera on call init action sheet * fix permissions * set colors when calling * update @react-native-community/hooks lib * move to useWindowDimensions * create action to handle video-conf calls * create videoConf reducer * add typed-redux-saga lib * fix return * change videoConf saga to TS * fix TS target * update action and types * create actionSheetRef * add notifyUser api * export video conf types * add action prop * use new reducer prop * add videoConferenceCancel and add allowRinging to videoConferenceStart * temp-patch * add locales * add handler to videoconf message * fix rest types * add message types * path to remove component from dom * remove notification when is videoconf * create sound hook * create dots loader * update call translation * the end is near * move to confirmed * better code reading * fix call type * fix tests * update podfile * wip * fix call order * move colors * move to jsx * fix colors * add pt-br * remove patch and point * fix colors * fix expo camera * move to style * remove unused styles * update types and style * wip * rename IncomingCallComponent * add custom notification * wip * fix naming * fix styles * fix import * fix styles * change colors * fixa ringing * fix import * organize * fix sizes * use realName * fix spacing * fix icon size * fix header gap * changeColor * fix safeArea * set calling only on direct calls * change ringer to be a component * cancel call on swipe * remove join on direct calls * add props * update package
1 parent fea4f16 commit 223550d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1245
-236
lines changed

android/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,9 @@ allprojects {
4949
maven {
5050
url "$rootDir/../node_modules/detox/Detox-android"
5151
}
52+
maven {
53+
// expo-camera bundles a custom com.google.android:cameraview
54+
url "$rootDir/../node_modules/expo-camera/android/maven"
55+
}
5256
}
5357
}

app/actions/actionsTypes.ts

+10
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,13 @@ export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DEC
8484

8585
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']);
8686
export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']);
87+
export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
88+
'HANDLE_INCOMING_WEBSOCKET_MESSAGES',
89+
'SET',
90+
'REMOVE',
91+
'CLEAR',
92+
'INIT_CALL',
93+
'CANCEL_CALL',
94+
'ACCEPT_CALL',
95+
'SET_CALLING'
96+
]);

app/actions/videoConf.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Action } from 'redux';
2+
3+
import { ICallInfo } from '../reducers/videoConf';
4+
import { VIDEO_CONF } from './actionsTypes';
5+
6+
interface IHandleVideoConfIncomingWebsocketMessages extends Action {
7+
data: any;
8+
}
9+
10+
export type TCallProps = { mic: boolean; cam: boolean; direct: boolean; rid: string; uid: string };
11+
type TInitCallAction = Action & { payload: TCallProps };
12+
type TSetCallingAction = Action & { payload: boolean };
13+
type TCancelCallAction = Action & { payload: { callId?: string } };
14+
type TAcceptCallAction = Action & { payload: { callId: string } };
15+
16+
export interface IVideoConfGenericAction extends Action {
17+
payload: ICallInfo;
18+
}
19+
20+
export type TActionVideoConf = IHandleVideoConfIncomingWebsocketMessages &
21+
IVideoConfGenericAction &
22+
TSetCallingAction &
23+
Action &
24+
TInitCallAction &
25+
TCancelCallAction &
26+
TAcceptCallAction;
27+
28+
export function handleVideoConfIncomingWebsocketMessages(data: any): IHandleVideoConfIncomingWebsocketMessages {
29+
return {
30+
type: VIDEO_CONF.HANDLE_INCOMING_WEBSOCKET_MESSAGES,
31+
data
32+
};
33+
}
34+
35+
export function setVideoConfCall(payload: ICallInfo): IVideoConfGenericAction {
36+
return {
37+
type: VIDEO_CONF.SET,
38+
payload
39+
};
40+
}
41+
42+
export function removeVideoConfCall(payload: ICallInfo): IVideoConfGenericAction {
43+
return {
44+
type: VIDEO_CONF.REMOVE,
45+
payload
46+
};
47+
}
48+
49+
export function clearVideoConfCalls(): Action {
50+
return {
51+
type: VIDEO_CONF.CLEAR
52+
};
53+
}
54+
55+
export function initVideoCall(payload: TCallProps): TInitCallAction {
56+
return {
57+
type: VIDEO_CONF.INIT_CALL,
58+
payload
59+
};
60+
}
61+
62+
export function cancelCall(payload: { callId?: string }): TCancelCallAction {
63+
return {
64+
type: VIDEO_CONF.CANCEL_CALL,
65+
payload
66+
};
67+
}
68+
69+
export function acceptCall(payload: { callId: string }): TAcceptCallAction {
70+
return {
71+
type: VIDEO_CONF.ACCEPT_CALL,
72+
payload
73+
};
74+
}
75+
76+
export function setCalling(payload: boolean): TSetCallingAction {
77+
return {
78+
type: VIDEO_CONF.SET_CALLING,
79+
payload
80+
};
81+
}

app/containers/ActionSheet/Provider.tsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import hoistNonReactStatics from 'hoist-non-react-statics';
2-
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
2+
import React, { createRef, ForwardedRef, forwardRef, useContext } from 'react';
33

44
import { TIconsName } from '../CustomIcon';
55
import ActionSheet from './ActionSheet';
@@ -47,23 +47,27 @@ export const withActionSheet = (Component: React.ComponentType<any>): typeof Com
4747
return WithActionSheetComponent;
4848
};
4949

50-
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
51-
const ref: ForwardedRef<IActionSheetProvider> = useRef(null);
50+
const actionSheetRef: React.Ref<IActionSheetProvider> = createRef();
5251

52+
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
5353
const getContext = () => ({
5454
showActionSheet: (options: TActionSheetOptions) => {
55-
ref.current?.showActionSheet(options);
55+
actionSheetRef.current?.showActionSheet(options);
5656
},
5757
hideActionSheet: () => {
58-
ref.current?.hideActionSheet();
58+
actionSheetRef.current?.hideActionSheet();
5959
}
6060
});
6161

6262
return (
6363
<Provider value={getContext()}>
64-
<ActionSheet ref={ref}>
64+
<ActionSheet ref={actionSheetRef}>
6565
<>{children}</>
6666
</ActionSheet>
6767
</Provider>
6868
);
6969
});
70+
71+
export const hideActionSheetRef = (): void => {
72+
actionSheetRef?.current?.hideActionSheet();
73+
};

app/containers/CallHeader.tsx

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React from 'react';
2+
import { StyleSheet, Text, View } from 'react-native';
3+
import Touchable from 'react-native-platform-touchable';
4+
5+
import { useAppSelector } from '../lib/hooks';
6+
import { useTheme } from '../theme';
7+
import sharedStyles from '../views/Styles';
8+
import { CustomIcon } from './CustomIcon';
9+
import { BUTTON_HIT_SLOP } from './message/utils';
10+
import AvatarContainer from './Avatar';
11+
import StatusContainer from './Status';
12+
import DotsLoader from './DotsLoader';
13+
14+
type TCallHeader = {
15+
mic: boolean;
16+
cam: boolean;
17+
setCam: Function;
18+
setMic: Function;
19+
title: string;
20+
avatar: string;
21+
uid: string;
22+
name: string;
23+
direct: boolean;
24+
};
25+
26+
export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, direct }: TCallHeader): React.ReactElement => {
27+
const style = useStyle();
28+
const { colors } = useTheme();
29+
const calling = useAppSelector(state => state.videoConf.calling);
30+
31+
const handleColors = (enabled: boolean) => {
32+
if (calling) {
33+
if (enabled) return { button: colors.conferenceCallCallBackButton, icon: colors.gray300 };
34+
return { button: 'transparent', icon: colors.gray100 };
35+
}
36+
if (enabled) return { button: colors.conferenceCallEnabledIconBackground, icon: colors.conferenceCallEnabledIcon };
37+
return { button: 'transparent', icon: colors.conferenceCallDisabledIcon };
38+
};
39+
40+
return (
41+
<View>
42+
<View style={style.actionSheetHeader}>
43+
<View style={style.rowContainer}>
44+
<Text style={style.actionSheetHeaderTitle}>{title}</Text>
45+
{calling && direct ? <DotsLoader /> : null}
46+
</View>
47+
<View style={style.actionSheetHeaderButtons}>
48+
<Touchable
49+
onPress={() => setCam(!cam)}
50+
style={[style.iconCallContainerRight, { backgroundColor: handleColors(cam).button }]}
51+
hitSlop={BUTTON_HIT_SLOP}
52+
disabled={calling}
53+
>
54+
<CustomIcon name={cam ? 'camera' : 'camera-disabled'} size={24} color={handleColors(cam).icon} />
55+
</Touchable>
56+
<Touchable
57+
onPress={() => setMic(!mic)}
58+
style={[style.iconCallContainer, { backgroundColor: handleColors(mic).button }]}
59+
hitSlop={BUTTON_HIT_SLOP}
60+
disabled={calling}
61+
>
62+
<CustomIcon name={mic ? 'microphone' : 'microphone-disabled'} size={24} color={handleColors(mic).icon} />
63+
</Touchable>
64+
</View>
65+
</View>
66+
<View style={style.actionSheetUsernameContainer}>
67+
<AvatarContainer text={avatar} size={36} />
68+
{direct ? <StatusContainer size={16} id={uid} style={style.statusContainerMargin} /> : null}
69+
<Text style={{ ...style.actionSheetUsername, marginLeft: !direct ? 8 : 0 }} numberOfLines={1}>
70+
{name}
71+
</Text>
72+
</View>
73+
</View>
74+
);
75+
};
76+
77+
function useStyle() {
78+
const { colors } = useTheme();
79+
const style = StyleSheet.create({
80+
actionSheetHeader: { flexDirection: 'row', alignItems: 'center' },
81+
actionSheetHeaderTitle: {
82+
fontSize: 14,
83+
...sharedStyles.textBold,
84+
color: colors.n900
85+
},
86+
actionSheetHeaderButtons: { flex: 1, alignItems: 'center', flexDirection: 'row', justifyContent: 'flex-end' },
87+
iconCallContainer: {
88+
padding: 6,
89+
borderRadius: 4
90+
},
91+
iconCallContainerRight: {
92+
padding: 6,
93+
borderRadius: 4,
94+
marginRight: 6
95+
},
96+
actionSheetUsernameContainer: { flexDirection: 'row', paddingTop: 8, alignItems: 'center' },
97+
actionSheetUsername: {
98+
fontSize: 16,
99+
...sharedStyles.textBold,
100+
color: colors.passcodePrimary,
101+
flexShrink: 1
102+
},
103+
rowContainer: { flexDirection: 'row' },
104+
statusContainerMargin: { marginLeft: 8, marginRight: 6 }
105+
});
106+
return style;
107+
}

app/containers/DotsLoader/index.tsx

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { StyleProp, View, ViewStyle, StyleSheet } from 'react-native';
3+
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
4+
5+
import { useTheme } from '../../theme';
6+
7+
const SIZE = 8;
8+
const MARGIN = 4;
9+
const dots = [1, 2, 3];
10+
const INTERVAL = 300;
11+
const ANIMATION_DURATION = 400;
12+
const ANIMATION_SCALE = 1.4;
13+
14+
function Dot({ active }: { active: boolean }): JSX.Element {
15+
const scale = useSharedValue(1);
16+
17+
useEffect(() => {
18+
scale.value = withTiming(active ? ANIMATION_SCALE : 1, {
19+
duration: ANIMATION_DURATION
20+
});
21+
}, [active]);
22+
23+
const animatedStyle = useAnimatedStyle(() => ({
24+
transform: [{ scale: scale.value }]
25+
}));
26+
27+
const { colors } = useTheme();
28+
29+
const style: StyleProp<ViewStyle> = {
30+
height: SIZE,
31+
width: SIZE,
32+
borderRadius: SIZE / 2,
33+
marginHorizontal: MARGIN,
34+
backgroundColor: active ? colors.dotActiveBg : colors.dotBg
35+
};
36+
37+
return <Animated.View style={[style, animatedStyle]} />;
38+
}
39+
40+
function DotsLoader(): JSX.Element {
41+
const [active, setActive] = useState(1);
42+
43+
useEffect(() => {
44+
const interval = setInterval(() => {
45+
setActive(prevActive => (prevActive > 2 ? 1 : prevActive + 1));
46+
}, INTERVAL);
47+
return () => {
48+
clearInterval(interval);
49+
};
50+
}, []);
51+
52+
return (
53+
<View style={styles.dotsContainer}>
54+
{dots.map(i => (
55+
<Dot key={i} active={i === active} />
56+
))}
57+
</View>
58+
);
59+
}
60+
61+
const styles = StyleSheet.create({
62+
dotsContainer: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginLeft: 6 }
63+
});
64+
65+
export default DotsLoader;

0 commit comments

Comments
 (0)