💫 Optimize lyrics
This commit is contained in:
parent
7e95c167ef
commit
ef40c2ffe4
@ -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(
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
73
lib/widgets/tracks/heart_button.dart
Normal file
73
lib/widgets/tracks/heart_button.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user