Skip to content

Commit c7aae4a

Browse files
made the chapter play screen work
1 parent c5f85e8 commit c7aae4a

File tree

4 files changed

+299
-71
lines changed

4 files changed

+299
-71
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
1212
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
1313

14+
<!-- Remove usesCleartextTraffic while going for Production -->
1415
<application
1516
android:label="Resonate"
1617
android:icon="@mipmap/ic_launcher"
17-
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
18+
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
19+
android:usesCleartextTraffic="true">
1820

1921
<!-- UCropActivity - For image cropper -->
2022
<activity
Lines changed: 230 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import 'package:audioplayers/audioplayers.dart';
12
import 'package:flutter/material.dart';
3+
import 'package:flutter_lyric/lyrics_reader.dart';
4+
import 'package:flutter_lyric/lyrics_reader_model.dart';
25
import 'package:resonate/models/chapter.dart';
36

47
class ChapterPlayScreen extends StatefulWidget {
@@ -15,13 +18,36 @@ class ChapterPlayScreen extends StatefulWidget {
1518

1619
class ChapterPlayScreenState extends State<ChapterPlayScreen> {
1720
int currentPage = 0;
18-
double currentTime = 0;
21+
int lyricProgress = 0;
22+
double sliderProgress = 0;
23+
bool isPlaying = false;
24+
AudioPlayer? audioPlayer;
25+
late Duration chapterDuration;
26+
late LyricsReaderModel lyricModel;
27+
UINetease lyricUI = UINetease();
28+
29+
@override
30+
void initState() {
31+
super.initState();
32+
chapterDuration = getChapterDurationFromString(widget.chapter.playDuration);
33+
lyricModel = LyricsModelBuilder.create()
34+
.bindLyricToMain(widget.chapter.lyrics)
35+
.getModel();
36+
}
37+
38+
Duration getChapterDurationFromString(String songLength) {
39+
List<String> parts = songLength.split(':');
40+
int minutes = int.parse(parts[0]);
41+
int seconds = int.parse(parts[1]);
42+
return Duration(minutes: minutes, seconds: seconds);
43+
}
1944

2045
@override
2146
Widget build(BuildContext context) {
2247
return Scaffold(
2348
body: SafeArea(
2449
child: Column(
50+
crossAxisAlignment: CrossAxisAlignment.start,
2551
children: [
2652
Container(
2753
height: MediaQuery.of(context).size.height * 0.65,
@@ -60,34 +86,66 @@ class ChapterPlayScreenState extends State<ChapterPlayScreen> {
6086
const SizedBox(height: 20),
6187
Text(
6288
widget.chapter.title,
63-
style: const TextStyle(
89+
style: TextStyle(
6490
fontSize: 26,
6591
fontWeight: FontWeight.bold,
66-
color: Colors.black87,
92+
color: widget.chapter.tintColor.computeLuminance() <
93+
0.5
94+
? Colors.white
95+
: Colors.black87,
6796
),
6897
),
6998
],
7099
),
71100
),
72101

73102
// Lyrics Screen
74-
Container(
75-
padding: const EdgeInsets.all(16.0),
76-
child: SingleChildScrollView(
103+
LyricsReader(
104+
padding: const EdgeInsets.symmetric(horizontal: 16),
105+
model: lyricModel,
106+
position: lyricProgress,
107+
lyricUi: lyricUI,
108+
playing: isPlaying,
109+
size: Size(double.infinity,
110+
MediaQuery.of(context).size.height * 65),
111+
emptyBuilder: () => Center(
77112
child: Text(
78-
widget.chapter.lyrics,
79-
style: const TextStyle(
80-
fontSize: 18,
81-
color: Colors.black87,
82-
height: 1.5,
83-
),
113+
"No lyrics",
114+
style: UINetease().getOtherMainTextStyle(),
84115
),
85116
),
86-
),
117+
selectLineBuilder: (progress, confirm) {
118+
return Row(
119+
children: [
120+
IconButton(
121+
onPressed: () {
122+
confirm.call();
123+
setState(() {
124+
audioPlayer
125+
?.seek(Duration(milliseconds: progress));
126+
});
127+
},
128+
icon: Icon(Icons.play_arrow,
129+
color:
130+
Theme.of(context).colorScheme.primary)),
131+
Expanded(
132+
child: Container(
133+
decoration: BoxDecoration(
134+
color: Theme.of(context).colorScheme.primary),
135+
height: 1,
136+
width: double.infinity,
137+
),
138+
),
139+
],
140+
);
141+
},
142+
)
87143
],
88144
),
89145
),
90-
146+
const SizedBox(
147+
height: 10,
148+
),
91149
// Page Indicator
92150
Row(
93151
mainAxisAlignment: MainAxisAlignment.center,
@@ -100,12 +158,12 @@ class ChapterPlayScreenState extends State<ChapterPlayScreen> {
100158
decoration: BoxDecoration(
101159
shape: BoxShape.circle,
102160
color: currentPage == index
103-
? Colors.greenAccent
161+
? widget.chapter.tintColor
104162
: Colors.grey.shade400,
105163
boxShadow: currentPage == index
106164
? [
107165
BoxShadow(
108-
color: Colors.greenAccent.withOpacity(0.5),
166+
color: widget.chapter.tintColor.withOpacity(0.5),
109167
blurRadius: 4,
110168
),
111169
]
@@ -114,66 +172,156 @@ class ChapterPlayScreenState extends State<ChapterPlayScreen> {
114172
),
115173
),
116174
),
117-
175+
const SizedBox(height: 20),
118176
// Play Controls and Progress Bar
119-
Padding(
120-
padding: const EdgeInsets.all(16.0),
121-
child: Column(
122-
children: [
123-
// Redesigned Progress Bar
124-
Slider(
125-
value: currentTime,
126-
onChanged: (value) {
127-
setState(() {
128-
currentTime = value;
129-
});
130-
},
131-
min: 0,
132-
max:
133-
double.parse(widget.chapter.playDuration.split(':')[0]),
134-
activeColor: Colors.greenAccent,
135-
inactiveColor: Colors.grey.shade300,
136-
),
137-
Row(
138-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
177+
Expanded(
178+
child: SingleChildScrollView(
179+
child: Padding(
180+
padding: const EdgeInsets.symmetric(horizontal: 16.0),
181+
child: Column(
182+
crossAxisAlignment: CrossAxisAlignment.start,
139183
children: [
140-
Text("${currentTime.toStringAsFixed(0)} min"),
141-
Text("${widget.chapter.playDuration} min"),
142-
],
143-
),
144-
],
145-
),
146-
),
184+
Text(
185+
widget.chapter.title,
186+
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
187+
color: Theme.of(context).colorScheme.onSurface,
188+
fontWeight: FontWeight.bold,
189+
fontSize: 24,
190+
fontStyle: FontStyle.normal,
191+
fontFamily: 'Inter',
192+
),
193+
),
194+
const SizedBox(
195+
height: 5,
196+
),
197+
// Redesigned Progress Bar
198+
Slider(
199+
value: sliderProgress,
200+
onChanged: (value) {
201+
setState(() {
202+
sliderProgress = value;
203+
});
204+
},
205+
onChangeEnd: (double value) {
206+
setState(() {
207+
lyricProgress = value.toInt();
208+
});
209+
audioPlayer
210+
?.seek(Duration(milliseconds: value.toInt()));
211+
},
212+
min: 0,
213+
max: chapterDuration.inMilliseconds.toDouble(),
214+
activeColor: widget.chapter.tintColor,
215+
inactiveColor: Colors.grey.shade300,
216+
),
217+
Stack(
218+
alignment: Alignment.topCenter,
219+
children: [
220+
Row(
221+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
222+
children: [
223+
Text("${formatDuration(sliderProgress)} min"),
224+
Text("${widget.chapter.playDuration} min"),
225+
],
226+
),
227+
IconButton(
228+
iconSize: 34,
229+
style: IconButton.styleFrom(
230+
backgroundColor:
231+
Theme.of(context).colorScheme.primary),
232+
onPressed: () {
233+
if (isPlaying) {
234+
audioPlayer?.pause();
235+
} else {
236+
if (audioPlayer == null) {
237+
audioPlayer = AudioPlayer()
238+
..play(UrlSource(
239+
widget.chapter.audioFileUrl));
240+
setState(() {
241+
isPlaying = true;
242+
});
147243

148-
// Title and About Button
149-
Padding(
150-
padding: const EdgeInsets.symmetric(horizontal: 16.0),
151-
child: Column(
152-
crossAxisAlignment: CrossAxisAlignment.start,
153-
mainAxisAlignment: MainAxisAlignment.start,
154-
children: [
155-
Text(
156-
widget.chapter.title,
157-
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
158-
color: Theme.of(context).colorScheme.onSurface,
159-
fontWeight: FontWeight.bold,
160-
fontSize: 24,
161-
fontStyle: FontStyle.normal,
162-
fontFamily: 'Inter',
244+
audioPlayer?.onPositionChanged
245+
.listen((Duration event) {
246+
setState(() {
247+
sliderProgress =
248+
event.inMilliseconds.toDouble();
249+
lyricProgress = event.inMilliseconds;
250+
});
251+
});
252+
253+
audioPlayer?.onPlayerStateChanged
254+
.listen((PlayerState state) {
255+
setState(() {
256+
isPlaying =
257+
state == PlayerState.playing;
258+
});
259+
});
260+
} else {
261+
audioPlayer?.resume();
262+
}
263+
}
264+
},
265+
icon: Icon(
266+
isPlaying ? Icons.pause : Icons.play_arrow)),
267+
],
268+
),
269+
270+
const SizedBox(height: 40),
271+
Container(
272+
padding: const EdgeInsets.all(10),
273+
decoration: BoxDecoration(
274+
color: const Color.fromARGB(106, 40, 39, 39),
275+
borderRadius: BorderRadius.circular(10),
163276
),
164-
),
165-
const SizedBox(height: 8),
166-
Text(
167-
widget.chapter.description,
168-
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
169-
color: Theme.of(context).colorScheme.onSurface,
170-
fontWeight: FontWeight.w500,
171-
fontSize: 17,
172-
fontStyle: FontStyle.normal,
173-
fontFamily: 'Inter',
277+
width: double.infinity,
278+
child: Column(
279+
crossAxisAlignment: CrossAxisAlignment.start,
280+
children: [
281+
Text(
282+
"About",
283+
style: Theme.of(context)
284+
.textTheme
285+
.bodyMedium!
286+
.copyWith(
287+
color:
288+
Theme.of(context).colorScheme.onSurface,
289+
fontWeight: FontWeight.w500,
290+
fontSize: 17,
291+
fontStyle: FontStyle.normal,
292+
fontFamily: 'Inter',
293+
),
294+
),
295+
const SizedBox(
296+
height: 10,
297+
),
298+
Padding(
299+
padding:
300+
const EdgeInsets.symmetric(horizontal: 5.0),
301+
child: Text(
302+
widget.chapter.description,
303+
style: Theme.of(context)
304+
.textTheme
305+
.bodyMedium!
306+
.copyWith(
307+
color: Theme.of(context)
308+
.colorScheme
309+
.onSurface,
310+
fontWeight: FontWeight.w300,
311+
fontSize: 16,
312+
fontStyle: FontStyle.normal,
313+
fontFamily: 'Inter',
314+
),
315+
),
316+
),
317+
const SizedBox(height: 5)
318+
],
174319
),
320+
),
321+
const SizedBox(height: 20)
322+
],
175323
),
176-
],
324+
),
177325
),
178326
),
179327
],
@@ -182,3 +330,15 @@ class ChapterPlayScreenState extends State<ChapterPlayScreen> {
182330
);
183331
}
184332
}
333+
334+
// Helper function to format Duration into minutes:seconds format
335+
String formatDuration(double milliseconds) {
336+
double totalSeconds = milliseconds / 1000; // Convert milliseconds to seconds
337+
int minutes = (totalSeconds / 60).floor();
338+
int remainingSeconds = (totalSeconds % 60).floor();
339+
340+
String twoDigits(int n) => n.toString().padLeft(2, "0");
341+
String secondsStr = twoDigits(remainingSeconds);
342+
343+
return "$minutes:$secondsStr";
344+
}

0 commit comments

Comments
 (0)