feat: 适配QQ音乐

chen
Spark 8 months ago
parent 10d326f3ff
commit 7f4a2a89cf

@ -54,6 +54,14 @@ android {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86'
}
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.music_player_miao"
// You can update the following values to match your application needs.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
7d34a1186938d51f8f2b6577f7a6094e
f649f7b73ed118947e70d0b9aa9e6a1f

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -8,12 +8,64 @@
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"type": "UNIVERSAL",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "AndroidManifest.xml"
"outputFile": "universal/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "x86_64/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "x86/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "armeabi-v7a/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "arm64-v8a/AndroidManifest.xml"
}
],
"elementType": "File"

@ -8,12 +8,64 @@
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"type": "UNIVERSAL",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "AndroidManifest.xml"
"outputFile": "universal/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "x86_64/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "x86/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "armeabi-v7a/AndroidManifest.xml"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "arm64-v8a/AndroidManifest.xml"
}
],
"elementType": "File"

@ -8,12 +8,64 @@
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"type": "UNIVERSAL",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "resources-debug.ap_"
"outputFile": "resources-universalDebug.ap_"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "resources-arm64-v8aDebug.ap_"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "resources-x86_64Debug.ap_"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "resources-x86Debug.ap_"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "resources-armeabi-v7aDebug.ap_"
}
],
"elementType": "File"

@ -8,12 +8,64 @@
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"type": "UNIVERSAL",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-debug.apk"
"outputFile": "app-universal-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-arm64-v8a-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-x86_64-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-x86-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-armeabi-v7a-debug.apk"
}
],
"elementType": "File"

File diff suppressed because it is too large Load Diff

@ -837,7 +837,7 @@ library {
}
library {
digests {
sha256: "\342\346o\361\313\365\357\032\221\022+`{\253\254\031\344\275\2603%p\371\264\230%\323\3062\377f\367"
sha256: "\263\345qt\2246\203g\vTm\025vRp\272\311\347\363]\226\211\220\255.\354\354+\v^\322\323"
}
}
library_dependencies {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -26,6 +26,7 @@ class GetMusic {
'Authorization': Authorization,
'Content-Type': 'application/json;charset=UTF-8'
}));
print(response.data);
return MusicsListBean.fromMap(response.data);
}

@ -0,0 +1,20 @@
import 'package:dio/dio.dart';
import 'package:music_player_miao/models/universal_bean.dart';
const String _qqmusicUrl = 'https://qqmusic.aspark.cc/musicUrl';
class QQMusicApiClient {
final Dio dio = Dio();
Future<UniversalBean> get({
required String mid,
}) async {
Response response = await dio.get(
'$_qqmusicUrl?mid=$mid',
options: Options(headers:{'Content-Type':'application/json;charset=UTF-8'})
);
print(mid);
print(response.data);
return UniversalBean.formMap(response.data);
}
}

@ -4,6 +4,7 @@ import 'package:music_player_miao/models/search_bean.dart';
import 'package:music_player_miao/models/songlist_bean.dart';
import '../models/getAllSongs_bean.dart';
import '../models/getLikeList_bean.dart';
import '../models/universal_bean.dart';
import 'package:music_player_miao/models/getMyWorks_bean.dart';
const String _SonglistURL = 'http://8.210.250.29:10010/songlists';
@ -15,7 +16,7 @@ class SonglistApi {
final Dio dio = Dio();
///
Future<MyLikes> getMyCollection({required String Authorization}) async {
Future<LikeListBean> getMyCollection({required String Authorization}) async {
Response response = await dio.get(_MyCollectionURL,
data: {
'Authorization': Authorization,
@ -25,7 +26,7 @@ class SonglistApi {
'Content-Type': 'application/json;charset=UTF-8'
}));
print(response.data);
return MyLikes.formMap(response.data);
return LikeListBean.formMap(response.data);
}
///
Future<MyMusicListBean> getAllSongs({required int id, required String Authorization}) async {

@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:math';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:music_player_miao/api/api_qqmusic.dart';
import '../api/api_collection.dart';
import '../api/api_music_likes.dart';
import '../common_widget/Song_widegt.dart';
@ -21,6 +22,7 @@ enum PlayMode {
class AudioPlayerController extends GetxController {
static AudioPlayerController? _instance;
static AudioPlayerController get instance {
_instance ??= AudioPlayerController._();
return _instance!;
@ -164,7 +166,8 @@ class AudioPlayerController extends GetxController {
final currentIndex = currentSongIndex.value;
likesStatus.value = !likesStatus.value;
likes[currentIndex] = likesStatus.value;
UniversalBean response = await LikesApiMusic().likesMusic(musicId: ids[currentIndex], Authorization: AppData().currentToken);
UniversalBean response = await LikesApiMusic().likesMusic(
musicId: ids[currentIndex], Authorization: AppData().currentToken);
if (response.code != 200) {
likesStatus.value = !likesStatus.value;
likes[currentIndex] = likesStatus.value;
@ -175,7 +178,8 @@ class AudioPlayerController extends GetxController {
final currentIndex = currentSongIndex.value;
collectionsStatus.value = !collectionsStatus.value;
collections[currentIndex] = collectionsStatus.value;
UniversalBean response = await CollectionApiMusic().addCollection(musicId: ids[currentIndex], Authorization: AppData().currentToken);
UniversalBean response = await CollectionApiMusic().addCollection(
musicId: ids[currentIndex], Authorization: AppData().currentToken);
if (response.code != 200) {
collectionsStatus.value = !collectionsStatus.value;
collections[currentIndex] = collectionsStatus.value;
@ -197,10 +201,12 @@ class AudioPlayerController extends GetxController {
await _checkAndUpdateSongStatus(currentSongIndex.value);
try {
final localSong = downloadManager.getLocalSong(songList[currentSongIndex.value].id);
final localSong =
downloadManager.getLocalSong(songList[currentSongIndex.value].id);
if (localSong == null && songUrls[currentSongIndex.value] == '') {
// TODO
if (localSong == null && songList[currentSongIndex.value].mid != null) {
final response = await QQMusicApiClient().get(mid: songList[currentSongIndex.value].mid!);
songUrls[currentSongIndex.value] = response.data!;
}
final audioSource = localSong != null

@ -50,6 +50,7 @@ class DownloadItem {
id: json['song']['id'],
likes: json['song']['likes'],
collection: json['song']['collection'],
mid: json['song']['mid'],
),
progress: json['progress'],
isCompleted: json['isCompleted'],
@ -161,7 +162,9 @@ class DownloadManager extends GetxController {
musicurl: song.musicurl,
id: song.id,
likes: song.likes,
collection: song.collection);
collection: song.collection,
mid: song.mid,
);
final downloadItem = DownloadItem(
song: songCopy,

@ -7,6 +7,7 @@ class Song {
int id;
bool? likes;
bool? collection;
String? mid;
//
Song({
@ -18,19 +19,21 @@ class Song {
required this.id,
required this.likes,
required this.collection,
required this.mid,
});
// 使 Map Song
factory Song.fromMap(Map<String, dynamic> map) {
return Song(
pic: map['coverPath'] ?? '', // coverPath
artistPic: map['coverPath'] ?? '', // artistPic
pic: map['coverPath'] ?? 'https://api.aspark.cc/image/1/6759856d288fd.jpg', // coverPath
artistPic: map['coverPath'] ?? 'https://api.aspark.cc/image/1/6759856d288fd.jpg', // artistPic
title: map['name'] ?? '', // name
artist: map['singerName'] ?? '', // singerName
musicurl: map['musicPath'] ?? '', // musicPath
id: map['id'] ?? 0, // ID id
likes: map['likeOrNot'] ?? false, // likeOrNot
collection: map['collectOrNot'] ?? false, // collectOrNot
mid: map['mid'] ?? '', // mid mid
);
}
}

@ -21,15 +21,17 @@ class MusicItem {
String? uploadUserName;
bool? likeOrNot;
bool? collectOrNot;
String? mid;
MusicItem.fromMap(Map map) {
id = map['id'];
name = map['name'];
coverPath = map['coverPath'];
coverPath = map['coverPath'] == '' ? 'https://api.aspark.cc/image/1/6759856d288fd.jpg' : map['coverPath'];
musicPath = map['musicPath'];
singerName = map['singerName'];
uploadUserName = map['uploadUserName'];
uploadUserName = map['uploadUserName'] ?? '';
likeOrNot = map['likeOrNot'];
collectOrNot = map['collectOrNot'];
mid = map['mid'] ?? '';
}
}

@ -23,16 +23,18 @@ class LikeListData {
String? uploadUserName;
bool? likes;
bool? collection;
String? mid;
LikeListData._formMap(Map map) {
id = map['id'];
name = map['name'];
coverPath = map['coverPath'];
musicPath = map['musicPath'];
musicPath = map['musicPath'] ?? '';
singerName = map['singerName'];
uploadUserName = map['uploadUserName'];
likes = map['likes'];
collection = map['collection'];
mid = map['mid'] ?? '';
}
// Map,
@ -46,6 +48,7 @@ class LikeListData {
'uploadUserName': uploadUserName,
'likes': likes,
'collection': collection,
'mid': mid,
};
}
}

@ -24,6 +24,7 @@ class DataBean {
String? musicPath;
String? singerName;
String? uploadUserName;
String? mid;
DataBean._formMap(Map map) {
id = map['id'];
@ -32,5 +33,6 @@ class DataBean {
musicPath = map['musicPath'];
singerName = map['singerName'];
uploadUserName = map['uploadUserName'];
mid = map['mid'] ?? '';
}
}

@ -23,6 +23,7 @@ class DataBean {
String? coverPath;
String? musicPath;
String? name;
String? mid;
DataBean._formMap(Map map) {
id = map['id'];
@ -30,5 +31,6 @@ class DataBean {
coverPath = map['coverPath'];
musicPath = map['musicPath'];
name = map['name'];
mid = map['mid'];
}
}

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.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/search_bean.dart';
import 'package:music_player_miao/view/comment_view.dart';
import 'package:music_player_miao/view/search_view.dart';
import '../../view_model/home_view_model.dart';
@ -11,14 +9,11 @@ import '../api/api_music_likes.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/MusicsListBean.dart';
import '../models/getMusicList_bean.dart';
import '../models/universal_bean.dart';
import 'music_view.dart';
import '../api/api_collection.dart';
import '../api/api_music_likes.dart';
import '../models/universal_bean.dart';
class HomeView extends StatefulWidget {
const HomeView({super.key});
@ -30,9 +25,6 @@ class HomeView extends StatefulWidget {
class _HomeViewState extends State<HomeView>
with AutomaticKeepAliveClientMixin {
final homeVM = Get.put(HomeViewModel());
// final TextEditingController _controller = TextEditingController();
bool _isSearching = false;
final downloadManager = Get.put(DownloadManager());
List<Song> selectedSongs = [];
@ -59,6 +51,7 @@ class _HomeViewState extends State<HomeView>
try {
MusicsListBean bean = await GetMusic()
.getMusicList(Authorization: AppData().currentToken, num: 10);
setState(() {
selectedSongs = [];
for (var data in bean.data!) {
@ -66,14 +59,17 @@ class _HomeViewState extends State<HomeView>
artistPic: data.coverPath!,
title: data.name!,
artist: data.singerName!,
musicurl: data.musicPath!,
musicurl: data.musicPath,
pic: data.coverPath!,
id: data.id!,
likes: data.likeOrNot!,
collection: data.collectOrNot!,
mid: data.mid,
));
print(data.coverPath!);
}
});
} catch (e) {
print('Error occurred while fetching song list: $e');
}
@ -86,101 +82,6 @@ class _HomeViewState extends State<HomeView>
{"image": "assets/img/banner2.png"},
];
List<Song> _filteredData = [];
Future<void> _filterData(String query) async {
if (query.isNotEmpty) {
try {
//
SearchBean bean = await SearchMusic().search(
keyword: query,
Authorization: AppData().currentToken,
);
//
if (bean.code == 200 && bean.data != null) {
// <EFBFBD><EFBFBD><EFBFBD>
List<Future<Song?>> songDetailsFutures = [];
// id
for (var data in bean.data!) {
if (data.id != null) {
// id null
// 使 id Future
songDetailsFutures.add(GetMusicDetail()
.getMusicDetail(
songId: data.id!,
Authorization: AppData().currentToken,
)
.then((details) {
if (details != null) {
// Song
return Song(
artistPic: details.artistPic ?? '',
//
title: data.name ?? '',
//
artist: details.artist ?? '',
//
musicurl: details.musicurl ?? '',
//
pic: details.pic ?? '',
//
id: details.id,
// ID
likes: details.likes,
//
collection: details.collection, //
);
}
return null; // null
}).catchError((error) {
print("Error occurred while fetching song details: $error");
return null; // null
}));
} else {
print("Song ID is null for song: ${data.name}");
}
}
// 使 Future.wait
List<Song?> songDetailsList = await Future.wait(songDetailsFutures);
// null
List<Song> validSongDetails = songDetailsList
.where((song) => song != null)
.cast<Song>()
.toList();
// UI _filteredData
setState(() {
_filteredData = validSongDetails; //
_isSearching = true; //
});
//
print("Filtered Data: $_filteredData");
} else {
setState(() {
_filteredData = [];
_isSearching = false;
});
}
} catch (error) {
print("Error occurred during search: $error");
setState(() {
_filteredData = [];
_isSearching = false;
});
}
} else {
setState(() {
_filteredData = [];
_isSearching = false;
});
}
}
@override
Widget build(BuildContext context) {
super.build(context);
@ -494,7 +395,7 @@ class _HomeViewState extends State<HomeView>
),
),
const SizedBox(
height: 110,
height: 130,
),
],
),

@ -1,7 +1,6 @@
// music_view.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:music_player_miao/common_widget/app_data.dart';
@ -14,8 +13,6 @@ import '../common/songlist_bottom_sheet.dart';
import '../common_widget/Song_widegt.dart';
import 'comment_view.dart';
import 'dart:async';
import 'package:flutter/material.dart';
class ScrollingText extends StatefulWidget {
final String text;
@ -338,7 +335,12 @@ class _MusicViewState extends State<MusicView>
color: Colors.grey[200],
borderRadius: BorderRadius.circular(112.5),
),
child: const Icon(Icons.error_outline, size: 40),
child: Image.asset(
"assets/img/error.jpg",
width: 225,
height: 225,
fit: BoxFit.cover,
),
);
},
),
@ -587,6 +589,7 @@ class _MusicViewState extends State<MusicView>
SnackBar(
content: Text(response.msg!),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
),
);
}
@ -597,6 +600,7 @@ class _MusicViewState extends State<MusicView>
SnackBar(
content: Text(e.toString()),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
),
);
}
@ -615,17 +619,21 @@ class _MusicViewState extends State<MusicView>
final currentId = audioController
.ids[audioController.currentSongIndex.value];
return IconButton(
onPressed:
downloadManager.isDownloading(currentId) ||
onPressed: downloadManager
.isDownloading(currentId) ||
downloadManager.isCompleted(currentId)
? null
: () async {
Song s = widget.songList[
audioController.currentSongIndex.value];
s.musicurl = audioController.songUrls[
audioController.currentSongIndex.value];
await downloadManager.startDownload(
song: widget.songList[audioController
.currentSongIndex.value],
song: s,
context: context,
);
Get.find<DownloadCountController>().refreshCount(downloadManager);
Get.find<DownloadCountController>()
.refreshCount(downloadManager);
},
icon: downloadManager.isDownloading(currentId)
? Stack(
@ -887,7 +895,7 @@ class _MusicViewState extends State<MusicView>
audioController.playNext(manual: true);
_rotationController.reset();
},
icon:ColorFiltered(
icon: ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.grey[700]!,
BlendMode.srcIn,

@ -1,190 +1,65 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:music_player_miao/common_widget/Song_widegt.dart';
import '../../api/api_songlist.dart';
import '../../common_widget/app_data.dart';
import '../../models/getMyWorks_bean.dart';
import '../../view_model/home_view_model.dart';
import '../models/getMyLike_bean.dart';
import 'package:music_player_miao/api/api_music_likes_list.dart';
import 'package:music_player_miao/common_widget/app_data.dart';
import 'package:music_player_miao/models/getLikeList_bean.dart';
import 'package:music_player_miao/view_model/home_view_model.dart';
import '../../common_widget/Song_widegt.dart';
import '../../common/download_manager.dart';
import '../api/api_songlist.dart';
import 'music_view.dart';
///
class MyCollectionView extends StatefulWidget {
const MyCollectionView({super.key});
const MyCollectionView({Key? key}) : super(key: key);
@override
State<MyCollectionView> createState() => _MyCollectionViewState();
}
class _MyCollectionViewState extends State<MyCollectionView> {
int MyWorkCount = 0;
List MyWorkNames = [];
List Songid = [];
List coverPath = [];
List musicPath = [];
List singerName = [];
List uploaderUserName = [];
final listVM = Get.put(HomeViewModel());
//
List<LikeListData> likedSongs = [];
//
bool _isSelectMode = false;
bool _isSelectListMode = false;
List<bool> _mySongListSelections = List.generate(2, (index) => false);
List<bool> _selectedItems = List.generate(100, (index) => false);
//
List<bool> _selectedItems = [];
//
final listVM = Get.put(HomeViewModel());
final downloadManager = Get.put(DownloadManager());
//
@override
void initState() {
super.initState();
print('初始化正常');
_fetchMyLikesData();
//
_fetchLikedSongs();
}
///
Future<void> _fetchMyLikesData() async {
///
Future<void> _fetchLikedSongs() async {
try {
MyLikes bean2 = await SonglistApi().getMyCollection(
// API
LikeListBean response = await SonglistApi().getMyCollection(
Authorization: AppData().currentToken,
);
//
if (response.code == 200 && response.data != null) {
setState(() {
print("bean2: $bean2");
print('bean2.data: ${bean2.data}');
MyWorkNames = bean2.data!.map((data) => data.name!).toList();
Songid = bean2.data!.map((data) => data.id!).toList();
coverPath = bean2.data!.map((data) => data.coverPath!).toList();
musicPath = bean2.data!.map((data) => data.musicPath!).toList();
singerName = bean2.data!.map((data) => data.singerName!).toList();
uploaderUserName = bean2.data!.map((data) => data.uploadUserName!).toList();
print('赋值开始');
MyWorkCount = MyWorkNames.length;
print('赋值结束');
likedSongs = response.data!;
//
_selectedItems = List.generate(likedSongs.length, (index) => false);
});
} catch (error) {
print('Error fetching myLikes data: $error');
}
} catch (error) {
print('Error fetching liked songs: $error');
}
//
void _toggleSelectMode() {
setState(() {
_isSelectMode = !_isSelectMode;
_isSelectListMode = !_isSelectListMode;
if (!_isSelectMode) {
_selectedItems = List.generate(10, (index) => false);
}
if (!_isSelectListMode) {
_mySongListSelections = List.generate(2, (index) => false);
}
});
}
void _selectAll() {
setState(() {
_selectedItems = List.generate(10, (index) => true);
});
}
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),
),
// Checkbox(
// value: _mySongListSelections[index],
// onChanged: (value) {
// setState(() {
// _mySongListSelections[index] = value!;
// });
// },
// shape: CircleBorder(),
// activeColor: 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"),
@ -193,14 +68,16 @@ class _MyCollectionViewState extends State<MyCollectionView> {
),
child: Scaffold(
backgroundColor: Colors.transparent,
//
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
elevation: 0,
//
leading: !_isSelectMode
?IconButton(
? IconButton( //
onPressed: () {
Get.back();
Get.back(result: true);
},
icon: Image.asset(
"assets/img/back.png",
@ -209,13 +86,23 @@ class _MyCollectionViewState extends State<MyCollectionView> {
fit: BoxFit.contain,
),
)
: TextButton(
onPressed: _selectAll,
: TextButton( //
onPressed: () {
setState(() {
_selectedItems = List.generate(likedSongs.length, (index) => true);
});
},
style: TextButton.styleFrom(
foregroundColor: Colors.black,
minimumSize: const Size(50, 40),
padding: const EdgeInsets.symmetric(horizontal: 8),
),
child: const Text(
'全选',
style: TextStyle(fontSize: 18),
),
child: const Text('全选',style: TextStyle(fontSize: 18),),
),
//
title: _isSelectMode
? Text(
'已选中 ${_selectedItems.where((item) => item).length} 首歌曲',
@ -224,43 +111,48 @@ class _MyCollectionViewState extends State<MyCollectionView> {
),
)
: const Text(
'我的收藏',
'我的点赞',
style: TextStyle(color: Colors.black),
),
//
actions: [
if (_isSelectMode)
TextButton(
onPressed: (){
onPressed: () {
setState(() {
_isSelectMode = false;
_selectedItems = List.generate(10, (index) => false);
_selectedItems = List.generate(likedSongs.length, (index) => false);
});
},
child: const Text(
"完成",
style: TextStyle(
color: Colors.black,
fontSize: 18
),
)
)
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(
//
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//
Row(
children: [
IconButton(
onPressed: (){},
onPressed: likedSongs.isEmpty
? null
: () {
// TODO:
},
icon: Image.asset(
"assets/img/button_play.png",
width: 20,
@ -269,21 +161,25 @@ class _MyCollectionViewState extends State<MyCollectionView> {
),
const Text(
'播放全部',
style: TextStyle(
fontSize: 16
),
),
const SizedBox(width: 5,),
const Text(
'50',
style: TextStyle(
fontSize: 16
style: TextStyle(fontSize: 16),
),
const SizedBox(width: 5),
Text(
'(${likedSongs.length})',
style: const TextStyle(fontSize: 16),
),
],
),
//
IconButton(
onPressed: _toggleSelectMode,
onPressed: likedSongs.isEmpty ? null : () {
setState(() {
_isSelectMode = !_isSelectMode;
if (!_isSelectMode) {
_selectedItems = List.generate(likedSongs.length, (index) => false);
}
});
},
icon: Image.asset(
"assets/img/list_op.png",
width: 20,
@ -292,17 +188,71 @@ class _MyCollectionViewState extends State<MyCollectionView> {
),
],
),
),
//
Expanded(
child: MyWorkCount == 0
? Center(child: Text('你还没有点赞')) //
: ListView.builder(
itemCount: MyWorkCount,
itemBuilder: (BuildContext context, int index) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: ListTile(
leading: _isSelectMode
? Checkbox(
child: ListView.builder(
itemCount: likedSongs.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
final song = likedSongs[index];
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
//
onTap: _isSelectMode
? () {
setState(() {
_selectedItems[index] = !_selectedItems[index];
});
}
: () async {
// Song
List<Song> songList = likedSongs.map((song) => Song(
id: song.id ?? 0,
title: song.name ?? '未知歌曲',
artist: song.singerName ?? '未知歌手',
artistPic: song.coverPath ?? '',
pic: song.coverPath ?? '',
musicurl: song.musicPath ?? '',
likes: song.likes,
collection: song.collection,
mid: song.mid,
)).toList();
//
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MusicView(
songList: songList,
initialSongIndex: index,
//
onSongStatusChanged: (index, isCollected, isLiked) {
setState(() {
songList[index].collection = isCollected;
songList[index].likes = isLiked;
downloadManager.updateSongInfo(
songList[index].id,
isCollected,
isLiked,
);
});
},
),
),
);
//
if (result != null) {
_fetchLikedSongs();
}
},
//
title: Row(
children: [
//
if (_isSelectMode)
Checkbox(
value: _selectedItems[index],
onChanged: (value) {
setState(() {
@ -311,202 +261,151 @@ class _MyCollectionViewState extends State<MyCollectionView> {
},
shape: const CircleBorder(),
activeColor: const Color(0xff429482),
)
: CircleAvatar(
backgroundImage: NetworkImage(coverPath[index]), //
radius: 25,
),
title: Text('${MyWorkNames[index]} - ${singerName[index]}'), //
trailing: _isSelectMode
? null
: IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
_bottomSheet(context);
},
),
),
//
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
song.coverPath ?? '',
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
"assets/img/artist_pic.png",
width: 60,
height: 60,
);
},
),
),
],
),
),
bottomNavigationBar: _isSelectMode
? BottomAppBar(
child: SizedBox(
height: 127.0,
const SizedBox(width: 12),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconButton(
onPressed: (){
_showSelectionDialog();
},
icon: Image.asset("assets/img/list_add.png"),
iconSize: 60,
Text(
song.name ?? '未知歌曲',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
const Text("添加到"),
],
),
Container(
height: 50,
width: 2,
color: const Color(0xff429482),
Text(
song.singerName ?? '未知歌手',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
color: Colors.black54,
),
Row(
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_download.png"),
iconSize: 60,
),
const Text("下载"),
],
),
),
],
),
ElevatedButton(
onPressed: () {
setState(() {
_isSelectMode = false;
_selectedItems = List.generate(10, (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){
return showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
builder: (context) =>Container(
height: 210,
padding: const EdgeInsets.only(top: 20),
//
bottomNavigationBar: _isSelectMode
? BottomAppBar(
height: 140,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
//
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
// "添加到"
Expanded(
child: InkWell(
onTap: () {
// TODO:
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_add.png"),
iconSize: 60,
SizedBox(
width: 25,
height: 25,
child: Image.asset("assets/img/add.png"),
),
const Text("加入歌单")
const SizedBox(width: 4),
const Text("添加到"),
],
),
Column(
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_download.png"),
iconSize: 60,
),
const Text("下载")
],
),
Column(
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_collection.png"),
iconSize: 60,
),
const Text("收藏")
],
// 线
Container(
height: 50,
width: 2,
color: const Color(0xff429482),
),
Column(
// "删除"
Expanded(
child: InkWell(
onTap: () {
// TODO:
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_good.png"),
iconSize: 60,
SizedBox(
width: 22,
height: 22,
child: Image.asset("assets/img/delete.png"),
),
const Text("点赞")
const SizedBox(width: 4),
const Text("删除"),
],
),
Column(
children: [
IconButton(
onPressed: (){},
icon: Image.asset("assets/img/list_comment.png"),
iconSize: 60,
),
const Text("评论")
],
),
],
),
const SizedBox(height: 10,),
),
//
ElevatedButton(
onPressed: () {
// Get.to(()=>const MusicView());
setState(() {
_isSelectMode = false;
_selectedItems = List.generate(likedSongs.length, (index) => false);
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffE6F4F1),
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),
),
),
ElevatedButton(
onPressed: () =>Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xff429482),
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text(
"取消",
style: TextStyle(color:Colors.black,fontSize: 18),
'取消',
style: TextStyle(color: Colors.black, fontSize: 16),
),
),
],
),
),
)
: null,
),
);
}
}

@ -27,6 +27,7 @@ class _RankViewState extends State<RankView> with AutomaticKeepAliveClientMixin
List rankSingerName = [];
List rankCoverPath = [];
List rankMusicPath = [];
List rankMid = [];
List<Song> songs = [];
final downloadManager = Get.put(DownloadManager());
@ -57,14 +58,9 @@ class _RankViewState extends State<RankView> with AutomaticKeepAliveClientMixin
List<int> 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();
rankCoverPath = bean2.data!.map((data) => data.coverPath == '' ? 'https://api.aspark.cc/image/1/6759856d288fd.jpg' : data.coverPath).toList();
rankMusicPath = bean2.data!.map((data) => data.musicPath ?? '').toList();
rankMid = bean2.data!.map((data) => data.mid).toList();
if (rankNames.isNotEmpty &&
rankNames.length == rankSingerName.length &&
@ -80,6 +76,7 @@ class _RankViewState extends State<RankView> with AutomaticKeepAliveClientMixin
id: ids[i],
likes: null,
collection: null,
mid: rankMid[i],
));
}
}
@ -272,25 +269,25 @@ class _RankViewState extends State<RankView> with AutomaticKeepAliveClientMixin
color: Colors.grey[100],
child: const Icon(Icons.music_note, size: 30),
),
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: 60,
height: 60,
color: Colors.grey[100],
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xff429482)),
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
// loadingBuilder: (context, child, loadingProgress) {
// if (loadingProgress == null) return child;
// return Container(
// width: 60,
// height: 60,
// color: Colors.grey[100],
// child: Center(
// child: CircularProgressIndicator(
// strokeWidth: 2,
// valueColor: const AlwaysStoppedAnimation<Color>(
// Color(0xff429482)),
// value: loadingProgress.expectedTotalBytes != null
// ? loadingProgress.cumulativeBytesLoaded /
// loadingProgress.expectedTotalBytes!
// : null,
// ),
// ),
// );
// },
),
),
),
@ -354,7 +351,7 @@ class _RankViewState extends State<RankView> with AutomaticKeepAliveClientMixin
),
),
const SizedBox(
height: 100,
height: 130,
),
],
),

@ -37,11 +37,12 @@ class _SearchViewState extends State<SearchView> {
artistPic: data.coverPath!,
title: data.name!,
artist: data.singerName!,
musicurl: data.musicPath!,
musicurl: data.musicPath,
pic: data.coverPath!,
id: data.id!,
likes: data.likeOrNot!,
collection: data.collectOrNot!,
mid: data.mid,
));
}
});

@ -6,17 +6,8 @@ import '../common_widget/app_data.dart';
import '../models/getRank_bean.dart';
import '../common_widget/Song_widegt.dart';
import 'music_view.dart'; // MusicView
import 'package:music_player_miao/common_widget/app_data.dart';
import '../common/download_manager.dart';
import '../models/getRank_bean.dart';
import '../view_model/rank_view_model.dart';
import 'music_view.dart';
import '../common_widget/Song_widegt.dart';
import '../api/api_collection.dart';
import '../api/api_music_likes.dart';
import '../api/api_music_list.dart';
import '../models/universal_bean.dart';
import 'comment_view.dart';
class SongRecommendationView extends StatefulWidget {
const SongRecommendationView({super.key});
@ -31,6 +22,7 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
List rankSingerName = [];
List rankCoverPath = [];
List rankMusicPath = [];
List rankMid = [];
List<double> relevanceValues = [];
List<Song> songs = [];
final downloadManager = Get.put(DownloadManager());
@ -71,8 +63,9 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
List<int> ids = bean2.data!.take(6).map((data) => data.id!).toList();
rankNames = bean2.data!.take(6).map((data) => data.name!).toList();
rankSingerName = bean2.data!.take(6).map((data) => data.singerName!).toList();
rankCoverPath = bean2.data!.take(6).map((data) => data.coverPath!).toList();
rankMusicPath = bean2.data!.take(6).map((data) => data.musicPath!).toList();
rankCoverPath = bean2.data!.take(6).map((data) => data.coverPath == '' ? 'https://api.aspark.cc/image/1/6759856d288fd.jpg' : data.coverPath).toList();
rankMusicPath = bean2.data!.take(6).map((data) => data.musicPath ?? '').toList();
rankMid = bean2.data!.take(6).map((data) => data.mid).toList();
songs.clear();
for (int i = 0; i < rankNames.length; i++) {
@ -85,6 +78,7 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
id: ids[i],
likes: null,
collection: null,
mid: rankMid[i],
));
relevanceValues.add(0.75);
}
@ -130,21 +124,25 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
return 0.6 + math.Random().nextDouble() * 0.4;
}
double _getCircleSize(double relevance) {
return 20.0 + (relevance * 130);
double _getCircleSize(double relevance, Size screenSize) {
return 0.08 * screenSize.width + (relevance * 80); //
}
Map<String, double> _getCirclePosition(int index, int totalItems, Size screenSize) {
final radius = math.min(screenSize.width, screenSize.height) * 0.28;
final angle = (index * 2 * math.pi / totalItems) - math.pi / 2;
//
Map<String, double> _getCirclePosition(int index, int totalItems, Size screenSize, Offset refreshButtonPosition) {
final radius = math.min(screenSize.width, screenSize.height) * 0.28; //
final angle = (index * 2 * math.pi / totalItems) - math.pi / 2; //
return {
'left': radius * math.cos(angle),
'top': radius * math.sin(angle),
'left': refreshButtonPosition.dx + radius * math.cos(angle), //
'top': refreshButtonPosition.dy + radius * math.sin(angle), //
};
}
//
Color _getCircleColor(double relevance) {
return Color(0xFFB2FF59).withOpacity(0.3);
return Color(0xFFB2FF59).withOpacity(0.3); // 使绿
}
@override
@ -152,6 +150,9 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
final screenSize = MediaQuery.of(context).size;
final safeAreaPadding = MediaQuery.of(context).padding;
//
final refreshButtonPosition = Offset(screenSize.width / 2, screenSize.height / 2);
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
@ -183,7 +184,7 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
child: Stack(
alignment: Alignment.center,
children: [
// 线
// 线
CustomPaint(
size: Size(screenSize.width, screenSize.height),
painter: LinePainter(
@ -191,6 +192,7 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
relevanceValues: relevanceValues,
screenSize: screenSize,
lineAnimation: _animationController,
refreshButtonPosition: refreshButtonPosition,
),
),
//
@ -199,13 +201,13 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
children: List.generate(6, (index) {
final song = songs[index];
final relevance = relevanceValues[index];
final size = _getCircleSize(relevance);
final position = _getCirclePosition(index, songs.length, screenSize);
final size = _getCircleSize(relevance, screenSize);
final position = _getCirclePosition(index, songs.length, screenSize, refreshButtonPosition);
return AnimatedPositioned(
duration: Duration(milliseconds: 500),
left: screenSize.width / 2 + position['left']! - size / 2,
top: screenSize.height / 2 + position['top']! - size / 2 - safeAreaPadding.top + 1, //
left: position['left']! - size / 2,
top: position['top']! - size / 2 - safeAreaPadding.top + 1,
child: FadeTransition(
opacity: _circleFadeInAnimations.isNotEmpty && index < _circleFadeInAnimations.length
? _circleFadeInAnimations[index]
@ -250,13 +252,13 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
animation: dotController,
builder: (context, child) {
final progress = dotController.value;
final position = _getCirclePosition(index, songs.length, screenSize);
final position = _getCirclePosition(index, songs.length, screenSize, refreshButtonPosition);
final radius = math.min(screenSize.width, screenSize.height) * 0.28;
final angle = (index * 2 * math.pi / songs.length) - math.pi / 2;
final dotPosition = Offset(
screenSize.width / 2 + position['left']! - progress * (radius - 60) * math.cos(angle),
screenSize.height / 2 + position['top']! - progress * (radius - 60) * math.sin(angle),
position['left']! - progress * (radius - 60) * math.cos(angle),
position['top']! - progress * (radius - 60) * math.sin(angle),
);
return Positioned(
@ -323,7 +325,54 @@ class _SongRecommendationViewState extends State<SongRecommendationView> with Ti
}
}
// Circle button widget
class LinePainter extends CustomPainter {
final List recommendedSongs;
final List<double> relevanceValues;
final Size screenSize;
final Animation<double> lineAnimation;
final Offset refreshButtonPosition;
LinePainter({
required this.recommendedSongs,
required this.relevanceValues,
required this.screenSize,
required this.lineAnimation,
required this.refreshButtonPosition,
});
@override
void paint(Canvas canvas, Size size) {
final center = refreshButtonPosition; // 使
for (int i = 0; i < recommendedSongs.length; i++) {
final relevance = relevanceValues[i];
final position = _getCirclePosition(i, recommendedSongs.length, size);
final end = Offset(center.dx + position['left']!, center.dy + position['top']!);
final paint = Paint()
..color = Colors.grey.withOpacity(0.2) // 0.5
..strokeWidth = 0.01 + math.pow(relevance, 2) * 10;
// 线
canvas.drawLine(center, end, paint);
}
}
Map<String, double> _getCirclePosition(int index, int totalItems, Size size) {
final radius = math.min(size.width, size.height) * 0.28;
final angle = (index * 2 * math.pi / totalItems) - math.pi / 2;
return {
'left': radius * math.cos(angle),
'top': radius * math.sin(angle),
};
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class SongCircleButton extends StatelessWidget {
final double relevance;
final double size;
@ -346,7 +395,7 @@ class SongCircleButton extends StatelessWidget {
});
Color _getCircleColor() {
return Color(0xFFFFC1E3);
return Color(0xFFFFC1E3); //
}
@override
@ -357,26 +406,22 @@ class SongCircleButton extends StatelessWidget {
width: size,
height: size,
decoration: BoxDecoration(
color: _getCircleColor().withOpacity(0.9),
borderRadius: BorderRadius.circular(size / 2),
color: _getCircleColor(),
borderRadius: BorderRadius.circular(size / 2), //
boxShadow: [
BoxShadow(
color: _getCircleColor().withOpacity(0.6), //
blurRadius: 12, //
spreadRadius: 8, //
color: _getCircleColor().withOpacity(0.6), //
blurRadius: 12, //
spreadRadius: 8, //
),
],//
],
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
Text(
' ',
'',
style: TextStyle(
color: Colors.white,
fontSize: 18,
@ -384,7 +429,6 @@ class SongCircleButton extends StatelessWidget {
),
),
const SizedBox(height: 4),
//
Text(
songTitle,
textAlign: TextAlign.center,
@ -395,7 +439,6 @@ class SongCircleButton extends StatelessWidget {
),
),
const SizedBox(height: 2),
//
Text(
artistName,
textAlign: TextAlign.center,
@ -411,48 +454,3 @@ class SongCircleButton extends StatelessWidget {
);
}
}
class LinePainter extends CustomPainter {
final List recommendedSongs;
final List<double> relevanceValues;
final Size screenSize;
final Animation<double> lineAnimation;
LinePainter({
required this.recommendedSongs,
required this.relevanceValues,
required this.screenSize,
required this.lineAnimation,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
for (int i = 0; i < recommendedSongs.length; i++) {
final relevance = relevanceValues[i];
final position = _getCirclePosition(i, recommendedSongs.length, size);
final end = Offset(center.dx + position['left']!, center.dy + position['top']!);
final paint = Paint()
..color = Colors.grey.withOpacity(0.2) // 0.5
..strokeWidth = 0.01 + math.pow(relevance, 2) * 10;
canvas.drawLine(center, end, paint);
}
}
Map<String, double> _getCirclePosition(int index, int totalItems, Size size) {
final radius = math.min(size.width, size.height) * 0.28;
final angle = (index * 2 * math.pi / totalItems) - math.pi / 2;
return {
'left': radius * math.cos(angle),
'top': radius * math.sin(angle),
};
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

@ -215,6 +215,7 @@ class _MyLikesViewState extends State<MyLikesView> {
musicurl: song.musicPath ?? '',
likes: song.likes,
collection: song.collection,
mid: song.mid,
)).toList();
//

Loading…
Cancel
Save