mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-19 08:22:00 +00:00
ui changes for next episode
This commit is contained in:
parent
a9bb8c1131
commit
ac504b99c8
3 changed files with 37 additions and 43 deletions
|
|
@ -781,15 +781,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
}
|
}
|
||||||
disableImmersiveMode();
|
disableImmersiveMode();
|
||||||
|
|
||||||
// For series, reset to streams screen for current episode to ensure no hidden players remain on the stack
|
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||||
if (type === 'series' && id && episodeId) {
|
if (type === 'series' && id && episodeId) {
|
||||||
(navigation as any).reset({
|
(navigation as any).reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||||
name: 'Streams',
|
|
||||||
params: { id, type: 'series', episodeId }
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -804,15 +801,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
}
|
}
|
||||||
disableImmersiveMode();
|
disableImmersiveMode();
|
||||||
|
|
||||||
// For series, reset to streams screen for current episode to ensure no hidden players remain on the stack
|
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||||
if (type === 'series' && id && episodeId) {
|
if (type === 'series' && id && episodeId) {
|
||||||
(navigation as any).reset({
|
(navigation as any).reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||||
name: 'Streams',
|
|
||||||
params: { id, type: 'series', episodeId }
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1418,7 +1412,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
// Handle next episode button visibility based on current time and next episode availability
|
// Handle next episode button visibility based on current time and next episode availability
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((type as any) !== 'series' || !nextEpisode || duration <= 0 || isLoadingNextEpisode) {
|
if ((type as any) !== 'series' || !nextEpisode || duration <= 0) {
|
||||||
if (showNextEpisodeButton) {
|
if (showNextEpisodeButton) {
|
||||||
// Hide button with animation
|
// Hide button with animation
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
|
|
@ -1439,9 +1433,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show button when 2.5 minutes (150 seconds) remain
|
// Show button when 1 minute (60 seconds) remains
|
||||||
const timeRemaining = duration - currentTime;
|
const timeRemaining = duration - currentTime;
|
||||||
const shouldShowButton = timeRemaining <= 150 && timeRemaining > 10; // Hide in last 10 seconds
|
const shouldShowButton = timeRemaining <= 60 && timeRemaining > 10; // Hide in last 10 seconds
|
||||||
|
|
||||||
if (shouldShowButton && !showNextEpisodeButton) {
|
if (shouldShowButton && !showNextEpisodeButton) {
|
||||||
setShowNextEpisodeButton(true);
|
setShowNextEpisodeButton(true);
|
||||||
|
|
@ -1474,7 +1468,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
setShowNextEpisodeButton(false);
|
setShowNextEpisodeButton(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [type, nextEpisode, duration, currentTime, showNextEpisodeButton, isLoadingNextEpisode]);
|
}, [type, nextEpisode, duration, currentTime, showNextEpisodeButton]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isMounted.current = true;
|
isMounted.current = true;
|
||||||
|
|
@ -1980,11 +1974,11 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
{/* Next Episode Button */}
|
{/* Next Episode Button */}
|
||||||
{showNextEpisodeButton && nextEpisode && (
|
{showNextEpisodeButton && nextEpisode && (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 80 + insets.bottom,
|
bottom: 80 + insets.bottom,
|
||||||
right: 24 + insets.right,
|
right: 8 + insets.right,
|
||||||
opacity: nextEpisodeButtonOpacity,
|
opacity: nextEpisodeButtonOpacity,
|
||||||
transform: [{ scale: nextEpisodeButtonScale }],
|
transform: [{ scale: nextEpisodeButtonScale }],
|
||||||
}}
|
}}
|
||||||
|
|
@ -1992,9 +1986,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
backgroundColor: 'rgba(255,255,255,0.95)',
|
||||||
borderRadius: 25,
|
borderRadius: 18,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 14,
|
||||||
paddingVertical: 12,
|
paddingVertical: 8,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
|
|
@ -2004,19 +1998,18 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
}}
|
}}
|
||||||
onPress={handlePlayNextEpisode}
|
onPress={handlePlayNextEpisode}
|
||||||
disabled={isLoadingNextEpisode}
|
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
{isLoadingNextEpisode ? (
|
{isLoadingNextEpisode ? (
|
||||||
<ActivityIndicator size="small" color="#000000" style={{ marginRight: 8 }} />
|
<ActivityIndicator size="small" color="#000000" style={{ marginRight: 8 }} />
|
||||||
) : (
|
) : (
|
||||||
<MaterialIcons name="skip-next" size={20} color="#000000" style={{ marginRight: 8 }} />
|
<MaterialIcons name="skip-next" size={18} color="#000000" style={{ marginRight: 8 }} />
|
||||||
)}
|
)}
|
||||||
<View>
|
<View>
|
||||||
<Text style={{ color: '#000000', fontSize: 12, fontWeight: '600', opacity: 0.7 }}>
|
<Text style={{ color: '#000000', fontSize: 11, fontWeight: '700', opacity: 0.8 }}>
|
||||||
{isLoadingNextEpisode ? 'Loading...' : 'Up Next'}
|
{isLoadingNextEpisode ? 'Loading next episode…' : 'Up next'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{ color: '#000000', fontSize: 14, fontWeight: '700' }} numberOfLines={1}>
|
<Text style={{ color: '#000000', fontSize: 13, fontWeight: '700' }} numberOfLines={1}>
|
||||||
S{nextEpisode.season_number}E{nextEpisode.episode_number}
|
S{nextEpisode.season_number}E{nextEpisode.episode_number}
|
||||||
{nextEpisode.name ? `: ${nextEpisode.name}` : ''}
|
{nextEpisode.name ? `: ${nextEpisode.name}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -825,15 +825,12 @@ const VideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
// Navigate back with proper handling for fullscreen modal
|
// Navigate back with proper handling for fullscreen modal
|
||||||
try {
|
try {
|
||||||
// For series, reset to streams screen for current episode to ensure no hidden players remain on the stack
|
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||||
if (type === 'series' && id && episodeId) {
|
if (type === 'series' && id && episodeId) {
|
||||||
(navigation as any).reset({
|
(navigation as any).reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||||
name: 'Streams',
|
|
||||||
params: { id, type: 'series', episodeId }
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
} else if (navigation.canGoBack()) {
|
} else if (navigation.canGoBack()) {
|
||||||
|
|
@ -1319,7 +1316,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
// Handle next episode button visibility based on current time and next episode availability
|
// Handle next episode button visibility based on current time and next episode availability
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type !== 'series' || !nextEpisode || duration <= 0 || isLoadingNextEpisode) {
|
if (type !== 'series' || !nextEpisode || duration <= 0) {
|
||||||
if (showNextEpisodeButton) {
|
if (showNextEpisodeButton) {
|
||||||
// Hide button with animation
|
// Hide button with animation
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
|
|
@ -1340,9 +1337,9 @@ const VideoPlayer: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show button when 2.5 minutes (150 seconds) remain
|
// Show button when 1 minute (60 seconds) remains
|
||||||
const timeRemaining = duration - currentTime;
|
const timeRemaining = duration - currentTime;
|
||||||
const shouldShowButton = timeRemaining <= 150 && timeRemaining > 10; // Hide in last 10 seconds
|
const shouldShowButton = timeRemaining <= 60 && timeRemaining > 10; // Hide in last 10 seconds
|
||||||
|
|
||||||
if (shouldShowButton && !showNextEpisodeButton) {
|
if (shouldShowButton && !showNextEpisodeButton) {
|
||||||
setShowNextEpisodeButton(true);
|
setShowNextEpisodeButton(true);
|
||||||
|
|
@ -1375,7 +1372,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
setShowNextEpisodeButton(false);
|
setShowNextEpisodeButton(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [type, nextEpisode, duration, currentTime, showNextEpisodeButton, isLoadingNextEpisode]);
|
}, [type, nextEpisode, duration, currentTime, showNextEpisodeButton]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isMounted.current = true;
|
isMounted.current = true;
|
||||||
|
|
@ -1888,7 +1885,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 80 + insets.bottom,
|
bottom: 80 + insets.bottom,
|
||||||
right: 24 + insets.right,
|
right: 8 + insets.right,
|
||||||
opacity: nextEpisodeButtonOpacity,
|
opacity: nextEpisodeButtonOpacity,
|
||||||
transform: [{ scale: nextEpisodeButtonScale }],
|
transform: [{ scale: nextEpisodeButtonScale }],
|
||||||
}}
|
}}
|
||||||
|
|
@ -1896,9 +1893,9 @@ const VideoPlayer: React.FC = () => {
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
backgroundColor: 'rgba(255,255,255,0.95)',
|
||||||
borderRadius: 25,
|
borderRadius: 18,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 14,
|
||||||
paddingVertical: 12,
|
paddingVertical: 8,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
|
|
@ -1908,19 +1905,18 @@ const VideoPlayer: React.FC = () => {
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
}}
|
}}
|
||||||
onPress={handlePlayNextEpisode}
|
onPress={handlePlayNextEpisode}
|
||||||
disabled={isLoadingNextEpisode}
|
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
{isLoadingNextEpisode ? (
|
{isLoadingNextEpisode ? (
|
||||||
<ActivityIndicator size="small" color="#000000" style={{ marginRight: 8 }} />
|
<ActivityIndicator size="small" color="#000000" style={{ marginRight: 8 }} />
|
||||||
) : (
|
) : (
|
||||||
<MaterialIcons name="skip-next" size={20} color="#000000" style={{ marginRight: 8 }} />
|
<MaterialIcons name="skip-next" size={18} color="#000000" style={{ marginRight: 8 }} />
|
||||||
)}
|
)}
|
||||||
<View>
|
<View>
|
||||||
<Text style={{ color: '#000000', fontSize: 12, fontWeight: '600', opacity: 0.7 }}>
|
<Text style={{ color: '#000000', fontSize: 11, fontWeight: '700', opacity: 0.8 }}>
|
||||||
{isLoadingNextEpisode ? 'Loading...' : 'Up Next'}
|
{isLoadingNextEpisode ? 'Loading next episode…' : 'Up next'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{ color: '#000000', fontSize: 14, fontWeight: '700' }} numberOfLines={1}>
|
<Text style={{ color: '#000000', fontSize: 13, fontWeight: '700' }} numberOfLines={1}>
|
||||||
S{nextEpisode.season_number}E{nextEpisode.episode_number}
|
S{nextEpisode.season_number}E{nextEpisode.episode_number}
|
||||||
{nextEpisode.name ? `: ${nextEpisode.name}` : ''}
|
{nextEpisode.name ? `: ${nextEpisode.name}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
Linking,
|
Linking,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||||
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||||
|
|
@ -349,6 +350,7 @@ const ProviderFilter = memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StreamsScreen = () => {
|
export const StreamsScreen = () => {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const route = useRoute<RouteProp<RootStackParamList, 'Streams'>>();
|
const route = useRoute<RouteProp<RootStackParamList, 'Streams'>>();
|
||||||
const navigation = useNavigation<RootStackNavigationProp>();
|
const navigation = useNavigation<RootStackNavigationProp>();
|
||||||
const { id, type, episodeId, episodeThumbnail } = route.params;
|
const { id, type, episodeId, episodeThumbnail } = route.params;
|
||||||
|
|
@ -1381,7 +1383,10 @@ export const StreamsScreen = () => {
|
||||||
style={[styles.backButtonContainer]}
|
style={[styles.backButtonContainer]}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.backButton}
|
style={[
|
||||||
|
styles.backButton,
|
||||||
|
Platform.OS === 'ios' ? { paddingTop: Math.max(insets.top, 12) + 6 } : null
|
||||||
|
]}
|
||||||
onPress={handleBack}
|
onPress={handleBack}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue