From f685eaebd7b2552141cd9bfb87cf1d3c473ceec0 Mon Sep 17 00:00:00 2001 From: ergosteur <1992147+ergosteur@users.noreply.github.com> Date: Sat, 7 Mar 2026 00:31:04 -0500 Subject: [PATCH] feat: enhance story viewer and media playback experience --- src/App.tsx | 236 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 145 insertions(+), 91 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 69486e0..c5844d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,9 @@ import { MessageCircle, Bookmark, MoreHorizontal, - Loader2 + Loader2, + Volume2, + VolumeX } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { clsx, type ClassValue } from 'clsx'; @@ -52,38 +54,56 @@ interface Post { const StoryViewer = ({ stories, - onClose + onClose, + profilePic }: { stories: Post[]; onClose: () => void; + profilePic: string | null; }) => { const [currentStoryIndex, setCurrentStoryIndex] = useState(0); const [progress, setProgress] = useState(0); + const [isMuted, setIsMuted] = useState(false); + const videoRef = React.useRef(null); const story = stories[currentStoryIndex]; useEffect(() => { setProgress(0); - const duration = 5000; // 5 seconds per story + let duration = 5000; // Default 5s for images const interval = 50; - const step = (interval / duration) * 100; + + const updateProgress = () => { + if (story.media[0].type === 'video' && videoRef.current) { + const currentTime = videoRef.current.currentTime; + const totalTime = videoRef.current.duration; + if (totalTime) { + setProgress((currentTime / totalTime) * 100); + } + } else { + setProgress(prev => { + const step = (interval / duration) * 100; + if (prev >= 100) return 100; + return prev + step; + }); + } + }; const timer = setInterval(() => { - setProgress(prev => { - if (prev >= 100) { - if (currentStoryIndex < stories.length - 1) { - setCurrentStoryIndex(prev => prev + 1); - return 0; - } else { - onClose(); - return 100; - } - } - return prev + step; - }); + updateProgress(); }, interval); return () => clearInterval(timer); - }, [currentStoryIndex, stories.length, onClose]); + }, [currentStoryIndex, story.media]); + + useEffect(() => { + if (progress >= 100) { + if (currentStoryIndex < stories.length - 1) { + setCurrentStoryIndex(prev => prev + 1); + } else { + onClose(); + } + } + }, [progress, currentStoryIndex, stories.length, onClose]); const nextStory = () => { if (currentStoryIndex < stories.length - 1) { @@ -101,22 +121,53 @@ const StoryViewer = ({ return ( + {/* Background Blur */} +
+ +
+ + {/* Navigation Arrows (Desktop) */} + + + + + {/* Main Container */}
e.stopPropagation()} > {/* Progress Bars */} -
+
100 ? '1px' : (stories.length > 50 ? '2px' : '4px') }} + > {stories.map((_, i) => ( -
+
{/* Header */} -
-
-
- {story.username[0]} +
+
+
+
+ {profilePic ? ( + + ) : ( + {story.username[0]} + )} +
-
- {story.username} - {format(parseISO(story.date), 'MMM d')} +
+ {story.username} + {format(parseISO(story.date), 'MMM d')}
- + +
+ {story.media[0].type === 'video' && ( + + )} + +
{/* Media */} -
+
{story.media[0].type === 'video' ? (
- {/* Navigation Areas */} -
-
-
+ {/* Interaction Areas */} +
+
+
- {/* Caption */} + {/* Caption Overlay */} {story.caption && ( -
+
{story.caption}
)} @@ -290,6 +359,7 @@ const VideoThumbnail = ({ url, className }: { url: string; className?: string }) }; const MediaRenderer = ({ file, className, isFullView }: { file: MediaFile; className?: string; isFullView?: boolean }) => { + const [isMuted, setIsMuted] = useState(false); const sizingClass = isFullView ? "w-full h-auto block" : "w-full h-full object-cover"; @@ -298,18 +368,27 @@ const MediaRenderer = ({ file, className, isFullView }: { file: MediaFile; class if (file.type === 'video') { return ( -