mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-11 12:30:46 +00:00
apple hero drag changes
This commit is contained in:
parent
e9a331dbd5
commit
771765f32b
2 changed files with 85 additions and 55 deletions
|
|
@ -3178,13 +3178,13 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
CHECKOUT OPTIONS:
|
CHECKOUT OPTIONS:
|
||||||
DisplayCriteria:
|
DisplayCriteria:
|
||||||
:commit: 83ba8419ca365e9397c0b45c4147755da522324e
|
:commit: cbc74996afb55e096bf1ff240f07d1d206ac86df
|
||||||
:git: https://github.com/kingslay/KSPlayer.git
|
:git: https://github.com/kingslay/KSPlayer.git
|
||||||
FFmpegKit:
|
FFmpegKit:
|
||||||
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
||||||
:git: https://github.com/kingslay/FFmpegKit.git
|
:git: https://github.com/kingslay/FFmpegKit.git
|
||||||
KSPlayer:
|
KSPlayer:
|
||||||
:commit: 83ba8419ca365e9397c0b45c4147755da522324e
|
:commit: cbc74996afb55e096bf1ff240f07d1d206ac86df
|
||||||
:git: https://github.com/kingslay/KSPlayer.git
|
:git: https://github.com/kingslay/KSPlayer.git
|
||||||
Libass:
|
Libass:
|
||||||
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ const STATUS_BAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||||
const HERO_HEIGHT = height * 0.85;
|
const HERO_HEIGHT = height * 0.85;
|
||||||
|
|
||||||
// Animated Pagination Dot Component
|
// Animated Pagination Dot Component
|
||||||
const PaginationDot: React.FC<{
|
const PaginationDot: React.FC<{
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
isNext: boolean;
|
isNext: boolean;
|
||||||
dragProgress: SharedValue<number>;
|
dragProgress: SharedValue<number>;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
|
|
@ -70,11 +70,11 @@ const PaginationDot: React.FC<{
|
||||||
const inactiveWidth = 8;
|
const inactiveWidth = 8;
|
||||||
const activeOpacity = 0.9;
|
const activeOpacity = 0.9;
|
||||||
const inactiveOpacity = 0.3;
|
const inactiveOpacity = 0.3;
|
||||||
|
|
||||||
// Calculate target width and opacity based on state
|
// Calculate target width and opacity based on state
|
||||||
let targetWidth = isActive ? activeWidth : inactiveWidth;
|
let targetWidth = isActive ? activeWidth : inactiveWidth;
|
||||||
let targetOpacity = isActive ? activeOpacity : inactiveOpacity;
|
let targetOpacity = isActive ? activeOpacity : inactiveOpacity;
|
||||||
|
|
||||||
// If this is the next dot during drag, interpolate between inactive and active
|
// If this is the next dot during drag, interpolate between inactive and active
|
||||||
if (isNext && dragProgress.value > 0) {
|
if (isNext && dragProgress.value > 0) {
|
||||||
targetWidth = interpolate(
|
targetWidth = interpolate(
|
||||||
|
|
@ -90,7 +90,7 @@ const PaginationDot: React.FC<{
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is the current active dot during drag, interpolate from active to inactive
|
// If this is the current active dot during drag, interpolate from active to inactive
|
||||||
if (isActive && dragProgress.value > 0) {
|
if (isActive && dragProgress.value > 0) {
|
||||||
targetWidth = interpolate(
|
targetWidth = interpolate(
|
||||||
|
|
@ -106,7 +106,7 @@ const PaginationDot: React.FC<{
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: withTiming(targetWidth, {
|
width: withTiming(targetWidth, {
|
||||||
duration: 300,
|
duration: 300,
|
||||||
|
|
@ -144,11 +144,11 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { settings, updateSetting } = useSettings();
|
const { settings, updateSetting } = useSettings();
|
||||||
const { isTrailerPlaying: globalTrailerPlaying, setTrailerPlaying } = useTrailer();
|
const { isTrailerPlaying: globalTrailerPlaying, setTrailerPlaying } = useTrailer();
|
||||||
|
|
||||||
// Create internal scrollY if not provided externally
|
// Create internal scrollY if not provided externally
|
||||||
const internalScrollY = useSharedValue(0);
|
const internalScrollY = useSharedValue(0);
|
||||||
const scrollY = externalScrollY || internalScrollY;
|
const scrollY = externalScrollY || internalScrollY;
|
||||||
|
|
||||||
// Determine items to display
|
// Determine items to display
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
if (allFeaturedContent && allFeaturedContent.length > 0) {
|
if (allFeaturedContent && allFeaturedContent.length > 0) {
|
||||||
|
|
@ -174,10 +174,10 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
const [trailerPreloaded, setTrailerPreloaded] = useState(false);
|
const [trailerPreloaded, setTrailerPreloaded] = useState(false);
|
||||||
const [trailerShouldBePaused, setTrailerShouldBePaused] = useState(false);
|
const [trailerShouldBePaused, setTrailerShouldBePaused] = useState(false);
|
||||||
const trailerVideoRef = useRef<any>(null);
|
const trailerVideoRef = useRef<any>(null);
|
||||||
|
|
||||||
// Use ref to avoid re-fetching trailer when trailerMuted changes
|
// Use ref to avoid re-fetching trailer when trailerMuted changes
|
||||||
const showTrailersEnabled = useRef(settings?.showTrailers ?? false);
|
const showTrailersEnabled = useRef(settings?.showTrailers ?? false);
|
||||||
|
|
||||||
// Update ref when showTrailers setting changes
|
// Update ref when showTrailers setting changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
showTrailersEnabled.current = settings?.showTrailers ?? false;
|
showTrailersEnabled.current = settings?.showTrailers ?? false;
|
||||||
|
|
@ -188,6 +188,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
// Animation values
|
// Animation values
|
||||||
const dragProgress = useSharedValue(0);
|
const dragProgress = useSharedValue(0);
|
||||||
const dragDirection = useSharedValue(0); // -1 for left, 1 for right
|
const dragDirection = useSharedValue(0); // -1 for left, 1 for right
|
||||||
|
const isDragging = useSharedValue(0); // 1 when dragging, 0 when not
|
||||||
const logoOpacity = useSharedValue(1);
|
const logoOpacity = useSharedValue(1);
|
||||||
const [nextIndex, setNextIndex] = useState(currentIndex);
|
const [nextIndex, setNextIndex] = useState(currentIndex);
|
||||||
const thumbnailOpacity = useSharedValue(1);
|
const thumbnailOpacity = useSharedValue(1);
|
||||||
|
|
@ -197,14 +198,14 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
|
|
||||||
// Animated style for trailer container - 60% height with zoom
|
// Animated style for trailer container - 60% height with zoom
|
||||||
const trailerContainerStyle = useAnimatedStyle(() => {
|
const trailerContainerStyle = useAnimatedStyle(() => {
|
||||||
// Fade out trailer during drag with smooth curve (inverse of next image fade)
|
// Faster fade out during drag - complete fade by 0.3 progress instead of 1.0
|
||||||
const dragFade = interpolate(
|
const dragFade = interpolate(
|
||||||
dragProgress.value,
|
dragProgress.value,
|
||||||
[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.85, 1],
|
[0, 0.05, 0.1, 0.15, 0.2, 0.3],
|
||||||
[1, 0.95, 0.88, 0.78, 0.65, 0.5, 0.35, 0.22, 0.08, 0],
|
[1, 0.85, 0.65, 0.4, 0.15, 0],
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
@ -225,26 +226,36 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Parallax style for background images
|
// Parallax style for background images - disabled during drag
|
||||||
const backgroundParallaxStyle = useAnimatedStyle(() => {
|
const backgroundParallaxStyle = useAnimatedStyle(() => {
|
||||||
'worklet';
|
'worklet';
|
||||||
const scrollYValue = scrollY.value;
|
const scrollYValue = scrollY.value;
|
||||||
|
|
||||||
|
// Disable parallax during drag to avoid transform conflicts
|
||||||
|
if (isDragging.value > 0) {
|
||||||
|
return {
|
||||||
|
transform: [
|
||||||
|
{ scale: 1.0 },
|
||||||
|
{ translateY: 0 }
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-calculated constants - start at 1.0 for normal size
|
// Pre-calculated constants - start at 1.0 for normal size
|
||||||
const DEFAULT_ZOOM = 1.0;
|
const DEFAULT_ZOOM = 1.0;
|
||||||
const SCROLL_UP_MULTIPLIER = 0.002;
|
const SCROLL_UP_MULTIPLIER = 0.002;
|
||||||
const SCROLL_DOWN_MULTIPLIER = 0.0001;
|
const SCROLL_DOWN_MULTIPLIER = 0.0001;
|
||||||
const MAX_SCALE = 1.3;
|
const MAX_SCALE = 1.3;
|
||||||
const PARALLAX_FACTOR = 0.3;
|
const PARALLAX_FACTOR = 0.3;
|
||||||
|
|
||||||
// Optimized scale calculation with minimal branching
|
// Optimized scale calculation with minimal branching
|
||||||
const scrollUpScale = DEFAULT_ZOOM + Math.abs(scrollYValue) * SCROLL_UP_MULTIPLIER;
|
const scrollUpScale = DEFAULT_ZOOM + Math.abs(scrollYValue) * SCROLL_UP_MULTIPLIER;
|
||||||
const scrollDownScale = DEFAULT_ZOOM + scrollYValue * SCROLL_DOWN_MULTIPLIER;
|
const scrollDownScale = DEFAULT_ZOOM + scrollYValue * SCROLL_DOWN_MULTIPLIER;
|
||||||
const scale = Math.min(scrollYValue < 0 ? scrollUpScale : scrollDownScale, MAX_SCALE);
|
const scale = Math.min(scrollYValue < 0 ? scrollUpScale : scrollDownScale, MAX_SCALE);
|
||||||
|
|
||||||
// Single parallax calculation
|
// Single parallax calculation
|
||||||
const parallaxOffset = scrollYValue * PARALLAX_FACTOR;
|
const parallaxOffset = scrollYValue * PARALLAX_FACTOR;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{ scale },
|
{ scale },
|
||||||
|
|
@ -253,26 +264,36 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Parallax style for trailer
|
// Parallax style for trailer - disabled during drag
|
||||||
const trailerParallaxStyle = useAnimatedStyle(() => {
|
const trailerParallaxStyle = useAnimatedStyle(() => {
|
||||||
'worklet';
|
'worklet';
|
||||||
const scrollYValue = scrollY.value;
|
const scrollYValue = scrollY.value;
|
||||||
|
|
||||||
|
// Disable parallax during drag to avoid transform conflicts
|
||||||
|
if (isDragging.value > 0) {
|
||||||
|
return {
|
||||||
|
transform: [
|
||||||
|
{ scale: 1.0 },
|
||||||
|
{ translateY: 0 }
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-calculated constants - start at 1.0 for normal size
|
// Pre-calculated constants - start at 1.0 for normal size
|
||||||
const DEFAULT_ZOOM = 1.0;
|
const DEFAULT_ZOOM = 1.0;
|
||||||
const SCROLL_UP_MULTIPLIER = 0.0015;
|
const SCROLL_UP_MULTIPLIER = 0.0015;
|
||||||
const SCROLL_DOWN_MULTIPLIER = 0.0001;
|
const SCROLL_DOWN_MULTIPLIER = 0.0001;
|
||||||
const MAX_SCALE = 1.2;
|
const MAX_SCALE = 1.2;
|
||||||
const PARALLAX_FACTOR = 0.2; // Slower than background for depth
|
const PARALLAX_FACTOR = 0.2; // Slower than background for depth
|
||||||
|
|
||||||
// Optimized scale calculation with minimal branching
|
// Optimized scale calculation with minimal branching
|
||||||
const scrollUpScale = DEFAULT_ZOOM + Math.abs(scrollYValue) * SCROLL_UP_MULTIPLIER;
|
const scrollUpScale = DEFAULT_ZOOM + Math.abs(scrollYValue) * SCROLL_UP_MULTIPLIER;
|
||||||
const scrollDownScale = DEFAULT_ZOOM + scrollYValue * SCROLL_DOWN_MULTIPLIER;
|
const scrollDownScale = DEFAULT_ZOOM + scrollYValue * SCROLL_DOWN_MULTIPLIER;
|
||||||
const scale = Math.min(scrollYValue < 0 ? scrollUpScale : scrollDownScale, MAX_SCALE);
|
const scale = Math.min(scrollYValue < 0 ? scrollUpScale : scrollDownScale, MAX_SCALE);
|
||||||
|
|
||||||
// Single parallax calculation
|
// Single parallax calculation
|
||||||
const parallaxOffset = scrollYValue * PARALLAX_FACTOR;
|
const parallaxOffset = scrollYValue * PARALLAX_FACTOR;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{ scale },
|
{ scale },
|
||||||
|
|
@ -316,16 +337,16 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
// Pause this screen's trailer
|
// Pause this screen's trailer
|
||||||
setTrailerShouldBePaused(true);
|
setTrailerShouldBePaused(true);
|
||||||
setTrailerPlaying(false);
|
setTrailerPlaying(false);
|
||||||
|
|
||||||
// Fade out trailer
|
// Fade out trailer
|
||||||
trailerOpacity.value = withTiming(0, { duration: 300 });
|
trailerOpacity.value = withTiming(0, { duration: 300 });
|
||||||
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
||||||
|
|
||||||
logger.info('[AppleTVHero] Screen lost focus - pausing trailer');
|
logger.info('[AppleTVHero] Screen lost focus - pausing trailer');
|
||||||
} else {
|
} else {
|
||||||
// Screen gained focus - allow trailer to resume if it was ready
|
// Screen gained focus - allow trailer to resume if it was ready
|
||||||
setTrailerShouldBePaused(false);
|
setTrailerShouldBePaused(false);
|
||||||
|
|
||||||
// If trailer was ready and loaded, restore the video opacity
|
// If trailer was ready and loaded, restore the video opacity
|
||||||
if (trailerReady && trailerUrl) {
|
if (trailerReady && trailerUrl) {
|
||||||
logger.info('[AppleTVHero] Screen gained focus - restoring trailer');
|
logger.info('[AppleTVHero] Screen gained focus - restoring trailer');
|
||||||
|
|
@ -370,20 +391,20 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
setTrailerReady(false);
|
setTrailerReady(false);
|
||||||
setTrailerPreloaded(false);
|
setTrailerPreloaded(false);
|
||||||
setTrailerPlaying(false);
|
setTrailerPlaying(false);
|
||||||
|
|
||||||
// Fade out any existing trailer
|
// Fade out any existing trailer
|
||||||
trailerOpacity.value = withTiming(0, { duration: 300 });
|
trailerOpacity.value = withTiming(0, { duration: 300 });
|
||||||
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract year from metadata
|
// Extract year from metadata
|
||||||
const year = currentItem.releaseInfo
|
const year = currentItem.releaseInfo
|
||||||
? parseInt(currentItem.releaseInfo.split('-')[0], 10)
|
? parseInt(currentItem.releaseInfo.split('-')[0], 10)
|
||||||
: new Date().getFullYear();
|
: new Date().getFullYear();
|
||||||
|
|
||||||
// Extract TMDB ID if available
|
// Extract TMDB ID if available
|
||||||
const tmdbId = currentItem.id?.startsWith('tmdb:')
|
const tmdbId = currentItem.id?.startsWith('tmdb:')
|
||||||
? currentItem.id.replace('tmdb:', '')
|
? currentItem.id.replace('tmdb:', '')
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const contentType = currentItem.type === 'series' ? 'tv' : 'movie';
|
const contentType = currentItem.type === 'series' ? 'tv' : 'movie';
|
||||||
|
|
@ -391,9 +412,9 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
logger.info('[AppleTVHero] Fetching trailer for:', currentItem.name, year, tmdbId);
|
logger.info('[AppleTVHero] Fetching trailer for:', currentItem.name, year, tmdbId);
|
||||||
|
|
||||||
const url = await TrailerService.getTrailerUrl(
|
const url = await TrailerService.getTrailerUrl(
|
||||||
currentItem.name,
|
currentItem.name,
|
||||||
year,
|
year,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
contentType
|
contentType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -435,13 +456,13 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
// Handle trailer ready to play
|
// Handle trailer ready to play
|
||||||
const handleTrailerReady = useCallback(() => {
|
const handleTrailerReady = useCallback(() => {
|
||||||
setTrailerReady(true);
|
setTrailerReady(true);
|
||||||
|
|
||||||
// Smooth crossfade: thumbnail out, trailer in
|
// Smooth crossfade: thumbnail out, trailer in
|
||||||
thumbnailOpacity.value = withTiming(0, { duration: 800 });
|
thumbnailOpacity.value = withTiming(0, { duration: 800 });
|
||||||
trailerOpacity.value = withTiming(1, { duration: 800 });
|
trailerOpacity.value = withTiming(1, { duration: 800 });
|
||||||
|
|
||||||
logger.info('[AppleTVHero] Trailer ready - starting playback');
|
logger.info('[AppleTVHero] Trailer ready - starting playback');
|
||||||
|
|
||||||
// Auto-start trailer
|
// Auto-start trailer
|
||||||
setTrailerPlaying(true);
|
setTrailerPlaying(true);
|
||||||
}, [thumbnailOpacity, trailerOpacity, setTrailerPlaying]);
|
}, [thumbnailOpacity, trailerOpacity, setTrailerPlaying]);
|
||||||
|
|
@ -451,11 +472,11 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
setTrailerError(true);
|
setTrailerError(true);
|
||||||
setTrailerReady(false);
|
setTrailerReady(false);
|
||||||
setTrailerPlaying(false);
|
setTrailerPlaying(false);
|
||||||
|
|
||||||
// Fade back to thumbnail
|
// Fade back to thumbnail
|
||||||
trailerOpacity.value = withTiming(0, { duration: 300 });
|
trailerOpacity.value = withTiming(0, { duration: 300 });
|
||||||
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
thumbnailOpacity.value = withTiming(1, { duration: 300 });
|
||||||
|
|
||||||
logger.error('[AppleTVHero] Trailer playback error');
|
logger.error('[AppleTVHero] Trailer playback error');
|
||||||
}, [trailerOpacity, thumbnailOpacity, setTrailerPlaying]);
|
}, [trailerOpacity, thumbnailOpacity, setTrailerPlaying]);
|
||||||
|
|
||||||
|
|
@ -463,11 +484,11 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
const handleTrailerEnd = useCallback(() => {
|
const handleTrailerEnd = useCallback(() => {
|
||||||
logger.info('[AppleTVHero] Trailer ended');
|
logger.info('[AppleTVHero] Trailer ended');
|
||||||
setTrailerPlaying(false);
|
setTrailerPlaying(false);
|
||||||
|
|
||||||
// Reset trailer state
|
// Reset trailer state
|
||||||
setTrailerReady(false);
|
setTrailerReady(false);
|
||||||
setTrailerPreloaded(false);
|
setTrailerPreloaded(false);
|
||||||
|
|
||||||
// Smooth fade back to thumbnail
|
// Smooth fade back to thumbnail
|
||||||
trailerOpacity.value = withTiming(0, { duration: 500 });
|
trailerOpacity.value = withTiming(0, { duration: 500 });
|
||||||
thumbnailOpacity.value = withTiming(1, { duration: 500 });
|
thumbnailOpacity.value = withTiming(1, { duration: 500 });
|
||||||
|
|
@ -531,12 +552,12 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
// Instant reset - no extra fade animation
|
// Instant reset - no extra fade animation
|
||||||
dragProgress.value = 0;
|
dragProgress.value = 0;
|
||||||
setNextIndex(currentIndex);
|
setNextIndex(currentIndex);
|
||||||
|
|
||||||
// Immediately hide trailer and show thumbnail when index changes
|
// Immediately hide trailer and show thumbnail when index changes
|
||||||
trailerOpacity.value = 0;
|
trailerOpacity.value = 0;
|
||||||
thumbnailOpacity.value = 1;
|
thumbnailOpacity.value = 1;
|
||||||
setTrailerPlaying(false);
|
setTrailerPlaying(false);
|
||||||
|
|
||||||
// Faster logo fade
|
// Faster logo fade
|
||||||
logoOpacity.value = 0;
|
logoOpacity.value = 0;
|
||||||
logoOpacity.value = withDelay(
|
logoOpacity.value = withDelay(
|
||||||
|
|
@ -580,6 +601,9 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
.activeOffsetX([-5, 5]) // Smaller activation area - more sensitive
|
.activeOffsetX([-5, 5]) // Smaller activation area - more sensitive
|
||||||
.failOffsetY([-15, 15]) // Fail if vertical movement is detected
|
.failOffsetY([-15, 15]) // Fail if vertical movement is detected
|
||||||
.onStart(() => {
|
.onStart(() => {
|
||||||
|
// Mark as dragging to disable parallax
|
||||||
|
isDragging.value = 1;
|
||||||
|
|
||||||
// Determine which direction and set preview
|
// Determine which direction and set preview
|
||||||
runOnJS(updateInteractionTime)();
|
runOnJS(updateInteractionTime)();
|
||||||
// Immediately stop trailer playback when drag starts
|
// Immediately stop trailer playback when drag starts
|
||||||
|
|
@ -589,10 +613,10 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
const translationX = event.translationX;
|
const translationX = event.translationX;
|
||||||
// Use larger width multiplier for smoother visual feedback on small swipes
|
// Use larger width multiplier for smoother visual feedback on small swipes
|
||||||
const progress = Math.abs(translationX) / (width * 1.2);
|
const progress = Math.abs(translationX) / (width * 1.2);
|
||||||
|
|
||||||
// Update drag progress (0 to 1) with eased curve
|
// Update drag progress (0 to 1) with eased curve
|
||||||
dragProgress.value = Math.min(progress, 1);
|
dragProgress.value = Math.min(progress, 1);
|
||||||
|
|
||||||
// Track drag direction: positive = right (previous), negative = left (next)
|
// Track drag direction: positive = right (previous), negative = left (next)
|
||||||
if (translationX > 0) {
|
if (translationX > 0) {
|
||||||
dragDirection.value = 1; // Swiping right - show previous
|
dragDirection.value = 1; // Swiping right - show previous
|
||||||
|
|
@ -626,6 +650,9 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
},
|
},
|
||||||
(finished) => {
|
(finished) => {
|
||||||
if (finished) {
|
if (finished) {
|
||||||
|
// Re-enable parallax after navigation completes
|
||||||
|
isDragging.value = withTiming(0, { duration: 200 });
|
||||||
|
|
||||||
if (translationX > 0) {
|
if (translationX > 0) {
|
||||||
runOnJS(goToPrevious)();
|
runOnJS(goToPrevious)();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -640,6 +667,9 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
duration: 450,
|
duration: 450,
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1), // Custom ease-out for buttery smooth return
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1), // Custom ease-out for buttery smooth return
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-enable parallax immediately on cancel
|
||||||
|
isDragging.value = withTiming(0, { duration: 200 });
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[goToPrevious, goToNext, updateInteractionTime, setPreviewIndex, hideTrailerOnDrag, currentIndex, items.length]
|
[goToPrevious, goToNext, updateInteractionTime, setPreviewIndex, hideTrailerOnDrag, currentIndex, items.length]
|
||||||
|
|
@ -654,15 +684,15 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
[0, 0.05, 0.12, 0.22, 0.35, 0.5, 0.65, 0.78, 0.92, 1],
|
[0, 0.05, 0.12, 0.22, 0.35, 0.5, 0.65, 0.78, 0.92, 1],
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ultra-subtle slide effect with smooth ease-out curve
|
// Ultra-subtle slide effect with smooth ease-out curve
|
||||||
const slideDistance = 6; // Even more subtle 6px movement
|
const slideDistance = 6; // Even more subtle 6px movement
|
||||||
const slideProgress = interpolate(
|
const slideProgress = interpolate(
|
||||||
dragProgress.value,
|
dragProgress.value,
|
||||||
[0, 0.2, 0.4, 0.6, 0.8, 1], // 6-point for ultra-smooth acceleration
|
[0, 0.2, 0.4, 0.6, 0.8, 1], // 6-point for ultra-smooth acceleration
|
||||||
[
|
[
|
||||||
-slideDistance * dragDirection.value,
|
-slideDistance * dragDirection.value,
|
||||||
-slideDistance * 0.8 * dragDirection.value,
|
-slideDistance * 0.8 * dragDirection.value,
|
||||||
-slideDistance * 0.6 * dragDirection.value,
|
-slideDistance * 0.6 * dragDirection.value,
|
||||||
-slideDistance * 0.35 * dragDirection.value,
|
-slideDistance * 0.35 * dragDirection.value,
|
||||||
-slideDistance * 0.12 * dragDirection.value,
|
-slideDistance * 0.12 * dragDirection.value,
|
||||||
|
|
@ -670,7 +700,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
],
|
],
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity,
|
opacity,
|
||||||
transform: [{ translateX: slideProgress }],
|
transform: [{ translateX: slideProgress }],
|
||||||
|
|
@ -685,7 +715,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
[1, 0.5, 0],
|
[1, 0.5, 0],
|
||||||
Extrapolation.CLAMP
|
Extrapolation.CLAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: dragFade * logoOpacity.value,
|
opacity: dragFade * logoOpacity.value,
|
||||||
};
|
};
|
||||||
|
|
@ -915,10 +945,10 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
style={logoAnimatedStyle}
|
style={logoAnimatedStyle}
|
||||||
>
|
>
|
||||||
{currentItem.logo && !logoError[currentIndex] ? (
|
{currentItem.logo && !logoError[currentIndex] ? (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.logoContainer,
|
styles.logoContainer,
|
||||||
logoHeights[currentIndex] && logoHeights[currentIndex] < 80
|
logoHeights[currentIndex] && logoHeights[currentIndex] < 80
|
||||||
? { marginBottom: 4 } // Minimal spacing for small logos
|
? { marginBottom: 4 } // Minimal spacing for small logos
|
||||||
: { marginBottom: 8 } // Small spacing for normal logos
|
: { marginBottom: 8 } // Small spacing for normal logos
|
||||||
]}
|
]}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue