import { View, StyleSheet, FlatList, TextInput, Text, ActivityIndicator, Animated } from 'react-native' import React, { useEffect } from 'react' import { useState } from 'react' import FoodItem from '@/components/FoodItem' import { AntDesign } from '@expo/vector-icons' import { useSQLiteContext } from 'expo-sqlite'; import { SearchFood } from '../../functions/SearchFood' import { useAppTheme, subscribeToTheme } from '../../hooks/colorScheme'; export default function Track({ navigation }) { const [colors, setColors] = useState(useAppTheme()); const [searchResults, setSearchResults] = useState([]); const [noResults, setNoResults] = useState(false); const [search, setSearch] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [showEmptySearchPrompt, setShowEmptySearchPrompt] = useState(false); const fadeAnim = React.useRef(new Animated.Value(0)).current; const searchCustomFoods = async (searchTerm) => { try { const results = await database.getAllAsync("SELECT * FROM customfoods"); const matchingResults = results.filter(item => item.name.toLowerCase().trim().includes(searchTerm.toLowerCase().trim()) ); return matchingResults; } catch (error) { console.error("Error searching custom foods:", error); return []; } }; // Updated performSearch function const performSearch = async (searchTerm) => { // Reset states setError(null); // Handle empty search if (!searchTerm?.trim()) { setSearchResults([]); setNoResults(false); setShowEmptySearchPrompt(true); // Trigger fade in fadeAnim.setValue(0); Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true, }).start(); return; } setShowEmptySearchPrompt(false); setIsLoading(true); try { const apiResults = await SearchFood(searchTerm); const customResults = await searchCustomFoods(searchTerm); const modifiedCustomResults = customResults.map(item => ({ ...item, id: `custom_${item.id}` })); const combinedResults = [...modifiedCustomResults, ...apiResults]; setSearchResults(combinedResults); setNoResults(combinedResults.length === 0); } catch (error) { // attempt search again try { const apiResults = await SearchFood(searchTerm); const customResults = await searchCustomFoods(searchTerm); const modifiedCustomResults = customResults.map(item => ({ ...item, id: `custom_${item.id}` })); const combinedResults = [...modifiedCustomResults, ...apiResults]; setSearchResults(combinedResults); setNoResults(combinedResults.length === 0); } catch (error) { console.error("second search failed:", error); } console.error("Search failed:", error); setError("Failed to load results. Please try again."); setSearchResults([]); } finally { setIsLoading(false); } }; const ItemSeparator = () => ( <View style={{ height: 5 }} /> ); const getItemLayout = (data, index) => ({ length: 80, // Approximate height of each item offset: 80 * index, index, }); const database = useSQLiteContext(); useEffect(() => { const unsubscribe = subscribeToTheme(newColors => { setColors(newColors); }); return () => unsubscribe(); }, []); return ( <View style={[styles.container, {backgroundColor: colors.background}]}> <View style={[styles.card, {backgroundColor: colors.boxes}]}> <TextInput value={search} onChangeText={setSearch} onSubmitEditing={() => performSearch(search)} returnKeyType="search" placeholder="Search for Foods..." placeholderTextColor={colors.text + '80'} style={[styles.input, {color: colors.text}]} /> </View> <View style={[styles.buttonContainer, {backgroundColor: colors.background}]}> <View style={[styles.buttonCard, {backgroundColor: colors.boxes}]}> <AntDesign.Button style={styles.button} name="search1" activeOpacity={0.5} underlayColor={'transparent'} onPress={() => performSearch(search)} backgroundColor="transparent" color={colors.accent}> Search </AntDesign.Button> </View> {false && <View style={[styles.buttonCard, {backgroundColor: colors.boxes}]}> <AntDesign.Button style={styles.button} name="scan1" activeOpacity={0.5} underlayColor={'transparent'} onPress={() => navigation.navigate('Scan')} backgroundColor="transparent" color={colors.accent}> Scan </AntDesign.Button> </View>} <View style={[styles.buttonCard, {backgroundColor: colors.boxes}]}> <AntDesign.Button style={styles.button} name="addfile" activeOpacity={0.5} underlayColor={'transparent'} onPress={() => navigation.navigate('QuickAdd')} backgroundColor="transparent" color={colors.accent}> Quick Add </AntDesign.Button> </View> </View> {isLoading && ( <View style={styles.centerContent}> <ActivityIndicator size="large" color={colors.accent} /> <Text style={[styles.messageText, {color: colors.text}]}> Searching... </Text> </View> )} {error && ( <View style={styles.centerContent}> <Text style={[styles.errorText, {color: colors.error || '#ff6b6b'}]}> {error} </Text> </View> )} {showEmptySearchPrompt && !isLoading && !error && ( <Animated.Text style={[ styles.noResults, { color: colors.text, opacity: fadeAnim } ]}> Please enter a food name to search </Animated.Text> )} {noResults && !isLoading && !error && !showEmptySearchPrompt && ( <Text style={[styles.noResults, {color: colors.text}]}> No results found... </Text> )} {!isLoading && !error && ( <FlatList data={searchResults} // Using state-managed results keyExtractor={item => item.id ? item.id.toString() : Math.random().toString()} renderItem={({item}) => <FoodItem item={item} />} ItemSeparatorComponent={ItemSeparator} getItemLayout={getItemLayout} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} showsVerticalScrollIndicator={false} removeClippedSubviews={true} contentContainerStyle={{paddingHorizontal: 5}} /> )} </View>) } const styles = StyleSheet.create({ container: { flex: 1, padding: 15, gap: 16, }, card: { borderRadius: 20, padding: 5, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 5, marginTop: 65, }, input: { padding: 10, fontSize: 16, fontWeight: '500', }, button: { justifyContent: 'center', minWidth: "45%", height: 50, paddingHorizontal: 15, }, buttonContainer: { flexDirection: 'row', justifyContent: 'space-evenly', paddingHorizontal: 5, }, buttonCard: { borderRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 5, }, noResults: { fontSize: 16, marginTop: 50, textAlign: 'center', }, centerContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, messageText: { fontSize: 16, marginTop: 10, textAlign: 'center', }, errorText: { fontSize: 16, textAlign: 'center', marginBottom: 10, }, })