Enhance ContinueWatchingSection with background refresh and loading state management

This update improves the ContinueWatchingSection component by implementing a background refresh mechanism and managing loading states more effectively. The loadContinueWatching function now prevents concurrent refreshes and allows for manual refreshes with a loading indicator. Additionally, the debounce time for background refreshes has been slightly increased, enhancing performance and user experience.
This commit is contained in:
tapframe 2025-06-21 20:22:27 +05:30
parent 314ece1238
commit e2db895d66
2 changed files with 24 additions and 16 deletions

View file

@ -77,10 +77,20 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
const appState = useRef(AppState.currentState); const appState = useRef(AppState.currentState);
const refreshTimerRef = useRef<NodeJS.Timeout | null>(null); const refreshTimerRef = useRef<NodeJS.Timeout | null>(null);
// Use a state to track if a background refresh is in progress
const [isRefreshing, setIsRefreshing] = useState(false);
// Modified loadContinueWatching to be more efficient // Modified loadContinueWatching to be more efficient
const loadContinueWatching = useCallback(async () => { const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
try { // Prevent multiple concurrent refreshes
if (isRefreshing) return;
if (!isBackgroundRefresh) {
setLoading(true); setLoading(true);
}
setIsRefreshing(true);
try {
const allProgress = await storageService.getAllWatchProgress(); const allProgress = await storageService.getAllWatchProgress();
if (Object.keys(allProgress).length === 0) { if (Object.keys(allProgress).length === 0) {
@ -193,8 +203,9 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
logger.error('Failed to load continue watching items:', error); logger.error('Failed to load continue watching items:', error);
} finally { } finally {
setLoading(false); setLoading(false);
setIsRefreshing(false);
} }
}, []); }, [isRefreshing]);
// Function to handle app state changes // Function to handle app state changes
const handleAppStateChange = useCallback((nextAppState: AppStateStatus) => { const handleAppStateChange = useCallback((nextAppState: AppStateStatus) => {
@ -202,8 +213,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
appState.current.match(/inactive|background/) && appState.current.match(/inactive|background/) &&
nextAppState === 'active' nextAppState === 'active'
) { ) {
// App has come to the foreground - refresh data // App has come to the foreground - trigger a background refresh
loadContinueWatching(); loadContinueWatching(true);
} }
appState.current = nextAppState; appState.current = nextAppState;
}, [loadContinueWatching]); }, [loadContinueWatching]);
@ -220,8 +231,9 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
clearTimeout(refreshTimerRef.current); clearTimeout(refreshTimerRef.current);
} }
refreshTimerRef.current = setTimeout(() => { refreshTimerRef.current = setTimeout(() => {
loadContinueWatching(); // Trigger a background refresh
}, 300); loadContinueWatching(true);
}, 500); // Increased debounce time slightly
}; };
// Try to set up a custom event listener or use a timer as fallback // Try to set up a custom event listener or use a timer as fallback
@ -236,7 +248,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
}; };
} else { } else {
// Fallback: poll for updates every 30 seconds // Fallback: poll for updates every 30 seconds
const intervalId = setInterval(loadContinueWatching, 30000); const intervalId = setInterval(() => loadContinueWatching(true), 30000);
return () => { return () => {
subscription.remove(); subscription.remove();
clearInterval(intervalId); clearInterval(intervalId);
@ -252,13 +264,12 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
loadContinueWatching(); loadContinueWatching();
}, [loadContinueWatching]); }, [loadContinueWatching]);
// Properly expose the refresh method // Expose the refresh function via the ref
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
refresh: async () => { refresh: async () => {
await loadContinueWatching(); // Allow manual refresh to show loading indicator
// Return whether there are items to help parent determine visibility await loadContinueWatching(false);
const hasItems = continueWatchingItems.length > 0; return true;
return hasItems;
} }
})); }));

View file

@ -10,7 +10,6 @@ import {
import { Image } from 'expo-image'; import { Image } from 'expo-image';
import Animated, { import Animated, {
FadeIn, FadeIn,
Layout,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
import { useTheme } from '../../contexts/ThemeContext'; import { useTheme } from '../../contexts/ThemeContext';
@ -43,7 +42,6 @@ export const CastSection: React.FC<CastSectionProps> = ({
<Animated.View <Animated.View
style={styles.castSection} style={styles.castSection}
entering={FadeIn.duration(300).delay(150)} entering={FadeIn.duration(300).delay(150)}
layout={Layout}
> >
<View style={styles.sectionHeader}> <View style={styles.sectionHeader}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.highEmphasis }]}>Cast</Text> <Text style={[styles.sectionTitle, { color: currentTheme.colors.highEmphasis }]}>Cast</Text>
@ -57,7 +55,6 @@ export const CastSection: React.FC<CastSectionProps> = ({
renderItem={({ item, index }) => ( renderItem={({ item, index }) => (
<Animated.View <Animated.View
entering={FadeIn.duration(300).delay(50 + index * 30)} entering={FadeIn.duration(300).delay(50 + index * 30)}
layout={Layout}
> >
<TouchableOpacity <TouchableOpacity
style={styles.castCard} style={styles.castCard}