feat: refactor app structure and components for improved organization and functionality
- Moved components and hooks into a new directory structure under `src/app` for better organization. - Updated the main entry point to use `index.ts` and adjusted routing accordingly. - Introduced new components such as `ActiveProcesses`, `AgentStreamView`, and `ConnectionStatus` to enhance the app's functionality. - Removed deprecated components and hooks to streamline the codebase. - Updated styles to align with the new theme structure and ensure consistency across the app. This refactor aims to improve maintainability and scalability of the application.
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
import { Pressable, View, Text, Animated } from 'react-native';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { StyleSheet } from 'react-native-unistyles';
|
||||
|
||||
interface VoiceButtonProps {
|
||||
state: 'idle' | 'recording' | 'processing' | 'playing';
|
||||
onPress: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing[4],
|
||||
},
|
||||
pressable: {
|
||||
opacity: 1,
|
||||
},
|
||||
pressableDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
button: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
},
|
||||
buttonIdle: {
|
||||
backgroundColor: theme.colors.muted,
|
||||
},
|
||||
buttonRecording: {
|
||||
backgroundColor: theme.colors.destructive,
|
||||
},
|
||||
buttonProcessing: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
},
|
||||
buttonPlaying: {
|
||||
backgroundColor: theme.colors.palette.green[500],
|
||||
},
|
||||
label: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
// Recording icon
|
||||
recordingIcon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
backgroundColor: theme.colors.foreground,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
// Processing icon
|
||||
processingIconContainer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
processingDot: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
backgroundColor: theme.colors.primaryForeground,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
position: 'absolute',
|
||||
},
|
||||
processingDotTop: {
|
||||
top: 0,
|
||||
left: 12,
|
||||
},
|
||||
processingDotRight: {
|
||||
top: 12,
|
||||
right: 0,
|
||||
},
|
||||
processingDotBottom: {
|
||||
bottom: 0,
|
||||
left: 12,
|
||||
},
|
||||
// Playing icon
|
||||
playingIconContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
playingBar: {
|
||||
backgroundColor: theme.colors.foreground,
|
||||
borderRadius: theme.borderRadius.sm,
|
||||
},
|
||||
playingBar1: {
|
||||
width: 4,
|
||||
height: 16,
|
||||
},
|
||||
playingBar2: {
|
||||
width: 4,
|
||||
height: 24,
|
||||
},
|
||||
playingBar3: {
|
||||
width: 4,
|
||||
height: 12,
|
||||
},
|
||||
// Idle icon (microphone)
|
||||
micContainer: {
|
||||
width: 24,
|
||||
height: 32,
|
||||
position: 'relative',
|
||||
},
|
||||
micCapsule: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 4,
|
||||
width: 16,
|
||||
height: 24,
|
||||
backgroundColor: theme.colors.foreground,
|
||||
borderTopLeftRadius: 999,
|
||||
borderTopRightRadius: 999,
|
||||
},
|
||||
micBase: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: 24,
|
||||
height: 6,
|
||||
backgroundColor: theme.colors.foreground,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
},
|
||||
}));
|
||||
|
||||
export function VoiceButton({ state, onPress, disabled = false }: VoiceButtonProps) {
|
||||
const pulseAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
useEffect(() => {
|
||||
if (state === 'recording') {
|
||||
Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1.2,
|
||||
duration: 800,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1,
|
||||
duration: 800,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
])
|
||||
).start();
|
||||
} else {
|
||||
pulseAnim.setValue(1);
|
||||
}
|
||||
}, [state, pulseAnim]);
|
||||
|
||||
const getButtonStyle = () => {
|
||||
switch (state) {
|
||||
case 'recording':
|
||||
return styles.buttonRecording;
|
||||
case 'processing':
|
||||
return styles.buttonProcessing;
|
||||
case 'playing':
|
||||
return styles.buttonPlaying;
|
||||
default:
|
||||
return styles.buttonIdle;
|
||||
}
|
||||
};
|
||||
|
||||
const getIcon = () => {
|
||||
switch (state) {
|
||||
case 'recording':
|
||||
return <View style={styles.recordingIcon} />;
|
||||
case 'processing':
|
||||
return (
|
||||
<View style={styles.processingIconContainer}>
|
||||
<View style={[styles.processingDot, styles.processingDotTop]} />
|
||||
<View style={[styles.processingDot, styles.processingDotRight]} />
|
||||
<View style={[styles.processingDot, styles.processingDotBottom]} />
|
||||
</View>
|
||||
);
|
||||
case 'playing':
|
||||
return (
|
||||
<View style={styles.playingIconContainer}>
|
||||
<View style={[styles.playingBar, styles.playingBar1]} />
|
||||
<View style={[styles.playingBar, styles.playingBar2]} />
|
||||
<View style={[styles.playingBar, styles.playingBar3]} />
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<View style={styles.micContainer}>
|
||||
<View style={styles.micCapsule} />
|
||||
<View style={styles.micBase} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getLabel = () => {
|
||||
switch (state) {
|
||||
case 'recording':
|
||||
return 'Recording...';
|
||||
case 'processing':
|
||||
return 'Processing...';
|
||||
case 'playing':
|
||||
return 'Playing...';
|
||||
default:
|
||||
return 'Tap to speak';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
style={disabled ? styles.pressableDisabled : styles.pressable}
|
||||
>
|
||||
<Animated.View style={[styles.button, getButtonStyle(), { transform: [{ scale: pulseAnim }] }]}>
|
||||
{getIcon()}
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
<Text style={styles.label}>{getLabel()}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user