Creating a RN example for simple-chatbot
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { View, Image, StyleSheet, LayoutChangeEvent, ImageStyle, ViewStyle } from 'react-native';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Icons } from '../theme/Assets';
|
||||
import Colors from '../theme/Colors';
|
||||
|
||||
import { useVoiceClient } from '../context/VoiceClientContext';
|
||||
import { VoiceClientVideoView } from '@pipecat-ai/react-native-daily-transport';
|
||||
|
||||
interface CameraButtonViewProps {
|
||||
style?: ViewStyle; // Optional additional styles for the button container
|
||||
}
|
||||
|
||||
const CameraButtonView: React.FC<CameraButtonViewProps> = ({ style }) => {
|
||||
const { videoTrack, isCamEnabled } = useVoiceClient();
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
setDimensions({ width, height });
|
||||
};
|
||||
|
||||
const mediaComponent = useMemo(() => {
|
||||
return (
|
||||
<VoiceClientVideoView
|
||||
videoTrack={videoTrack || null}
|
||||
audioTrack={null}
|
||||
mirror={true}
|
||||
zOrder={1}
|
||||
style={styles.media}
|
||||
objectFit="cover"
|
||||
/>
|
||||
);
|
||||
}, [videoTrack]);
|
||||
|
||||
const { width } = dimensions;
|
||||
const circleSize = width * 0.9;
|
||||
const innerCircleSize = width * 0.82;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, style]} onLayout={onLayout}>
|
||||
<View
|
||||
style={[
|
||||
styles.outerCircle,
|
||||
{ width: circleSize, height: circleSize, borderRadius: circleSize / 2 },
|
||||
]}
|
||||
>
|
||||
{isCamEnabled ? (
|
||||
<View style={[styles.videoView, { borderRadius: circleSize / 2 }]}>
|
||||
{mediaComponent}
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.innerCircle,
|
||||
{
|
||||
width: innerCircleSize,
|
||||
height: innerCircleSize,
|
||||
borderRadius: innerCircleSize / 2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Image
|
||||
source={Icons.vision}
|
||||
style={[
|
||||
styles.image,
|
||||
{
|
||||
width: width * 0.3,
|
||||
height: width * 0.3,
|
||||
tintColor: 'green',
|
||||
},
|
||||
]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
outerCircle: {
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.buttonsBorder,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
innerCircle: {
|
||||
backgroundColor: Colors.disabledVision,
|
||||
position: 'absolute',
|
||||
} as ViewStyle,
|
||||
videoView: {
|
||||
aspectRatio: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
} as ViewStyle,
|
||||
image: {} as ImageStyle,
|
||||
media: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
|
||||
export default CameraButtonView;
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { View, StyleSheet, LayoutChangeEvent, ViewStyle } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import Colors from '../theme/Colors';
|
||||
import { useVoiceClient } from '../context/VoiceClientContext';
|
||||
|
||||
interface MicrophoneViewProps {
|
||||
style?: ViewStyle; // Optional additional styles for the button container
|
||||
}
|
||||
|
||||
const MicrophoneView: React.FC<MicrophoneViewProps> = ({ style }) => {
|
||||
const { isMicEnabled, localAudioLevel: audioLevel } = useVoiceClient();
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
setDimensions({ width, height });
|
||||
};
|
||||
|
||||
const { width } = dimensions;
|
||||
|
||||
const circleSize = useMemo(() => width * 0.9, [width]);
|
||||
const innerCircleSize = useMemo(() => width * 0.82, [width]);
|
||||
const audioCircleSize = useMemo(() => audioLevel * width * 0.95, [audioLevel, width]);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, style]} onLayout={onLayout}>
|
||||
<View
|
||||
style={[
|
||||
styles.outerCircle,
|
||||
{ width: circleSize, height: circleSize, borderRadius: circleSize / 2 },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.innerCircle,
|
||||
{
|
||||
backgroundColor: !isMicEnabled ? Colors.disabledMic : Colors.backgroundCircle,
|
||||
width: innerCircleSize,
|
||||
height: innerCircleSize,
|
||||
borderRadius: innerCircleSize / 2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{isMicEnabled && (
|
||||
<View
|
||||
style={[
|
||||
styles.audioCircle,
|
||||
{
|
||||
width: audioCircleSize,
|
||||
height: audioCircleSize,
|
||||
borderRadius: audioCircleSize / 2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MaterialIcons
|
||||
name={!isMicEnabled ? "mic-off" : "mic"}
|
||||
size={width * 0.2}
|
||||
color="white"
|
||||
style={styles.micIcon}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
outerCircle: {
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.buttonsBorder,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
innerCircle: {
|
||||
position: 'absolute',
|
||||
} as ViewStyle,
|
||||
audioCircle: {
|
||||
position: 'absolute',
|
||||
backgroundColor: Colors.micVolume,
|
||||
opacity: 0.5,
|
||||
} as ViewStyle,
|
||||
micIcon: {
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
|
||||
export default MicrophoneView;
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { LayoutChangeEvent, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import Colors from '../theme/Colors';
|
||||
import { useVoiceClient } from '../context/VoiceClientContext';
|
||||
|
||||
const dotCount = 5;
|
||||
|
||||
const WaveformView: React.FC = () => {
|
||||
const [audioLevels, setAudioLevels] = useState(Array(dotCount).fill(0));
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
|
||||
const { currentState: voiceClientStatus, botReady: isBotReady, remoteAudioLevel: audioLevel } = useVoiceClient();
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
setDimensions({ width, height });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAudioLevels((prevLevels) => [...prevLevels.slice(1), audioLevel]);
|
||||
}, [audioLevel]);
|
||||
|
||||
const { width, height } = dimensions;
|
||||
const circleSize = width * 0.9;
|
||||
const innerCircleSize = width * 0.82;
|
||||
const barWidth = (width * 0.5) / dotCount;
|
||||
|
||||
return (
|
||||
<View style={styles.container} onLayout={onLayout}>
|
||||
<View style={[styles.outerCircle, { width: circleSize, height: circleSize, borderRadius: circleSize / 2 }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.innerCircle,
|
||||
{
|
||||
backgroundColor: isBotReady ? Colors.backgroundCircle : Colors.backgroundCircleNotConnected,
|
||||
width: innerCircleSize,
|
||||
height: innerCircleSize,
|
||||
borderRadius: innerCircleSize / 2,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{isBotReady ? (
|
||||
audioLevel > 0 ? (
|
||||
<View style={[styles.waveformContainer, { width: width * 0.5, height: width * 0.5 }]}>
|
||||
{audioLevels.map((level, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.waveformBar,
|
||||
{
|
||||
width: barWidth - 10, // Subtract some margin
|
||||
height: level * height,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
) : (
|
||||
<View style={[styles.dotContainer, { width: width * 0.5, height: height * 0.5 }]}>
|
||||
{Array(dotCount)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<View key={index} style={[styles.dot, { width: height * 0.1, height: height * 0.1 }]} />
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
) : (
|
||||
<View style={styles.notReadyContainer}>
|
||||
<MaterialIcons name="hourglass-empty" size={32} color="white" />
|
||||
<Text style={styles.voiceClientStatusText}>{voiceClientStatus}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: "100%",
|
||||
} as ViewStyle,
|
||||
outerCircle: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'gray',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
innerCircle: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
} as ViewStyle,
|
||||
waveformContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
waveformBar: {
|
||||
backgroundColor: 'white',
|
||||
maxHeight: '100%',
|
||||
borderRadius: 12,
|
||||
} as ViewStyle,
|
||||
dotContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
dot: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 50,
|
||||
} as ViewStyle,
|
||||
notReadyContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
voiceClientStatusText: {
|
||||
color: 'white',
|
||||
marginTop: 10,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
} as ViewStyle,
|
||||
});
|
||||
|
||||
export default WaveformView;
|
||||
Reference in New Issue
Block a user