// app/(tabs)/social/following.tsx import React, { useCallback, useState, useRef } from 'react'; import { View, FlatList, RefreshControl, TouchableOpacity } from 'react-native'; import { Text } from '@/components/ui/text'; import EnhancedSocialPost from '@/components/social/EnhancedSocialPost'; import { router } from 'expo-router'; import NostrLoginPrompt from '@/components/social/NostrLoginPrompt'; import { useNDKCurrentUser, useNDK } from '@/lib/hooks/useNDK'; import { useContactList } from '@/lib/hooks/useContactList'; import { ChevronUp, Bug } from 'lucide-react-native'; import { withOfflineState } from '@/components/social/SocialOfflineState'; import { useSocialFeed } from '@/lib/hooks/useSocialFeed'; function FollowingScreen() { const { isAuthenticated, currentUser } = useNDKCurrentUser(); const { ndk } = useNDK(); // Get the user's contact list const { contacts, isLoading: isLoadingContacts } = useContactList(currentUser?.pubkey); // Add debug logging for contact list React.useEffect(() => { console.log(`[FollowingScreen] Contact list has ${contacts.length} contacts`); if (contacts.length > 0) { console.log(`[FollowingScreen] First few contacts: ${contacts.slice(0, 3).join(', ')}`); } }, [contacts.length]); // Feed loading state tracking const [hasLoadedWithContent, setHasLoadedWithContent] = useState(false); const [hasLoadedWithContacts, setHasLoadedWithContacts] = useState(false); const [loadedContactsCount, setLoadedContactsCount] = useState(0); const [isRefreshingWithContacts, setIsRefreshingWithContacts] = useState(false); // Contact refresh retry tracking const [contactRefreshAttempts, setContactRefreshAttempts] = useState(0); const maxContactRefreshAttempts = 3; // Limit to prevent infinite refresh attempts // Use the enhanced useSocialFeed hook with the contact list // Always pass an array, even if empty, to ensure consistent behavior const { feedItems, loading, refresh, isOffline, socialService } = useSocialFeed({ feedType: 'following', limit: 30, authors: contacts // Always pass the contacts array, even if empty }); // Convert feed items to the format expected by the UI const entries = React.useMemo(() => { return feedItems.map(item => ({ id: item.id, eventId: item.id, event: item.originalEvent, content: item.parsedContent, timestamp: item.createdAt * 1000, type: item.type as any })); }, [feedItems]); // Update hasLoadedWithContent when we get feed items React.useEffect(() => { if (feedItems.length > 0 && !loading) { setHasLoadedWithContent(true); // Check if we've loaded with the full contact list if (contacts.length > 0 && loadedContactsCount === contacts.length) { setHasLoadedWithContacts(true); } } }, [feedItems.length, loading, contacts.length, loadedContactsCount]); // Update loadedContactsCount when contacts change React.useEffect(() => { if (contacts.length > 0 && contacts.length !== loadedContactsCount) { console.log(`[FollowingScreen] Contact list changed from ${loadedContactsCount} to ${contacts.length} contacts`); setLoadedContactsCount(contacts.length); // Reset hasLoadedWithContacts flag when contacts change setHasLoadedWithContacts(false); } }, [contacts.length, loadedContactsCount]); // Auto-refresh when contacts list changes React.useEffect(() => { // Prevent multiple simultaneous refresh attempts if (isRefreshingWithContacts) { return; } // Only refresh if we have contacts, aren't currently loading contacts, // and either haven't loaded with contacts yet or have no feed items const shouldRefresh = contacts.length > 0 && !isLoadingContacts && (!hasLoadedWithContacts || feedItems.length === 0) && contactRefreshAttempts < maxContactRefreshAttempts; if (shouldRefresh) { console.log(`[FollowingScreen] Refreshing feed with ${contacts.length} contacts (attempt ${contactRefreshAttempts + 1}/${maxContactRefreshAttempts})`); setIsRefreshingWithContacts(true); setContactRefreshAttempts(prev => prev + 1); refresh(true) .then(() => { setHasLoadedWithContent(true); setHasLoadedWithContacts(true); setIsRefreshingWithContacts(false); }) .catch(error => { console.error('[FollowingScreen] Error refreshing feed:', error); setIsRefreshingWithContacts(false); // Prevent infinite retries by marking as loaded after max attempts if (contactRefreshAttempts >= maxContactRefreshAttempts - 1) { setHasLoadedWithContacts(true); } }); } }, [ contacts.length, isLoadingContacts, hasLoadedWithContacts, feedItems.length, refresh, contactRefreshAttempts, isRefreshingWithContacts ]); const [isRefreshing, setIsRefreshing] = useState(false); const [showNewButton, setShowNewButton] = useState(false); const [newEntries, setNewEntries] = useState([]); const [showDebug, setShowDebug] = useState(false); // Use refs const listRef = useRef(null); const retryTimerRef = useRef(null); // Show new entries button when we have new content React.useEffect(() => { if (newEntries.length > 0) { setShowNewButton(true); } }, [newEntries.length]); // Depend on length, not array reference // If not authenticated, show login prompt if (!isAuthenticated) { return ; } // Handle showing new entries const handleShowNewEntries = useCallback(() => { setNewEntries([]); setShowNewButton(false); // Scroll to top listRef.current?.scrollToOffset({ offset: 0, animated: true }); }, []); // Manual refresh handler with improved error handling const handleRefresh = useCallback(async () => { setIsRefreshing(true); try { // Reset retry counter on manual refresh setContactRefreshAttempts(0); // Force refresh to bypass cooldown await refresh(true); // Small delay to ensure UI updates await new Promise(resolve => setTimeout(resolve, 300)); // Update loading states if content is available if (feedItems.length > 0) { setHasLoadedWithContent(true); if (contacts.length > 0) { setLoadedContactsCount(contacts.length); setHasLoadedWithContacts(true); } } } catch (error) { console.error('[FollowingScreen] Error refreshing feed:', error); } finally { setIsRefreshing(false); } }, [refresh, contacts.length, feedItems.length]); // Check relay connections const checkRelayConnections = useCallback(() => { if (!ndk) return; console.log("=== RELAY CONNECTION STATUS ==="); if (ndk.pool && ndk.pool.relays) { console.log(`Connected to ${ndk.pool.relays.size} relays:`); ndk.pool.relays.forEach((relay) => { console.log(`- ${relay.url}: ${relay.status}`); }); } else { console.log("No relay pool or connections available"); } console.log("==============================="); }, [ndk]); // Handle post selection - simplified for testing const handlePostPress = useCallback((entry: any) => { // Just show an alert with the entry info for testing alert(`Selected ${entry.type} with ID: ${entry.id || entry.eventId}`); // Alternatively, log to console for debugging console.log(`Selected ${entry.type}:`, entry); }, []); // Memoize render item function const renderItem = useCallback(({ item }: { item: any }) => ( handlePostPress(item)} /> ), [handlePostPress]); // Debug controls component - memoized const DebugControls = useCallback(() => ( Debug Info: User: {currentUser?.pubkey?.substring(0, 8)}... Feed Items: {entries.length} Loading: {loading ? "Yes" : "No"} Offline: {isOffline ? "Yes" : "No"} Contacts: {contacts.length} Loading Contacts: {isLoadingContacts ? "Yes" : "No"} Check Relays Force Refresh ), [currentUser?.pubkey, entries.length, loading, isOffline, contacts.length, isLoadingContacts, checkRelayConnections, handleRefresh]); // If user doesn't follow anyone or no content is available if (isAuthenticated && entries.length === 0 && !loading) { return ( {isOffline ? "You're offline. No cached content from followed users is available." : "No content from followed users found. Try following more users or check your relay connections."} {/* Debug toggle */} setShowDebug(!showDebug)} > {showDebug ? "Hide" : "Show"} Debug Info {showDebug && ( User pubkey: {currentUser?.pubkey?.substring(0, 12)}... Authenticated: {isAuthenticated ? "Yes" : "No"} Offline: {isOffline ? "Yes" : "No"} Has NDK follows: {currentUser?.follows ? "Yes" : "No"} {/* Toggle relays button */} Check Relay Connections {/* Manual refresh */} Force Refresh Feed )} ); } return ( {/* Debug toggle button */} setShowDebug(!showDebug)} > {/* Debug panel */} {showDebug && } {showNewButton && ( New Posts ({newEntries.length}) )} item.id} renderItem={renderItem} refreshControl={ } ListEmptyComponent={ loading ? ( Loading followed content... ) : ( No posts from followed users found {isOffline && ( You're currently offline. Connect to the internet to see the latest content. )} ) } contentContainerStyle={{ paddingVertical: 0 }} // Changed from paddingVertical: 8 /> ); } // Export the component wrapped with the offline state HOC export default withOfflineState(FollowingScreen);