A React Native app for the ultimate thinking partner.

refactor(ui): create unified LiveStatusIndicator component

- Create new LiveStatusIndicator component with consistent styling
- Use same font size (14px), font family, and colors across all status indicators
- Replace inline status displays with reusable component
- Maintains rainbow animation effect on "co" text
- Ensures "(co is thinking)", "(co is searching the web)", and "(co is saying)" all have identical styling

+69 -31
+4 -31
App.tsx
··· 36 36 import AnimatedStreamingText from './src/components/AnimatedStreamingText'; 37 37 import ToolCallItem from './src/components/ToolCallItem'; 38 38 import ReasoningToggle from './src/components/ReasoningToggle'; 39 + import LiveStatusIndicator from './src/components/LiveStatusIndicator'; 39 40 import MemoryBlockViewer from './src/components/MemoryBlockViewer'; 40 41 import MessageInput from './src/components/MessageInput'; 41 42 import { createMarkdownStyles } from './src/components/markdownStyles'; ··· 2329 2330 <Animated.View style={[styles.assistantFullWidthContainer, { minHeight: spacerHeightAnim }]}> 2330 2331 {/* Show current status when streaming */} 2331 2332 {!streamingMessage && ( 2332 - <Animated.View style={{ flexDirection: 'row', alignItems: 'baseline', marginBottom: 12, opacity: statusFadeAnim }}> 2333 - <Text style={{ fontSize: 24, fontFamily: 'Lexend_400Regular', color: darkTheme.colors.text.primary }}>(</Text> 2334 - <Animated.Text 2335 - style={{ 2336 - fontSize: 24, 2337 - fontFamily: 'Lexend_700Bold', 2338 - color: rainbowAnimValue.interpolate({ 2339 - inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1], 2340 - outputRange: ['#FF6B6B', '#FFD93D', '#6BCF7F', '#4D96FF', '#9D4EDD', '#FF6B6B'] 2341 - }) 2342 - }} 2343 - > 2344 - co 2345 - </Animated.Text> 2346 - <Text style={{ fontSize: 24, fontFamily: 'Lexend_400Regular', color: darkTheme.colors.text.primary }}> is {currentStreamingStatus})</Text> 2333 + <Animated.View style={{ opacity: statusFadeAnim }}> 2334 + <LiveStatusIndicator status={currentStreamingStatus} /> 2347 2335 </Animated.View> 2348 2336 )} 2349 2337 ··· 2369 2357 )} 2370 2358 {streamingMessage && ( 2371 2359 <> 2372 - <View style={{ flexDirection: 'row', alignItems: 'baseline', paddingVertical: 4, marginBottom: 8 }}> 2373 - <Text style={{ fontSize: 14, fontFamily: 'Lexend_500Medium', color: darkTheme.colors.text.secondary }}>(</Text> 2374 - <Animated.Text 2375 - style={{ 2376 - fontSize: 14, 2377 - fontFamily: 'Lexend_600SemiBold', 2378 - color: rainbowAnimValue.interpolate({ 2379 - inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1], 2380 - outputRange: ['#FF6B6B', '#FFD93D', '#6BCF7F', '#4D96FF', '#9D4EDD', '#FF6B6B'] 2381 - }) 2382 - }} 2383 - > 2384 - co 2385 - </Animated.Text> 2386 - <Text style={{ fontSize: 14, fontFamily: 'Lexend_500Medium', color: darkTheme.colors.text.secondary }}> is saying)</Text> 2387 - </View> 2360 + <LiveStatusIndicator status="saying" /> 2388 2361 <View style={{ flex: 1 }}> 2389 2362 <MessageContent 2390 2363 content={streamingMessage}
+65
src/components/LiveStatusIndicator.tsx
··· 1 + import React, { useEffect, useRef } from 'react'; 2 + import { View, Text, Animated, StyleSheet } from 'react-native'; 3 + import { darkTheme } from '../theme'; 4 + 5 + interface LiveStatusIndicatorProps { 6 + status: string; // e.g., "thinking", "searching the web", "saying" 7 + } 8 + 9 + const LiveStatusIndicator: React.FC<LiveStatusIndicatorProps> = ({ status }) => { 10 + const rainbowAnimValue = useRef(new Animated.Value(0)).current; 11 + 12 + // Animate rainbow gradient 13 + useEffect(() => { 14 + rainbowAnimValue.setValue(0); 15 + const animation = Animated.loop( 16 + Animated.timing(rainbowAnimValue, { 17 + toValue: 1, 18 + duration: 3000, 19 + useNativeDriver: false, 20 + }) 21 + ); 22 + animation.start(); 23 + return () => animation.stop(); 24 + }, []); 25 + 26 + return ( 27 + <View style={styles.container}> 28 + <Text style={styles.text}>(</Text> 29 + <Animated.Text 30 + style={[ 31 + styles.coText, 32 + { 33 + color: rainbowAnimValue.interpolate({ 34 + inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1], 35 + outputRange: ['#FF6B6B', '#FFD93D', '#6BCF7F', '#4D96FF', '#9D4EDD', '#FF6B6B'] 36 + }) 37 + } 38 + ]} 39 + > 40 + co 41 + </Animated.Text> 42 + <Text style={styles.text}> is {status})</Text> 43 + </View> 44 + ); 45 + }; 46 + 47 + const styles = StyleSheet.create({ 48 + container: { 49 + flexDirection: 'row', 50 + alignItems: 'baseline', 51 + paddingVertical: 4, 52 + marginBottom: 8, 53 + }, 54 + text: { 55 + fontSize: 14, 56 + fontFamily: 'Lexend_500Medium', 57 + color: darkTheme.colors.text.secondary, 58 + }, 59 + coText: { 60 + fontSize: 14, 61 + fontFamily: 'Lexend_600SemiBold', 62 + }, 63 + }); 64 + 65 + export default React.memo(LiveStatusIndicator);