You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

615 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:music_player_miao/common_widget/app_data.dart';
import 'package:music_player_miao/models/universal_bean.dart';
import '../../view_model/home_view_model.dart';
import '../api/api_music_likes.dart';
import '../api/api_collection.dart';
import '../common/download_manager.dart';
import '../common_widget/Song_widegt.dart';
class MusicView extends StatefulWidget {
final List<Song> songList;
final int initialSongIndex;
const MusicView({
super.key,
required this.songList,
required this.initialSongIndex
});
@override
State<MusicView> createState() => _MusicViewState();
}
class _MusicViewState extends State<MusicView> {
final homeVM = Get.put(HomeViewModel());
bool _isDisposed = false;
AppData appData = AppData();
late int currentSongIndex;
late AudioPlayer _audioPlayer;
final downloadManager = Get.put(DownloadManager());
// Stream values
Duration _duration = Duration.zero;
Duration _position = Duration.zero;
// Current song info
late String artistName;
late String musicName;
late bool likesnot;
late bool collectionsnot;
// Song lists
List<int> id = [];
List<String> song2 = [];
List<String> artist = [];
List<String> music = [];
List<bool> likes = [];
List<bool> collection = [];
bool _isLoading = false;
Future<void> _fetchSonglistData() async {
setState(() {
for (int i = 0; i < widget.songList.length; i++) {
id.add(widget.songList[i].id);
song2.add(widget.songList[i].musicurl);
artist.add(widget.songList[i].artist);
music.add(widget.songList[i].title);
likes.add(widget.songList[i].likes);
collection.add(widget.songList[i].collection);
}
});
}
Future<void> _updateCurrentSong() async {
// 立即更新UI和歌曲信息
setState(() {
_isLoading = true;
_position = Duration.zero;
_duration = Duration.zero;
artistName = artist[currentSongIndex];
musicName = music[currentSongIndex];
likesnot = likes[currentSongIndex];
collectionsnot = collection[currentSongIndex];
});
try {
// 在后台停止当前播放
unawaited(_audioPlayer.stop());
// Check for local file first
final localSong = downloadManager.getLocalSong(currentSongIndex);
final audioSource = localSong != null
? AudioSource.file(localSong.musicurl)
: AudioSource.uri(Uri.parse(song2[currentSongIndex]));
// Set the audio source and get duration
final duration = await _audioPlayer.setAudioSource(
audioSource,
preload: true,
);
if (!_isDisposed) {
setState(() {
_duration = duration ?? Duration.zero;
_isLoading = false;
});
}
// 开始播放
await _audioPlayer.play();
} catch (e) {
print('Error loading audio source: $e');
if (!_isDisposed) {
setState(() {
_isLoading = false;
});
}
}
}
void playerInit() {
_audioPlayer = AudioPlayer();
// Initialize with first song
artistName = widget.songList[widget.initialSongIndex].artist;
musicName = widget.songList[widget.initialSongIndex].title;
likesnot = widget.songList[widget.initialSongIndex].likes;
collectionsnot = widget.songList[widget.initialSongIndex].collection;
// Listen to player events
_audioPlayer.positionStream.listen((position) {
if (!_isDisposed) {
setState(() => _position = position);
}
});
_audioPlayer.durationStream.listen((duration) {
if (!_isDisposed) {
setState(() => _duration = duration ?? Duration.zero);
}
});
_audioPlayer.processingStateStream.listen((state) {
if (state == ProcessingState.completed) {
playNextSong();
}
});
}
Widget _buildPlayButton() {
return SizedBox(
width: 52,
height: 52,
child: Center(
child: _isLoading
? const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xff429482)),
strokeWidth: 3.0,
)
: IconButton(
padding: EdgeInsets.zero, // 移除内边距
constraints: const BoxConstraints(
minWidth: 52,
minHeight: 52,
maxWidth: 52,
maxHeight: 52,
),
onPressed: playOrPause,
icon: _audioPlayer.playing
? Image.asset(
"assets/img/music_play.png",
width: 52,
height: 52,
)
: Image.asset(
"assets/img/music_pause.png",
width: 52,
height: 52,
),
),
),
);
}
void playOrPause() async {
if (_audioPlayer.playing) {
await _audioPlayer.pause();
} else {
await _audioPlayer.play();
}
setState(() {});
}
void playNextSong() {
if (currentSongIndex < widget.songList.length - 1) {
currentSongIndex++;
} else {
currentSongIndex = 0;
}
_updateCurrentSong();
}
void playPreviousSong() {
if (currentSongIndex > 0) {
currentSongIndex--;
} else {
currentSongIndex = widget.songList.length - 1;
}
_updateCurrentSong();
}
String formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "$twoDigitMinutes:$twoDigitSeconds";
}
Future<void> _initializeAsync() async {
await _fetchSonglistData();
await _updateCurrentSong();
}
@override
void initState() {
super.initState();
currentSongIndex = widget.initialSongIndex;
playerInit();
_initializeAsync();
}
@override
void dispose() {
_isDisposed = true;
_audioPlayer.dispose();
super.dispose();
}
void _changeCurrentSong(int index) {
if (!_isDisposed) {
setState(() {
currentSongIndex = index;
_updateCurrentSong();
});
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/img/app_bg.png"),
fit: BoxFit.cover,
),
),
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 45, left: 10, right: 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Get.back();
},
icon: Image.asset(
"assets/img/back.png",
width: 25,
height: 25,
fit: BoxFit.contain,
),
),
Row(
children: [
IconButton(
onPressed: () {},
icon: Image.asset(
"assets/img/music_add.png",
width: 30,
height: 30,
),
),
IconButton(
onPressed: downloadManager.isDownloading(id[currentSongIndex]) ||
downloadManager.isCompleted(id[currentSongIndex])
? null
: () async {
await downloadManager.startDownload(
song: widget.songList[currentSongIndex],
context: context,
);
},
icon: Obx(() {
if (downloadManager.isDownloading(id[currentSongIndex])) {
return Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 32,
height: 32,
child: CircularProgressIndicator(
value: downloadManager.getProgress(id[currentSongIndex]),
backgroundColor: Colors.grey[200],
valueColor: const AlwaysStoppedAnimation<Color>(Color(0xff429482)),
strokeWidth: 3.0,
),
),
Text(
'${(downloadManager.getProgress(id[currentSongIndex]) * 100).toInt()}',
style: const TextStyle(fontSize: 12),
),
],
);
}
return Image.asset(
downloadManager.isCompleted(id[currentSongIndex])
? "assets/img/music_download_completed.png"
: "assets/img/music_download.png",
width: 30,
height: 30,
);
}),
),
],
)
],
),
const SizedBox(
height: 80,
),
Center(
child: Stack(
alignment: Alignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
"assets/img/music_Ellipse.png",
width: 320,
height: 320,
fit: BoxFit.cover,
),
),
Positioned(
child: ClipRRect(
borderRadius: BorderRadius.circular(80),
child: Image.network(
widget.songList[currentSongIndex].artistPic,
width: 230,
height: 230,
fit: BoxFit.cover,
),
),
)
],
),
),
const SizedBox(
height: 60,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
musicName,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
artistName,
style: const TextStyle(fontSize: 20),
),
],
),
Row(
children: [
IconButton(
// TODO 该处存在 BUG影响功能点赞
// 原因:同收藏功能
onPressed: () async{
setState(() {
likesnot = !likesnot;
likes[currentSongIndex] = !likes[currentSongIndex];
});
await LikesApiMusic().likesMusic(musicId: id[currentSongIndex], Authorization: AppData().currentToken);
},
icon: Image.asset(
likesnot
? "assets/img/music_good.png"
: "assets/img/music_good_un.png",
width: 29,
height: 29,
),
),
IconButton(
// TODO 该处存在 BUG影响功能收藏
// 原因:这个视图接收的数据从父组件传递过来
// 在这更新数据不会影响父组件的数据,所以在父组件中的数据不会改变
// 所以从父组件进入该组件,显示的点赞和收藏数据均是父组件初始化的时候从服务器获取的,而不是最新的
// 同理,点赞逻辑也存在这个 BUG
onPressed: () async{
setState(() {
collectionsnot = !collectionsnot;
collection[currentSongIndex] = !collection[currentSongIndex];
});
await CollectionApiMusic().addCollection(musicId: id[currentSongIndex], Authorization: AppData().currentToken);
},
icon: Image.asset(
collectionsnot
? "assets/img/music_star.png"
: "assets/img/music_star_un.png",
width: 29,
height: 29,
),
),
IconButton(
onPressed: () {
Image.asset("assets/img/music_good.png");
},
icon: Image.asset(
"assets/img/music_commend_un.png",
width: 29,
height: 29,
),
),
],
)
],
),
const SizedBox(
height: 80,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${formatDuration(_position)}",
style: const TextStyle(
color: Colors.black,
),
),
Expanded(
child: SliderTheme(
data: const SliderThemeData(
trackHeight: 3.0, // 调整轨道的高度
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 7.0), // 调整拇指的大小
overlayShape:
RoundSliderOverlayShape(overlayRadius: 12.0),
),
child: Slider(
min: 0,
max: _duration.inSeconds.toDouble(),
value: _position.inSeconds.toDouble(),
onChanged: (value) async {
await _audioPlayer
.seek(Duration(seconds: value.toInt()));
setState(() {});
},
activeColor: const Color(0xff429482),
inactiveColor: const Color(0xffE3F0ED),
),
),
),
Text(
"${formatDuration(_duration)}",
style: const TextStyle(
color: Colors.black,
),
),
],
),
const SizedBox(
height: 30,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: Image.asset(
"assets/img/music_random.png",
width: 35,
height: 35,
),
),
Row(
children: [
IconButton(
onPressed: playPreviousSong,
icon: Image.asset(
"assets/img/music_back.png",
width: 42,
height: 42,
),
),
const SizedBox(
width: 10,
),
_buildPlayButton(), // 这里使用新的方法替换原来的IconButton
const SizedBox(
width: 10,
),
IconButton(
onPressed: playNextSong,
icon: Image.asset(
"assets/img/music_next.png",
width: 42,
height: 42,
),
),
],
),
IconButton(
onPressed: _showPlaylist,
icon: Image.asset(
"assets/img/music_more.png",
width: 35,
height: 35,
),
),
],
)
],
),
),
),
),
);
}
void _showPlaylist() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
builder: (BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 15),
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.45),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Center(
child: Text(
"播放列表",
style: TextStyle(
fontSize: 20,
),
),
),
Expanded(
child: ListView.builder(
itemCount: music.length,
itemBuilder: (BuildContext context, int index) {
bool isCurrentlyPlaying = currentSongIndex == index;
return ListTile(
tileColor: isCurrentlyPlaying ? const Color(0xffE3F0ED) : null,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 20),
// Add left padding
child: Text(
music[index],
style: const TextStyle(fontSize: 18),
),
),
Padding(
padding: const EdgeInsets.only(right: 20),
// Add right padding
child: Image.asset(
"assets/img/songs_run.png",
width: 25,
), // Add your desired icon here
),
],
),
onTap: () {
_changeCurrentSong(index);
Navigator.pop(context);
},
);
},
),
),
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xff429482),
padding: const EdgeInsets.symmetric(vertical: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text(
"关闭",
style: TextStyle(color: Colors.black, fontSize: 20),
),
),
],
),
);
},
);
}
}