This commit is contained in:
2026-03-28 23:51:50 -04:00
parent 0a477300f4
commit e56e3ba531
47 changed files with 13489 additions and 201 deletions

View File

@@ -0,0 +1,82 @@
import { useState, useCallback, useEffect } from 'react';
import { FeedItem } from '@/types/feed';
import { getAllFeedItems } from '@/services/database';
const PAGE_SIZE = 20;
export function useFeedList() {
const [localFeedItems, setLocalFeedItems] = useState<FeedItem[]>([]);
const [page, setPage] = useState(0);
const [hasMore, setHasMore] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState<string | null>(null);
const loadMore = useCallback(async () => {
if (loadingMore || !hasMore || isRefreshing) return;
setLoadingMore(true);
try {
const offset = page * PAGE_SIZE;
const items = await getAllFeedItems(PAGE_SIZE + 1, offset);
if (items.length > PAGE_SIZE) {
const newItems = items.slice(0, PAGE_SIZE);
setLocalFeedItems(prev => [...prev, ...newItems]);
setHasMore(true);
} else {
setLocalFeedItems(prev => [...prev, ...items]);
setHasMore(false);
}
setPage(prev => prev + 1);
setError(null);
} catch (err) {
console.error('Failed to load more items:', err);
setError('Failed to load more items. Please try again.');
} finally {
setLoadingMore(false);
}
}, [loadingMore, hasMore, isRefreshing, page]);
const refreshFeed = useCallback(async () => {
if (isRefreshing) return;
setIsRefreshing(true);
setPage(0);
setHasMore(true);
setError(null);
try {
const items = await getAllFeedItems(PAGE_SIZE + 1);
if (items.length > PAGE_SIZE) {
setLocalFeedItems(items.slice(0, PAGE_SIZE));
setHasMore(true);
} else {
setLocalFeedItems(items);
setHasMore(false);
}
} catch (err) {
console.error('Failed to refresh feed:', err);
setError('Failed to refresh feed. Please try again.');
} finally {
setIsRefreshing(false);
}
}, [isRefreshing]);
useEffect(() => {
refreshFeed();
}, []);
return {
feedItems: localFeedItems,
loading: loadingMore,
hasMore,
loadMore,
refreshFeed,
isRefreshing,
error,
};
}

99
src/hooks/use-offline.ts Normal file
View File

@@ -0,0 +1,99 @@
// Offline/Online Network Hook
import { useState, useEffect, useCallback } from 'react';
import { hasLocalData, getAllLocalFeedItems, getLocalFeedItems, syncAllFeeds } from '@/services/sync-service';
import { FeedItem } from '@/types/feed';
type NetworkStatus = 'online' | 'offline' | 'unknown';
interface UseOfflineReturn {
isOnline: boolean;
networkStatus: NetworkStatus;
hasOfflineData: boolean;
isSyncing: boolean;
lastSyncTime: Date | null;
syncNow: () => Promise<void>;
getOfflineFeedItems: (subscriptionId?: string, limit?: number, offset?: number) => Promise<FeedItem[]>;
}
export function useOffline(): UseOfflineReturn {
const [networkStatus, setNetworkStatus] = useState<NetworkStatus>('unknown');
const [hasOfflineData, setHasOfflineData] = useState(false);
const [isSyncing, setIsSyncing] = useState(false);
const [lastSyncTime, setLastSyncTime] = useState<Date | null>(null);
// Check network status (simplified - in production would use @react-native-community/netinfo)
const checkNetworkStatus = useCallback(async () => {
try {
// Try to fetch from a known endpoint to test connectivity
const response = await fetch('https://www.google.com/favicon.ico', {
method: 'HEAD',
mode: 'no-cors',
});
setNetworkStatus('online');
return true;
} catch {
setNetworkStatus('offline');
return false;
}
}, []);
// Check if we have offline data
const checkOfflineData = useCallback(async () => {
const hasData = await hasLocalData();
setHasOfflineData(hasData);
return hasData;
}, []);
// Sync now
const syncNow = useCallback(async () => {
if (networkStatus === 'offline') {
console.log('[Offline] Cannot sync while offline');
return;
}
setIsSyncing(true);
try {
await syncAllFeeds();
setLastSyncTime(new Date());
await checkOfflineData();
} finally {
setIsSyncing(false);
}
}, [networkStatus, checkOfflineData]);
// Get offline feed items
const getOfflineFeedItems = useCallback(async (
subscriptionId?: string,
limit: number = 50,
offset: number = 0
): Promise<FeedItem[]> => {
if (subscriptionId) {
return getLocalFeedItems(subscriptionId, limit, offset);
}
return getAllLocalFeedItems(limit);
}, []);
// Initial checks
useEffect(() => {
checkNetworkStatus();
checkOfflineData();
// Periodic network check
const interval = setInterval(() => {
checkNetworkStatus();
}, 30000); // Every 30 seconds
return () => clearInterval(interval);
}, [checkNetworkStatus, checkOfflineData]);
return {
isOnline: networkStatus === 'online',
networkStatus,
hasOfflineData,
isSyncing,
lastSyncTime,
syncNow,
getOfflineFeedItems,
};
}