Skip to content

Commit 6ff4162

Browse files
authored
Merge pull request #69 from adeeteya/lyrics
Lyrics
2 parents bba1336 + d03be26 commit 6ff4162

10 files changed

Lines changed: 169 additions & 12 deletions

File tree

.github/workflows/pr-checker.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55

66
env:
7-
FLUTTER_VERSION: 3.35.6
7+
FLUTTER_VERSION: 3.35.7
88

99
jobs:
1010
lint:

lib/core/models/music_metadata.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ class MusicMetadata extends HiveObject {
100100
/// Rating of the track.
101101
final int rating;
102102

103+
final String? lyrics;
104+
103105
MusicMetadata({
104106
this.trackName,
105107
this.trackArtistNames,
@@ -118,6 +120,7 @@ class MusicMetadata extends HiveObject {
118120
this.originalSongIndex = 0,
119121
this.isOnDevice = true,
120122
this.rating = 0,
123+
this.lyrics,
121124
});
122125

123126
factory MusicMetadata.fromAudioMetadata(
@@ -148,6 +151,7 @@ class MusicMetadata extends HiveObject {
148151
filePath: audioMetadata.file.path,
149152
thumbnailPath: thumbnailPath,
150153
originalSongIndex: originalSongIndex,
154+
lyrics: audioMetadata.lyrics,
151155
);
152156
}
153157

@@ -173,6 +177,7 @@ class MusicMetadata extends HiveObject {
173177
originalSongIndex: map['originalSongIndex'],
174178
isOnDevice: map['isOnDevice'],
175179
rating: map['rating'],
180+
lyrics: map['lyrics'],
176181
);
177182

178183
Map<String, dynamic> toMap() => {
@@ -193,6 +198,7 @@ class MusicMetadata extends HiveObject {
193198
'originalSongIndex': originalSongIndex,
194199
'isOnDevice': isOnDevice,
195200
'rating': rating,
201+
'lyrics': lyrics,
196202
};
197203

198204
factory MusicMetadata.fromJson(String source) =>
@@ -218,6 +224,7 @@ class MusicMetadata extends HiveObject {
218224
int? originalSongIndex,
219225
bool? isOnDevice,
220226
int? rating,
227+
String? lyrics,
221228
}) {
222229
return MusicMetadata(
223230
trackName: trackName ?? this.trackName,
@@ -237,6 +244,7 @@ class MusicMetadata extends HiveObject {
237244
originalSongIndex: originalSongIndex ?? this.originalSongIndex,
238245
isOnDevice: isOnDevice ?? this.isOnDevice,
239246
rating: rating ?? this.rating,
247+
lyrics: lyrics ?? this.lyrics,
240248
);
241249
}
242250

@@ -376,7 +384,8 @@ class MusicMetadata extends HiveObject {
376384
trackDuration == other.trackDuration &&
377385
bitrate == other.bitrate &&
378386
filePath == other.filePath &&
379-
rating == other.rating;
387+
rating == other.rating &&
388+
lyrics == other.lyrics;
380389
}
381390

382391
@override
@@ -395,6 +404,7 @@ class MusicMetadata extends HiveObject {
395404
bitrate,
396405
filePath,
397406
rating,
407+
lyrics,
398408
);
399409
}
400410

lib/features/now_playing/screen/now_playing_screen.dart

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:classipod/core/widgets/empty_state_widget.dart';
88
import 'package:classipod/features/device/models/device_action.dart';
99
import 'package:classipod/features/device/services/device_buttons_service_provider.dart';
1010
import 'package:classipod/features/now_playing/provider/now_playing_details_provider.dart';
11+
import 'package:classipod/features/now_playing/widgets/lyrics_view.dart';
1112
import 'package:classipod/features/now_playing/widgets/now_playing_bottom_bar.dart';
1213
import 'package:classipod/features/now_playing/widgets/now_playing_widget.dart';
1314
import 'package:classipod/features/now_playing/widgets/rating_bar.dart';
@@ -27,6 +28,7 @@ enum _NowPlayingBottomBarPage {
2728
volumeBar,
2829
shuffleBar,
2930
ratingBar,
31+
lyrics,
3032
}
3133

3234
class NowPlayingScreen extends ConsumerStatefulWidget {
@@ -38,14 +40,22 @@ class NowPlayingScreen extends ConsumerStatefulWidget {
3840

3941
class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
4042
final PageController _bottomBarPageController = PageController();
43+
final ScrollController _lyricsScrollController = ScrollController();
4144
late Timer _longPressTimer;
4245
Timer _lastVolumeChangeTimer = Timer(Duration.zero, () {});
4346
bool _isShuffleEnabled = false;
4447
_NowPlayingBottomBarPage _bottomBarPage = _NowPlayingBottomBarPage.seekBar;
48+
int? _lastLyricsSongIndex;
4549

4650
String get routeName => Routes.nowPlaying.name;
4751

4852
Future<void> onSelectPressed() async {
53+
final String? lyrics = ref
54+
.read(nowPlayingDetailsProvider)
55+
.currentMetadata
56+
?.lyrics;
57+
final bool hasLyrics = lyrics != null && lyrics.trim().isNotEmpty;
58+
4959
if (_bottomBarPage == _NowPlayingBottomBarPage.seekBar) {
5060
await _bottomBarPageController.animateToPage(
5161
1,
@@ -77,6 +87,26 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
7787
_bottomBarPage = _NowPlayingBottomBarPage.ratingBar;
7888
});
7989
} else if (_bottomBarPage == _NowPlayingBottomBarPage.ratingBar) {
90+
if (hasLyrics) {
91+
await _bottomBarPageController.animateToPage(
92+
4,
93+
duration: const Duration(milliseconds: 300),
94+
curve: Curves.ease,
95+
);
96+
setState(() {
97+
_bottomBarPage = _NowPlayingBottomBarPage.lyrics;
98+
});
99+
} else {
100+
await _bottomBarPageController.animateToPage(
101+
0,
102+
duration: const Duration(milliseconds: 300),
103+
curve: Curves.ease,
104+
);
105+
setState(() {
106+
_bottomBarPage = _NowPlayingBottomBarPage.seekBar;
107+
});
108+
}
109+
} else if (_bottomBarPage == _NowPlayingBottomBarPage.lyrics) {
80110
await _bottomBarPageController.animateToPage(
81111
0,
82112
duration: const Duration(milliseconds: 300),
@@ -92,6 +122,28 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
92122
unawaited(context.pushNamed(Routes.nowPlayingMoreOptions.name));
93123
}
94124

125+
Future<void> scrollLyrics(double offsetChange) async {
126+
if (!_lyricsScrollController.hasClients) {
127+
return;
128+
}
129+
130+
final ScrollPosition position = _lyricsScrollController.position;
131+
final double targetOffset = (position.pixels + offsetChange).clamp(
132+
position.minScrollExtent,
133+
position.maxScrollExtent,
134+
);
135+
136+
if (targetOffset == position.pixels) {
137+
return;
138+
}
139+
140+
await _lyricsScrollController.animateTo(
141+
targetOffset,
142+
duration: const Duration(milliseconds: 200),
143+
curve: Curves.easeOut,
144+
);
145+
}
146+
95147
void startVolumeTimer() {
96148
if (_lastVolumeChangeTimer.isActive) {
97149
_lastVolumeChangeTimer.cancel();
@@ -122,6 +174,9 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
122174
.read(nowPlayingDetailsProvider.notifier)
123175
.increaseCurrentMetadataRating();
124176
return;
177+
} else if (_bottomBarPage == _NowPlayingBottomBarPage.lyrics) {
178+
await scrollLyrics(60);
179+
return;
125180
}
126181
setState(() {
127182
_bottomBarPage = _NowPlayingBottomBarPage.volumeBar;
@@ -146,6 +201,9 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
146201
.read(nowPlayingDetailsProvider.notifier)
147202
.decreaseCurrentMetadataRating();
148203
return;
204+
} else if (_bottomBarPage == _NowPlayingBottomBarPage.lyrics) {
205+
await scrollLyrics(-60);
206+
return;
149207
}
150208
setState(() {
151209
_bottomBarPage = _NowPlayingBottomBarPage.volumeBar;
@@ -203,6 +261,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
203261
void dispose() {
204262
_lastVolumeChangeTimer.cancel();
205263
_bottomBarPageController.dispose();
264+
_lyricsScrollController.dispose();
206265
super.dispose();
207266
}
208267

@@ -249,6 +308,34 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
249308
@override
250309
Widget build(BuildContext context) {
251310
final nowPlayingDetails = ref.watch(nowPlayingDetailsProvider);
311+
final String? lyrics = nowPlayingDetails.currentMetadata?.lyrics;
312+
final bool hasLyrics = lyrics != null && lyrics.trim().isNotEmpty;
313+
final int? currentLyricsSongIndex =
314+
nowPlayingDetails.currentMetadata?.originalSongIndex;
315+
316+
if (_lastLyricsSongIndex != currentLyricsSongIndex) {
317+
_lastLyricsSongIndex = currentLyricsSongIndex;
318+
WidgetsBinding.instance.addPostFrameCallback((_) {
319+
if (!_lyricsScrollController.hasClients) {
320+
return;
321+
}
322+
_lyricsScrollController.jumpTo(0);
323+
});
324+
}
325+
326+
if (!hasLyrics && _bottomBarPage == _NowPlayingBottomBarPage.lyrics) {
327+
WidgetsBinding.instance.addPostFrameCallback((_) {
328+
if (!mounted) {
329+
return;
330+
}
331+
if (_bottomBarPageController.hasClients) {
332+
_bottomBarPageController.jumpToPage(0);
333+
}
334+
setState(() {
335+
_bottomBarPage = _NowPlayingBottomBarPage.seekBar;
336+
});
337+
});
338+
}
252339

253340
ref.listen(deviceButtonsServiceProvider, deviceControlHandler);
254341

@@ -305,7 +392,23 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
305392
onLongPress: onSelectLongPress,
306393
child: Padding(
307394
padding: const EdgeInsets.only(left: 10),
308-
child: NowPlayingWidget(nowPlayingDetails: nowPlayingDetails),
395+
child: AnimatedSwitcher(
396+
duration: const Duration(milliseconds: 300),
397+
child:
398+
(_bottomBarPage == _NowPlayingBottomBarPage.lyrics &&
399+
hasLyrics)
400+
? LyricsView(
401+
key: ValueKey(
402+
'lyrics-view-${nowPlayingDetails.currentMetadata?.originalSongIndex ?? 0}',
403+
),
404+
lyrics: lyrics,
405+
scrollController: _lyricsScrollController,
406+
)
407+
: NowPlayingWidget(
408+
key: const ValueKey('now-playing-view'),
409+
nowPlayingDetails: nowPlayingDetails,
410+
),
411+
),
309412
),
310413
),
311414
),
@@ -360,6 +463,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
360463
.setCurrentMetadataRating(val ?? 0);
361464
},
362465
),
466+
if (hasLyrics) const SizedBox(height: 10),
363467
],
364468
),
365469
),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:flutter/cupertino.dart';
2+
3+
class LyricsView extends StatelessWidget {
4+
final String lyrics;
5+
final ScrollController scrollController;
6+
7+
const LyricsView({
8+
super.key,
9+
required this.lyrics,
10+
required this.scrollController,
11+
});
12+
13+
@override
14+
Widget build(BuildContext context) {
15+
return SizedBox(
16+
width: double.infinity,
17+
child: CupertinoScrollbar(
18+
controller: scrollController,
19+
child: SingleChildScrollView(
20+
controller: scrollController,
21+
padding: const EdgeInsets.only(right: 10),
22+
child: Text(
23+
lyrics,
24+
style: const TextStyle(
25+
fontSize: 16,
26+
height: 1.4,
27+
fontWeight: FontWeight.bold,
28+
),
29+
),
30+
),
31+
),
32+
);
33+
}
34+
}

lib/hive/hive_adapters.g.dart

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/hive/hive_adapters.g.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ types:
1313
index: 2
1414
MusicMetadata:
1515
typeId: 2
16-
nextIndex: 17
16+
nextIndex: 18
1717
fields:
1818
trackName:
1919
index: 0
@@ -49,6 +49,8 @@ types:
4949
index: 15
5050
rating:
5151
index: 16
52+
lyrics:
53+
index: 17
5254
ExcludeDirectoryModel:
5355
typeId: 3
5456
nextIndex: 2

pubspec.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ packages:
149149
dependency: "direct dev"
150150
description:
151151
name: build_runner
152-
sha256: "8cd45bdd6217138f4cfbaf6286c93f270ae4b3e2e281e69c904bd00cdf8aa626"
152+
sha256: a9461b8e586bf018dd4afd2e13b49b08c6a844a4b226c8d1d10f3a723cdd78c3
153153
url: "https://pub.dev"
154154
source: hosted
155-
version: "2.10.0"
155+
version: "2.10.1"
156156
built_collection:
157157
dependency: transitive
158158
description:
@@ -1578,4 +1578,4 @@ packages:
15781578
version: "2.1.0"
15791579
sdks:
15801580
dart: ">=3.9.2 <4.0.0"
1581-
flutter: "3.35.6"
1581+
flutter: "3.35.7"

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ version: 1.11.0+24
77

88
environment:
99
sdk: ^3.9.2
10-
flutter: 3.35.6
10+
flutter: 3.35.7
1111

1212
dependencies:
1313
audio_metadata_reader: ^1.4.2
@@ -50,7 +50,7 @@ dependencies:
5050
volume_controller: ^3.4.0
5151

5252
dev_dependencies:
53-
build_runner: ^2.10.0
53+
build_runner: ^2.10.1
5454
custom_lint: ^0.8.1
5555
flutter_lints: ^6.0.0
5656
flutter_test:

0 commit comments

Comments
 (0)