💫 Optimize lyrics

This commit is contained in:
LittleSheep 2024-08-29 19:10:54 +08:00
parent 7e95c167ef
commit ef40c2ffe4
4 changed files with 148 additions and 30 deletions

View File

@ -17,6 +17,7 @@ import 'package:rhythm_box/services/duration.dart';
import 'package:rhythm_box/widgets/auto_cache_image.dart'; import 'package:rhythm_box/widgets/auto_cache_image.dart';
import 'package:rhythm_box/services/audio_services/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/lyrics/synced_lyrics.dart';
import 'package:rhythm_box/widgets/tracks/heart_button.dart';
import 'package:rhythm_box/widgets/tracks/querying_track_info.dart'; import 'package:rhythm_box/widgets/tracks/querying_track_info.dart';
class PlayerScreen extends StatefulWidget { class PlayerScreen extends StatefulWidget {
@ -108,22 +109,36 @@ class _PlayerScreenState extends State<PlayerScreen> {
), ),
const Gap(24), const Gap(24),
Obx( Obx(
() => Text( () => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_playback.state.value.activeTrack?.name ?? _playback.state.value.activeTrack?.name ??
'Not playing', 'Not playing',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center, textAlign: TextAlign.left,
), ),
), Text(
Obx(
() => Text(
_playback.state.value.activeTrack?.artists _playback.state.value.activeTrack?.artists
?.asString() ?? ?.asString() ??
'No author', 'No author',
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center, textAlign: TextAlign.left,
), ),
],
),
),
if (_playback.state.value.activeTrack != null)
TrackHeartButton(
trackId: _playback.state.value.activeTrack!.id!,
),
],
).paddingSymmetric(horizontal: 32),
), ),
const Gap(24), const Gap(24),
Obx( Obx(

View File

@ -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/audio_player/audio_player.dart';
import 'package:rhythm_box/services/lyrics/model.dart'; import 'package:rhythm_box/services/lyrics/model.dart';
import 'package:rhythm_box/services/lyrics/provider.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'; import 'package:scroll_to_index/scroll_to_index.dart';
class SyncedLyrics extends StatefulWidget { class SyncedLyrics extends StatefulWidget {
@ -45,7 +46,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
List<StreamSubscription>? _subscriptions; List<StreamSubscription>? _subscriptions;
Color get _unFocusColor => Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75); Theme.of(context).colorScheme.onSurface.withOpacity(0.5);
@override @override
void initState() { void initState() {
@ -79,6 +80,12 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
return CustomScrollView( return CustomScrollView(
controller: _autoScrollController, controller: _autoScrollController,
slivers: [ slivers: [
if (_lyric == null)
const SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
),
if (_lyric != null && _lyric!.lyrics.isNotEmpty) if (_lyric != null && _lyric!.lyrics.isNotEmpty)
SliverList.builder( SliverList.builder(
itemCount: _lyric!.lyrics.length, itemCount: _lyric!.lyrics.length,
@ -113,8 +120,9 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
) )
: Padding( : Padding(
padding: idx == _lyric!.lyrics.length - 1 padding: idx == _lyric!.lyrics.length - 1
? const EdgeInsets.all(8.0).copyWith(bottom: 80) ? const EdgeInsets.symmetric(vertical: 8)
: const EdgeInsets.all(8.0), .copyWith(bottom: 80)
: const EdgeInsets.symmetric(vertical: 8),
child: AnimatedDefaultTextStyle( child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
style: TextStyle( style: TextStyle(
@ -137,26 +145,48 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
audioPlayer.seek(time); audioPlayer.seek(time);
}, },
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Text( return AnimatedDefaultTextStyle(
lyricSlice.text,
style: TextStyle( style: TextStyle(
fontSize: isActive ? 20 : 16,
color: isActive color: isActive
? Theme.of(context).colorScheme.onSurface ? Theme.of(context).colorScheme.onSurface
: _unFocusColor, : _unFocusColor,
fontSize: 16,
), ),
).animate(target: isActive ? 1 : 0).scale( duration: 500.ms,
duration: 300.ms,
begin: const Offset(1, 1),
end: const Offset(1.25, 1.25),
curve: Curves.easeInOut, curve: Curves.easeInOut,
child: Text(
lyricSlice.text,
textAlign:
MediaQuery.of(context).size.width >= 720
? TextAlign.center
: TextAlign.left,
),
); );
}).paddingSymmetric(horizontal: 12), }).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,
),
],
),
),
), ),
], ],
); );

View File

@ -144,7 +144,7 @@ class _SiblingTracksState extends State<SiblingTracks> {
return Column( return Column(
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainer,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: TextField( child: TextField(
controller: _searchTermController, controller: _searchTermController,

View File

@ -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<TrackHeartButton> createState() => _TrackHeartButtonState();
}
class _TrackHeartButtonState extends State<TrackHeartButton> {
late final SpotifyProvider _spotify = Get.find();
bool _isLoading = true;
bool _isLiked = false;
Future<void> _pullHeart() async {
final res = await _spotify.api.tracks.me.containsOne(widget.trackId);
setState(() {
_isLiked = res;
_isLoading = false;
});
}
Future<void> _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,
);
}
}