diff --git a/lib/api/api_music_list.dart b/lib/api/api_music_list.dart index eb9123a..39fa32b 100644 --- a/lib/api/api_music_list.dart +++ b/lib/api/api_music_list.dart @@ -4,6 +4,7 @@ import '../common_widget/Song_widegt.dart'; import '../models/getMusicList_bean.dart'; import '../models/getRank_bean.dart'; +const String _getMusic = 'http://8.210.250.29:10010/musics/'; const String _getMusic1 = 'http://8.210.250.29:10010/musics/1'; const String _getMusic2 = 'http://8.210.250.29:10010/musics/2'; const String _getMusic3 = 'http://8.210.250.29:10010/musics/3'; @@ -13,6 +14,21 @@ const String _getSongDetail = 'http://8.210.250.29:10010/musics'; class GetMusic { final Dio dio = Dio(); + Future getMusicById({required int id, required String Authorization}) async { + print(_getMusic + id.toString()); + Response response = await dio.get( + _getMusic + id.toString(), + data: { + 'Authorization': Authorization, + }, + options: Options(headers: { + 'Authorization': Authorization, + 'Content-Type': 'application/json;charset=UTF-8' + })); + print(response.data); + return MusicListBean.formMap(response.data); + } + Future getMusic1({required String Authorization}) async { Response response = await dio.get( _getMusic1, diff --git a/lib/api/api_music_return.dart b/lib/api/api_music_return.dart index 6612bb8..26cc290 100644 --- a/lib/api/api_music_return.dart +++ b/lib/api/api_music_return.dart @@ -42,7 +42,7 @@ class commentMusic { _postComment, data: { 'content': content, - 'MusicId': musicId, + 'musicId': musicId, 'Authorization':Authorization }, @@ -57,9 +57,9 @@ class commentMusic { class getCommentApi { final Dio dio = Dio(); Future getComment({ - required String musicId, - required String pageNo, - required String pageSize, + required int musicId, + required int pageNo, + required int pageSize, required String Authorization, }) async { Response response = await dio.get( diff --git a/lib/common/download_manager.dart b/lib/common/download_manager.dart index 1394b4c..79c85ff 100644 --- a/lib/common/download_manager.dart +++ b/lib/common/download_manager.dart @@ -121,7 +121,7 @@ class DownloadManager extends GetxController { bool removeSong(int id) { if (_downloads[id.toString()]?.isCompleted ?? false) { - File file = File.fromUri(Uri.parse(_downloads[id.toString()]!.song.musicurl)); + File file = File.fromUri(Uri.parse(_downloads[id.toString()]!.song.musicurl!)); file.deleteSync(); _downloads.remove(id.toString()); _saveDownloadsToPrefs(); @@ -156,7 +156,7 @@ class DownloadManager extends GetxController { try { final filePath = await downloadApi.downloadMusic( - musicUrl: song.musicurl, + musicUrl: song.musicurl!, name: fileName, context: context, onProgress: (progress) { @@ -169,7 +169,7 @@ class DownloadManager extends GetxController { downloadItem.isCompleted = true; downloadItem.isDownloading = false; downloadItem.progress = 1.0; - song.musicurl = _getLocalAudioPath(fileName, song.musicurl); + song.musicurl = _getLocalAudioPath(fileName, song.musicurl!); print(song.musicurl); } else { downloadItem.isDownloading = false; diff --git a/lib/common_widget/Song_widegt.dart b/lib/common_widget/Song_widegt.dart index 1b3b92e..2c130ef 100644 --- a/lib/common_widget/Song_widegt.dart +++ b/lib/common_widget/Song_widegt.dart @@ -3,10 +3,10 @@ class Song { String artistPic; String title; String artist; - String musicurl; + String? musicurl; int id; - bool likes; - bool collection; + bool? likes; + bool? collection; // 构造函数 Song({ diff --git a/lib/models/getRank_bean.dart b/lib/models/getRank_bean.dart index 574d5d1..74dda9d 100644 --- a/lib/models/getRank_bean.dart +++ b/lib/models/getRank_bean.dart @@ -24,8 +24,6 @@ class DataBean { String? musicPath; String? name; - - DataBean._formMap(Map map) { id = map['id']; singerName = map['singerName']; diff --git a/lib/view/begin/login_v.dart b/lib/view/begin/login_v.dart index ef0c495..336cd47 100644 --- a/lib/view/begin/login_v.dart +++ b/lib/view/begin/login_v.dart @@ -81,6 +81,7 @@ class _LoginVState extends State { Get.dialog( Center( child: CircularProgressIndicator( + color: const Color(0xff429482), backgroundColor: Colors.grey[200], ), ), diff --git a/lib/view/commend_view.dart b/lib/view/commend_view.dart deleted file mode 100644 index 36ef0c5..0000000 --- a/lib/view/commend_view.dart +++ /dev/null @@ -1,329 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:music_player_miao/api/api_music_return.dart'; -import 'package:music_player_miao/common_widget/app_data.dart'; -import 'package:music_player_miao/models/getComment_bean.dart'; -import 'package:music_player_miao/models/universal_bean.dart'; -import 'package:music_player_miao/widget/text_field.dart'; - - -class CommentView extends StatefulWidget { - - - @override - _CommentViewState createState() => _CommentViewState(); - - CommentView({super.key, required this.initialSongIndex}); - - late final int initialSongIndex; -} - -class _CommentViewState extends State { - List comments = []; - - TextEditingController commentController = TextEditingController(); - FocusNode commentFocusNode = FocusNode(); - List commentTimes = []; - List commentHeader = []; - List commentName = []; - bool ascendingOrder = true; - int playlistCount = 0; - - @override - void initState() { - super.initState(); - _fetchSonglistData(); - } - - Future _fetchSonglistData() async { - try { - GetCommentBean bean1 = await getCommentApi().getComment( - musicId: '1', - pageNo: '0', - pageSize: '10', - Authorization: AppData().currentToken, - ); - - // 检查 rows 是否为空 - if (bean1.rows == null) { - print('No comments found'); - return; - } - - setState(() { - comments = bean1.rows!.map((rows) => rows.content ?? 'No content').toList(); - commentTimes = bean1.rows!.map((rows) => rows.time ?? 'Unknown time').toList(); - commentHeader = bean1.rows!.map((rows) => rows.avatar ?? 'Default avatar').toList(); - commentName = bean1.rows!.map((rows) => rows.username ?? 'Anonymous').toList(); - playlistCount = comments.length; - }); - } catch (error) { - print('Error fetching songlist data: $error'); - } - } - - - - - @override - Widget build(BuildContext context) { - return Scaffold( - // backgroundColor: Colors.transparent, - appBar: AppBar( - centerTitle: true, - backgroundColor: const Color(0xffF6FFD1), - title: const Text( - '评论(200)', - style: TextStyle( - color: Colors.black, - fontSize: 22 - ), - ), - elevation: 0, - leading: IconButton( - onPressed: () { - Get.back(); - }, - icon: Image.asset( - "assets/img/back.png", - width: 25, - height: 25, - fit: BoxFit.contain, - ), - ), - ), - body: Column( - children: [ - Container( - height: 80, - padding: const EdgeInsets.only(left: 20, right: 10), - decoration: BoxDecoration( - color: const Color(0xffF9F2AF), - borderRadius: BorderRadius.circular(20) - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Image.asset( - "assets/img/artist_pic.png", - width: 60, - height: 60, - fit: BoxFit.cover, - ), - ), - const SizedBox(width: 20,), - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "背对背拥抱", - maxLines: 1, - style: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.w400), - ), - Text( - "林俊杰", - maxLines: 1, - style: TextStyle(color: Colors.black, fontSize: 14), - ) - ], - ), - ], - ), - IconButton( - onPressed: () {}, - icon: Image.asset( - "assets/img/music_pause.png", - width: 25, - height: 25, - fit: BoxFit.contain, - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "评论区", - style: TextStyle( - fontSize: 18 - ), - ), - Row( - children: [ - const Text( - "时间", - style: TextStyle( - fontSize: 16 - ), - ), - IconButton( - onPressed: () { - setState(() { - ascendingOrder = !ascendingOrder; - - // 使用时间排序索引列表 - List sortedIndexes = List.generate(comments.length, (i) => i); - sortedIndexes.sort((a, b) { - DateTime timeA = DateTime.parse(commentTimes[a]); - DateTime timeB = DateTime.parse(commentTimes[b]); - return ascendingOrder ? timeA.compareTo(timeB) : timeB.compareTo(timeA); - }); - - comments = [for (var i in sortedIndexes) comments[i]]; - commentTimes = [for (var i in sortedIndexes) commentTimes[i]]; - commentHeader = [for (var i in sortedIndexes) commentHeader[i]]; - commentName = [for (var i in sortedIndexes) commentName[i]]; - }); - }, - icon: Image.asset( - ascendingOrder - ? "assets/img/commend_up.png" - : "assets/img/commend_down.png", - fit: BoxFit.contain, - ), - ), - - ], - ) - ], - ), - ), - /// 显示评论的区域 - Expanded( - child: - ListView.builder( - itemCount: comments.length, - itemBuilder: (context, index) { - return ListTile( - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - CircleAvatar( - backgroundImage: NetworkImage(commentHeader[index]) - ), - const SizedBox(width: 10,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(commentName[index], - style: const TextStyle(fontSize: 18)), - const SizedBox(width: 8), - // Adjust the spacing between elements - Text(commentTimes[index], - style: const TextStyle(fontSize: 14),), - // Add the timestamp - ], - ), - ], - ), // Ad - Padding( - padding: const EdgeInsets.only( - left: 50, top: 10, bottom: 20), - child: Text( - comments[index], - style: const TextStyle( - fontSize: 18), // Customize the font size if needed - ), - ), - Container( - width: 560, - height: 2, - color: const Color(0xffE3F0ED), - ) - ], - ), - ); - }, - ), - ), - - ///输入框和提交按钮 - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: TextFieldColor( - controller: commentController, - hintText: '来发表你的评论吧!', - ) - ), - const SizedBox(width: 8.0), - ElevatedButton( - onPressed: () async { - submitComment(); - UniversalBean bean = await commentMusic().comment( - musicId: widget.initialSongIndex, - content: commentController.text, - Authorization: AppData().currentToken); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xff429482), - // Change Colors.blue to your desired background color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 10), // Adjust the radius as needed - ), - minimumSize: const Size(30, 44), - ), - child: const Text( - '提交', - style: TextStyle( - fontSize: 16 - ), - ), - ), - ], - ), - ), - ], - ), - ); - } - - void submitComment() async { - String comment = commentController.text.trim(); - if (comment.isEmpty) { - print('Comment cannot be empty'); - Get.snackbar('错误', '评论不能为空'); - return; - } - - print('Submitting comment with content: $comment'); - - try { - UniversalBean bean = await commentMusic().comment( - musicId: widget.initialSongIndex, - content: comment, - Authorization: AppData().currentToken, - ); - - // 处理响应结果 - if (bean.code == 200) { - print('Comment submitted successfully'); - commentController.clear(); - _fetchSonglistData(); // 刷新评论列表 - } else { - print('Failed to submit comment: ${bean.msg}'); - Get.snackbar('错误', bean.msg ?? '评论提交失败'); - } - } catch (error) { - print('Error submitting comment: $error'); - } - } - -} \ No newline at end of file diff --git a/lib/view/comment_view.dart b/lib/view/comment_view.dart new file mode 100644 index 0000000..4ad4149 --- /dev/null +++ b/lib/view/comment_view.dart @@ -0,0 +1,436 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:music_player_miao/api/api_music_return.dart'; +import 'package:music_player_miao/common_widget/app_data.dart'; +import 'package:music_player_miao/models/getComment_bean.dart'; +import 'package:music_player_miao/models/universal_bean.dart'; +import 'package:music_player_miao/widget/text_field.dart'; + +class CommentView extends StatefulWidget { + final int id; + final String song; + final String singer; + final String cover; + + const CommentView({ + super.key, + required this.id, + required this.song, + required this.singer, + required this.cover, + }); + + @override + _CommentViewState createState() => _CommentViewState(); +} + +class _CommentViewState extends State { + List comments = []; + ScrollController _scrollController = ScrollController(); + TextEditingController commentController = TextEditingController(); + FocusNode commentFocusNode = FocusNode(); + List commentTimes = []; + List commentHeader = []; + List commentName = []; + bool ascendingOrder = true; + int _page = 1; + int _pageSize = 10; + int _total = 0; + bool _isLoading = false; + bool _hasMoreData = true; + bool _isSendingComment = false; + + String avatar = AppData().currentAvatar; + String username = AppData().currentUsername; + + bool _isInitialLoading = true; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_scrollListener); + _fetchCommentData(); + commentController.addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _scrollController.dispose(); + commentController.dispose(); + super.dispose(); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + if (!_isLoading && _hasMoreData) { + _loadMoreData(); + } + } + } + + Future _loadMoreData() async { + if (_isLoading || !_hasMoreData) return; + + setState(() { + _isLoading = true; + }); + + _page++; + await _fetchCommentData(); + + setState(() { + _isLoading = false; + }); + } + + String formatDateTime(String dateTimeStr) { + try { + DateTime dateTime = DateTime.parse(dateTimeStr); + // 格式化时间,只保留年月日时分 + return "${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; + } catch (e) { + return dateTimeStr; // 如果解析失败,返回原始字符串 + } + } + + Future _fetchCommentData() async { + print('Fetching page $_page'); + try { + GetCommentBean bean1 = await getCommentApi().getComment( + musicId: widget.id, + pageNo: _page, + pageSize: _pageSize, + Authorization: AppData().currentToken, + ); + + if (bean1.rows == null || bean1.rows!.isEmpty) { + setState(() { + _hasMoreData = false; + _isInitialLoading = false; + }); + return; + } + + _total = bean1.total!; + + setState(() { + if (_page == 1) { + comments = + bean1.rows!.map((rows) => rows.content ?? 'No content').toList(); + commentTimes = bean1.rows! + .map((rows) => formatDateTime(rows.time ?? 'Unknown time')) + .toList(); + commentHeader = bean1.rows! + .map((rows) => rows.avatar ?? 'Default avatar') + .toList(); + commentName = + bean1.rows!.map((rows) => rows.username ?? 'Anonymous').toList(); + } else { + comments.addAll( + bean1.rows!.map((rows) => rows.content ?? 'No content').toList()); + commentTimes.addAll(bean1.rows! + .map((rows) => formatDateTime(rows.time ?? 'Unknown time')) + .toList()); + commentHeader.addAll(bean1.rows! + .map((rows) => rows.avatar ?? 'Default avatar') + .toList()); + commentName.addAll( + bean1.rows!.map((rows) => rows.username ?? 'Anonymous').toList()); + } + + _hasMoreData = comments.length < _total; + _isInitialLoading = false; + }); + } catch (error) { + print('Error fetching comment data: $error'); + setState(() { + _isLoading = false; + _isInitialLoading = false; + }); + } + } + + void _sortComments() { + setState(() { + comments = comments.reversed.toList(); + commentTimes = commentTimes.reversed.toList(); + commentHeader = commentHeader.reversed.toList(); + commentName = commentName.reversed.toList(); + }); + } + + // 新增:检查评论是否有效 + bool isCommentValid() { + String comment = commentController.text.trim(); + return comment.isNotEmpty; + } + + void submitComment() async { + String comment = commentController.text.trim(); + if (!isCommentValid()) { + return; // 直接返回,不显示提示 + } + + setState(() { + _isSendingComment = true; + }); + + try { + UniversalBean bean = await commentMusic().comment( + musicId: widget.id, + content: comment, + Authorization: AppData().currentToken, + ); + + if (bean.code == 200) { + commentController.clear(); + setState(() { + comments = [comment, ...comments]; + commentTimes = [ + formatDateTime(DateTime.now().toString()), + ...commentTimes + ]; + commentHeader = [avatar, ...commentHeader]; + commentName = [username, ...commentName]; + _total++; + }); + } + } catch (error) { + print('Error submitting comment: $error'); + } finally { + setState(() { + _isSendingComment = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: const Color(0xffF6FFD1), + title: Text( + '评论($_total)', + style: TextStyle(color: Colors.black, fontSize: 22), + ), + elevation: 0, + leading: IconButton( + onPressed: () { + Get.back(); + }, + icon: Image.asset( + "assets/img/back.png", + width: 25, + height: 25, + fit: BoxFit.contain, + ), + ), + ), + body: Column( + children: [ + // ... 保持原有的顶部歌曲信息部分不变 ... + Container( + height: 80, + padding: const EdgeInsets.only(left: 20, right: 10), + decoration: BoxDecoration( + color: const Color(0xffF9F2AF), + borderRadius: BorderRadius.circular(20)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + widget.cover, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.song, + maxLines: 1, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w400), + ), + Text( + widget.singer, + maxLines: 1, + style: const TextStyle( + color: Colors.black, fontSize: 14), + ) + ], + ), + ], + ), + ], + ), + ), + + // 评论区标题和排序 + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "评论区", + style: TextStyle(fontSize: 18), + ), + Row( + children: [ + const Text( + "时间", + style: TextStyle(fontSize: 16), + ), + IconButton( + onPressed: () { + setState(() { + ascendingOrder = !ascendingOrder; + _sortComments(); + }); + }, + icon: Image.asset( + ascendingOrder + ? "assets/img/commend_up.png" + : "assets/img/commend_down.png", + fit: BoxFit.contain, + ), + ), + ], + ) + ], + ), + ), + + // 评论列表 + Expanded( + child: _isInitialLoading + ? const Center( + child: CircularProgressIndicator( + color: Color(0xff429482), + ), + ) + : ListView.builder( + controller: _scrollController, + itemCount: comments.length + 1, // +1 为了显示加载更多的提示 + itemBuilder: (context, index) { + if (index == comments.length) { + // 显示加载状态或"没有更多数据"的提示 + return Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.center, + child: _isLoading + ? const CircularProgressIndicator( + color: Color(0xff429482), + ) + : _hasMoreData + ? const Text('上拉加载更多') + : const Text('没有更多评论了'), + ); + } + + return ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + backgroundImage: + NetworkImage(commentHeader[index])), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(commentName[index], + style: const TextStyle(fontSize: 18)), + Text( + commentTimes[index], + style: const TextStyle(fontSize: 14), + ), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 50, top: 10, bottom: 20), + child: Text( + comments[index], + style: const TextStyle(fontSize: 18), + ), + ), + Container( + width: 560, + height: 2, + color: const Color(0xffE3F0ED), + ) + ], + ), + ); + }, + ), + ), + + // 评论输入框 + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: TextFieldColor( + controller: commentController, + hintText: '来发表你的评论吧!', + enabled: !_isSendingComment, + ), + ), + const SizedBox(width: 8.0), + ElevatedButton( + onPressed: isCommentValid() && !_isSendingComment + ? submitComment + : null, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xff429482), + foregroundColor: Colors.black45, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(30, 44), + disabledBackgroundColor: Colors.grey, + ), + child: _isSendingComment + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Text( + '发送', + style: TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/home_view.dart b/lib/view/home_view.dart index 3b21abf..31a1214 100644 --- a/lib/view/home_view.dart +++ b/lib/view/home_view.dart @@ -4,7 +4,7 @@ import 'package:get/get.dart'; import 'package:music_player_miao/api/api_music_return.dart'; import 'package:music_player_miao/common_widget/app_data.dart'; import 'package:music_player_miao/models/search_bean.dart'; -import 'package:music_player_miao/view/commend_view.dart'; +import 'package:music_player_miao/view/comment_view.dart'; import '../../view_model/home_view_model.dart'; import '../api/api_music_list.dart'; import '../common/download_manager.dart'; @@ -396,7 +396,15 @@ class _HomeViewState extends State shrinkWrap: true, itemBuilder: (context, index) { return ListTile( - leading: Image.network(selectedSongs[index].pic), + leading: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + selectedSongs[index].pic, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + ), title: Text( selectedSongs[index].title, style: const TextStyle( @@ -423,7 +431,6 @@ class _HomeViewState extends State onSongStatusChanged: (index, isCollected, isLiked) { setState(() { - // 更新父组件中的数据 selectedSongs[index].collection = isCollected; selectedSongs[index].likes = isLiked; @@ -551,9 +558,12 @@ class _HomeViewState extends State IconButton( onPressed: () { Navigator.pop(context); - Get.to(() => CommentView( - initialSongIndex: index, - )); + // Get.to(() => + // CommentView( + // id:, + // song:, + // singer:, + // )); }, icon: Image.asset("assets/img/list_comment.png"), iconSize: 60, diff --git a/lib/view/music_view.dart b/lib/view/music_view.dart index ae82590..b3b2b0e 100644 --- a/lib/view/music_view.dart +++ b/lib/view/music_view.dart @@ -6,9 +6,11 @@ import 'package:music_player_miao/common_widget/app_data.dart'; import '../../view_model/home_view_model.dart'; import '../api/api_music_likes.dart'; import '../api/api_collection.dart'; +import '../api/api_music_list.dart'; import '../common/download_manager.dart'; import '../common_widget/Song_widegt.dart'; -import '../view/commend_view.dart'; +import '../models/getMusicList_bean.dart'; +import '../view/comment_view.dart'; import '../models/universal_bean.dart'; class MusicView extends StatefulWidget { @@ -63,8 +65,10 @@ class _MusicViewState extends State with SingleTickerProviderStateMix void initState() { super.initState(); currentSongIndex = widget.initialSongIndex; + // _initializeAsync(); + _fetchSonglistData(); + _updateCurrentSong(); playerInit(); - _initializeAsync(); _rotationController = AnimationController( duration: const Duration(seconds: 20), vsync: this, @@ -95,18 +99,60 @@ class _MusicViewState extends State with SingleTickerProviderStateMix setState(() { for (int i = 0; i < widget.songList.length; i++) { id.add(widget.songList[i].id); - song2.add(widget.songList[i].musicurl); + // TODO 处理为 musicurl 为空的情况, 需要通过网络 + if (widget.songList[i].musicurl == null) { + song2.add(""); + } else { + 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); + + // 初始化喜欢和收藏状态,后续再更新 + likes.add(widget.songList[i].likes ?? false); + collection.add(widget.songList[i].collection ?? false); } }); } + // 检查并更新歌曲状态的方法 + Future _checkAndUpdateSongStatus(int index) async { + // 只有当likes和collection为null时才需要请求 + if (widget.songList[index].likes == null || widget.songList[index].collection == null) { + try { + MusicListBean musicListBean = await GetMusic().getMusicById( + id: id[index], + Authorization: AppData().currentToken, + ); + + if (!_isDisposed && musicListBean.code == 200) { + setState(() { + likes[index] = musicListBean.likeOrNot!; + collection[index] = musicListBean.collectOrNot!; + // 如果是当前播放的歌曲,更新当前状态 + if (index == currentSongIndex) { + likesnot = musicListBean.likeOrNot!; + collectionsnot = musicListBean.collectOrNot!; + } + + widget.onSongStatusChanged?.call( + index, + musicListBean.collectOrNot!, + musicListBean.likeOrNot!, + ); + + }); + } + } catch (e) { + print('Error fetching song status: $e'); + } + } + } + + Future _updateCurrentSong() async { if (_isDisposed) return; - // 立即更新UI和歌曲信息 + setState(() { _isLoading = true; _position = Duration.zero; @@ -117,22 +163,23 @@ class _MusicViewState extends State with SingleTickerProviderStateMix collectionsnot = collection[currentSongIndex]; }); + await _checkAndUpdateSongStatus(currentSongIndex); + try { - // 在后台停止当前播放 await _audioPlayer.stop(); _rotationController.reset(); - // Check for local file first + // 检查本地文件 final localSong = downloadManager.getLocalSong(currentSongIndex); final audioSource = localSong != null - ? AudioSource.file(localSong.musicurl) + ? 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, - ); + // 设置音频源并获取时长 + await _audioPlayer.setAudioSource(audioSource, preload: true); + + // 等待获取真实的音频时长 + final duration = await _audioPlayer.duration; if (!_isDisposed) { setState(() { @@ -153,35 +200,86 @@ class _MusicViewState extends State with SingleTickerProviderStateMix } } - void playerInit() { + void playerInit() async { _audioPlayer = AudioPlayer(); - - // Initialize with first song + await _checkAndUpdateSongStatus(widget.initialSongIndex); + // 初始化第一首歌的信息 artistName = widget.songList[widget.initialSongIndex].artist; musicName = widget.songList[widget.initialSongIndex].title; - likesnot = widget.songList[widget.initialSongIndex].likes; - collectionsnot = widget.songList[widget.initialSongIndex].collection; + 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) { + // 修改播放状态监听 + _audioPlayer.playerStateStream.listen((state) { + if (_isDisposed) return; + + if (state.processingState == ProcessingState.completed) { + // 在这里直接调用下一首,而不是通过 Stream + _handleSongCompletion(); + } + }); + } + + // 新增方法处理歌曲播放完成 + void _handleSongCompletion() { + if (_isDisposed) return; + + setState(() { + _position = Duration.zero; + _duration = Duration.zero; + }); + + // 使用 Future.microtask 确保状态更新后再切换歌曲 + Future.microtask(() { + if (!_isDisposed) { playNextSong(); } }); } + // 修改构建 Slider 的部分 + Widget _buildProgressSlider() { + // 确保最大值至少为 0.1 以避免除零错误 + final max = _duration.inSeconds.toDouble() == 0 ? 0.1 : _duration.inSeconds.toDouble(); + // 确保当前值不超过最大值 + final current = _position.inSeconds.toDouble().clamp(0, max); + + return SliderTheme( + data: const SliderThemeData( + trackHeight: 3.0, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7.0), + overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0), + ), + child: Slider( + min: 0, + max: max, + value: current.toDouble(), + onChanged: (value) async { + if (!_isDisposed && _duration.inSeconds > 0) { + await _audioPlayer.seek(Duration(seconds: value.toInt())); + setState(() {}); + } + }, + activeColor: const Color(0xff429482), + inactiveColor: const Color(0xffE3F0ED), + ), + ); + } + Widget _buildPlayButton() { return SizedBox( width: 52, @@ -478,7 +576,12 @@ class _MusicViewState extends State with SingleTickerProviderStateMix Navigator.push( context, MaterialPageRoute( - builder: (context) => CommentView(initialSongIndex: widget.initialSongIndex), + builder: (context) => CommentView( + id: id[currentSongIndex], + song: musicName, + singer: artistName, + cover: widget.songList[currentSongIndex].artistPic, + ), ), ); }, @@ -499,39 +602,15 @@ class _MusicViewState extends State with SingleTickerProviderStateMix mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "${formatDuration(_position)}", - style: const TextStyle( - color: Colors.black, - ), + 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), - ), - ), + child: _buildProgressSlider(), ), Text( - "${formatDuration(_duration)}", - style: const TextStyle( - color: Colors.black, - ), + formatDuration(_duration), + style: const TextStyle(color: Colors.black), ), ], ), diff --git a/lib/view/rank_view.dart b/lib/view/rank_view.dart index 5fa73dc..9a8c633 100644 --- a/lib/view/rank_view.dart +++ b/lib/view/rank_view.dart @@ -31,22 +31,35 @@ class _RankViewState extends State with AutomaticKeepAliveClientMixin @override void initState() { super.initState(); - _fetchSonglistData(); + _fetchTop50Data(); } Future _onRefresh() async { - await _fetchSonglistData(); + await _fetchTop50Data(); } - Future _fetchSonglistData() async { + Future _fetchTop50Data() async { try { RankBean bean2 = await GetRank().getRank(Authorization: AppData().currentToken); + if (bean2.code != 200) return; + rankNames.clear(); + rankSingerName.clear(); + rankCoverPath.clear(); + rankMusicPath.clear(); + setState(() { + List ids = bean2.data!.map((data) => data.id!).toList(); rankNames = bean2.data!.map((data) => data.name!).toList(); rankSingerName = bean2.data!.map((data) => data.singerName!).toList(); rankCoverPath = bean2.data!.map((data) => data.coverPath!).toList(); rankMusicPath = bean2.data!.map((data) => data.musicPath!).toList(); + for (int i = 0; i < ids.length; i++) { + print(ids[i]); + } + + songs.clear(); + if (rankNames.isNotEmpty && rankNames.length == rankSingerName.length && rankNames.length == rankCoverPath.length && @@ -58,9 +71,9 @@ class _RankViewState extends State with AutomaticKeepAliveClientMixin artist: rankSingerName[i], musicurl: rankMusicPath[i], pic: rankCoverPath[i], - id: i, - likes: false, - collection: false, + id: ids[i], + likes: null, + collection: null, )); } } diff --git a/lib/view/user/user_view.dart b/lib/view/user/user_view.dart index 9f44c1e..511ea60 100644 --- a/lib/view/user/user_view.dart +++ b/lib/view/user/user_view.dart @@ -90,10 +90,14 @@ class _UserViewState extends State with AutomaticKeepAliveClientMixin children: [ Row( children: [ - Image.network( - avatar, - width: 64, - height: 64, + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + avatar, + width: 60, + height: 60, + fit: BoxFit.cover, + ), ), const SizedBox( width: 25, diff --git a/lib/widget/text_field.dart b/lib/widget/text_field.dart index 9c8e58f..9b4a27d 100644 --- a/lib/widget/text_field.dart +++ b/lib/widget/text_field.dart @@ -8,6 +8,7 @@ class TextFieldColor extends StatelessWidget { final String? Function(String?)? validator; final FocusNode? focusNode; final String? Function(String?)? onChanged; + final bool enabled; // 添加enabled属性 const TextFieldColor({ super.key, @@ -18,11 +19,13 @@ class TextFieldColor extends StatelessWidget { this.validator, this.focusNode, this.onChanged, + this.enabled = true, // 设置默认值 }); @override Widget build(BuildContext context) { return TextFormField( + enabled: enabled, validator: validator, controller: controller, focusNode: focusNode, @@ -34,23 +37,26 @@ class TextFieldColor extends StatelessWidget { suffixIcon: suffixIcon, contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none, ), - fillColor: Color(0xffE3F0ED), + disabledBorder: OutlineInputBorder( // 添加禁用状态的边框样式 + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + fillColor: enabled ? const Color(0xffE3F0ED) : const Color(0xffE3F0ED).withOpacity(0.7), // 禁用时稍微调整透明度 filled: true, hintText: hintText, alignLabelWithHint: true, hintStyle: TextStyle( - color: Color(0xff6E6E6E), - fontSize: 16 + color: enabled ? const Color(0xff6E6E6E) : const Color(0xff6E6E6E).withOpacity(0.5), + fontSize: 16, ), ), ); } - } \ No newline at end of file