106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { View, Pressable } from "react-native";
|
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
import { MicOff, Square } from "lucide-react-native";
|
|
import { VolumeMeter } from "./volume-meter";
|
|
import { useVoice, useVoiceTelemetry } from "@/contexts/voice-context";
|
|
import { useHosts } from "@/runtime/host-runtime";
|
|
|
|
export function VoicePanel() {
|
|
const { theme } = useUnistyles();
|
|
const daemons = useHosts();
|
|
const { volume, isSpeaking } = useVoiceTelemetry();
|
|
const { isMuted, stopVoice, toggleMute, activeServerId } = useVoice();
|
|
const hostLabel = activeServerId
|
|
? (daemons.find((daemon) => daemon.serverId === activeServerId)?.label ?? null)
|
|
: null;
|
|
const hostSuffix = hostLabel ? ` (${hostLabel})` : "";
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.contentRow}>
|
|
<View style={styles.meterContainer}>
|
|
<VolumeMeter
|
|
volume={volume}
|
|
isMuted={isMuted}
|
|
isSpeaking={isSpeaking}
|
|
orientation="horizontal"
|
|
variant="compact"
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.actionsRow}>
|
|
<Pressable
|
|
onPress={toggleMute}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={`${isMuted ? "Unmute voice" : "Mute voice"}${hostSuffix}`}
|
|
style={[styles.iconButton, isMuted && styles.iconButtonMuted]}
|
|
>
|
|
<MicOff
|
|
size={18}
|
|
color={isMuted ? theme.colors.palette.white : theme.colors.foreground}
|
|
/>
|
|
</Pressable>
|
|
|
|
<Pressable
|
|
onPress={() => void stopVoice()}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={`Stop voice mode${hostSuffix}`}
|
|
style={[styles.iconButton, styles.iconButtonStop]}
|
|
>
|
|
<Square size={16} color="white" fill="white" />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
container: {
|
|
marginHorizontal: theme.spacing[4],
|
|
marginBottom: theme.spacing[3],
|
|
borderRadius: theme.borderRadius["2xl"],
|
|
borderWidth: theme.borderWidth[1],
|
|
borderColor: theme.colors.border,
|
|
backgroundColor: theme.colors.surface2,
|
|
paddingVertical: theme.spacing[2],
|
|
paddingHorizontal: theme.spacing[3],
|
|
},
|
|
contentRow: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
gap: theme.spacing[3],
|
|
},
|
|
meterContainer: {
|
|
flex: 1,
|
|
justifyContent: "center",
|
|
alignItems: "flex-start",
|
|
paddingLeft: theme.spacing[1],
|
|
},
|
|
actionsRow: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "flex-end",
|
|
gap: theme.spacing[2],
|
|
},
|
|
iconButton: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: theme.borderRadius.full,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
backgroundColor: theme.colors.surface0,
|
|
borderWidth: theme.borderWidth[1],
|
|
borderColor: theme.colors.border,
|
|
},
|
|
iconButtonMuted: {
|
|
backgroundColor: theme.colors.palette.red[500],
|
|
borderWidth: 0,
|
|
},
|
|
iconButtonStop: {
|
|
backgroundColor: theme.colors.palette.red[600],
|
|
borderColor: theme.colors.palette.red[800],
|
|
},
|
|
}));
|