Show the list of musics
This commit is contained in:
parent
1ff98b7bbf
commit
702ce78a70
18
lib/api.dart
18
lib/api.dart
@ -1,7 +1,7 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:music_web_player/config.dart';
|
||||
|
||||
class MusicEntry {
|
||||
class MusicEntry implements Comparable<MusicEntry> {
|
||||
final int id;
|
||||
final String artist;
|
||||
final String title;
|
||||
@ -13,6 +13,16 @@ class MusicEntry {
|
||||
});
|
||||
|
||||
String get coverCacheKey => "cover-$id";
|
||||
|
||||
String get fullName => "$artist - $title";
|
||||
|
||||
@override
|
||||
String toString() => "MusicEntry($id, $artist, $title)";
|
||||
|
||||
@override
|
||||
int compareTo(MusicEntry other) {
|
||||
return fullName.compareTo(other.fullName);
|
||||
}
|
||||
}
|
||||
|
||||
typedef MusicsList = List<MusicEntry>;
|
||||
@ -27,11 +37,15 @@ class API {
|
||||
throw Exception("Request failed with status ${response.statusCode} !");
|
||||
}
|
||||
|
||||
return response.data
|
||||
final MusicsList list = response.data
|
||||
.map((r) =>
|
||||
MusicEntry(id: r["id"], artist: r["artist"], title: r["title"]))
|
||||
.toList()
|
||||
.cast<MusicEntry>();
|
||||
|
||||
list.sort();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ extension DurationExt on Duration {
|
||||
}
|
||||
}
|
||||
|
||||
const double playlistWidth = 300;
|
||||
|
||||
class MusicPlayer extends StatefulWidget {
|
||||
final MusicsList musicsList;
|
||||
|
||||
@ -38,19 +40,21 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
||||
|
||||
Duration? get _position => _videoPlayerController?.value.position;
|
||||
|
||||
final List<MusicEntry> stack = [];
|
||||
final List<MusicEntry> _stack = [];
|
||||
int currMusicPos = 0;
|
||||
|
||||
var _showPlaylist = true;
|
||||
|
||||
MusicEntry get currMusic {
|
||||
if (currMusicPos < 0) currMusicPos = 0;
|
||||
|
||||
// Automatically choose next music if required
|
||||
if (currMusicPos >= stack.length) {
|
||||
if (currMusicPos >= _stack.length) {
|
||||
var nextId = rng.nextInt(widget.musicsList.length);
|
||||
stack.add(widget.musicsList[nextId]);
|
||||
_stack.add(widget.musicsList[nextId]);
|
||||
}
|
||||
|
||||
return stack[currMusicPos];
|
||||
return _stack[currMusicPos];
|
||||
}
|
||||
|
||||
Future<void> _play() async {
|
||||
@ -101,6 +105,12 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
||||
await _play();
|
||||
}
|
||||
|
||||
void _playMusic(MusicEntry music) async {
|
||||
_stack.insert(currMusicPos + 1, music);
|
||||
_playNext();
|
||||
print(_stack);
|
||||
}
|
||||
|
||||
void _updatePosition(Duration d) => _videoPlayerController?.seekTo(d);
|
||||
|
||||
@override
|
||||
@ -112,108 +122,156 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Stack(
|
||||
children: [
|
||||
// Background image
|
||||
CoverImage(
|
||||
music: currMusic,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
builder: (context, constraints) {
|
||||
final mainAreaWidth =
|
||||
constraints.maxWidth - (_showPlaylist ? playlistWidth : 0);
|
||||
|
||||
ClipRRect(
|
||||
// Clip it cleanly.
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.8),
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
return fluent.Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: mainAreaWidth,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Background image
|
||||
CoverImage(
|
||||
music: currMusic,
|
||||
width: mainAreaWidth,
|
||||
height: constraints.maxHeight,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
|
||||
// Blur background image
|
||||
ClipRRect(
|
||||
// Clip it cleanly.
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.8),
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: mainAreaWidth,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
fluent.SizedBox(
|
||||
width: mainAreaWidth,
|
||||
child: _buildPlayerWidget(),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 10, right: 10, child: _buildToggleListButton()),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
|
||||
_buildCenter(),
|
||||
],
|
||||
),
|
||||
// Playlist
|
||||
_showPlaylist
|
||||
? SizedBox(
|
||||
width: playlistWidth,
|
||||
child: _buildPlaylistPane(),
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCenter() {
|
||||
return fluent.Center(
|
||||
child: SizedBox(
|
||||
width: 250,
|
||||
child: Column(
|
||||
mainAxisAlignment: fluent.MainAxisAlignment.center,
|
||||
crossAxisAlignment: fluent.CrossAxisAlignment.center,
|
||||
children: [
|
||||
Material(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(18.0),
|
||||
Widget _buildPlayerWidget() => fluent.Center(
|
||||
child: SizedBox(
|
||||
width: 250,
|
||||
child: Column(
|
||||
mainAxisAlignment: fluent.MainAxisAlignment.center,
|
||||
crossAxisAlignment: fluent.CrossAxisAlignment.center,
|
||||
children: [
|
||||
Material(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(18.0),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: CoverImage(
|
||||
width: 250,
|
||||
height: 250,
|
||||
music: currMusic,
|
||||
fit: BoxFit.cover,
|
||||
backgroundColor: Colors.black12,
|
||||
icon:
|
||||
const Icon(FluentIcons.music_note_2_24_regular, size: 90),
|
||||
),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: CoverImage(
|
||||
width: 250,
|
||||
height: 250,
|
||||
music: currMusic,
|
||||
fit: BoxFit.cover,
|
||||
backgroundColor: Colors.black12,
|
||||
icon: const Icon(FluentIcons.music_note_2_24_regular, size: 90),
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
currMusic.title,
|
||||
style: const TextStyle(fontSize: 22),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
currMusic.title,
|
||||
style: const TextStyle(fontSize: 22),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(currMusic.artist, textAlign: TextAlign.center),
|
||||
const fluent.SizedBox(height: 40),
|
||||
fluent.Row(
|
||||
children: [
|
||||
DurationText(_position),
|
||||
const SizedBox(width: 15),
|
||||
Flexible(
|
||||
child: fluent.Slider(
|
||||
max: _duration?.inSeconds as double? ?? 0,
|
||||
value: _position?.inSeconds as double? ?? 0,
|
||||
onChanged: (d) =>
|
||||
_updatePosition(Duration(seconds: d.toInt())),
|
||||
label: _position?.formatted,
|
||||
const SizedBox(height: 20),
|
||||
Text(currMusic.artist, textAlign: TextAlign.center),
|
||||
const fluent.SizedBox(height: 40),
|
||||
fluent.Row(
|
||||
children: [
|
||||
DurationText(_position),
|
||||
const SizedBox(width: 15),
|
||||
Flexible(
|
||||
child: fluent.Slider(
|
||||
max: _duration?.inSeconds as double? ?? 0,
|
||||
value: _position?.inSeconds as double? ?? 0,
|
||||
onChanged: (d) =>
|
||||
_updatePosition(Duration(seconds: d.toInt())),
|
||||
label: _position?.formatted,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
DurationText(_duration),
|
||||
],
|
||||
),
|
||||
const fluent.SizedBox(height: 40),
|
||||
fluent.Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const PlayerIcon(fluent.FluentIcons.previous),
|
||||
onPressed: currMusicPos == 0 ? null : _playPrevious,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: PlayerIcon(_isPlaying
|
||||
? fluent.FluentIcons.pause
|
||||
: fluent.FluentIcons.play),
|
||||
onPressed: _isPlaying ? _pause : _play,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const PlayerIcon(fluent.FluentIcons.next),
|
||||
onPressed: _playNext,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
const SizedBox(width: 15),
|
||||
DurationText(_duration),
|
||||
],
|
||||
),
|
||||
const fluent.SizedBox(height: 40),
|
||||
fluent.Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const PlayerIcon(fluent.FluentIcons.previous),
|
||||
onPressed: currMusicPos == 0 ? null : _playPrevious,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: PlayerIcon(_isPlaying
|
||||
? fluent.FluentIcons.pause
|
||||
: fluent.FluentIcons.play),
|
||||
onPressed: _isPlaying ? _pause : _play,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const PlayerIcon(fluent.FluentIcons.next),
|
||||
onPressed: _playNext,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildToggleListButton() => fluent.ToggleButton(
|
||||
checked: _showPlaylist,
|
||||
onChanged: (s) => setState(() => _showPlaylist = s),
|
||||
child: const fluent.Icon(fluent.FluentIcons.playlist_music),
|
||||
);
|
||||
|
||||
Widget _buildPlaylistPane() {
|
||||
return ListView.builder(
|
||||
itemBuilder: (c, i) {
|
||||
final music = widget.musicsList[i];
|
||||
return ListTile(
|
||||
title: Text(music.title),
|
||||
subtitle: Text(music.artist),
|
||||
selected: currMusic == music,
|
||||
onTap: () => _playMusic(music),
|
||||
);
|
||||
},
|
||||
itemCount: widget.musicsList.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user