From ef40c2ffe4c81adfe19f975bb341554250dd2bd4 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 29 Aug 2024 19:10:54 +0800 Subject: [PATCH] :dizzy: Optimize lyrics --- lib/screens/player/view.dart | 47 +++++++++++------ lib/widgets/lyrics/synced_lyrics.dart | 56 +++++++++++++++----- lib/widgets/player/sibling_tracks.dart | 2 +- lib/widgets/tracks/heart_button.dart | 73 ++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 lib/widgets/tracks/heart_button.dart diff --git a/lib/screens/player/view.dart b/lib/screens/player/view.dart index 3e0d6c0..2f3ac7e 100644 --- a/lib/screens/player/view.dart +++ b/lib/screens/player/view.dart @@ -17,6 +17,7 @@ import 'package:rhythm_box/services/duration.dart'; import 'package:rhythm_box/widgets/auto_cache_image.dart'; import 'package:rhythm_box/services/audio_services/image.dart'; import 'package:rhythm_box/widgets/lyrics/synced_lyrics.dart'; +import 'package:rhythm_box/widgets/tracks/heart_button.dart'; import 'package:rhythm_box/widgets/tracks/querying_track_info.dart'; class PlayerScreen extends StatefulWidget { @@ -108,22 +109,36 @@ class _PlayerScreenState extends State { ), const Gap(24), Obx( - () => Text( - _playback.state.value.activeTrack?.name ?? - 'Not playing', - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - ), - Obx( - () => Text( - _playback.state.value.activeTrack?.artists - ?.asString() ?? - 'No author', - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), + () => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _playback.state.value.activeTrack?.name ?? + 'Not playing', + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.left, + ), + Text( + _playback.state.value.activeTrack?.artists + ?.asString() ?? + 'No author', + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + ), + ], + ), + ), + if (_playback.state.value.activeTrack != null) + TrackHeartButton( + trackId: _playback.state.value.activeTrack!.id!, + ), + ], + ).paddingSymmetric(horizontal: 32), ), const Gap(24), Obx( diff --git a/lib/widgets/lyrics/synced_lyrics.dart b/lib/widgets/lyrics/synced_lyrics.dart index e07a9ca..a2ace7b 100644 --- a/lib/widgets/lyrics/synced_lyrics.dart +++ b/lib/widgets/lyrics/synced_lyrics.dart @@ -7,6 +7,7 @@ import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/lyrics/model.dart'; import 'package:rhythm_box/services/lyrics/provider.dart'; +import 'package:rhythm_box/widgets/sized_container.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; class SyncedLyrics extends StatefulWidget { @@ -45,7 +46,7 @@ class _SyncedLyricsState extends State { List? _subscriptions; Color get _unFocusColor => - Theme.of(context).colorScheme.onSurface.withOpacity(0.75); + Theme.of(context).colorScheme.onSurface.withOpacity(0.5); @override void initState() { @@ -79,6 +80,12 @@ class _SyncedLyricsState extends State { return CustomScrollView( controller: _autoScrollController, slivers: [ + if (_lyric == null) + const SliverFillRemaining( + child: Center( + child: CircularProgressIndicator(), + ), + ), if (_lyric != null && _lyric!.lyrics.isNotEmpty) SliverList.builder( itemCount: _lyric!.lyrics.length, @@ -113,8 +120,9 @@ class _SyncedLyricsState extends State { ) : Padding( padding: idx == _lyric!.lyrics.length - 1 - ? const EdgeInsets.all(8.0).copyWith(bottom: 80) - : const EdgeInsets.all(8.0), + ? const EdgeInsets.symmetric(vertical: 8) + .copyWith(bottom: 80) + : const EdgeInsets.symmetric(vertical: 8), child: AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 250), style: TextStyle( @@ -137,26 +145,48 @@ class _SyncedLyricsState extends State { audioPlayer.seek(time); }, child: Builder(builder: (context) { - return Text( - lyricSlice.text, + return AnimatedDefaultTextStyle( style: TextStyle( + fontSize: isActive ? 20 : 16, color: isActive ? Theme.of(context).colorScheme.onSurface : _unFocusColor, - fontSize: 16, ), - ).animate(target: isActive ? 1 : 0).scale( - duration: 300.ms, - begin: const Offset(1, 1), - end: const Offset(1.25, 1.25), - curve: Curves.easeInOut, - ); - }).paddingSymmetric(horizontal: 12), + duration: 500.ms, + curve: Curves.easeInOut, + child: Text( + lyricSlice.text, + textAlign: + MediaQuery.of(context).size.width >= 720 + ? TextAlign.center + : TextAlign.left, + ), + ); + }).paddingSymmetric(horizontal: 24), ), ), ), ); }), + ) + else if (_lyric != null && _lyric!.lyrics.isEmpty) + SliverFillRemaining( + child: CenteredContainer( + maxWidth: 280, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Lyrics Not Found', + style: Theme.of(context).textTheme.bodyLarge, + ), + const Text( + "This song haven't lyrics that recorded in our database.", + textAlign: TextAlign.center, + ), + ], + ), + ), ), ], ); diff --git a/lib/widgets/player/sibling_tracks.dart b/lib/widgets/player/sibling_tracks.dart index 9c51a61..0e0f246 100644 --- a/lib/widgets/player/sibling_tracks.dart +++ b/lib/widgets/player/sibling_tracks.dart @@ -144,7 +144,7 @@ class _SiblingTracksState extends State { return Column( children: [ Container( - color: Theme.of(context).colorScheme.surfaceContainerHigh, + color: Theme.of(context).colorScheme.surfaceContainer, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: TextField( controller: _searchTermController, diff --git a/lib/widgets/tracks/heart_button.dart b/lib/widgets/tracks/heart_button.dart new file mode 100644 index 0000000..6e8efe4 --- /dev/null +++ b/lib/widgets/tracks/heart_button.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rhythm_box/providers/spotify.dart'; + +class TrackHeartButton extends StatefulWidget { + final String trackId; + + const TrackHeartButton({super.key, required this.trackId}); + + @override + State createState() => _TrackHeartButtonState(); +} + +class _TrackHeartButtonState extends State { + late final SpotifyProvider _spotify = Get.find(); + + bool _isLoading = true; + + bool _isLiked = false; + + Future _pullHeart() async { + final res = await _spotify.api.tracks.me.containsOne(widget.trackId); + setState(() { + _isLiked = res; + _isLoading = false; + }); + } + + Future _toggleHeart() async { + if (_isLoading) return; + + setState(() => _isLoading = true); + + if (_isLiked) { + await _spotify.api.tracks.me.removeOne(widget.trackId); + _isLiked = false; + } else { + await _spotify.api.tracks.me.saveOne(widget.trackId); + _isLiked = true; + } + + setState(() => _isLoading = false); + } + + @override + void initState() { + super.initState(); + _pullHeart(); + } + + @override + Widget build(BuildContext context) { + return IconButton( + icon: AnimatedSwitcher( + switchInCurve: Curves.fastOutSlowIn, + switchOutCurve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + child: Icon( + _isLiked ? Icons.favorite_rounded : Icons.favorite_outline_rounded, + key: ValueKey(_isLiked), + color: _isLiked ? Colors.red[600] : null, + ), + ), + onPressed: _isLoading ? null : _toggleHeart, + ); + } +}