Compare commits
No commits in common. "d7b63b754f30c653845461f3479abf6655a58200" and "1ff98b7bbf238033254c530d29cf95083a5470f8" have entirely different histories.
d7b63b754f
...
1ff98b7bbf
18
lib/api.dart
18
lib/api.dart
@ -1,7 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:music_web_player/config.dart';
|
import 'package:music_web_player/config.dart';
|
||||||
|
|
||||||
class MusicEntry implements Comparable<MusicEntry> {
|
class MusicEntry {
|
||||||
final int id;
|
final int id;
|
||||||
final String artist;
|
final String artist;
|
||||||
final String title;
|
final String title;
|
||||||
@ -13,16 +13,6 @@ class MusicEntry implements Comparable<MusicEntry> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
String get coverCacheKey => "cover-$id";
|
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>;
|
typedef MusicsList = List<MusicEntry>;
|
||||||
@ -37,15 +27,11 @@ class API {
|
|||||||
throw Exception("Request failed with status ${response.statusCode} !");
|
throw Exception("Request failed with status ${response.statusCode} !");
|
||||||
}
|
}
|
||||||
|
|
||||||
final MusicsList list = response.data
|
return response.data
|
||||||
.map((r) =>
|
.map((r) =>
|
||||||
MusicEntry(id: r["id"], artist: r["artist"], title: r["title"]))
|
MusicEntry(id: r["id"], artist: r["artist"], title: r["title"]))
|
||||||
.toList()
|
.toList()
|
||||||
.cast<MusicEntry>();
|
.cast<MusicEntry>();
|
||||||
|
|
||||||
list.sort();
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,6 @@ extension DurationExt on Duration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const double playlistWidth = 300;
|
|
||||||
|
|
||||||
class MusicPlayer extends StatefulWidget {
|
class MusicPlayer extends StatefulWidget {
|
||||||
final MusicsList musicsList;
|
final MusicsList musicsList;
|
||||||
|
|
||||||
@ -40,24 +38,19 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
|||||||
|
|
||||||
Duration? get _position => _videoPlayerController?.value.position;
|
Duration? get _position => _videoPlayerController?.value.position;
|
||||||
|
|
||||||
final List<MusicEntry> _stack = [];
|
final List<MusicEntry> stack = [];
|
||||||
int currMusicPos = 0;
|
int currMusicPos = 0;
|
||||||
|
|
||||||
var _showPlaylist = false;
|
|
||||||
|
|
||||||
final _filterController = fluent.TextEditingController();
|
|
||||||
MusicsList? _filteredList;
|
|
||||||
|
|
||||||
MusicEntry get currMusic {
|
MusicEntry get currMusic {
|
||||||
if (currMusicPos < 0) currMusicPos = 0;
|
if (currMusicPos < 0) currMusicPos = 0;
|
||||||
|
|
||||||
// Automatically choose next music if required
|
// Automatically choose next music if required
|
||||||
if (currMusicPos >= _stack.length) {
|
if (currMusicPos >= stack.length) {
|
||||||
var nextId = rng.nextInt(widget.musicsList.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 {
|
Future<void> _play() async {
|
||||||
@ -108,33 +101,8 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
|||||||
await _play();
|
await _play();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _playMusic(MusicEntry music) async {
|
|
||||||
_stack.insert(currMusicPos + 1, music);
|
|
||||||
_playNext();
|
|
||||||
print(_stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updatePosition(Duration d) => _videoPlayerController?.seekTo(d);
|
void _updatePosition(Duration d) => _videoPlayerController?.seekTo(d);
|
||||||
|
|
||||||
void _refreshFilteredList() {
|
|
||||||
final value = _filterController.value.text.toLowerCase();
|
|
||||||
if (value.isEmpty) {
|
|
||||||
setState(() => _filteredList = null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_filteredList = widget.musicsList
|
|
||||||
.where((m) => m.fullName.toLowerCase().contains(value))
|
|
||||||
.toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _clearFilter() {
|
|
||||||
_filterController.text = "";
|
|
||||||
_refreshFilteredList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -144,174 +112,108 @@ class _MusicPlayerState extends State<MusicPlayer> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) => Stack(
|
||||||
final mainAreaWidth =
|
children: [
|
||||||
constraints.maxWidth - (_showPlaylist ? playlistWidth : 0);
|
// Background image
|
||||||
|
CoverImage(
|
||||||
|
music: currMusic,
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
height: constraints.maxHeight,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
|
||||||
return fluent.Row(
|
ClipRRect(
|
||||||
children: [
|
// Clip it cleanly.
|
||||||
SizedBox(
|
child: BackdropFilter(
|
||||||
width: mainAreaWidth,
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||||
child: Stack(
|
child: Container(
|
||||||
children: [
|
color: Colors.black.withOpacity(0.8),
|
||||||
// Background image
|
alignment: Alignment.center,
|
||||||
CoverImage(
|
child: SizedBox(
|
||||||
music: currMusic,
|
width: constraints.maxWidth,
|
||||||
width: mainAreaWidth,
|
height: constraints.maxHeight,
|
||||||
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()),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)),
|
||||||
|
|
||||||
// Playlist
|
_buildCenter(),
|
||||||
_showPlaylist
|
],
|
||||||
? SizedBox(
|
),
|
||||||
width: playlistWidth,
|
|
||||||
height: constraints.maxHeight,
|
|
||||||
child: _buildPlaylistPane(),
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPlayerWidget() => fluent.Center(
|
Widget _buildCenter() {
|
||||||
child: SizedBox(
|
return fluent.Center(
|
||||||
width: 250,
|
child: SizedBox(
|
||||||
child: Column(
|
width: 250,
|
||||||
mainAxisAlignment: fluent.MainAxisAlignment.center,
|
child: Column(
|
||||||
crossAxisAlignment: fluent.CrossAxisAlignment.center,
|
mainAxisAlignment: fluent.MainAxisAlignment.center,
|
||||||
children: [
|
crossAxisAlignment: fluent.CrossAxisAlignment.center,
|
||||||
Material(
|
children: [
|
||||||
borderRadius: const BorderRadius.all(
|
Material(
|
||||||
Radius.circular(18.0),
|
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),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
clipBehavior: Clip.hardEdge,
|
||||||
Text(
|
child: CoverImage(
|
||||||
currMusic.title,
|
width: 250,
|
||||||
style: const TextStyle(fontSize: 22),
|
height: 250,
|
||||||
textAlign: TextAlign.center,
|
music: currMusic,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
backgroundColor: Colors.black12,
|
||||||
|
icon: const Icon(FluentIcons.music_note_2_24_regular, size: 90),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Text(currMusic.artist, textAlign: TextAlign.center),
|
const SizedBox(height: 40),
|
||||||
const fluent.SizedBox(height: 40),
|
Text(
|
||||||
fluent.Row(
|
currMusic.title,
|
||||||
children: [
|
style: const TextStyle(fontSize: 22),
|
||||||
DurationText(_position),
|
textAlign: TextAlign.center,
|
||||||
const SizedBox(width: 15),
|
),
|
||||||
Flexible(
|
const SizedBox(height: 20),
|
||||||
child: fluent.Slider(
|
Text(currMusic.artist, textAlign: TextAlign.center),
|
||||||
max: _duration?.inSeconds as double? ?? 0,
|
const fluent.SizedBox(height: 40),
|
||||||
value: _position?.inSeconds as double? ?? 0,
|
fluent.Row(
|
||||||
onChanged: (d) =>
|
children: [
|
||||||
_updatePosition(Duration(seconds: d.toInt())),
|
DurationText(_position),
|
||||||
label: _position?.formatted,
|
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildToggleListButton() => fluent.ToggleButton(
|
|
||||||
checked: _showPlaylist,
|
|
||||||
onChanged: (s) => setState(() => _showPlaylist = s),
|
|
||||||
child: const fluent.Icon(fluent.FluentIcons.playlist_music),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildPlaylistPane() {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
fluent.TextBox(
|
|
||||||
controller: _filterController,
|
|
||||||
placeholder: "Filter list...",
|
|
||||||
onChanged: (s) => _refreshFilteredList(),
|
|
||||||
suffix: _filterController.text.isEmpty
|
|
||||||
? null
|
|
||||||
: fluent.IconButton(
|
|
||||||
icon: const Icon(fluent.FluentIcons.clear),
|
|
||||||
onPressed: _clearFilter,
|
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Flexible(
|
),
|
||||||
child: ListView.builder(
|
|
||||||
itemBuilder: (c, i) {
|
|
||||||
final music = (_filteredList ?? widget.musicsList)[i];
|
|
||||||
return ListTile(
|
|
||||||
title: Text(music.title),
|
|
||||||
subtitle: Text(music.artist),
|
|
||||||
selected: currMusic == music,
|
|
||||||
onTap: () => _playMusic(music),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: (_filteredList ?? widget.musicsList).length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,7 @@ class PlayerApp extends StatelessWidget {
|
|||||||
home: fluent.FluentTheme(
|
home: fluent.FluentTheme(
|
||||||
child: const AppHome(),
|
child: const AppHome(),
|
||||||
data: fluent.ThemeData(
|
data: fluent.ThemeData(
|
||||||
iconTheme: const IconThemeData(color: Colors.white),
|
iconTheme: const IconThemeData(color: Colors.white)),
|
||||||
brightness: fluent.Brightness.dark,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user