adding video call screen

This commit is contained in:
balzack 2025-02-05 14:56:23 -08:00
parent 67ea8c9925
commit dda3eb23ed
7 changed files with 168 additions and 5 deletions

View File

@ -0,0 +1,20 @@
import {StyleSheet} from 'react-native';
import { Colors } from '../constants/Colors';
export const styles = StyleSheet.create({
active: {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
},
inactive: {
display: 'none',
},
call: {
width: '100%',
height: '100%',
},
});

View File

@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import { useCall } from './useCall.hook';
import { styles } from './Call.styled'
import { Card as Contact } from '../card/Card';
import { Text, Surface, IconButton, ActivityIndicator } from 'react-native-paper';
import { Confirm } from '../confirm/Confirm';
import { Colors } from '../constants/Colors';
export function Call() {
const { state, actions } = useCall();
const [alert, setAlert] = useState(false);
const [ending, setEnding] = useState(false);
const [applyingAudio, setApplyingAudio] = useState(false);
const [accepting, setAccepting] = useState(null as null|string);
const [ignoring, setIgnoring] = useState(null as null|string);
const [declining, setDeclining] = useState(null as null|string);
const toggleAudio = async () => {
if (!applyingAudio) {
setApplyingAudio(true);
try {
if (state.audioEnabled) {
await actions.disableAudio();
} else if (!state.audioEnabled) {
await actions.enableAudio();
}
} catch (err) {
console.log(err);
setAlert(true);
}
setApplyingAudio(false);
}
}
const toggleVideo = async () => {
if (!applyingVideo) {
setApplyingVideo(true);
try {
if (state.videoEnabled) {
await actions.disableVideo();
} else if (!state.videoEnabled) {
await actions.enableVideo();
}
} catch (err) {
console.log(err);
setAlert(true);
}
setApplyingVideo(false);
}
}
const end = async () => {
if (!ending) {
setEnding(true);
try {
await actions.end();
} catch (err) {
console.log(err);
setAlert(true);
}
setEnding(false);
}
}
const alertParams = {
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,
action: () => {
setAlert(false);
},
},
};
return (
<View style={(state.calling && state.fullscreen) ? styles.active : styles.inactive}>
<Surface elevation={4} mode="flat" style={styles.call}>
</Surface>
<Confirm show={alert} params={alertParams} />
</View>
);
}

View File

@ -0,0 +1,54 @@
import { useState, useContext, useEffect, useRef } from 'react'
import { RingContext } from '../context/RingContext'
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
import { Card } from 'databag-client-sdk';
export function useCall() {
const ring = useContext(RingContext) as ContextType;
const display = useContext(DisplayContext) as ContextType;
const offsetTime = useRef(0);
const offset = useRef(false);
const [state, setState] = useState({
strings: display.state.strings,
calls: [] as { callId: string, card: Card }[],
calling: null as null | Card,
remoteVideo: false,
localVideo: false,
audioEnabled: false,
videoEnabled: false,
connected: false,
duration: 0,
failed: false,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
useEffect(() => {
const interval = setInterval(() => {
if (offset.current) {
const now = new Date();
const duration = Math.floor((now.getTime() / 1000) - offsetTime.current);
updateState({ duration });
}
}, 1000);
return () => {
clearInterval(interval);
}
}, []);
useEffect(() => {
const { calls, calling, fullscreen, remoteVideo, localVideo, audioEnabled, videoEnabled, connected, connectedTime, failed } = ring.state;
offsetTime.current = connectedTime;
offset.current = connected;
const duration = connected ? Math.floor(((new Date()).getTime() / 1000) - connectedTime) : 0;
updateState({ calls, calling, fullscreen, duration, remoteVideo, localVideo, audioEnabled, videoEnabled, connected, failed });
}, [ring.state]);
const actions = ring.actions;
return { state, actions };
}

View File

@ -49,6 +49,7 @@ export function useRingContext() {
connected: false,
connectedTime: 0,
failed: false,
fullscreen: false,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -121,11 +121,11 @@ export function Ring() {
)}
{ state.calling && (
<Surface elevation={4} mode="flat" style={styles.ring}>
<IconButton style={styles.circleIcon} iconColor="white" containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} compact="true" mode="contained" size={24} onPress={toggleAudio} />
<IconButton style={styles.circleIcon} iconColor="white" containerColor={Colors.confirmed} icon="arrow-expand-all" compact="true" mode="contained" size={24} onPress={toggleAudio} />
<IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} compact="true" mode="contained" size={24} onPress={toggleAudio} />
<IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.confirmed} icon={(state.remoteVideo || state.localVideo) ? 'video-switch-outline' : 'arrow-expand-all'} compact="true" mode="contained" size={24} onPress={()=>actions.setFullscreen(true)} />
<View style={styles.name}>
{ state.calling.name && (
<Text style={styles.nameSet} numberOfLines={1}>asuperlongnamehere asuperlongnamehere</Text>
<Text style={styles.nameSet} numberOfLines={1}>{ state.calling.name }</Text>
)}
{ !state.calling.name && (
<Text style={styles.nameUnset} adjustsFontSizeToFit={true} numberOfLines={1}>{ state.strings.name }</Text>

View File

@ -15,6 +15,7 @@ export function useRing() {
calls: [] as { callId: string, card: Card }[],
calling: null as null | Card,
remoteVideo: false,
localVideo: false,
audioEnabled: false,
connected: false,
duration: 0,
@ -40,11 +41,11 @@ export function useRing() {
}, []);
useEffect(() => {
const { calls, calling, remoteVideo, audioEnabled, connected, connectedTime, failed } = ring.state;
const { calls, calling, localVideo, remoteVideo, audioEnabled, connected, connectedTime, failed } = ring.state;
offsetTime.current = connectedTime;
offset.current = connected;
const duration = connected ? Math.floor(((new Date()).getTime() / 1000) - connectedTime) : 0;
updateState({ calls, calling, duration, remoteVideo, audioEnabled, connected, failed });
updateState({ calls, calling, duration, localVideo, remoteVideo, audioEnabled, connected, failed });
}, [ring.state]);
const actions = ring.actions;

View File

@ -19,6 +19,7 @@ import {createDrawerNavigator} from '@react-navigation/drawer';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {Colors} from '../constants/Colors';
import {Ring} from '../ring/Ring';
import {Call} from '../call/Call';
const SettingsDrawer = createDrawerNavigator();
const ContactsDrawer = createDrawerNavigator();
@ -198,6 +199,7 @@ export function Session() {
</Surface>
</View>
)}
<Call />
</View>
</RingContextProvider>
);