From 91386b243f6f652a10b2d085bc369e310ea64759 Mon Sep 17 00:00:00 2001 From: Spark <2666652@gmail.com> Date: Mon, 11 Nov 2024 12:01:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9C=AC=E5=9C=B0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=A1=B5=E9=9D=A2=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=A6=BB?= =?UTF-8?q?=E5=BC=80=E9=A1=B5=E9=9D=A2=E4=B8=8B=E8=BD=BD=E7=BB=88=E6=AD=A2?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/img/add.png | Bin 0 -> 749 bytes assets/img/delete.png | Bin 0 -> 356 bytes devtools_options.yaml | 3 + lib/api/api_download.dart | 40 +- lib/common/download_manager.dart | 201 +++++++ lib/common_widget/Song_widegt.dart | 26 +- lib/view/home_view.dart | 2 + lib/view/music_view.dart | 198 ++----- lib/view/user/my_download_view.dart | 508 ++++++++++++++++++ lib/view/user/my_music_view.dart | 2 +- lib/view/user/user_view.dart | 26 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 56 ++ pubspec.yaml | 1 + 14 files changed, 873 insertions(+), 192 deletions(-) create mode 100644 assets/img/add.png create mode 100644 assets/img/delete.png create mode 100644 devtools_options.yaml create mode 100644 lib/common/download_manager.dart create mode 100644 lib/view/user/my_download_view.dart diff --git a/assets/img/add.png b/assets/img/add.png new file mode 100644 index 0000000000000000000000000000000000000000..be08a223bae5be8675232ce3f67fbfe27882fcb3 GIT binary patch literal 749 zcmVPx%r%6OXR9Hu)S3!2-FbtKPi@<_37l3wGI7#6oEhi~GN$GBeUI6G~auREv96PZc zIX09P@NDZ#`udU(Zum5d!3G|khRw0_SRW&^5x~P@`58Pybi}IKm*!TCT=&K<1~CE3gJ|K zEq7ks8IHvHdU}hZGyEJ~P-;`^bqYuhL?3~sd76>d|EAE!MoiuT+8omb$L8+N0H7DVchAGhE>Z?{-a zq$UEeDT;vB^lD~+z&$e$xc^!p3feBAYOmM7yBPB*1u-#7MCINp5J_nN98{`6gzg0( z5#E$sAP_>wl2U(<5J`8O6c0oMXtC>plcUr<0DQf%HjcgMq?$#F_R`u+_FVKy8|{iyLs17+!q zAB0^{P)EE#&%_dw_5q%10X}leh9xI=$v`wHz3#hURdG79BYKSZ fE&wIrFFo@gjt~m}ZM?dt00000NkvXXu0mjf%Fj|n literal 0 HcmV?d00001 diff --git a/assets/img/delete.png b/assets/img/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..36168d89149cadb33bf435b03f4097beca025bf5 GIT binary patch literal 356 zcmV-q0h|7bP)Px$9!W$&R9HvtSHTU!FbuqGLN|d;U=qY6unBAen?NtrC~DoVdV!ELraW+F~I7PbUpm zQfqG0;)@vQ06GJmfjI-58D(TkKL;i2RnO2nbmZdN0U#30C9Ry)P$}`#y%+ downloadMusic({ required String musicUrl, required String name, @@ -52,35 +62,9 @@ class DownloadApi { required Function(double) onProgress, }) async { try { - // 音频文件后缀列表 - final List audioExtensions = [ - '.mp3', - '.wav', - '.m4a', - '.aac', - '.ogg', - '.flac', - '.wma', - '.amr' - ]; - - // 检查URL中是否包含音频后缀 - String fileExtension = ''; - - final Uri uri = Uri.parse(musicUrl); - final String pathOnly = uri.path.toLowerCase(); - - for (var ext in audioExtensions) { - if (pathOnly.contains(ext)) { - fileExtension = ext; - break; - } - } - // 如果找到后缀,添加到文件名 - final fileName = fileExtension.isNotEmpty - ? name.endsWith(fileExtension) ? name : name + fileExtension - : name; + String fileExtension = _getFileExtension(musicUrl); + final fileName = '$name$fileExtension'; // 检查并申请权限 if (!await _requestStoragePermission(context)) { diff --git a/lib/common/download_manager.dart b/lib/common/download_manager.dart new file mode 100644 index 0000000..18cec7a --- /dev/null +++ b/lib/common/download_manager.dart @@ -0,0 +1,201 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../api/api_download.dart'; +import '../common_widget/Song_widegt.dart'; +import 'package:path/path.dart' as path; + +class DownloadItem { + final Song song; + double progress; + bool isCompleted; + bool isDownloading; + + DownloadItem({ + required this.song, + this.progress = 0.0, + this.isCompleted = false, + this.isDownloading = false, + }); + + Map toJson() { + return { + 'song': { + 'pic': song.pic, + 'artistPic': song.artistPic, + 'title': song.title, + 'artist': song.artist, + 'musicurl': song.musicurl, + 'id': song.id, + 'likes': song.likes, + 'collection': song.collection + }, + 'progress': progress, + 'isCompleted': isCompleted, + 'isDownloading': isDownloading, + }; + } + + // 从JSON创建DownloadItem + factory DownloadItem.fromJson(Map json) { + return DownloadItem( + song: Song( + pic: json['song']['pic'], + artistPic: json['song']['artistPic'], + title: json['song']['title'], + artist: json['song']['artist'], + musicurl: json['song']['musicurl'], + id: json['song']['id'], + likes: json['song']['likes'], + collection: json['song']['collection'], + ), + progress: json['progress'], + isCompleted: json['isCompleted'], + isDownloading: json['isDownloading'], + ); + } +} + +class DownloadManager extends GetxController { + static const String PREFS_KEY = 'downloads_data'; + final _downloads = {}.obs; + final downloadApi = DownloadApi(); + late SharedPreferences _prefs; + + @override + void onInit() async { + super.onInit(); + await _initPrefs(); + } + + Future _initPrefs() async { + _prefs = await SharedPreferences.getInstance(); + await _loadDownloadsFromPrefs(); + } + + // 从SharedPreferences加载数据 + Future _loadDownloadsFromPrefs() async { + final String? downloadsJson = _prefs.getString(PREFS_KEY); + if (downloadsJson != null) { + final Map downloadsMap = json.decode(downloadsJson); + downloadsMap.forEach((key, value) { + _downloads[key] = DownloadItem.fromJson(value); + }); + } + } + + // 保存数据到SharedPreferences + Future _saveDownloadsToPrefs() async { + final Map downloadsMap = {}; + _downloads.forEach((key, value) { + downloadsMap[key] = value.toJson(); + }); + await _prefs.setString(PREFS_KEY, json.encode(downloadsMap)); + } + + List getLocalSongs() { + final localSongs = []; + _downloads.forEach((key, value) { + if (value.isCompleted) { + localSongs.add(value.song); + } + }); + return localSongs; + } + + Map get downloads => _downloads; + + bool isDownloading(int id) => _downloads[id.toString()]?.isDownloading ?? false; + bool isCompleted(int id) => _downloads[id.toString()]?.isCompleted ?? false; + double getProgress(int id) => _downloads[id.toString()]?.progress ?? 0.0; + + Song? getLocalSong(int id) { + final downloadItem = _downloads[id.toString()]; + if (downloadItem?.isCompleted ?? false) { + return downloadItem!.song; + } + return null; + } + + bool removeSong(int id) { + if (_downloads[id.toString()]?.isCompleted ?? false) { + File file = File.fromUri(Uri.parse(_downloads[id.toString()]!.song.musicurl)); + file.deleteSync(); + _downloads.remove(id.toString()); + _saveDownloadsToPrefs(); + return true; + } + return false; + } + + int completedNumber() { + int count = 0; + _downloads.forEach((key, value) { + if (value.isCompleted) { + count++; + } + }); + return count; + } + + Future startDownload({ + required Song song, + required context, + }) async { + if (_downloads[song.id.toString()]?.isDownloading ?? false) return; + + final fileName = '${song.id}_${song.title}_${song.artist}'; + + final downloadItem = DownloadItem( + song: song, + isDownloading: true, + ); + _downloads[song.id.toString()] = downloadItem; + + try { + final filePath = await downloadApi.downloadMusic( + musicUrl: song.musicurl, + name: fileName, + context: context, + onProgress: (progress) { + downloadItem.progress = progress; + _downloads[song.id.toString()] = downloadItem; + }, + ); + + if (filePath != null) { + downloadItem.isCompleted = true; + downloadItem.isDownloading = false; + downloadItem.progress = 1.0; + song.musicurl = _getLocalAudioPath(fileName, song.musicurl); + print(song.musicurl); + } else { + downloadItem.isDownloading = false; + downloadItem.progress = 0.0; + } + } catch (e) { + print('Download error: $e'); + downloadItem.isDownloading = false; + downloadItem.progress = 0.0; + } + + _downloads[song.id.toString()] = downloadItem; + await _saveDownloadsToPrefs(); + } + + String _getFileExtension(String url) { + // Remove query parameters + final urlWithoutQuery = url.split('?').first; + // Get the extension including the dot + final extension = path.extension(urlWithoutQuery); + return extension.isNotEmpty ? extension : '.mp3'; // Default to .mp3 if no extension found + } + + String _getLocalAudioPath(String fileName, String url) { + final extension = _getFileExtension(url); + final fullFileName = '$fileName$extension'; + return path.join('/storage/emulated/0/MTMusic', fullFileName); + } +} \ No newline at end of file diff --git a/lib/common_widget/Song_widegt.dart b/lib/common_widget/Song_widegt.dart index 96109cc..b59cb08 100644 --- a/lib/common_widget/Song_widegt.dart +++ b/lib/common_widget/Song_widegt.dart @@ -1,12 +1,20 @@ class Song { - final String pic; - final String artistPic; - final String title; - final String artist; - final String musicurl; - final int id; - final bool likes; - final bool collection; + String pic; + String artistPic; + String title; + String artist; + String musicurl; + int id; + bool likes; + bool collection; - Song({required this.pic,required this.artistPic,required this.title, required this.artist, required this.musicurl,required this.id,required this.likes,required this.collection}); + Song( + {required this.pic, + required this.artistPic, + required this.title, + required this.artist, + required this.musicurl, + required this.id, + required this.likes, + required this.collection}); } diff --git a/lib/view/home_view.dart b/lib/view/home_view.dart index a64f87a..84b6d3e 100644 --- a/lib/view/home_view.dart +++ b/lib/view/home_view.dart @@ -7,6 +7,7 @@ import 'package:music_player_miao/models/search_bean.dart'; import 'package:music_player_miao/view/commend_view.dart'; import '../../view_model/home_view_model.dart'; import '../api/api_music_list.dart'; +import '../common/download_manager.dart'; import '../common_widget/Song_widegt.dart'; import '../common_widget/list_cell.dart'; import '../models/getMusicList_bean.dart'; @@ -23,6 +24,7 @@ class _HomeViewState extends State { final homeVM = Get.put(HomeViewModel()); final TextEditingController _controller = TextEditingController(); bool _isSearching = false; + final downloadManager = Get.put(DownloadManager()); void initState() { super.initState(); diff --git a/lib/view/music_view.dart b/lib/view/music_view.dart index b527d1c..01833ba 100644 --- a/lib/view/music_view.dart +++ b/lib/view/music_view.dart @@ -1,14 +1,12 @@ import 'dart:async'; -import 'dart:io'; -import 'package:path/path.dart' as path; 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_download.dart'; import '../api/api_music_likes.dart'; +import '../common/download_manager.dart'; import '../common_widget/Song_widegt.dart'; class MusicView extends StatefulWidget { @@ -32,9 +30,7 @@ class _MusicViewState extends State { late int currentSongIndex; late AudioPlayer _audioPlayer; - double _downloadProgress = 0.0; - bool _isDownloading = false; - bool _isDownloaded = false; + final downloadManager = Get.put(DownloadManager()); // Stream values Duration _duration = Duration.zero; @@ -55,7 +51,6 @@ class _MusicViewState extends State { List collection = []; bool _isLoading = false; - double _bufferProgress = 0.0; Future _fetchSonglistData() async { setState(() { @@ -70,63 +65,6 @@ class _MusicViewState extends State { }); } - String _getFileExtension(String url) { - // Remove query parameters - final urlWithoutQuery = url.split('?').first; - // Get the extension including the dot - final extension = path.extension(urlWithoutQuery); - return extension.isNotEmpty ? extension : '.mp3'; // Default to .mp3 if no extension found - } - - Future _getLocalAudioPath() async { - if (!_isDownloaded) return null; - - final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; - final extension = _getFileExtension(song2[currentSongIndex]); - final fullFileName = '$fileName$extension'; - return path.join('/storage/emulated/0/MTMusic', fullFileName); - } - - Future _checkIfDownloaded() async { - try { - final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; - final directory = Directory('/storage/emulated/0/MTMusic'); - - if (await directory.exists()) { - final files = directory.listSync(); - final isExists = files.any((file) { - final name = path.basename(file.path); - return name.startsWith(fileName); - }); - - if (mounted) { - setState(() { - _isDownloaded = isExists; - _isDownloading = false; - _downloadProgress = 0.0; - }); - } - } else { - if (mounted) { - setState(() { - _isDownloaded = false; - _isDownloading = false; - _downloadProgress = 0.0; - }); - } - } - } catch (e) { - print('Error checking downloaded file: $e'); - if (mounted) { - setState(() { - _isDownloaded = false; - _isDownloading = false; - _downloadProgress = 0.0; - }); - } - } - } - Future _updateCurrentSong() async { // 立即更新UI和歌曲信息 setState(() { @@ -139,21 +77,16 @@ class _MusicViewState extends State { collectionsnot = collection[currentSongIndex]; }); - // 异步检查下载状态 - await _checkIfDownloaded(); - try { // 在后台停止当前播放 unawaited(_audioPlayer.stop()); // Check for local file first - final localPath = await _getLocalAudioPath(); - final audioSource = localPath != null - ? AudioSource.file(localPath) + final localSong = downloadManager.getLocalSong(currentSongIndex); + final audioSource = localSong != null + ? AudioSource.file(localSong.musicurl) : AudioSource.uri(Uri.parse(song2[currentSongIndex])); - print("-------------" + audioSource.uri.toString()); - // Set the audio source and get duration final duration = await _audioPlayer.setAudioSource( audioSource, @@ -181,7 +114,6 @@ class _MusicViewState extends State { void playerInit() { _audioPlayer = AudioPlayer(); - currentSongIndex = widget.initialSongIndex; // Initialize with first song artistName = widget.songList[widget.initialSongIndex].artist; @@ -244,20 +176,6 @@ class _MusicViewState extends State { ); } - void _initBufferingListener() { - _audioPlayer.bufferedPositionStream.listen((bufferedPosition) { - if (!_isDisposed) { - final bufferProgress = _duration.inMilliseconds > 0 - ? bufferedPosition.inMilliseconds / _duration.inMilliseconds - : 0.0; - - setState(() { - _bufferProgress = bufferProgress; - }); - } - }); - } - void playOrPause() async { if (_audioPlayer.playing) { await _audioPlayer.pause(); @@ -300,8 +218,8 @@ class _MusicViewState extends State { @override void initState() { super.initState(); + currentSongIndex = widget.initialSongIndex; playerInit(); - _initBufferingListener(); _initializeAsync(); } @@ -362,63 +280,45 @@ class _MusicViewState extends State { ), ), IconButton( - onPressed: _isDownloading || _isDownloaded ? null: () async { - setState(() { - _isDownloading = true; - _downloadProgress = 0; - }); - - final downloadApi = DownloadApi(); - final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; - final filePath = await downloadApi.downloadMusic( - musicUrl: song2[currentSongIndex], - name: fileName, + onPressed: downloadManager.isDownloading(id[currentSongIndex]) || + downloadManager.isCompleted(id[currentSongIndex]) + ? null + : () async { + await downloadManager.startDownload( + song: widget.songList[currentSongIndex], context: context, - onProgress: (progress) { // 添加进度回调 - setState(() { - _downloadProgress = progress; - }); - }, ); - - setState(() { - _isDownloading = false; - }); - - if (filePath != null) { - setState(() { - _isDownloaded = true; - }); - } }, - - icon: _isDownloading - ? Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator( - value: _downloadProgress, - backgroundColor: Colors.grey[200], - valueColor: AlwaysStoppedAnimation(Color(0xff429482)), - strokeWidth: 3.0, - ), - ), - Text( - '${(_downloadProgress * 100).toInt()}', - style: TextStyle(fontSize: 12), - ), - ], - ) - : Image.asset( - _isDownloaded - ? "assets/img/music_download_completed.png" // 已下载显示完成图标 - : "assets/img/music_download.png", // 未下载显示下载图标 - width: 30, - height: 30, - ), + 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(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, + ); + }), ), ], ) @@ -479,11 +379,13 @@ class _MusicViewState extends State { children: [ IconButton( onPressed: () async{ - UniversalBean bean1 = - await LikesApiMusic().likesMusic(musicId: currentSongIndex+1, Authorization: AppData().currentToken); - setState(() { - likesnot = !likesnot; - }); + UniversalBean response = + await LikesApiMusic().likesMusic(musicId: id[currentSongIndex], Authorization: AppData().currentToken); + if (response.code == 200) { + setState(() { + likesnot = !likesnot; + }); + } }, icon: Image.asset( likesnot @@ -646,7 +548,7 @@ class _MusicViewState extends State { ), Expanded( child: ListView.builder( - itemCount: 3, + itemCount: music.length, itemBuilder: (BuildContext context, int index) { bool isCurrentlyPlaying = currentSongIndex == index; return ListTile( diff --git a/lib/view/user/my_download_view.dart b/lib/view/user/my_download_view.dart new file mode 100644 index 0000000..6eb6dc9 --- /dev/null +++ b/lib/view/user/my_download_view.dart @@ -0,0 +1,508 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:music_player_miao/common_widget/Song_widegt.dart'; +import '../../common/download_manager.dart'; +import '../../view_model/home_view_model.dart'; +import '../music_view.dart'; + +class MyDownloadView extends StatefulWidget { + const MyDownloadView({super.key}); + + @override + State createState() => _MyDownloadViewState(); +} + +class _MyDownloadViewState extends State { + final listVM = Get.put(HomeViewModel()); + bool _isSelectMode = false; + final List _mySongListSelections = List.generate(2, (index) => false); + List _selectedItems = []; + List _songs = []; + final downloadManager = Get.put(DownloadManager()); + + @override + void initState() { + super.initState(); + _getSongs(); + } + + void _toggleSelectMode() { + setState(() { + _isSelectMode = !_isSelectMode; + if (!_isSelectMode) { + _selectedItems = List.generate(_songs.length, (index) => false); + } + }); + } + + void _getSongs() async { + setState(() { + _songs = downloadManager.getLocalSongs(); + }); + } + + void _selectAll() { + setState(() { + _selectedItems = List.generate(_songs.length, (index) => true); + }); + } + + void _deleteSongs(List selectedItems) { + // List songsToDelete = []; + for (int i = 0; i < selectedItems.length; i++) { + if (selectedItems[i]) { + // songsToDelete.add(_songs[i]); + downloadManager.removeSong(_songs[i].id); + } + } + + // 重新加载歌曲列表 + _getSongs(); + } + + void _deleteSong(int index) { + downloadManager.removeSong(_songs[index].id); + _getSongs(); + } + + void _showSelectionDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + title: Row( + children: [ + const Text( + "添加到", + ), + Text( + '(${_selectedItems.where((item) => item).length} 首)', + style: const TextStyle(color: Color(0xff429482), fontSize: 16), + ) + ], + ), + content: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + for (int i = 0; i < _mySongListSelections.length; i++) + _buildSongListTile(i), + ], + ), + ), + 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: () {}, + 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), + ), + ), + ], + ); + }, + ); + } + + Widget _buildSongListTile(int index) { + return ListTile( + title: Text("我的歌单 $index"), + trailing: Checkbox( + value: _mySongListSelections[index], + onChanged: (value) { + setState(() { + _mySongListSelections[index] = value ?? false; + }); + }, + shape: const CircleBorder(), + activeColor: const Color(0xff429482), + ), + onTap: () { + setState(() { + _mySongListSelections[index] = !_mySongListSelections[index]; + }); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/img/app_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + elevation: 0, + leading: !_isSelectMode + ? IconButton( + onPressed: () { + Get.back(result: true); + }, + icon: Image.asset( + "assets/img/back.png", + width: 25, + height: 25, + fit: BoxFit.contain, + ), + ) + : TextButton( + onPressed: _selectAll, + style: TextButton.styleFrom( + foregroundColor: Colors.black, + minimumSize: const Size(50, 40), // 设置最小宽度,确保文字有足够空间 + padding: + const EdgeInsets.symmetric(horizontal: 8), // 添加水平内边距 + ), + child: const Text( + '全选', + style: TextStyle(fontSize: 18), + ), + ), + title: _isSelectMode + ? Text( + '已选中 ${_selectedItems.where((item) => item).length} 首歌曲', + style: const TextStyle( + color: Colors.black, + ), + ) + : const Text( + '本地下载', + style: TextStyle(color: Colors.black), + ), + actions: [ + if (_isSelectMode) + TextButton( + onPressed: () { + setState(() { + _isSelectMode = false; + _selectedItems = List.generate(_songs.length, (index) => false); + }); + }, + child: const Text( + "完成", + style: TextStyle(color: Colors.black, fontSize: 18), + )) + ], + ), + body: Container( + padding: const EdgeInsets.only(left: 10), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(30)), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + IconButton( + onPressed: _songs.isEmpty ? null : () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MusicView( + songList: _songs, + initialSongIndex: 0, + ), + ), + ); + }, + icon: Image.asset( + "assets/img/button_play.png", + width: 20, + height: 20, + ), + ), + const Text( + '播放全部', + style: TextStyle(fontSize: 16), + ), + const SizedBox( + width: 5, + ), + Text( + '${_songs.length}', + style: TextStyle(fontSize: 16), + ), + ], + ), + IconButton( + onPressed: _songs.isEmpty ? null : _toggleSelectMode, + icon: Image.asset( + "assets/img/list_op.png", + width: 20, + height: 20, + ), + ), + ], + ), + Expanded( + child: ListView.builder( + itemCount: _songs.length, + itemBuilder: (BuildContext context, int index) { + final song = _songs[index]; + return Container( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: ListTile( + leading: _isSelectMode + ? Checkbox( + value: _selectedItems[index], + onChanged: (value) { + setState(() { + _selectedItems[index] = value!; + }); + }, + shape: const CircleBorder(), + activeColor: const Color(0xff429482), + ) + : null, + title: Text('${song.title} - ${song.artist}'), + trailing: _isSelectMode + ? null + : IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + _bottomSheet(context, index); + }, + ), + // 添加点击事件 + onTap: _isSelectMode + ? () { + // 在选择模式下点击整行触发复选框 + setState(() { + _selectedItems[index] = + !_selectedItems[index]; + }); + } + : () { + // 非选择模式下跳转到播放页面 + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MusicView( + songList: _songs, + initialSongIndex: index, // 使用当前点击的歌曲索引 + ), + ), + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + bottomNavigationBar: _isSelectMode + ? BottomAppBar( + height: 140, // 增加 BottomAppBar 的高度 + child: SingleChildScrollView( + // 使用 SingleChildScrollView 包装内容 + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + // 左半边 "添加到" 按钮 + Expanded( + child: InkWell( + onTap: () { + _showSelectionDialog(); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 25, + height: 25, + child: Image.asset("assets/img/add.png"), + ), + const SizedBox(width: 4), + const Text("添加到"), + ], + ), + ), + ), + // 中间分隔线 + Container( + height: 50, + width: 2, + color: const Color(0xff429482), + ), + // 右半边 "删除" 按钮 + Expanded( + child: InkWell( + onTap: () { + _deleteSongs(_selectedItems); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 22, + height: 22, + child: + Image.asset("assets/img/delete.png"), + ), + const SizedBox(width: 4), + const Text("删除"), + ], + ), + ), + ), + ], + ), + ), + ElevatedButton( + onPressed: () { + setState(() { + _isSelectMode = false; + _selectedItems = + List.generate(_songs.length, (index) => false); + }); + }, + 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: 16), + ), + ), + ], + ), + ), + ) + : null, + ), + ); + } + + Future _bottomSheet(BuildContext context, int index) { + return showModalBottomSheet( + context: context, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(30))), + builder: (context) => Container( + height: 180, + padding: const EdgeInsets.only(top: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + IconButton( + onPressed: () { + _deleteSong(index); + Navigator.pop(context); + }, + icon: Image.asset("assets/img/list_remove.png"), + iconSize: 60, + ), + const Text("删除") + ], + ), + Column( + children: [ + IconButton( + onPressed: () {}, + icon: Image.asset("assets/img/list_collection.png"), + iconSize: 60, + ), + const Text("收藏") + ], + ), + Column( + children: [ + IconButton( + onPressed: () {}, + icon: Image.asset("assets/img/list_good.png"), + iconSize: 60, + ), + const Text("点赞") + ], + ), + Column( + children: [ + IconButton( + onPressed: () {}, + icon: Image.asset("assets/img/list_comment.png"), + iconSize: 60, + ), + const Text("评论") + ], + ), + ], + ), + const SizedBox( + height: 22, + ), + ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xff429482), + padding: const EdgeInsets.symmetric(vertical: 8), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + ), + child: const Text( + "取消", + style: TextStyle(color: Colors.black, fontSize: 18), + ), + ), + ], + ), + )); + } +} diff --git a/lib/view/user/my_music_view.dart b/lib/view/user/my_music_view.dart index ff386f0..7302124 100644 --- a/lib/view/user/my_music_view.dart +++ b/lib/view/user/my_music_view.dart @@ -139,7 +139,7 @@ class _MyMusicViewState extends State { leading: !_isSelectMode ?IconButton( onPressed: () { - Get.back(); + Get.back(result: true); }, icon: Image.asset( "assets/img/back.png", diff --git a/lib/view/user/user_view.dart b/lib/view/user/user_view.dart index c30ebb1..aa46e31 100644 --- a/lib/view/user/user_view.dart +++ b/lib/view/user/user_view.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:music_player_miao/api/api_songlist.dart'; @@ -5,11 +7,13 @@ import 'package:music_player_miao/common_widget/app_data.dart'; import 'package:music_player_miao/models/songlist_bean.dart'; import 'package:music_player_miao/models/universal_bean.dart'; import 'package:music_player_miao/view/begin/begin_view.dart'; +import 'package:music_player_miao/view/user/my_download_view.dart'; import 'package:music_player_miao/view/user/my_music_view.dart'; import 'package:music_player_miao/view/user/user_info.dart'; import 'package:music_player_miao/widget/text_field.dart'; import '../../../view_model/home_view_model.dart'; import '../../api/api_client.dart'; +import '../../common/download_manager.dart'; import '../../models/search_bean.dart'; import 'my_work_view.dart'; @@ -26,14 +30,17 @@ class _UserViewState extends State { int playlistCount = 2; List playlistNames = []; List playlistid = []; + int downloadCount = 0; + + final downloadManager = Get.put(DownloadManager()); @override void initState() { super.initState(); _fetchSonglistData(); + downloadCount = downloadManager.completedNumber(); } - ///getSonglist Future _fetchSonglistData() async { try { SearchBean bean2 = await SonglistApi().getSonglist( @@ -144,22 +151,29 @@ class _UserViewState extends State { height: 10, ), InkWell( - onTap: () { - Get.to(const MyMusicView()); + onTap: () async { + final result = await Get.to(const MyDownloadView()); + + if (result == true) { + setState(() { + downloadCount = downloadManager.completedNumber(); + }); + } + }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Image.asset("assets/img/artist_pic.png"), - const Column( + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( "本地下载", style: TextStyle(fontSize: 20), ), Text( - "19首", + '${downloadCount}首', style: TextStyle(fontSize: 16), ), ], diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8a04889..0d22e1e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import audioplayers_darwin import file_selector_macos import just_audio import path_provider_foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) @@ -17,4 +18,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 9dd8006..4d22eee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -592,6 +592,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index f212b9d..7fafe94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: audioplayers: ^5.2.1 permission_handler: ^11.0.1 just_audio: ^0.9.34 + shared_preferences: ^2.2.0 # The following adds the Cupertino Icons font to your application.