mirror of
https://github.com/wahyd4/you-music.git
synced 2026-08-09 05:06:44 +10:00
Ui update
This commit is contained in:
@@ -5,6 +5,7 @@ import { X, Play, Pause, SkipBack, SkipForward, Heart, ListPlus, Share2, Message
|
|||||||
import { formatDuration } from '@/lib/utils'
|
import { formatDuration } from '@/lib/utils'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { musicApi } from '@/api/client'
|
import { musicApi } from '@/api/client'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
interface FullScreenPlayerProps {
|
interface FullScreenPlayerProps {
|
||||||
currentMusic: Music
|
currentMusic: Music
|
||||||
@@ -43,7 +44,12 @@ export default function FullScreenPlayer({
|
|||||||
onToggleLyrics,
|
onToggleLyrics,
|
||||||
onSeek,
|
onSeek,
|
||||||
}: FullScreenPlayerProps) {
|
}: FullScreenPlayerProps) {
|
||||||
// Fetch lyrics when enabled
|
const [userScrolling, setUserScrolling] = React.useState(false)
|
||||||
|
const scrollTimeoutRef = React.useRef<number>()
|
||||||
|
const lyricsContainerRef = React.useRef<HTMLDivElement>(null)
|
||||||
|
const lastScrollTop = React.useRef(0)
|
||||||
|
|
||||||
|
// Fetch lyrics when enabled with caching
|
||||||
const { data: lyrics, isLoading: lyricsLoading } = useQuery({
|
const { data: lyrics, isLoading: lyricsLoading } = useQuery({
|
||||||
queryKey: ['lyrics', currentMusic.id],
|
queryKey: ['lyrics', currentMusic.id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -51,44 +57,87 @@ export default function FullScreenPlayer({
|
|||||||
return response.data.lyrics
|
return response.data.lyrics
|
||||||
},
|
},
|
||||||
enabled: showLyrics && !!currentMusic.id,
|
enabled: showLyrics && !!currentMusic.id,
|
||||||
|
staleTime: Infinity, // Cache lyrics forever
|
||||||
|
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle manual scroll
|
||||||
|
const handleScroll = React.useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
const currentScrollTop = e.currentTarget.scrollTop
|
||||||
|
|
||||||
|
// Only set userScrolling if scroll position actually changed (user initiated)
|
||||||
|
if (Math.abs(currentScrollTop - lastScrollTop.current) > 1) {
|
||||||
|
setUserScrolling(true)
|
||||||
|
lastScrollTop.current = currentScrollTop
|
||||||
|
|
||||||
|
// Clear existing timeout
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume auto-scroll after 5 seconds of no scrolling
|
||||||
|
scrollTimeoutRef.current = window.setTimeout(() => {
|
||||||
|
setUserScrolling(false)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Cleanup timeout on unmount or when lyrics disabled
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Reset user scrolling when lyrics change or disabled
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!showLyrics) {
|
||||||
|
setUserScrolling(false)
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [showLyrics, currentMusic.id])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 bg-background/95 backdrop-blur-sm flex flex-col">
|
<div className="fixed inset-0 z-50 bg-background/95 backdrop-blur-sm flex flex-col">
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 flex flex-col items-center justify-center p-8 overflow-y-auto">
|
<div className="flex-1 flex flex-col items-center justify-center p-8 overflow-y-auto">
|
||||||
{/* Large Album Art */}
|
{/* Large Album Art */}
|
||||||
<div className="w-full max-w-lg mb-8">
|
<div className={`w-full max-w-lg ${showLyrics ? 'mb-4 md:mb-8' : 'mb-8'}`}>
|
||||||
{currentMusic.thumbnail ? (
|
{currentMusic.thumbnail ? (
|
||||||
<img
|
<img
|
||||||
src={currentMusic.thumbnail.startsWith('http') ? currentMusic.thumbnail : `/music/${currentMusic.thumbnail}`}
|
src={currentMusic.thumbnail.startsWith('http') ? currentMusic.thumbnail : `/music/${currentMusic.thumbnail}`}
|
||||||
alt={currentMusic.title}
|
alt={currentMusic.title}
|
||||||
className="w-full aspect-square rounded-2xl object-cover shadow-2xl"
|
className={`w-full aspect-square rounded-2xl object-cover shadow-2xl ${showLyrics ? 'max-w-xs md:max-w-lg mx-auto' : ''}`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full aspect-square rounded-2xl bg-secondary flex items-center justify-center shadow-2xl">
|
<div className={`w-full aspect-square rounded-2xl bg-secondary flex items-center justify-center shadow-2xl ${showLyrics ? 'max-w-xs md:max-w-lg mx-auto' : ''}`}>
|
||||||
<Play className="h-32 w-32 text-muted-foreground" />
|
<Play className={showLyrics ? 'h-24 w-24 md:h-32 md:w-32 text-muted-foreground' : 'h-32 w-32 text-muted-foreground'} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Song Info */}
|
{/* Song Info */}
|
||||||
<div className="text-center mb-6 max-w-lg w-full">
|
<div className={`text-center max-w-lg w-full ${showLyrics ? 'mb-3 md:mb-6' : 'mb-6'}`}>
|
||||||
<h1 className="text-3xl font-bold mb-2">{currentMusic.title}</h1>
|
<h1 className={showLyrics ? 'text-2xl md:text-3xl font-bold mb-1 md:mb-2' : 'text-3xl font-bold mb-2'}>{currentMusic.title}</h1>
|
||||||
{currentMusic.artist && currentMusic.artist !== 'Unknown' ? (
|
{currentMusic.artist && currentMusic.artist !== 'Unknown' ? (
|
||||||
<button
|
<button
|
||||||
onClick={onNavigateToArtist}
|
onClick={onNavigateToArtist}
|
||||||
className="text-xl text-muted-foreground hover:underline"
|
className={showLyrics ? 'text-lg md:text-xl text-muted-foreground hover:underline' : 'text-xl text-muted-foreground hover:underline'}
|
||||||
>
|
>
|
||||||
{currentMusic.artist}
|
{currentMusic.artist}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xl text-muted-foreground">
|
<p className={showLyrics ? 'text-lg md:text-xl text-muted-foreground' : 'text-xl text-muted-foreground'}>
|
||||||
{currentMusic.artist || 'Unknown Artist'}
|
{currentMusic.artist || 'Unknown Artist'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{currentMusic.album && (
|
{currentMusic.album && (
|
||||||
<p className="text-sm text-muted-foreground mt-1">{currentMusic.album}</p>
|
<p className={showLyrics ? 'text-xs md:text-sm text-muted-foreground mt-1' : 'text-sm text-muted-foreground mt-1'}>{currentMusic.album}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -98,21 +147,27 @@ export default function FullScreenPlayer({
|
|||||||
{lyricsLoading ? (
|
{lyricsLoading ? (
|
||||||
<p className="text-center text-foreground py-8 md:py-12 text-lg md:text-xl">Loading lyrics...</p>
|
<p className="text-center text-foreground py-8 md:py-12 text-lg md:text-xl">Loading lyrics...</p>
|
||||||
) : lyrics && lyrics.length > 0 ? (
|
) : lyrics && lyrics.length > 0 ? (
|
||||||
<div className="relative h-64 md:h-96 overflow-hidden p-2 md:p-4">
|
<div
|
||||||
|
ref={lyricsContainerRef}
|
||||||
|
className="relative h-80 md:h-96 overflow-y-auto p-2 md:p-4 scrollbar-hide"
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="lyrics-scroll-content"
|
className={userScrolling ? '' : 'lyrics-scroll-content'}
|
||||||
style={{
|
style={{
|
||||||
animationDuration: `${Math.max(lyrics.split('\n').length * 1.5, 30)}s`,
|
animationDuration: userScrolling ? undefined : `${Math.max(lyrics.split('\n').length * 1.5, 30)}s`,
|
||||||
animationPlayState: isPlaying ? 'running' : 'paused'
|
animationPlayState: userScrolling ? 'paused' : (isPlaying ? 'running' : 'paused'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="whitespace-pre-wrap text-center text-base md:text-xl leading-relaxed font-normal text-foreground py-4 md:py-8 px-2">
|
<div className="whitespace-pre-wrap text-center text-base md:text-xl leading-relaxed font-normal text-foreground py-4 md:py-8 px-2">
|
||||||
{lyrics}
|
{lyrics}
|
||||||
</div>
|
</div>
|
||||||
{/* Duplicate for seamless loop */}
|
{/* Duplicate for seamless loop */}
|
||||||
<div className="whitespace-pre-wrap text-center text-base md:text-xl leading-relaxed font-normal text-foreground py-4 md:py-8 px-2">
|
{!userScrolling && (
|
||||||
{lyrics}
|
<div className="whitespace-pre-wrap text-center text-base md:text-xl leading-relaxed font-normal text-foreground py-4 md:py-8 px-2">
|
||||||
</div>
|
{lyrics}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Gradient overlays for fade effect */}
|
{/* Gradient overlays for fade effect */}
|
||||||
<div className="absolute top-0 left-0 right-0 h-16 md:h-20 bg-gradient-to-b from-background to-transparent pointer-events-none" />
|
<div className="absolute top-0 left-0 right-0 h-16 md:h-20 bg-gradient-to-b from-background to-transparent pointer-events-none" />
|
||||||
@@ -144,26 +199,26 @@ export default function FullScreenPlayer({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Controls */}
|
{/* Controls */}
|
||||||
<div className="flex items-center gap-6 mb-6">
|
<div className={`flex items-center mb-6 ${showLyrics ? 'gap-4 md:gap-6' : 'gap-6'}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={onPrevious}
|
onClick={onPrevious}
|
||||||
className="h-12 w-12"
|
className={showLyrics ? 'h-10 w-10 md:h-12 md:w-12' : 'h-12 w-12'}
|
||||||
>
|
>
|
||||||
<SkipBack className="h-6 w-6" />
|
<SkipBack className={showLyrics ? 'h-5 w-5 md:h-6 md:w-6' : 'h-6 w-6'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={onTogglePlay}
|
onClick={onTogglePlay}
|
||||||
className="h-16 w-16"
|
className={showLyrics ? 'h-12 w-12 md:h-16 md:w-16' : 'h-16 w-16'}
|
||||||
>
|
>
|
||||||
{isPlaying ? (
|
{isPlaying ? (
|
||||||
<Pause className="h-8 w-8" />
|
<Pause className={showLyrics ? 'h-6 w-6 md:h-8 md:w-8' : 'h-8 w-8'} />
|
||||||
) : (
|
) : (
|
||||||
<Play className="h-8 w-8" />
|
<Play className={showLyrics ? 'h-6 w-6 md:h-8 md:w-8' : 'h-8 w-8'} />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -171,9 +226,9 @@ export default function FullScreenPlayer({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
className="h-12 w-12"
|
className={showLyrics ? 'h-10 w-10 md:h-12 md:w-12' : 'h-12 w-12'}
|
||||||
>
|
>
|
||||||
<SkipForward className="h-6 w-6" />
|
<SkipForward className={showLyrics ? 'h-5 w-5 md:h-6 md:w-6' : 'h-6 w-6'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user