cleanup on calling

This commit is contained in:
Roland Osborne 2025-02-06 11:04:09 -08:00
parent cefba52b25
commit fc0ba2a187
5 changed files with 6 additions and 433 deletions

View File

@ -129,7 +129,7 @@ export function Call() {
)}
<Surface elevation={3} mode="flat" style={styles.controls}>
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} icon="arrow-collapse-all" loading={applyingAudio} compact="true" mode="contained" size={32} onPress={()=>actions.setFullscreen(false)} />
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.confirmed} icon="arrow-collapse-all" compact="true" mode="contained" size={32} onPress={()=>actions.setFullscreen(false)} />
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} loading={applyingAudio} compact="true" mode="contained" size={32} onPress={toggleAudio} />
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.videoEnabled ? 'video-outline' : 'video-off-outline'} loading={applyingVideo} compact="true" mode="contained" size={32} onPress={toggleVideo} />
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.danger} icon="phone-hangup-outline" compact="true" mode="contained" size={32} onPress={end} />

View File

@ -1,128 +0,0 @@
import {StyleSheet} from 'react-native';
import { Colors } from '../constants/Colors';
export const styles = StyleSheet.create({
blur: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
active: {
display: 'flex',
width: '100%',
height: '100%',
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
},
inactive: {
display: 'none',
width: '100%',
height: '100%',
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
},
base: {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
container: {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
calls: {
borderRadius: 8,
overflow: 'hidden',
},
call: {
width: '100%',
height: '100%',
display: 'flex',
},
image: {
width: '100%',
height: '100%',
borderRadius: 8,
},
canvas: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
frame: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
overflow: 'hidden',
padding: 2,
position: 'absolute',
},
closeIcon: {
borderRadius: 8,
},
circleIcon: {
},
flipIcon: {
transform: [{ rotate: '135deg' }],
},
name: {
fontSize: 28,
paddingLeft: 16,
paddingRight: 16,
width: '100%',
textAlign: 'center',
},
connecting: {
fontSize: 12,
paddingLeft: 16,
paddingRight: 16,
paddingTop: 4,
paddingBottom: 4,
},
overlap: {
display: 'flex',
flexDirection: 'row',
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 8,
paddingTop: 8,
gap: 32,
},
box: {
position: 'absolute',
top: 0,
right: 16,
width: '20%',
height: '20%',
},
full: {
width: '100%',
height: '100%',
},
card: {
width: '100%',
height: 64,
paddingTop: 16,
paddingBottom: 16,
paddingLeft: 32,
paddingRight: 32,
borderBottomWidth: 1,
}
full: {
width: '100%',
height: '100%',
}
});

View File

@ -1,262 +0,0 @@
import React, { useEffect, useState } from 'react';
import { useWindowDimensions, Image, SafeAreaView, Modal, ScrollView, View } from 'react-native';
import { Surface, Icon, Divider, Button, IconButton, Text, TextInput, useTheme} from 'react-native-paper';
import {styles} from './Calling.styled';
import {useCalling} from './useCalling.hook';
import {BlurView} from '@react-native-community/blur';
import { Confirm } from '../confirm/Confirm';
import { ActivityIndicator } from 'react-native-paper';
import FastImage from 'react-native-fast-image'
import { Colors } from '../constants/Colors';
import { type Card } from 'databag-client-sdk';
import { RTCView } from 'react-native-webrtc';
import { Card as Contact } from '../card/Card';
import { activateKeepAwake, deactivateKeepAwake} from "@sayem314/react-native-keep-awake";
export function Calling({ callCard }: { callCard: null|Card }) {
const { state, actions } = useCalling();
const [alert, setAlert] = useState(false);
const [connecting, setConnecting] = useState(false);
const [ending, setEnding] = useState(false);
const {height, width} = useWindowDimensions();
const [applyingVideo, setApplyingVideo] = 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 theme = useTheme();
const surface = theme.dark ? {
base: 'rgba(16,16,16,1)',
blend: 'rgba(16,16,16,0)',
control: 'rgba(64,64,64,0.6)',
title: 'rgba(32,32,32,0.8)',
} : {
base: 'rgba(212,212,212,1)',
blend: 'rgba(212,212,212,0)',
control: 'rgba(128,128,128,0.6)',
title: 'rgba(192,192,192,0.8)',
};
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 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 end = async () => {
if (!ending) {
setEnding(true);
try {
await actions.end();
} catch (err) {
console.log(err);
setAlert(true);
}
setEnding(false);
}
}
const call = async (card) => {
if (!connecting) {
setConnecting(true);
try {
await actions.call(card);
} catch (err) {
console.log(err);
setAlert(true);
}
setConnecting(false);
}
}
const accept = async (callId, card) => {
if (!accepting) {
setAccepting(callId);
try {
await actions.accept(callId, card);
} catch (err) {
console.log(err);
setAlert(true);
}
setAccepting(null);
}
}
const ignore = async (callId, card) => {
if (!ignoring) {
setIgnoring(callId);
try {
await actions.ignore(callId, card);
} catch (err) {
console.log(err);
setAlert(true);
}
setIgnoring(null);
}
}
const decline = async (callId, card) => {
if (!declining) {
setDeclining(callId);
try {
await actions.decline(callId, card);
} catch (err) {
console.log(err);
setAlert(true);
}
setDeclining(null);
}
}
const alertParams = {
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,
action: () => {
setAlert(false);
},
},
};
useEffect(() => {
const { card } = callCard;
if (card) {
call(card);
}
}, [callCard]);
useEffect(() => {
if (state.calling) {
activateKeepAwake();
} else {
deactivateKeepAwake();
}
}, [state.calling]);
const calls = state.calls.map((contact, index) => {
const { callId, card } = contact;
const { name, handle, node, imageUrl } = card;
const ignoreButton = <IconButton key="ignore" style={styles.circleIcon} iconColor="white" containerColor={Colors.pending} icon="eye-off-outline" compact="true" mode="contained" size={24} loading={ignoring===callId} onPress={()=>ignore(callId, card)} />
const declineButton = <IconButton key="decline" style={styles.flipIcon} iconColor="white" containerColor={Colors.offsync} icon="phone-outline" compact="true" mode="contained" size={24} loading={declining===callId} onPress={()=>decline(callId, card)} />
const acceptButton = <IconButton key="accept" style={styles.circleIcon} iconColor="white" containerColor={Colors.primary} icon="phone-outline" compact="true" mode="contained" size={24} loading={accepting===callId} onPress={()=>accept(callId, card)} />
return (
<Surface mode="flat" key={index}>
<Contact containerStyle={styles.card} placeholder={''} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignoreButton, declineButton, acceptButton]} />
</Surface>
)
});
const overlap = (width + 128) > height;
const frameWidth = width > height ? height : width - 16;
const frameHeight = frameWidth;
const frameOffset = (height - frameHeight) / 3;
return (
<View style={(connecting || state.calling || state.calls.length > 0 || alert) ? styles.active : styles.inactive}>
{ state.calls.length > 0 && !connecting && !state.calling && (
<View style={styles.base}>
<BlurView style={styles.blur} />
<View style={styles.calls}>
{ calls }
</View>
</View>
)}
{ connecting && !state.calling && (
<View style={{ ...styles.container, backgroundColor: surface.base }}>
<ActivityIndicator size={72} />
</View>
)}
{ state.calling && (
<View style={{ ...styles.container, backgroundColor: surface.base }}>
<View style={{ ...styles.frame, top: frameOffset, width: frameWidth > 400 ? 400 : frameWidth, height: frameHeight > 400 ? 400 : frameHeight }}>
<Image
style={styles.image}
resizeMode="contain"
source={{ uri: state.calling.imageUrl }}
/>
</View>
</View>
)}
{ state.calling && (
<View style={{ ...styles.overlap, top: 64 }}>
<View style={{backgroundColor: surface.title, borderRadius: 16 }}>
{ state.calling.name && (
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ state.calling.name }</Text>
)}
{ !state.calling.name && (
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ `${state.calling.handle}/${state.calling.node}` }</Text>
)}
</View>
</View>
)}
{ state.calling && state.remoteStream && state.remoteVideo && (
<View style={{ ...styles.canvas, backgroundColor: surface.base }}>
<RTCView
style={styles.full}
mirror={true}
objectFit={'contain'}
streamURL={state.remoteStream.toURL()}
/>
</View>
)}
{ state.calling && state.localStream && state.localVideo && (
<View style={{ ...styles.canvas, backgroundColor: surface.base }}>
<RTCView
style={ state.remoteVideo ? styles.box : styles.full}
mirror={true}
objectFit={'contain'}
streamURL={state.localStream.toURL()}
zOrder={2}
/>
</View>
)}
{ state.calling && (
<View style={{ ...styles.overlap, bottom: 64 }}>
<View style={{ paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16, gap: 16, display: 'flex', flexDirection: 'row', borderRadius: 16, backgroundColor: surface.control }}>
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} loading={applyingAudio} compact="true" mode="contained" size={32} onPress={toggleAudio} />
<IconButton style={styles.closeIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.videoEnabled ? 'video-outline' : 'video-off-outline'} loading={applyingVideo} compact="true" mode="contained" size={32} onPress={toggleVideo} />
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.danger} icon="phone-hangup-outline" compact="true" mode="contained" size={32} onPress={end} />
</View>
</View>
)}
{ state.calling && !state.connected && (
<View style={{ ...styles.overlap, bottom: 24 }}>
<View style={{backgroundColor: surface.title, borderRadius: 16 }}>
<Text style={styles.connecting}>{ state.strings.connecting }</Text>
</View>
</View>
)}
<Confirm show={alert} params={alertParams} />
</View>
);
}

View File

@ -1,37 +0,0 @@
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 useCalling() {
const ring = useContext(RingContext) as ContextType;
const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({
strings: display.state.strings,
calls: [] as { callId: string, card: Card }[],
calling: null as null | Card,
localStream: null as null|MediaStream,
remoteStream: null as null|MediaStream,
localVideo: false,
remoteVideo: false,
audioEnabled: false,
videoEnabled: false,
connected: false,
failed: false,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
useEffect(() => {
const { calls, calling, localStream, remoteStream, localVideo, remoteVideo, audioEnabled, videoEnabled, connected, failed } = ring.state;
updateState({ calls, calling, localStream, remoteStream, localVideo, remoteVideo, audioEnabled, videoEnabled, connected, failed });
}, [ring.state]);
const actions = ring.actions;
return { state, actions };
}

View File

@ -288,7 +288,7 @@ function DetailsScreen({nav}) {
const DetailsComponent = useCallback(
(props) => (
<Surface elevation={3}>
<Surface elevation={3} mode="flat">
<Details
closeAll={()=>closeAll(props)}
/>
@ -322,7 +322,7 @@ function ProfileScreen({nav}) {
};
const ProfileComponent = useCallback(() => (
<Surface elevation={3}>
<Surface elevation={3} mode="flat">
<Profile params={contactParams} />
</Surface>
), [contactParams]);
@ -346,7 +346,7 @@ function ProfileScreen({nav}) {
function RegistryScreen({nav}) {
const RegistryComponent = useCallback(
() => (
<Surface elevation={3}>
<Surface elevation={3} mode="flat">
<Registry
openContact={(params: ContactParams) => {
nav.openContact(params, nav.profile.openDrawer);
@ -376,7 +376,7 @@ function RegistryScreen({nav}) {
function ContactsScreen({nav}) {
const ContactsComponent = useCallback(
() => (
<Surface elevation={3}>
<Surface elevation={3} mode="flat">
<Contacts
openRegistry={nav.registry.openDrawer}
openContact={(params: ContactParams) => {
@ -411,7 +411,7 @@ function ContactsScreen({nav}) {
function SettingsScreen({nav}) {
const SettingsComponent = useCallback(
() => (
<Surface elevation={3}>
<Surface elevation={3} mode="flat">
<SafeAreaView>
<Settings />
</SafeAreaView>