forked from Rockachopa/Timmy-time-dashboard
- Single-screen chat interface with Timmy's sovereign AI personality - Text messaging with real-time AI responses via server chat API - Voice recording and playback with waveform visualization - Image sharing (camera + photo library) with full-screen viewer - File attachments via document picker - Dark arcane theme matching the Timmy Time dashboard - Custom app icon with glowing T circuit design - Timmy system prompt ported from dashboard prompts.py - Unit tests for chat utilities and message types
90 lines
2.2 KiB
TypeScript
90 lines
2.2 KiB
TypeScript
import { useEffect } from "react";
|
|
import { View, StyleSheet } from "react-native";
|
|
import Animated, {
|
|
useSharedValue,
|
|
useAnimatedStyle,
|
|
withRepeat,
|
|
withTiming,
|
|
withDelay,
|
|
withSequence,
|
|
} from "react-native-reanimated";
|
|
import { useColors } from "@/hooks/use-colors";
|
|
|
|
export function TypingIndicator() {
|
|
const colors = useColors();
|
|
const dot1 = useSharedValue(0.3);
|
|
const dot2 = useSharedValue(0.3);
|
|
const dot3 = useSharedValue(0.3);
|
|
|
|
useEffect(() => {
|
|
const anim = (sv: { value: number }, delay: number) => {
|
|
sv.value = withDelay(
|
|
delay,
|
|
withRepeat(
|
|
withSequence(
|
|
withTiming(1, { duration: 400 }),
|
|
withTiming(0.3, { duration: 400 }),
|
|
),
|
|
-1,
|
|
),
|
|
);
|
|
};
|
|
anim(dot1, 0);
|
|
anim(dot2, 200);
|
|
anim(dot3, 400);
|
|
}, []);
|
|
|
|
const style1 = useAnimatedStyle(() => ({ opacity: dot1.value }));
|
|
const style2 = useAnimatedStyle(() => ({ opacity: dot2.value }));
|
|
const style3 = useAnimatedStyle(() => ({ opacity: dot3.value }));
|
|
|
|
const dotBase = [styles.dot, { backgroundColor: colors.primary }];
|
|
|
|
return (
|
|
<View style={[styles.row, { alignItems: "flex-end" }]}>
|
|
<View style={[styles.avatar, { backgroundColor: colors.primary }]}>
|
|
<Animated.Text style={styles.avatarText}>T</Animated.Text>
|
|
</View>
|
|
<View style={[styles.bubble, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
|
<Animated.View style={[dotBase, style1]} />
|
|
<Animated.View style={[dotBase, style2]} />
|
|
<Animated.View style={[dotBase, style3]} />
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
row: {
|
|
flexDirection: "row",
|
|
paddingHorizontal: 12,
|
|
marginBottom: 8,
|
|
},
|
|
avatar: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
marginRight: 8,
|
|
},
|
|
avatarText: {
|
|
color: "#fff",
|
|
fontWeight: "700",
|
|
fontSize: 14,
|
|
},
|
|
bubble: {
|
|
flexDirection: "row",
|
|
gap: 5,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 14,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
},
|
|
dot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
},
|
|
});
|