mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-13 13:10:56 +00:00
some fixes
This commit is contained in:
parent
99424d37be
commit
6f24275ff0
5 changed files with 114 additions and 244 deletions
8
App.tsx
8
App.tsx
|
|
@ -8,7 +8,8 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet
|
||||
StyleSheet,
|
||||
I18nManager
|
||||
} from 'react-native';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
|
@ -44,6 +45,11 @@ Sentry.init({
|
|||
// spotlight: __DEV__,
|
||||
});
|
||||
|
||||
// Force LTR layout to prevent RTL issues when Arabic is set as system language
|
||||
// This ensures posters and UI elements remain visible and properly positioned
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
|
||||
// This fixes many navigation layout issues by using native screen containers
|
||||
enableScreens(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
TouchableOpacity,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StatusBar,
|
||||
} from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
|
|
@ -28,6 +27,7 @@ import Animated, {
|
|||
} from 'react-native-reanimated';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { useTraktContext } from '../../contexts/TraktContext';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { TMDBService } from '../../services/tmdbService';
|
||||
import TrailerService from '../../services/trailerService';
|
||||
|
|
@ -311,7 +311,8 @@ const WatchProgressDisplay = memo(({
|
|||
type,
|
||||
getEpisodeDetails,
|
||||
animatedStyle,
|
||||
isWatched
|
||||
isWatched,
|
||||
isTrailerPlaying
|
||||
}: {
|
||||
watchProgress: {
|
||||
currentTime: number;
|
||||
|
|
@ -325,6 +326,7 @@ const WatchProgressDisplay = memo(({
|
|||
getEpisodeDetails: (episodeId: string) => { seasonNumber: string; episodeNumber: string; episodeName: string } | null;
|
||||
animatedStyle: any;
|
||||
isWatched: boolean;
|
||||
isTrailerPlaying: boolean;
|
||||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const { isAuthenticated: isTraktAuthenticated, forceSyncTraktProgress } = useTraktContext();
|
||||
|
|
@ -398,8 +400,8 @@ const WatchProgressDisplay = memo(({
|
|||
}
|
||||
|
||||
const watchedDate = watchProgress?.lastUpdated
|
||||
? new Date(watchProgress.lastUpdated).toLocaleDateString()
|
||||
: new Date().toLocaleDateString();
|
||||
? new Date(watchProgress.lastUpdated).toLocaleDateString('en-US')
|
||||
: new Date().toLocaleDateString('en-US');
|
||||
|
||||
// Determine if watched via Trakt or local
|
||||
const watchedViaTrakt = isTraktAuthenticated &&
|
||||
|
|
@ -429,7 +431,7 @@ const WatchProgressDisplay = memo(({
|
|||
} else {
|
||||
progressPercent = (watchProgress.currentTime / watchProgress.duration) * 100;
|
||||
}
|
||||
const formattedTime = new Date(watchProgress.lastUpdated).toLocaleDateString();
|
||||
const formattedTime = new Date(watchProgress.lastUpdated).toLocaleDateString('en-US');
|
||||
let episodeInfo = '';
|
||||
|
||||
if (type === 'series' && watchProgress.episodeId) {
|
||||
|
|
@ -534,6 +536,9 @@ const WatchProgressDisplay = memo(({
|
|||
}));
|
||||
|
||||
if (!progressData) return null;
|
||||
|
||||
// Hide watch progress when trailer is playing
|
||||
if (isTrailerPlaying) return null;
|
||||
|
||||
const isCompleted = progressData.isWatched || progressData.progressPercent >= 85;
|
||||
|
||||
|
|
@ -688,6 +693,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const { isAuthenticated: isTraktAuthenticated } = useTraktContext();
|
||||
const { settings } = useSettings();
|
||||
|
||||
// Performance optimization: Refs for avoiding re-renders
|
||||
const interactionComplete = useRef(false);
|
||||
|
|
@ -703,13 +709,17 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
const [isTrailerPlaying, setIsTrailerPlaying] = useState(false);
|
||||
const [trailerReady, setTrailerReady] = useState(false);
|
||||
const [trailerPreloaded, setTrailerPreloaded] = useState(false);
|
||||
const [isTrailerFullscreen, setIsTrailerFullscreen] = useState(false);
|
||||
const imageOpacity = useSharedValue(1);
|
||||
const imageLoadOpacity = useSharedValue(0);
|
||||
const shimmerOpacity = useSharedValue(0.3);
|
||||
const trailerOpacity = useSharedValue(0);
|
||||
const thumbnailOpacity = useSharedValue(1);
|
||||
|
||||
// Animation values for trailer unmute effects
|
||||
const actionButtonsOpacity = useSharedValue(1);
|
||||
const titleCardTranslateY = useSharedValue(0);
|
||||
const genreOpacity = useSharedValue(1);
|
||||
|
||||
// Performance optimization: Cache theme colors
|
||||
const themeColors = useMemo(() => ({
|
||||
black: currentTheme.colors.black,
|
||||
|
|
@ -747,13 +757,6 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
trailerOpacity.value = withTiming(0, { duration: 300 });
|
||||
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
||||
}, [trailerOpacity, thumbnailOpacity]);
|
||||
|
||||
// Handle trailer fullscreen toggle
|
||||
const handleTrailerFullscreenToggle = useCallback(() => {
|
||||
setIsTrailerFullscreen(true);
|
||||
// Hide status bar when entering fullscreen
|
||||
StatusBar.setHidden(true, 'slide');
|
||||
}, []);
|
||||
|
||||
// Memoized image source
|
||||
const imageSource = useMemo(() =>
|
||||
|
|
@ -772,10 +775,10 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
return () => timer.cancel();
|
||||
}, []);
|
||||
|
||||
// Fetch trailer URL when component mounts
|
||||
// Fetch trailer URL when component mounts (only if trailers are enabled)
|
||||
useEffect(() => {
|
||||
const fetchTrailer = async () => {
|
||||
if (!metadata?.name || !metadata?.year) return;
|
||||
if (!metadata?.name || !metadata?.year || !settings?.showTrailers) return;
|
||||
|
||||
setTrailerLoading(true);
|
||||
setTrailerError(false);
|
||||
|
|
@ -800,7 +803,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
};
|
||||
|
||||
fetchTrailer();
|
||||
}, [metadata?.name, metadata?.year]);
|
||||
}, [metadata?.name, metadata?.year, settings?.showTrailers]);
|
||||
|
||||
// Optimized shimmer animation for loading state
|
||||
useEffect(() => {
|
||||
|
|
@ -898,7 +901,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
|
||||
// Simplified buttons animation
|
||||
const buttonsAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: buttonsOpacity.value,
|
||||
opacity: buttonsOpacity.value * actionButtonsOpacity.value,
|
||||
transform: [{
|
||||
translateY: interpolate(
|
||||
buttonsTranslateY.value,
|
||||
|
|
@ -909,6 +912,16 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
}]
|
||||
}), []);
|
||||
|
||||
// Title card animation for lowering position when trailer is unmuted
|
||||
const titleCardAnimatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ translateY: titleCardTranslateY.value }]
|
||||
}), []);
|
||||
|
||||
// Genre animation for hiding when trailer is unmuted
|
||||
const genreAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: genreOpacity.value
|
||||
}), []);
|
||||
|
||||
// Optimized genre rendering with lazy loading and memory management
|
||||
const genreElements = useMemo(() => {
|
||||
if (!shouldLoadSecondaryData || !metadata?.genres?.length) return null;
|
||||
|
|
@ -960,9 +973,6 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
imageLoadOpacity.value = 0;
|
||||
shimmerOpacity.value = 0.3;
|
||||
interactionComplete.current = false;
|
||||
|
||||
// Restore status bar when component unmounts
|
||||
StatusBar.setHidden(false, 'slide');
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -980,38 +990,6 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
}
|
||||
});
|
||||
|
||||
// Don't render hero content when trailer is in fullscreen
|
||||
if (isTrailerFullscreen) {
|
||||
return (
|
||||
<View style={styles.fullscreenTrailerContainer}>
|
||||
{/* Back button for fullscreen mode */}
|
||||
<TouchableOpacity
|
||||
style={styles.fullscreenBackButton}
|
||||
onPress={() => {
|
||||
setIsTrailerFullscreen(false);
|
||||
// Restore status bar when exiting fullscreen
|
||||
StatusBar.setHidden(false, 'slide');
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons name="arrow-back" size={28} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{trailerUrl && (
|
||||
<TrailerPlayer
|
||||
trailerUrl={trailerUrl}
|
||||
autoPlay={true}
|
||||
muted={trailerMuted}
|
||||
style={styles.fullscreenTrailerPlayer}
|
||||
hideLoadingSpinner={false}
|
||||
onLoad={handleTrailerReady}
|
||||
onError={handleTrailerError}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.heroSection, heroAnimatedStyle]}>
|
||||
{/* Optimized Background */}
|
||||
|
|
@ -1047,7 +1025,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
)}
|
||||
|
||||
{/* Hidden preload trailer player - loads in background */}
|
||||
{shouldLoadSecondaryData && trailerUrl && !trailerLoading && !trailerError && !trailerPreloaded && (
|
||||
{shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && !trailerPreloaded && (
|
||||
<View style={[styles.absoluteFill, { opacity: 0, pointerEvents: 'none' }]}>
|
||||
<TrailerPlayer
|
||||
trailerUrl={trailerUrl}
|
||||
|
|
@ -1062,7 +1040,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
)}
|
||||
|
||||
{/* Visible trailer player - rendered on top with fade transition */}
|
||||
{shouldLoadSecondaryData && trailerUrl && !trailerLoading && !trailerError && trailerPreloaded && (
|
||||
{shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && trailerPreloaded && (
|
||||
<Animated.View style={[styles.absoluteFill, {
|
||||
opacity: trailerOpacity
|
||||
}]}>
|
||||
|
|
@ -1084,7 +1062,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
)}
|
||||
|
||||
{/* Unmute button for trailer */}
|
||||
{trailerReady && trailerUrl && (
|
||||
{settings?.showTrailers && trailerReady && trailerUrl && (
|
||||
<Animated.View style={{
|
||||
position: 'absolute',
|
||||
top: Platform.OS === 'android' ? 40 : 50,
|
||||
|
|
@ -1092,32 +1070,34 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
zIndex: 10,
|
||||
opacity: trailerOpacity
|
||||
}}>
|
||||
<View style={styles.trailerControlsContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setTrailerMuted(!trailerMuted)}
|
||||
activeOpacity={0.7}
|
||||
style={styles.trailerControlButton}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={trailerMuted ? 'volume-off' : 'volume-up'}
|
||||
size={24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Fullscreen button */}
|
||||
<TouchableOpacity
|
||||
onPress={handleTrailerFullscreenToggle}
|
||||
activeOpacity={0.7}
|
||||
style={styles.trailerControlButton}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="fullscreen"
|
||||
size={24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setTrailerMuted(!trailerMuted);
|
||||
if (trailerMuted) {
|
||||
// When unmuting, hide action buttons, genre, and lower title card more
|
||||
actionButtonsOpacity.value = withTiming(0, { duration: 300 });
|
||||
genreOpacity.value = withTiming(0, { duration: 300 });
|
||||
titleCardTranslateY.value = withTiming(60, { duration: 300 });
|
||||
} else {
|
||||
// When muting, show action buttons, genre, and restore title card position
|
||||
actionButtonsOpacity.value = withTiming(1, { duration: 300 });
|
||||
genreOpacity.value = withTiming(1, { duration: 300 });
|
||||
titleCardTranslateY.value = withTiming(0, { duration: 300 });
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
style={{
|
||||
padding: 8,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: 20,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={trailerMuted ? 'volume-off' : 'volume-up'}
|
||||
size={24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
|
|
@ -1161,11 +1141,9 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
style={styles.bottomFadeGradient}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<View style={[styles.heroContent, isTablet && { maxWidth: 800, alignSelf: 'center' }, {
|
||||
opacity: trailerReady && !trailerMuted ? 0.3 : 1
|
||||
}]}>
|
||||
<View style={[styles.heroContent, isTablet && { maxWidth: 800, alignSelf: 'center' }]}>
|
||||
{/* Optimized Title/Logo */}
|
||||
<View style={styles.logoContainer}>
|
||||
<Animated.View style={[styles.logoContainer, titleCardAnimatedStyle]}>
|
||||
<Animated.View style={[styles.titleLogoContainer, logoAnimatedStyle]}>
|
||||
{shouldLoadSecondaryData && metadata.logo && !logoLoadError ? (
|
||||
<Image
|
||||
|
|
@ -1183,7 +1161,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
</Text>
|
||||
)}
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* Enhanced Watch Progress with Trakt integration */}
|
||||
<WatchProgressDisplay
|
||||
|
|
@ -1192,13 +1170,14 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
getEpisodeDetails={getEpisodeDetails}
|
||||
animatedStyle={watchProgressAnimatedStyle}
|
||||
isWatched={isWatched}
|
||||
isTrailerPlaying={isTrailerPlaying}
|
||||
/>
|
||||
|
||||
{/* Optimized genre display with lazy loading */}
|
||||
{shouldLoadSecondaryData && genreElements && (
|
||||
<View style={isTablet ? styles.tabletGenreContainer : styles.genreContainer}>
|
||||
<Animated.View style={[isTablet ? styles.tabletGenreContainer : styles.genreContainer, genreAnimatedStyle]}>
|
||||
{genreElements}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Optimized Action Buttons */}
|
||||
|
|
@ -1228,20 +1207,6 @@ const styles = StyleSheet.create({
|
|||
backgroundColor: '#000',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
fullscreenTrailerContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 9999,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
fullscreenTrailerPlayer: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
absoluteFill: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
|
|
@ -1263,15 +1228,6 @@ const styles = StyleSheet.create({
|
|||
textShadowOffset: { width: 0, height: 2 },
|
||||
textShadowRadius: 3,
|
||||
},
|
||||
fullscreenBackButton: {
|
||||
position: 'absolute',
|
||||
top: Platform.OS === 'android' ? 40 : 50,
|
||||
left: isTablet ? 32 : 16,
|
||||
zIndex: 10,
|
||||
padding: 8,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: 20,
|
||||
},
|
||||
|
||||
heroGradient: {
|
||||
flex: 1,
|
||||
|
|
@ -1786,15 +1742,6 @@ const styles = StyleSheet.create({
|
|||
opacity: 0.8,
|
||||
marginBottom: 1,
|
||||
},
|
||||
trailerControlsContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
},
|
||||
trailerControlButton: {
|
||||
padding: 8,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default HeroSection;
|
||||
|
|
@ -10,7 +10,7 @@ import { Episode } from '../../types/metadata';
|
|||
import { tmdbService } from '../../services/tmdbService';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import Animated, { FadeIn, FadeOut, SlideInRight, SlideOutLeft, withTiming, withSpring, useSharedValue, useAnimatedStyle, Easing } from 'react-native-reanimated';
|
||||
import Animated, { FadeIn, FadeOut, SlideInRight, SlideOutLeft } from 'react-native-reanimated';
|
||||
import { TraktService } from '../../services/traktService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
|
@ -53,30 +53,9 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
// Add state for season view mode (persists for current show across navigation)
|
||||
const [seasonViewMode, setSeasonViewMode] = useState<'posters' | 'text'>('posters');
|
||||
|
||||
// Animated values for view mode transitions
|
||||
const posterViewOpacity = useSharedValue(1);
|
||||
const textViewOpacity = useSharedValue(0);
|
||||
const posterViewTranslateX = useSharedValue(0);
|
||||
const textViewTranslateX = useSharedValue(50);
|
||||
const posterViewScale = useSharedValue(1);
|
||||
const textViewScale = useSharedValue(0.95);
|
||||
|
||||
// Animated styles for view transitions
|
||||
const posterViewAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: posterViewOpacity.value,
|
||||
transform: [
|
||||
{ translateX: posterViewTranslateX.value },
|
||||
{ scale: posterViewScale.value }
|
||||
],
|
||||
}));
|
||||
|
||||
const textViewAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: textViewOpacity.value,
|
||||
transform: [
|
||||
{ translateX: textViewTranslateX.value },
|
||||
{ scale: textViewScale.value }
|
||||
],
|
||||
}));
|
||||
// View mode state (no animations)
|
||||
const [posterViewVisible, setPosterViewVisible] = useState(true);
|
||||
const [textViewVisible, setTextViewVisible] = useState(false);
|
||||
|
||||
// Add refs for the scroll views
|
||||
const seasonScrollViewRef = useRef<ScrollView | null>(null);
|
||||
|
|
@ -102,28 +81,20 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
loadViewModePreference();
|
||||
}, [metadata?.id]);
|
||||
|
||||
// Initialize animated values based on current view mode
|
||||
// Initialize view mode visibility based on current view mode
|
||||
useEffect(() => {
|
||||
if (seasonViewMode === 'text') {
|
||||
// Initialize text view as visible
|
||||
posterViewOpacity.value = 0;
|
||||
posterViewTranslateX.value = -60;
|
||||
posterViewScale.value = 0.95;
|
||||
textViewOpacity.value = 1;
|
||||
textViewTranslateX.value = 0;
|
||||
textViewScale.value = 1;
|
||||
setPosterViewVisible(false);
|
||||
setTextViewVisible(true);
|
||||
} else {
|
||||
// Initialize poster view as visible
|
||||
posterViewOpacity.value = 1;
|
||||
posterViewTranslateX.value = 0;
|
||||
posterViewScale.value = 1;
|
||||
textViewOpacity.value = 0;
|
||||
textViewTranslateX.value = 50;
|
||||
textViewScale.value = 0.95;
|
||||
setPosterViewVisible(true);
|
||||
setTextViewVisible(false);
|
||||
}
|
||||
}, [seasonViewMode]);
|
||||
|
||||
// Save view mode preference when it changes
|
||||
|
||||
|
||||
// Update view mode without animations
|
||||
const updateViewMode = (newMode: 'posters' | 'text') => {
|
||||
setSeasonViewMode(newMode);
|
||||
if (metadata?.id) {
|
||||
|
|
@ -132,73 +103,6 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Animate view mode transition
|
||||
const animateViewModeTransition = (newMode: 'posters' | 'text') => {
|
||||
if (newMode === 'text') {
|
||||
// Animate to text view with spring animations for smoother feel
|
||||
posterViewOpacity.value = withTiming(0, {
|
||||
duration: 250,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||
});
|
||||
posterViewTranslateX.value = withSpring(-60, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
posterViewScale.value = withSpring(0.95, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
|
||||
textViewOpacity.value = withTiming(1, {
|
||||
duration: 300,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||
});
|
||||
textViewTranslateX.value = withSpring(0, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
textViewScale.value = withSpring(1, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
} else {
|
||||
// Animate to poster view with spring animations
|
||||
textViewOpacity.value = withTiming(0, {
|
||||
duration: 250,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||
});
|
||||
textViewTranslateX.value = withSpring(60, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
textViewScale.value = withSpring(0.95, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
|
||||
posterViewOpacity.value = withTiming(1, {
|
||||
duration: 300,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||
});
|
||||
posterViewTranslateX.value = withSpring(0, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
posterViewScale.value = withSpring(1, {
|
||||
damping: 20,
|
||||
stiffness: 200,
|
||||
mass: 0.8
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Add refs for the scroll views
|
||||
|
||||
|
|
@ -452,7 +356,6 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
]}
|
||||
onPress={() => {
|
||||
const newMode = seasonViewMode === 'posters' ? 'text' : 'posters';
|
||||
animateViewModeTransition(newMode);
|
||||
updateViewMode(newMode);
|
||||
console.log('[SeriesContent] View mode changed to:', newMode, 'Current ref value:', seasonViewMode);
|
||||
}}
|
||||
|
|
@ -497,11 +400,9 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
// Text-only view
|
||||
console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
return (
|
||||
<Animated.View
|
||||
<View
|
||||
key={season}
|
||||
style={textViewAnimatedStyle}
|
||||
entering={SlideInRight.duration(400).easing(Easing.bezier(0.25, 0.1, 0.25, 1.0))}
|
||||
exiting={SlideOutLeft.duration(350).easing(Easing.bezier(0.25, 0.1, 0.25, 1.0))}
|
||||
style={{ opacity: textViewVisible ? 1 : 0 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
|
@ -524,18 +425,16 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
Season {season}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Poster view (current implementation)
|
||||
console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
return (
|
||||
<Animated.View
|
||||
<View
|
||||
key={season}
|
||||
style={posterViewAnimatedStyle}
|
||||
entering={SlideInRight.duration(400).easing(Easing.bezier(0.25, 0.1, 0.25, 1.0))}
|
||||
exiting={SlideOutLeft.duration(350).easing(Easing.bezier(0.25, 0.1, 0.25, 1.0))}
|
||||
style={{ opacity: posterViewVisible ? 1 : 0 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
|
@ -579,10 +478,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
>
|
||||
Season {season}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
}}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
keyExtractor={season => season.toString()}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ export interface AppSettings {
|
|||
posterSize: 'small' | 'medium' | 'large'; // Predefined sizes
|
||||
posterBorderRadius: number; // 0-20 range for border radius
|
||||
postersPerRow: number; // 3-6 range for number of posters per row
|
||||
// Trailer settings
|
||||
showTrailers: boolean; // Enable/disable trailer playback in hero section
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: AppSettings = {
|
||||
|
|
@ -98,10 +100,12 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
themeId: 'default',
|
||||
customThemes: [],
|
||||
useDominantBackgroundColor: true,
|
||||
// Home screen poster defaults
|
||||
// Home screen poster customization
|
||||
posterSize: 'medium',
|
||||
posterBorderRadius: 12,
|
||||
postersPerRow: 4,
|
||||
// Trailer settings
|
||||
showTrailers: true, // Enable trailers by default
|
||||
};
|
||||
|
||||
const SETTINGS_STORAGE_KEY = 'app_settings';
|
||||
|
|
|
|||
|
|
@ -530,6 +530,20 @@ const SettingsScreen: React.FC = () => {
|
|||
onPress={() => navigation.navigate('PlayerSettings')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Show Trailers"
|
||||
description="Display trailers in hero section"
|
||||
icon="movie"
|
||||
renderControl={() => (
|
||||
<Switch
|
||||
value={settings?.showTrailers ?? true}
|
||||
onValueChange={(value) => updateSetting('showTrailers', value)}
|
||||
trackColor={{ false: 'rgba(255,255,255,0.2)', true: currentTheme.colors.primary }}
|
||||
thumbColor={settings?.showTrailers ? '#fff' : '#f4f3f4'}
|
||||
/>
|
||||
)}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Notifications"
|
||||
description="Episode reminders"
|
||||
|
|
|
|||
Loading…
Reference in a new issue