@@ -103,9 +103,12 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
103103
104104 List <Map <String , dynamic >> _songOptionsPluginButtons = [];
105105
106+ late GlobalKey <_LyricsOverlayState > _lyricsOverlayKey;
107+
106108 @override
107109 void initState () {
108110 super .initState ();
111+ _lyricsOverlayKey = GlobalKey <_LyricsOverlayState >();
109112 rust_api.getCvol ().then ((volume) {
110113 if (mounted) {
111114 setState (() {
@@ -815,20 +818,20 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
815818 context: context,
816819 builder: (context) => AlertDialog (
817820 backgroundColor: dominantColor.withAlpha (30 ),
818- title: Text ('Delete Song?' , style: TextStyle (color: Colors .white)),
821+ title: Text ('Delete Song?' , style: GoogleFonts . inter (color: Colors .white)),
819822 content: Text ('This will permanently delete "${song .title }"' ,
820- style: TextStyle (color: Colors .white70)),
823+ style: GoogleFonts . inter (color: Colors .white70)),
821824 actions: [
822825 TextButton (
823- child: Text ('Cancel' , style: TextStyle (color: Colors .white70)),
826+ child: Text ('Cancel' , style: GoogleFonts . inter (color: Colors .white70)),
824827 onPressed: () {
825828 ScaffoldMessenger .of (context).hideCurrentSnackBar ();
826829 Navigator .pop (context, false );
827830 },
828831 ),
829832 TextButton (
830833 child:
831- Text ('Delete' , style: TextStyle (color: Colors .redAccent)),
834+ Text ('Delete' , style: GoogleFonts . inter (color: Colors .redAccent)),
832835 onPressed: () {
833836 ScaffoldMessenger .of (context).hideCurrentSnackBar ();
834837 Navigator .pop (context, true );
@@ -1113,6 +1116,7 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
11131116 await _updateDominantColor ();
11141117 widget.service.updatePlaylist (widget.songList, currentIndex);
11151118 widget.service.updateMetadata ();
1119+ return ;
11161120 }
11171121 }
11181122 }
@@ -1140,6 +1144,10 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
11401144 currentSong.duration.inSeconds.toDouble (),
11411145 );
11421146 await rust_api.seekToPosition (position: seekPosition);
1147+ if (_showLyrics && _lyricsOverlayKey.currentState != null ) {
1148+ _lyricsOverlayKey.currentState! .updateCurrentLyric ();
1149+ _lyricsOverlayKey.currentState! .scrollToCurrentLyric ();
1150+ }
11431151 //if (!isPlaying && mounted) {
11441152 // setState(() {
11451153 // isPlaying = true;
@@ -1230,6 +1238,9 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
12301238 await rust_api.playSong (path: currentSong.path);
12311239 await rust_api.preloadNextSong (
12321240 path: widget.songList[currentIndex + 1 ].path);
1241+ if (_showLyrics && _lyricsOverlayKey.currentState != null ) {
1242+ _lyricsOverlayKey.currentState! .scrollToTop ();
1243+ }
12331244 }
12341245
12351246 void _generateShuffleOrder () {
@@ -1764,31 +1775,88 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
17641775 const EdgeInsets .symmetric (horizontal: 32.0 ),
17651776 child: Column (
17661777 children: [
1767- GlowText (
1768- currentSong.title,
1769- style: TextStyle (
1770- color:
1771- dominantColor.computeLuminance () > 0.01
1772- ? dominantColor
1773- : Theme .of (context)
1774- .textTheme
1775- .bodyLarge
1776- ? .color,
1777- fontSize: 28 ,
1778- fontWeight: FontWeight .w700,
1779- ),
1780- textAlign: TextAlign .center,
1781- maxLines: 1 ,
1782- overflow: TextOverflow .ellipsis,
1783- ),
1784- const SizedBox (height: 4 ),
1785- Text (
1786- currentSong.artist,
1787- style: TextStyle (
1788- color: textColor.withValues (alpha: 0.8 ),
1789- fontSize: 16 ,
1790- ),
1791- ),
1778+ Hero (
1779+ tag: 'title-${currentSong .path }' ,
1780+ flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
1781+ final textWidget = flightDirection == HeroFlightDirection .push
1782+ ? toHeroContext.widget
1783+ : fromHeroContext.widget;
1784+
1785+ return AnimatedBuilder (
1786+ animation: animation,
1787+ builder: (context, child) {
1788+ return Transform .translate (
1789+ offset: Offset (0 , - 20 * (1 - animation.value)),
1790+ child: Opacity (
1791+ opacity: Tween <double >(begin: 0.0 , end: 1.0 )
1792+ .animate (CurvedAnimation (
1793+ parent: animation,
1794+ curve: Interval (0.5 , 1.0 ),
1795+ ))
1796+ .value,
1797+ child: child,
1798+ ),
1799+ );
1800+ },
1801+ child: textWidget,
1802+ );
1803+ },
1804+ child: Material (
1805+ color: Colors .transparent,
1806+ child: GlowText (
1807+ currentSong.title,
1808+ style: TextStyle (
1809+ color: dominantColor.computeLuminance () > 0.01
1810+ ? dominantColor
1811+ : Theme .of (context).textTheme.bodyLarge? .color,
1812+ fontSize: 28 ,
1813+ fontWeight: FontWeight .w700,
1814+ ),
1815+ textAlign: TextAlign .center,
1816+ maxLines: 1 ,
1817+ overflow: TextOverflow .ellipsis,
1818+ ),
1819+ ),
1820+ ),
1821+ const SizedBox (height: 4 ),
1822+ Hero (
1823+ tag: 'artist-${currentSong .path }' ,
1824+ flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
1825+ final textWidget = flightDirection == HeroFlightDirection .push
1826+ ? toHeroContext.widget
1827+ : fromHeroContext.widget;
1828+
1829+ return AnimatedBuilder (
1830+ animation: animation,
1831+ builder: (context, child) {
1832+ return Transform .translate (
1833+ offset: Offset (0 , - 20 * (1 - animation.value)),
1834+ child: Opacity (
1835+ opacity: Tween <double >(begin: 0.0 , end: 1.0 )
1836+ .animate (CurvedAnimation (
1837+ parent: animation,
1838+ curve: Interval (0.6 , 1.0 ),
1839+ ))
1840+ .value,
1841+ child: child,
1842+ ),
1843+ );
1844+ },
1845+ child: textWidget,
1846+ );
1847+ },
1848+ child: Material (
1849+ color: Colors .transparent,
1850+ child: Text (
1851+ currentSong.artist,
1852+ style: TextStyle (
1853+ color: textColor.withValues (alpha: 0.8 ),
1854+ fontSize: 16 ,
1855+ ),
1856+ textAlign: TextAlign .center,
1857+ ),
1858+ ),
1859+ ),
17921860 ],
17931861 ),
17941862 ),
@@ -1900,7 +1968,8 @@ class _MusicPlayerScreenState extends State<MusicPlayerScreen>
19001968 Positioned .fill (
19011969 child: LyricsOverlay (
19021970 isPlaying: isPlaying,
1903- key: ValueKey (currentSong.path),
1971+ //key: ValueKey(currentSong.path),
1972+ key: _lyricsOverlayKey,
19041973 lrc: _lrcData! ,
19051974 currentPosition: Duration (
19061975 seconds: (_currentSliderValue *
@@ -2485,11 +2554,11 @@ class _LyricsOverlayState extends State<LyricsOverlay>
24852554 void initState () {
24862555 super .initState ();
24872556 _initializeAnimations ();
2488- _currentLyricNotifier.addListener (_scrollToCurrentLyric );
2557+ _currentLyricNotifier.addListener (scrollToCurrentLyric );
24892558 _scrollController.addListener (_handleParallaxScroll);
2490- _updateCurrentLyric ();
2559+ updateCurrentLyric ();
24912560 WidgetsBinding .instance.addPostFrameCallback ((_) {
2492- _scrollToCurrentLyric ();
2561+ scrollToCurrentLyric ();
24932562 });
24942563 }
24952564
@@ -2539,7 +2608,7 @@ class _LyricsOverlayState extends State<LyricsOverlay>
25392608 });
25402609 }
25412610
2542- void _scrollToCurrentLyric () {
2611+ void scrollToCurrentLyric () {
25432612 final index = _currentLyricNotifier.value;
25442613 if (index >= 0 && _lyricKeys.containsKey (index)) {
25452614 final context = _lyricKeys[index]? .currentContext;
@@ -2551,6 +2620,20 @@ class _LyricsOverlayState extends State<LyricsOverlay>
25512620 alignment: 0.5 , // Center the current lyric
25522621 );
25532622 }
2623+ } else if (index >= 0 && _scrollController.hasClients) {
2624+ // Scroll to estimated position if key is not available
2625+ final estimatedPosition = index * 85.0 ; // Approximate item height
2626+ final viewportHeight = _scrollController.position.viewportDimension;
2627+
2628+ // Always center the lyric in the viewport
2629+ final targetPos = (estimatedPosition - viewportHeight / 2 + 85.0 / 2 )
2630+ .clamp (0.0 , _scrollController.position.maxScrollExtent);
2631+
2632+ _scrollController.animateTo (
2633+ targetPos,
2634+ duration: const Duration (milliseconds: 800 ),
2635+ curve: Curves .easeOutQuint,
2636+ );
25542637 }
25552638 }
25562639
@@ -2565,6 +2648,15 @@ class _LyricsOverlayState extends State<LyricsOverlay>
25652648 return relativePosition * 20.0 ; // Parallax amount
25662649 }
25672650
2651+ void scrollToTop () {
2652+ _scrollController.animateTo (
2653+ 0.0 ,
2654+ duration: const Duration (milliseconds: 300 ),
2655+ curve: Curves .easeOut,
2656+ );
2657+ _currentLyricNotifier.value = - 1 ; // Reset current lyric highlight
2658+ }
2659+
25682660 @override
25692661 Widget build (BuildContext context) {
25702662 final breathingValue =
@@ -2800,7 +2892,13 @@ class _LyricsOverlayState extends State<LyricsOverlay>
28002892 @override
28012893 void didUpdateWidget (LyricsOverlay oldWidget) {
28022894 super .didUpdateWidget (oldWidget);
2803- _updateCurrentLyric ();
2895+ updateCurrentLyric ();
2896+
2897+ if ((widget.currentPosition - oldWidget.currentPosition).inSeconds.abs () > 5 ) {
2898+ WidgetsBinding .instance.addPostFrameCallback ((_) {
2899+ scrollToCurrentLyric ();
2900+ });
2901+ }
28042902
28052903 if (widget.isPlaying != oldWidget.isPlaying) {
28062904 if (widget.isPlaying) {
@@ -2825,15 +2923,25 @@ class _LyricsOverlayState extends State<LyricsOverlay>
28252923 }
28262924 }
28272925
2828- void _updateCurrentLyric () {
2926+ void updateCurrentLyric () {
28292927 int newIndex = - 1 ;
2830- for (var i = 0 ; i < widget.lrc.lyrics.length; i++ ) {
2831- if (widget.lrc.lyrics[i].timestamp <= widget.currentPosition) {
2832- newIndex = i;
2928+ final currentPos = widget.currentPosition;
2929+
2930+ var low = 0 ;
2931+ var high = widget.lrc.lyrics.length - 1 ;
2932+
2933+ while (low <= high) {
2934+ final mid = (low + high) ~ / 2 ;
2935+ final midTimestamp = widget.lrc.lyrics[mid].timestamp;
2936+
2937+ if (midTimestamp <= currentPos) {
2938+ newIndex = mid;
2939+ low = mid + 1 ;
28332940 } else {
2834- break ;
2941+ high = mid - 1 ;
28352942 }
28362943 }
2944+
28372945 if (newIndex != _currentLyricNotifier.value) {
28382946 _currentLyricNotifier.value = newIndex;
28392947 }
0 commit comments