diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 7f36a02..a4822d0 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -256,6 +256,14 @@ const WatchProgressDisplay = React.memo(({ const { currentTheme } = useTheme(); const { isAuthenticated: isTraktAuthenticated, forceSyncTraktProgress } = useTraktContext(); + // Animated values for enhanced effects + const completionGlow = useSharedValue(0); + const celebrationScale = useSharedValue(1); + const progressPulse = useSharedValue(1); + const progressBoxOpacity = useSharedValue(0); + const progressBoxScale = useSharedValue(0.8); + const progressBoxTranslateY = useSharedValue(20); + // Handle manual Trakt sync const handleTraktSync = useMemo(() => async () => { if (isTraktAuthenticated && forceSyncTraktProgress) { @@ -357,69 +365,177 @@ const WatchProgressDisplay = React.memo(({ }; }, [watchProgress, type, getEpisodeDetails, isTraktAuthenticated, isWatched]); + // Trigger appearance and completion animations + useEffect(() => { + if (progressData) { + // Smooth entrance animation for the glassmorphic box + progressBoxOpacity.value = withTiming(1, { duration: 400 }); + progressBoxScale.value = withTiming(1, { duration: 400 }); + progressBoxTranslateY.value = withTiming(0, { duration: 400 }); + + if (progressData.isWatched || (progressData.progressPercent && progressData.progressPercent >= 95)) { + // Celebration animation sequence + celebrationScale.value = withRepeat( + withTiming(1.05, { duration: 200 }), + 2, + true + ); + + // Glow effect + completionGlow.value = withRepeat( + withTiming(1, { duration: 1500 }), + -1, + true + ); + } else { + // Subtle progress pulse for ongoing content + progressPulse.value = withRepeat( + withTiming(1.02, { duration: 2000 }), + -1, + true + ); + } + } else { + // Hide animation when no progress data + progressBoxOpacity.value = withTiming(0, { duration: 300 }); + progressBoxScale.value = withTiming(0.8, { duration: 300 }); + progressBoxTranslateY.value = withTiming(20, { duration: 300 }); + } + }, [progressData]); + + // Animated styles for enhanced effects + const celebrationAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{ scale: celebrationScale.value }], + })); + + const glowAnimatedStyle = useAnimatedStyle(() => ({ + opacity: interpolate(completionGlow.value, [0, 1], [0.3, 0.8], Extrapolate.CLAMP), + })); + + const progressPulseStyle = useAnimatedStyle(() => ({ + transform: [{ scale: progressPulse.value }], + })); + + const progressBoxAnimatedStyle = useAnimatedStyle(() => ({ + opacity: progressBoxOpacity.value, + transform: [ + { scale: progressBoxScale.value }, + { translateY: progressBoxTranslateY.value } + ], + })); + if (!progressData) return null; + const isCompleted = progressData.isWatched || progressData.progressPercent >= 95; + return ( - - - {/* Subtle watched indicator */} - {progressData.isWatched && ( - - - + {/* Glass morphism background with entrance animation */} + + {Platform.OS === 'ios' ? ( + + ) : ( + )} - {/* Trakt sync indicator for non-watched content */} - {progressData.isTraktSynced && !progressData.isWatched && ( - - - - )} - - - - {progressData.displayText}{progressData.episodeInfo} • Last watched on {progressData.formattedTime} - {progressData.syncStatus} - - {/* Manual Trakt sync button */} - {isTraktAuthenticated && forceSyncTraktProgress && ( - - + + {/* Background glow for completed content */} + {isCompleted && ( + + )} + + - - )} - + + {/* Shimmer effect for active progress */} + {!isCompleted && progressData.progressPercent > 0 && ( + + )} + + + + {/* Enhanced text container with better typography */} + + + + {progressData.displayText} + + + {/* Progress percentage badge */} + {!isCompleted && ( + + + {Math.round(progressData.progressPercent)}% + + + )} + + + + {progressData.episodeInfo} • Last watched {progressData.formattedTime} + + + {/* Trakt sync status with enhanced styling */} + {progressData.syncStatus && ( + + + + {progressData.syncStatus} + + + {/* Enhanced manual Trakt sync button - moved inline */} + {isTraktAuthenticated && forceSyncTraktProgress && ( + + + + + + )} + + )} + + ); }); @@ -513,17 +629,28 @@ const HeroSection: React.FC = ({ opacity: heroOpacity.value, }), []); - const logoAnimatedStyle = useAnimatedStyle(() => ({ - opacity: logoOpacity.value, - transform: [{ - translateY: interpolate( - scrollY.value, - [0, 100], - [0, -20], - Extrapolate.CLAMP - ) - }] - }), []); + const logoAnimatedStyle = useAnimatedStyle(() => { + // Determine if progress bar should be shown + const hasProgress = watchProgress && watchProgress.duration > 0; + + // Scale down logo when progress bar is present + const logoScale = hasProgress ? 0.85 : 1; + + return { + opacity: logoOpacity.value, + transform: [ + { + translateY: interpolate( + scrollY.value, + [0, 100], + [0, -20], + Extrapolate.CLAMP + ) + }, + { scale: withTiming(logoScale, { duration: 300 }) } + ] + }; + }, [watchProgress]); const watchProgressAnimatedStyle = useAnimatedStyle(() => ({ opacity: watchProgressOpacity.value, @@ -818,18 +945,40 @@ const styles = StyleSheet.create({ }, watchProgressContainer: { marginTop: 4, - marginBottom: 6, + marginBottom: 4, width: '100%', alignItems: 'center', - height: 44, + minHeight: 36, + position: 'relative', + }, + progressGlassBackground: { + width: '75%', + backgroundColor: 'rgba(255,255,255,0.08)', + borderRadius: 12, + padding: 8, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.1)', + overflow: 'hidden', + }, + androidProgressBlur: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + borderRadius: 16, + backgroundColor: 'rgba(0,0,0,0.3)', + }, + watchProgressBarContainer: { + position: 'relative', + marginBottom: 6, }, watchProgressBar: { - width: '70%', - height: 2.5, - backgroundColor: 'rgba(255, 255, 255, 0.2)', - borderRadius: 1.25, + width: '100%', + height: 3, + backgroundColor: 'rgba(255, 255, 255, 0.15)', + borderRadius: 1.5, overflow: 'hidden', - marginBottom: 6, position: 'relative', }, watchProgressFill: { @@ -845,6 +994,18 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + traktSyncIndicatorEnhanced: { + position: 'absolute', + right: 4, + top: -2, + bottom: -2, + width: 16, + height: 16, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + }, watchedProgressIndicator: { position: 'absolute', right: 2, @@ -855,10 +1016,10 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, watchProgressTextContainer: { - flexDirection: 'row', + flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - gap: 8, + width: '100%', }, watchProgressText: { fontSize: 11, @@ -933,6 +1094,138 @@ const styles = StyleSheet.create({ marginLeft: 6, fontSize: 15, }, + // Enhanced progress indicator styles + progressShimmer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + borderRadius: 2, + backgroundColor: 'rgba(255,255,255,0.1)', + }, + completionGlow: { + position: 'absolute', + top: -2, + left: -2, + right: -2, + bottom: -2, + borderRadius: 4, + backgroundColor: 'rgba(0,255,136,0.2)', + }, + completionIndicator: { + position: 'absolute', + right: 4, + top: -6, + bottom: -6, + width: 16, + height: 16, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + }, + completionGradient: { + width: 16, + height: 16, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + }, + sparkleContainer: { + position: 'absolute', + top: -10, + left: 0, + right: 0, + bottom: -10, + borderRadius: 2, + }, + sparkle: { + position: 'absolute', + width: 8, + height: 8, + borderRadius: 4, + alignItems: 'center', + justifyContent: 'center', + }, + progressInfoMain: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: 2, + }, + watchProgressMainText: { + fontSize: 11, + fontWeight: '600', + textAlign: 'center', + }, + percentageBadge: { + backgroundColor: 'rgba(255,255,255,0.2)', + borderRadius: 8, + paddingHorizontal: 6, + paddingVertical: 2, + marginLeft: 8, + }, + percentageText: { + fontSize: 10, + fontWeight: '600', + color: '#fff', + }, + watchProgressSubText: { + fontSize: 9, + textAlign: 'center', + opacity: 0.8, + marginBottom: 1, + }, + syncStatusContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginTop: 2, + width: '100%', + flexWrap: 'wrap', + }, + syncStatusText: { + fontSize: 9, + marginLeft: 4, + fontWeight: '500', + }, + traktSyncButtonEnhanced: { + position: 'absolute', + top: 8, + right: 8, + width: 24, + height: 24, + borderRadius: 12, + overflow: 'hidden', + }, + traktSyncButtonInline: { + marginLeft: 8, + width: 20, + height: 20, + borderRadius: 10, + overflow: 'hidden', + }, + syncButtonGradient: { + width: 24, + height: 24, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + }, + syncButtonGradientInline: { + width: 20, + height: 20, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + traktIndicatorGradient: { + width: 16, + height: 16, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + }, }); export default React.memo(HeroSection); \ No newline at end of file