mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-10 20:10:54 +00:00
hero changes
This commit is contained in:
parent
2c524020af
commit
ea7f6bf7d7
1 changed files with 64 additions and 40 deletions
|
|
@ -128,6 +128,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 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);
|
||||||
|
|
@ -337,13 +338,14 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
|
|
||||||
// Reset drag progress and animate logo when index changes
|
// Reset drag progress and animate logo when index changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Instant reset - no extra fade animation
|
||||||
dragProgress.value = 0;
|
dragProgress.value = 0;
|
||||||
setNextIndex(currentIndex);
|
setNextIndex(currentIndex);
|
||||||
|
|
||||||
// Fade out and fade in logo/title
|
// Quick logo fade
|
||||||
logoOpacity.value = 0;
|
logoOpacity.value = 0;
|
||||||
logoOpacity.value = withDelay(
|
logoOpacity.value = withDelay(
|
||||||
200,
|
150,
|
||||||
withTiming(1, {
|
withTiming(1, {
|
||||||
duration: 400,
|
duration: 400,
|
||||||
easing: Easing.out(Easing.cubic),
|
easing: Easing.out(Easing.cubic),
|
||||||
|
|
@ -375,18 +377,26 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
const panGesture = useMemo(
|
const panGesture = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Gesture.Pan()
|
Gesture.Pan()
|
||||||
.activeOffsetX([-10, 10]) // Only activate on horizontal movement
|
.activeOffsetX([-5, 5]) // Smaller activation area - more sensitive
|
||||||
.failOffsetY([-10, 10]) // Fail if vertical movement is detected
|
.failOffsetY([-15, 15]) // Fail if vertical movement is detected
|
||||||
.onStart(() => {
|
.onStart(() => {
|
||||||
// Determine which direction and set preview
|
// Determine which direction and set preview
|
||||||
runOnJS(updateInteractionTime)();
|
runOnJS(updateInteractionTime)();
|
||||||
})
|
})
|
||||||
.onUpdate((event) => {
|
.onUpdate((event) => {
|
||||||
const translationX = event.translationX;
|
const translationX = event.translationX;
|
||||||
const progress = Math.abs(translationX) / width;
|
// Use smaller width multiplier for easier drag
|
||||||
|
const progress = Math.abs(translationX) / (width * 0.6);
|
||||||
|
|
||||||
// Update drag progress (0 to 1)
|
// 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)
|
||||||
|
if (translationX > 0) {
|
||||||
|
dragDirection.value = 1; // Swiping right - show previous
|
||||||
|
} else if (translationX < 0) {
|
||||||
|
dragDirection.value = -1; // Swiping left - show next
|
||||||
|
}
|
||||||
|
|
||||||
// Determine preview index based on direction
|
// Determine preview index based on direction
|
||||||
if (translationX > 0) {
|
if (translationX > 0) {
|
||||||
|
|
@ -402,55 +412,57 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
.onEnd((event) => {
|
.onEnd((event) => {
|
||||||
const velocity = event.velocityX;
|
const velocity = event.velocityX;
|
||||||
const translationX = event.translationX;
|
const translationX = event.translationX;
|
||||||
const swipeThreshold = width * 0.25;
|
const swipeThreshold = width * 0.15; // Smaller threshold - easier to swipe
|
||||||
|
|
||||||
if (Math.abs(translationX) > swipeThreshold || Math.abs(velocity) > 800) {
|
if (Math.abs(translationX) > swipeThreshold || Math.abs(velocity) > 600) {
|
||||||
// Complete the swipe
|
// Complete the swipe - instant navigation
|
||||||
if (translationX > 0) {
|
if (translationX > 0) {
|
||||||
runOnJS(goToPrevious)();
|
runOnJS(goToPrevious)();
|
||||||
} else {
|
} else {
|
||||||
runOnJS(goToNext)();
|
runOnJS(goToNext)();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Cancel the swipe - animate back
|
// Cancel the swipe - animate back with ease
|
||||||
dragProgress.value = withTiming(0, {
|
dragProgress.value = withTiming(0, {
|
||||||
duration: 300,
|
duration: 250,
|
||||||
easing: Easing.out(Easing.cubic),
|
easing: Easing.bezier(0.4, 0.0, 0.2, 1), // Material design ease curve
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[goToPrevious, goToNext, updateInteractionTime, setPreviewIndex, currentIndex, items.length]
|
[goToPrevious, goToNext, updateInteractionTime, setPreviewIndex, currentIndex, items.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Animated styles for current and next images
|
// Animated styles for next image only - smooth crossfade + slide during drag
|
||||||
const currentImageStyle = useAnimatedStyle(() => {
|
|
||||||
return {
|
|
||||||
opacity: interpolate(
|
|
||||||
dragProgress.value,
|
|
||||||
[0, 1],
|
|
||||||
[1, 0],
|
|
||||||
Extrapolate.CLAMP
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextImageStyle = useAnimatedStyle(() => {
|
const nextImageStyle = useAnimatedStyle(() => {
|
||||||
|
// Enhanced 4-point curve for smoother crossfade
|
||||||
|
const opacity = interpolate(
|
||||||
|
dragProgress.value,
|
||||||
|
[0, 0.3, 0.7, 1],
|
||||||
|
[0, 0.4, 0.8, 1],
|
||||||
|
Extrapolate.CLAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
// Smoother slide effect with ease-out curve
|
||||||
|
const slideDistance = 20; // Subtle 20px movement
|
||||||
|
const slideProgress = interpolate(
|
||||||
|
dragProgress.value,
|
||||||
|
[0, 0.4, 0.8, 1], // 4-point for smoother acceleration
|
||||||
|
[-slideDistance * dragDirection.value, -slideDistance * 0.5 * dragDirection.value, -slideDistance * 0.15 * dragDirection.value, 0],
|
||||||
|
Extrapolate.CLAMP
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(
|
opacity,
|
||||||
dragProgress.value,
|
transform: [{ translateX: slideProgress }],
|
||||||
[0, 1],
|
|
||||||
[0, 1],
|
|
||||||
Extrapolate.CLAMP
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Animated style for logo/title only - fades during drag
|
// Animated style for logo/title only - fades during drag with smoother curve
|
||||||
const logoAnimatedStyle = useAnimatedStyle(() => {
|
const logoAnimatedStyle = useAnimatedStyle(() => {
|
||||||
const dragFade = interpolate(
|
const dragFade = interpolate(
|
||||||
dragProgress.value,
|
dragProgress.value,
|
||||||
[0, 0.3],
|
[0, 0.2, 0.4],
|
||||||
[1, 0],
|
[1, 0.5, 0],
|
||||||
Extrapolate.CLAMP
|
Extrapolate.CLAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -506,10 +518,8 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
>
|
>
|
||||||
{/* Background Images with Crossfade */}
|
{/* Background Images with Crossfade */}
|
||||||
<View style={styles.backgroundContainer}>
|
<View style={styles.backgroundContainer}>
|
||||||
{/* Current Image - Thumbnail with fade */}
|
{/* Current Image - Always visible as base */}
|
||||||
<Animated.View style={[StyleSheet.absoluteFillObject, currentImageStyle, {
|
<View style={styles.imageWrapper}>
|
||||||
opacity: thumbnailOpacity
|
|
||||||
}]}>
|
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{
|
source={{
|
||||||
uri: bannerUrl,
|
uri: bannerUrl,
|
||||||
|
|
@ -520,11 +530,11 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
onLoad={() => setBannerLoaded((prev) => ({ ...prev, [currentIndex]: true }))}
|
onLoad={() => setBannerLoaded((prev) => ({ ...prev, [currentIndex]: true }))}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</View>
|
||||||
|
|
||||||
{/* Next/Preview Image */}
|
{/* Next/Preview Image - Animated overlay during drag */}
|
||||||
{nextIndex !== currentIndex && (
|
{nextIndex !== currentIndex && (
|
||||||
<Animated.View style={[StyleSheet.absoluteFillObject, nextImageStyle]}>
|
<Animated.View style={[styles.imageWrapperAbsolute, nextImageStyle]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{
|
source={{
|
||||||
uri: nextBannerUrl,
|
uri: nextBannerUrl,
|
||||||
|
|
@ -791,6 +801,20 @@ const styles = StyleSheet.create({
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
|
imageWrapper: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: -50, // Extend 50px to left
|
||||||
|
right: -50, // Extend 50px to right
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
imageWrapperAbsolute: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: -50, // Extend 50px to left
|
||||||
|
right: -50, // Extend 50px to right
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue