Compare commits

..

No commits in common. "d7b63b754f30c653845461f3479abf6655a58200" and "1ff98b7bbf238033254c530d29cf95083a5470f8" have entirely different histories.

3 changed files with 97 additions and 211 deletions

View File

@ -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;
} }
} }

View File

@ -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,
),
),
],
); );
} }
} }

View File

@ -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,
),
), ),
); );
} }