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.
743 lines
24 KiB
743 lines
24 KiB
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:dio/dio.dart'; // 添加 dio 导入
|
|
import 'package:path/path.dart' as path; // 添加 path 导入
|
|
import 'package:music_player_miao/common_widget/app_data.dart';
|
|
import '../api/api_release.dart';
|
|
import '../models/universal_bean.dart';
|
|
import '../widget/text_field.dart';
|
|
|
|
class ReleaseView extends StatefulWidget {
|
|
const ReleaseView({Key? key});
|
|
|
|
@override
|
|
State<ReleaseView> createState() => _ReleaseViewState();
|
|
}
|
|
|
|
class SongInfo {
|
|
String songName;
|
|
String artistName;
|
|
|
|
SongInfo({required this.songName, required this.artistName});
|
|
}
|
|
|
|
class _ReleaseViewState extends State<ReleaseView> with AutomaticKeepAliveClientMixin {
|
|
|
|
List<File> coverImages = [];
|
|
List<SongInfo> songInfoList = [];
|
|
|
|
late File selectedMp3File;
|
|
File? selectedCoverFile;
|
|
File? selectedMusicFile;
|
|
bool isUploading = false;
|
|
double uploadProgress = 0.0;
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return Scaffold(
|
|
resizeToAvoidBottomInset: false,
|
|
backgroundColor: Colors.transparent,
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage("assets/img/app_bg.png"),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
child:
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 20, right: 20, top: 50),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
///立即发布
|
|
const Center(
|
|
child: Text(
|
|
"立即发布",
|
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
|
|
),
|
|
),
|
|
const SizedBox(height: 30,),
|
|
///上传文件upload
|
|
Container(
|
|
width: MediaQuery.of(context).size.width,
|
|
height: 120,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
_showUploadDialog();
|
|
},
|
|
icon: Image.asset("assets/img/release_upload.png", width: 45,),
|
|
),
|
|
const Text(
|
|
"上传文件",
|
|
style: TextStyle(
|
|
fontSize: 20
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 30,),
|
|
],
|
|
),
|
|
///音乐列表
|
|
const Text(
|
|
"音乐列表",
|
|
style: TextStyle(
|
|
fontSize: 20
|
|
),
|
|
),
|
|
const SizedBox(height: 20,),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child:
|
|
coverImages.isEmpty
|
|
? Container(
|
|
width: MediaQuery.of(context).size.width,
|
|
height: 200,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color: Colors.white,
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
"目前还是空的",
|
|
style: TextStyle(fontSize: 18),
|
|
),
|
|
),
|
|
)
|
|
:
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: List.generate(songInfoList.length, (index) {
|
|
return ListTile(
|
|
title: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: coverImages[index] != null
|
|
? Image.file(
|
|
coverImages[index]!,
|
|
width: 60,
|
|
height: 60,
|
|
fit: BoxFit.cover,
|
|
)
|
|
: Container(
|
|
color: const Color(0xffC4C4C4),
|
|
width: 60,
|
|
height: 60,
|
|
),
|
|
),
|
|
const SizedBox(width: 20,),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
songInfoList[index].songName,
|
|
style: const TextStyle(fontSize: 18),
|
|
),
|
|
Text(songInfoList[index].artistName),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
_bottomSheet(context, index);
|
|
},
|
|
icon: Image.asset(
|
|
"assets/img/More.png",
|
|
width: 25,
|
|
height: 25,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _bottomSheet(BuildContext context, int index) async {
|
|
await showModalBottomSheet(
|
|
context: context,
|
|
backgroundColor: Colors.white,
|
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
|
|
builder: (context) => Container(
|
|
height: 200,
|
|
padding: const EdgeInsets.only(top: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
_editSongInformation(context, index);
|
|
},
|
|
icon: Image.asset("assets/img/release_info.png"),
|
|
iconSize: 60,
|
|
),
|
|
const Text("编辑歌曲信息")
|
|
],
|
|
),
|
|
Column(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
_confirmDelete(context, index);
|
|
},
|
|
icon: Image.asset("assets/img/release_delete.png"),
|
|
iconSize: 60,
|
|
),
|
|
const Text("删除")
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 30,),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text(
|
|
"取消",
|
|
style: TextStyle(color: Colors.black, fontSize: 18),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.zero,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
);
|
|
}
|
|
|
|
///编辑信息
|
|
void _editSongInformation(BuildContext context, int index) {
|
|
TextEditingController songNameController = TextEditingController();
|
|
TextEditingController artistNameController = TextEditingController();
|
|
|
|
songNameController.text = songInfoList[index].songName;
|
|
artistNameController.text = songInfoList[index].artistName;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
title: const Center(child: Text('编辑歌曲信息')),
|
|
content: SizedBox(
|
|
height: 260,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text("封面"),
|
|
IconButton(
|
|
onPressed: () {
|
|
_getImageFromGallery(index);
|
|
},
|
|
icon: coverImages[index] != null
|
|
? Image.file(
|
|
coverImages[index]!,
|
|
width: 100,
|
|
height: 100,
|
|
fit: BoxFit.cover,
|
|
)
|
|
: Image.asset(
|
|
"assets/img/release_pic1.png",
|
|
width: 60,
|
|
height: 60,
|
|
),
|
|
iconSize: 60,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Text("歌名"),
|
|
TextFieldColor(
|
|
controller: songNameController,
|
|
hintText: '请输入歌曲名称'
|
|
),
|
|
const SizedBox(height: 20,),
|
|
const Text("歌手"),
|
|
TextFieldColor(
|
|
controller: artistNameController,
|
|
hintText: '请输入歌手名称'
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
minimumSize: const Size(130, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
child: const Text(
|
|
"取消",
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Handle the edited song information here
|
|
String editedSongName = songNameController.text;
|
|
String editedArtistName = artistNameController.text;
|
|
|
|
// Update the song information in your data structure
|
|
songInfoList[index] = SongInfo(
|
|
songName: editedSongName,
|
|
artistName: editedArtistName,
|
|
);
|
|
|
|
// For demonstration, print the edited values
|
|
print('Edited Song Name: $editedSongName');
|
|
print('Edited Artist Name: $editedArtistName');
|
|
|
|
// Update the displayed song information in the UI
|
|
setState(() {});
|
|
Navigator.pop(context);
|
|
},
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
minimumSize: const Size(130, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
child: const Text('确认', style: TextStyle(color: Colors.white),),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
///上传
|
|
Future<void> _showUploadDialog() async {
|
|
final songNameController = TextEditingController();
|
|
final artistNameController = TextEditingController();
|
|
|
|
await showDialog(
|
|
context: context,
|
|
barrierDismissible: false, // 防止误触关闭
|
|
builder: (context) => StatefulBuilder( // 使用 StatefulBuilder 以更新对话框状态
|
|
builder: (context, setState) => AlertDialog(
|
|
title: const Text('上传音乐'),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 音乐文件选择
|
|
ListTile(
|
|
leading: const Icon(Icons.music_note),
|
|
title: Text(selectedMusicFile?.path.split('/').last ?? '未选择音乐文件'),
|
|
trailing: IconButton(
|
|
icon: const Icon(Icons.upload_file),
|
|
onPressed: () async {
|
|
final file = await _pickMusicFile();
|
|
if (file != null) {
|
|
setState(() => selectedMusicFile = file);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
|
|
// 封面图片选择
|
|
ListTile(
|
|
leading: const Icon(Icons.image),
|
|
title: Text(selectedCoverFile?.path.split('/').last ?? '未选择封面图片'),
|
|
trailing: IconButton(
|
|
icon: const Icon(Icons.upload_file),
|
|
onPressed: () async {
|
|
final file = await _pickImage();
|
|
if (file != null) {
|
|
setState(() => selectedCoverFile = file);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
|
|
// 歌曲信息输入
|
|
const SizedBox(height: 16),
|
|
TextFieldColor(
|
|
controller: songNameController,
|
|
hintText: '请输入歌曲名称',
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFieldColor(
|
|
controller: artistNameController,
|
|
hintText: '请输入歌手名称',
|
|
),
|
|
|
|
// 显示进度条
|
|
if (isUploading) _buildProgressIndicator(),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: isUploading ? null : () => Navigator.pop(context),
|
|
child: const Text('取消'),
|
|
),
|
|
TextButton(
|
|
onPressed: isUploading
|
|
? null
|
|
: () async {
|
|
if (_validateInputs(
|
|
selectedMusicFile,
|
|
selectedCoverFile,
|
|
songNameController.text,
|
|
artistNameController.text,
|
|
)) {
|
|
await _performUpload(
|
|
songNameController.text,
|
|
artistNameController.text,
|
|
setState,
|
|
);
|
|
}
|
|
},
|
|
child: Text(isUploading ? '上传中...' : '确认上传'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
bool _validateInputs(
|
|
File? musicFile,
|
|
File? coverFile,
|
|
String songName,
|
|
String artistName,
|
|
) {
|
|
if (musicFile == null) {
|
|
_showErrorMessage('请选择音乐文件');
|
|
return false;
|
|
}
|
|
if (coverFile == null) {
|
|
_showErrorMessage('请选择封面图片');
|
|
return false;
|
|
}
|
|
if (songName.isEmpty) {
|
|
_showErrorMessage('请输入歌曲名称');
|
|
return false;
|
|
}
|
|
if (artistName.isEmpty) {
|
|
_showErrorMessage('请输入歌手名称');
|
|
return false;
|
|
}
|
|
|
|
// 检查文件大小
|
|
if (musicFile.lengthSync() > 10 * 1024 * 1024) { // 10MB 限制
|
|
_showErrorMessage('音乐文件大小不能超过10MB');
|
|
return false;
|
|
}
|
|
if (coverFile.lengthSync() > 2 * 1024 * 1024) { // 2MB 限制
|
|
_showErrorMessage('封面图片大小不能超过2MB');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Future<void> _performUpload(
|
|
String songName,
|
|
String artistName,
|
|
StateSetter setState,
|
|
) async {
|
|
setState(() {
|
|
isUploading = true;
|
|
uploadProgress = 0;
|
|
});
|
|
|
|
try {
|
|
final dio = Dio();
|
|
|
|
// 设置拦截器来监听上传进度
|
|
dio.interceptors.add(InterceptorsWrapper(
|
|
onRequest: (options, handler) {
|
|
return handler.next(options);
|
|
},
|
|
onResponse: (response, handler) {
|
|
return handler.next(response);
|
|
},
|
|
onError: (error, handler) {
|
|
return handler.next(error);
|
|
},
|
|
));
|
|
|
|
String coverFileName = path.basename(selectedCoverFile!.path);
|
|
String musicFileName = path.basename(selectedMusicFile!.path);
|
|
|
|
FormData formData = FormData.fromMap({
|
|
'Authorization': AppData().currentToken,
|
|
'coverFile': await MultipartFile.fromFile(
|
|
selectedCoverFile!.path,
|
|
filename: coverFileName,
|
|
),
|
|
'musicFile': await MultipartFile.fromFile(
|
|
selectedMusicFile!.path,
|
|
filename: musicFileName,
|
|
),
|
|
'singerName': artistName,
|
|
'name': songName,
|
|
'introduce': '暂无简介',
|
|
});
|
|
|
|
final response = await dio.post(
|
|
'http://8.210.250.29:10010/musics',
|
|
data: formData,
|
|
options: Options(
|
|
headers: {'Authorization': AppData().currentToken},
|
|
),
|
|
onSendProgress: (count, total) {
|
|
setState(() {
|
|
uploadProgress = count / total;
|
|
});
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
_showSuccessDialog();
|
|
} else {
|
|
_showErrorMessage(response.data['msg'] ?? '上传失败');
|
|
}
|
|
} catch (e) {
|
|
_showErrorMessage('上传失败: $e');
|
|
} finally {
|
|
setState(() {
|
|
isUploading = false;
|
|
uploadProgress = 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
// 修改进度条显示
|
|
Widget _buildProgressIndicator() {
|
|
return Column(
|
|
children: [
|
|
const SizedBox(height: 16),
|
|
LinearProgressIndicator(
|
|
value: uploadProgress,
|
|
backgroundColor: Colors.grey[200],
|
|
valueColor: AlwaysStoppedAnimation<Color>(Color(0xff429482)),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'上传进度: ${(uploadProgress * 100).toStringAsFixed(1)}%',
|
|
style: TextStyle(
|
|
color: Color(0xff429482),
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<File?> _getImageFromGallery([int? index]) async {
|
|
final picker = ImagePicker();
|
|
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
|
|
|
if (pickedFile != null) {
|
|
setState(() {
|
|
if (index != null && index < coverImages.length) {
|
|
coverImages[index] = File(pickedFile.path);
|
|
} else {
|
|
coverImages.add(File(pickedFile.path));
|
|
}
|
|
});
|
|
return File(pickedFile.path);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
Future<File> _getMp3File() async {
|
|
final filePickerResult = await FilePicker.platform.pickFiles(
|
|
type: FileType.custom,
|
|
allowedExtensions: ['mp3'],
|
|
);
|
|
|
|
if (filePickerResult != null && filePickerResult.files.isNotEmpty) {
|
|
return File(filePickerResult.files.first.path!);
|
|
}
|
|
|
|
return File('');
|
|
}
|
|
|
|
void _confirmDelete(BuildContext context, int index) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:BorderRadius.circular(10),
|
|
),
|
|
title: Image.asset("assets/img/warning.png",width: 47,height: 46,),
|
|
content: const Text('确认删除?',textAlign: TextAlign.center,),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context); // Close the dialog
|
|
},
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
minimumSize: const Size(130, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
child: const Text('取消',style: TextStyle(color: Colors.white),),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Remove the selected song from the lists
|
|
setState(() {
|
|
coverImages.removeAt(index);
|
|
songInfoList.removeAt(index);
|
|
});
|
|
Navigator.pop(context); // Close the dialog
|
|
},
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
minimumSize: const Size(130, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
child: const Text('确认',style: TextStyle(color: Colors.white),),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 选择音乐文件
|
|
Future<File?> _pickMusicFile() async {
|
|
final result = await FilePicker.platform.pickFiles(
|
|
type: FileType.custom,
|
|
allowedExtensions: ['mp3'],
|
|
);
|
|
|
|
if (result != null && result.files.isNotEmpty) {
|
|
return File(result.files.first.path!);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 选择图片
|
|
Future<File?> _pickImage() async {
|
|
final picker = ImagePicker();
|
|
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
|
|
|
if (pickedFile != null) {
|
|
return File(pickedFile.path);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 显示错误消息
|
|
void _showErrorMessage(String message) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('提示'),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('确定'),
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 显示成功对话框
|
|
void _showSuccessDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Image.asset(
|
|
"assets/img/correct.png",
|
|
width: 47,
|
|
height: 46,
|
|
),
|
|
content: const Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('上传成功'),
|
|
Text('审核通过后将自动发布'),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop(); // 关闭对话框
|
|
// 清空输入和选择的文件
|
|
setState(() {
|
|
selectedMusicFile = null;
|
|
selectedCoverFile = null;
|
|
// 重置其他需要清空的状态
|
|
});
|
|
},
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: const Color(0xff429482),
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('确定'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |