diff --git a/app/client/mobile/src/call/Call.styled.ts b/app/client/mobile/src/call/Call.styled.ts new file mode 100644 index 00000000..f0df1d78 --- /dev/null +++ b/app/client/mobile/src/call/Call.styled.ts @@ -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%', + }, +}); diff --git a/app/client/mobile/src/call/Call.tsx b/app/client/mobile/src/call/Call.tsx new file mode 100644 index 00000000..8d5f2a4b --- /dev/null +++ b/app/client/mobile/src/call/Call.tsx @@ -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 ( + + + + + + ); +} + diff --git a/app/client/mobile/src/call/useCall.hook.ts b/app/client/mobile/src/call/useCall.hook.ts new file mode 100644 index 00000000..8aeeb4bf --- /dev/null +++ b/app/client/mobile/src/call/useCall.hook.ts @@ -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 }; +} diff --git a/app/client/mobile/src/context/useRingContext.hook.ts b/app/client/mobile/src/context/useRingContext.hook.ts index 4884162f..ea977681 100644 --- a/app/client/mobile/src/context/useRingContext.hook.ts +++ b/app/client/mobile/src/context/useRingContext.hook.ts @@ -49,6 +49,7 @@ export function useRingContext() { connected: false, connectedTime: 0, failed: false, + fullscreen: false, }) // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/app/client/mobile/src/ring/Ring.tsx b/app/client/mobile/src/ring/Ring.tsx index 49752ed0..0ec33ff2 100644 --- a/app/client/mobile/src/ring/Ring.tsx +++ b/app/client/mobile/src/ring/Ring.tsx @@ -121,11 +121,11 @@ export function Ring() { )} { state.calling && ( - - + + actions.setFullscreen(true)} /> { state.calling.name && ( - asuperlongnamehere asuperlongnamehere + { state.calling.name } )} { !state.calling.name && ( { state.strings.name } diff --git a/app/client/mobile/src/ring/useRing.hook.ts b/app/client/mobile/src/ring/useRing.hook.ts index 49b95c08..47aca1e2 100644 --- a/app/client/mobile/src/ring/useRing.hook.ts +++ b/app/client/mobile/src/ring/useRing.hook.ts @@ -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; diff --git a/app/client/mobile/src/session/Session.tsx b/app/client/mobile/src/session/Session.tsx index 7421677c..eb061342 100644 --- a/app/client/mobile/src/session/Session.tsx +++ b/app/client/mobile/src/session/Session.tsx @@ -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() { )} + );