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