Add close button to VideoPlayer and improve loading indicators in StreamsScreen

This update introduces a close button in the VideoPlayer component for better user control during video loading. Additionally, the StreamsScreen has been enhanced to show loading indicators for individual stream providers, improving the user experience by providing visual feedback during data fetching.
This commit is contained in:
tapframe 2025-06-09 02:21:41 +05:30
parent 988a746a5b
commit 6c44c0ec59
3 changed files with 67 additions and 18 deletions

View file

@ -9,6 +9,7 @@ import * as ScreenOrientation from 'expo-screen-orientation';
import { storageService } from '../../services/storageService';
import { logger } from '../../utils/logger';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MaterialIcons } from '@expo/vector-icons';
import {
DEFAULT_SUBTITLE_SIZE,
@ -747,6 +748,14 @@ const VideoPlayer: React.FC = () => {
]}
pointerEvents={isOpeningAnimationComplete ? 'none' : 'auto'}
>
<TouchableOpacity
style={styles.loadingCloseButton}
onPress={handleClose}
activeOpacity={0.7}
>
<MaterialIcons name="close" size={24} color="#ffffff" />
</TouchableOpacity>
<View style={styles.openingContent}>
<ActivityIndicator size="large" color="#E50914" />
<Text style={styles.openingText}>Loading video...</Text>

View file

@ -752,4 +752,16 @@ export const styles = StyleSheet.create({
fontSize: 12,
marginTop: 2,
},
loadingCloseButton: {
position: 'absolute',
top: 40,
right: 20,
width: 44,
height: 44,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
borderRadius: 22,
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
},
});

View file

@ -856,11 +856,8 @@ export const StreamsScreen = () => {
const renderItem = useCallback(({ item, index, section }: { item: Stream; index: number; section: any }) => {
const stream = item;
const isLoading = loadingProviders[section.addonId];
// Special handling for HDRezka streams
const quality = stream.title?.match(/(\d+)p/)?.[1] || null;
const isHDRezka = section.addonId === 'hdrezka';
// Don't show loading for individual streams that are already available and displayed
const isLoading = false; // If streams are being rendered, they're available and shouldn't be loading
return (
<StreamCard
@ -869,20 +866,35 @@ export const StreamsScreen = () => {
onPress={() => handleStreamPress(stream)}
index={index}
isLoading={isLoading}
statusMessage={providerStatus[section.addonId]?.message}
statusMessage={undefined}
theme={currentTheme}
/>
);
}, [handleStreamPress, loadingProviders, providerStatus, currentTheme]);
}, [handleStreamPress, currentTheme]);
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => (
<Animated.View
entering={FadeIn.duration(400)}
layout={Layout.springify()}
>
<Text style={styles.streamGroupTitle}>{section.title}</Text>
</Animated.View>
), [styles.streamGroupTitle]);
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => {
const isProviderLoading = loadingProviders[section.addonId];
return (
<Animated.View
entering={FadeIn.duration(400)}
layout={Layout.springify()}
style={styles.sectionHeaderContainer}
>
<View style={styles.sectionHeaderContent}>
<Text style={styles.streamGroupTitle}>{section.title}</Text>
{isProviderLoading && (
<View style={styles.sectionLoadingIndicator}>
<ActivityIndicator size="small" color={colors.primary} />
<Text style={[styles.sectionLoadingText, { color: colors.primary }]}>
Loading...
</Text>
</View>
)}
</View>
</Animated.View>
);
}, [styles.streamGroupTitle, styles.sectionHeaderContainer, styles.sectionHeaderContent, styles.sectionLoadingIndicator, styles.sectionLoadingText, loadingProviders, colors.primary]);
// Cleanup on unmount
useEffect(() => {
@ -1088,7 +1100,8 @@ export const StreamsScreen = () => {
)}
</Animated.View>
{isLoading || (Object.keys(streams).length === 0 && (loadingStreams || loadingEpisodeStreams)) ? (
{/* Show streams immediately as they become available, with loading indicators for pending providers */}
{Object.keys(streams).length === 0 && (loadingStreams || loadingEpisodeStreams) ? (
<Animated.View
entering={FadeIn.duration(300)}
style={styles.loadingContainer}
@ -1096,7 +1109,7 @@ export const StreamsScreen = () => {
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.loadingText}>Finding available streams...</Text>
</Animated.View>
) : Object.keys(streams).length === 0 ? (
) : Object.keys(streams).length === 0 && !loadingStreams && !loadingEpisodeStreams ? (
<Animated.View
entering={FadeIn.duration(300)}
style={styles.noStreams}
@ -1122,7 +1135,7 @@ export const StreamsScreen = () => {
bounces={true}
overScrollMode="never"
ListFooterComponent={
isLoading ? (
(loadingStreams || loadingEpisodeStreams) ? (
<View style={styles.footerLoading}>
<ActivityIndicator size="small" color={colors.primary} />
<Text style={styles.footerLoadingText}>Loading more sources...</Text>
@ -1540,6 +1553,21 @@ const createStyles = (colors: any) => StyleSheet.create({
alignItems: 'center',
zIndex: 9999,
},
sectionHeaderContainer: {
padding: 16,
},
sectionHeaderContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
sectionLoadingIndicator: {
flexDirection: 'row',
alignItems: 'center',
},
sectionLoadingText: {
marginLeft: 8,
},
});
export default memo(StreamsScreen);