Compare commits

..

3 Commits

Author SHA1 Message Date
Spark 874c491b0d fix: 修复合并后出现的一些Bug,调整了一些样式
2 weeks ago
Spark 08a374f864 Merge branch 'refs/heads/dev2'
2 weeks ago
Spark db425e3cbe feat: 完成搜索功能
2 weeks ago

@ -0,0 +1,2 @@
#Mon Oct 28 21:45:22 CST 2024
java.home=C\:\\Program Files\\Android\\Android Studio\\jbr

@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Oct 28 21:45:22 CST 2024
sdk.dir=C\:\\Users\\zxp\\AppData\\Local\\Android\\Sdk

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
systemProp.org.gradle.wrapper.timeout=300000

@ -1,7 +1,6 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../models/universal_bean.dart';
const String _changeNameURL = 'http://flyingpig.fun:10010/users/username';
@ -9,6 +8,7 @@ const String _changeHeaderURL = 'http://flyingpig.fun:10010/users/avatar';
class ChangeApiClient {
final Dio dio = Dio();
final ValueNotifier<String> avatarUrlNotifier = ValueNotifier<String>("");
///
Future<UniversalBean> changeName({
required String Authorization,
@ -16,13 +16,19 @@ class ChangeApiClient {
}) async {
Response response = await dio.put(
_changeNameURL,
data: {
'Authorization': Authorization,
},
// data: {
// 'Authorization': Authorization,
// },
queryParameters: {'userName':userName},
options: Options(headers:{'Authorization':Authorization,'Content-Type':'application/json;charset=UTF-8'})
options: Options(
headers:{
'Authorization':Authorization,
'Content-Type':'application/json;charset=UTF-8'
}
)
);
print(response.data);
return UniversalBean.formMap(response.data);
}
///
@ -32,7 +38,7 @@ class ChangeApiClient {
required File avatar,
}) async {
FormData formData = FormData.fromMap({
'Authorization': Authorization,
// 'Authorization': Authorization,
'avatar': await MultipartFile.fromFile(avatar.path, filename: 'avatar.jpg'),
});
@ -46,8 +52,8 @@ class ChangeApiClient {
},
),
);
print(response.data);
avatarUrlNotifier.value = avatar.path;
return UniversalBean.formMap(response.data);
}

@ -1,13 +1,15 @@
import 'package:dio/dio.dart';
import '../common_widget/Song_widegt.dart';
import '../models/getMusicList_bean.dart';
import '../models/getRank_bean.dart';
const String _getMusic1 = 'http://flyingpig.fun:10010/musics/1';
const String _getMusic2 = 'http://flyingpig.fun:10010/musics/2';
const String _getMusic3 = 'http://flyingpig.fun:10010/musics/3';
const String _getSongDetail = 'http://flyingpig.fun:10010/musics';
///
///
class GetMusic {
final Dio dio = Dio();
@ -51,3 +53,40 @@ class GetMusic {
return MusicListBean.formMap(response.data);
}
}
//
class GetMusicDetail {
final Dio dio = Dio();
// ID
Future<Song> getMusicDetail({required int songId, required String Authorization}) async {
try {
// songId URL
final String url = 'http://flyingpig.fun:10010/musics/$songId'; // API
// GET
Response response = await dio.get(
url,
options: Options(
headers: {
'Authorization': Authorization,
'Content-Type': 'application/json;charset=UTF-8',
},
),
);
print("Song detail response: ${response.data}");
//
if (response.statusCode == 200) {
// Song
return Song.fromMap(response.data['data']);
} else {
throw Exception("Failed to load song details");
}
} catch (e) {
print("Error occurred while fetching song details: $e");
rethrow; //
}
}
}

@ -9,18 +9,27 @@ import '../models/search_bean.dart';
const String _SearchURL = 'http://flyingpig.fun:10010/musics/search';
const String _postComment = 'http://flyingpig.fun:10010/comments';
///
class SearchMusic{
class SearchMusic {
final Dio dio = Dio();
Future<SearchBean> search({required String keyword,}) async {
Future<SearchBean> search({
required String keyword,
required String Authorization,
}) async {
Response response = await dio.get(
_SearchURL,
queryParameters: {'keyword':keyword}
queryParameters: {'keyword': keyword},
options: Options(headers: {
'Authorization': Authorization,
'Content-Type': 'application/json;charset=UTF-8',
}),
);
print(response.data);
return SearchBean.formMap(response.data);
}
}
///
class commentMusic {
final Dio dio = Dio();

@ -8,13 +8,29 @@ class Song {
bool likes;
bool collection;
Song(
{required this.pic,
//
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});
required this.collection,
});
// 使 Map Song
factory Song.fromMap(Map<String, dynamic> map) {
return Song(
pic: map['coverPath'] ?? '', // coverPath
artistPic: map['coverPath'] ?? '', // 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
);
}
}

@ -21,17 +21,25 @@ class HomeView extends StatefulWidget {
}
class _HomeViewState extends State<HomeView> {
// 使 GetX HomeViewModel homeVM
// Get.put() HomeViewModel GetX 便使
final homeVM = Get.put(HomeViewModel());
// TextEditingController
//
final TextEditingController _controller = TextEditingController();
// _isSearching
// _isSearching true_isSearching false
bool _isSearching = false;
final downloadManager = Get.put(DownloadManager());
void initState() {
super.initState();
_fetchSonglistData();
}
List<Song> songs = [];
List<Song> selectedSongs = [];
Future<void> _fetchSonglistData() async {
MusicListBean bean1 =
await GetMusic().getMusic1(Authorization: AppData().currentToken);
@ -41,7 +49,7 @@ class _HomeViewState extends State<HomeView> {
await GetMusic().getMusic3(Authorization: AppData().currentToken);
setState(() {
songs = [
selectedSongs = [
Song(
artistPic: bean1.coverPath!,
title: bean1.name!,
@ -81,23 +89,85 @@ class _HomeViewState extends State<HomeView> {
{"image": "assets/img/banner.png"},
];
List<String> _filteredData = [];
List<Song> _filteredData = [];
Future<void> _filterData(String query) async {
if (query.isNotEmpty) {
SearchBean bean = await SearchMusic().search(keyword: query);
if (bean.code == 200) {
try {
//
SearchBean bean = await SearchMusic().search(
keyword: query,
Authorization: AppData().currentToken,
);
//
if (bean.code == 200 && bean.data != null) {
//
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 = bean.data
?.map((data) =>
"${data.name} ") // Adjust this based on your data structure
.toList() ??
[];
_isSearching = true;
_filteredData = [];
_isSearching = false;
});
}
} catch (error) {
print("Error occurred during search: $error");
setState(() {
_filteredData = [];
_isSearching = false;
});
}
} else {
setState(() {
_filteredData = [];
_isSearching = false;
});
}
@ -161,7 +231,7 @@ class _HomeViewState extends State<HomeView> {
],
),
),
const SizedBox(height: 10,),
///
Container(
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
@ -226,7 +296,21 @@ class _HomeViewState extends State<HomeView> {
itemCount: _filteredData.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_filteredData[index]),
title: Text(_filteredData[index].title),
onTap: () {
//
Navigator.push(
// 使 Navigator
context,
MaterialPageRoute(
// MusicView
builder: (context) => MusicView(
songList: _filteredData, //
initialSongIndex: index, //
),
),
);
},
);
},
),
@ -235,14 +319,16 @@ class _HomeViewState extends State<HomeView> {
),
),
const SizedBox(height: 10,),
///+
Container(
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
child: const Text(
'每日推荐',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 5,),
Container(
padding: const EdgeInsets.only(left: 20, right: 20, top: 5),
height: 186,
@ -250,29 +336,32 @@ class _HomeViewState extends State<HomeView> {
child: MySwiperWidget,
),
const SizedBox(height: 10),
///
Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.only(left: 20, right: 20, top: 5),
child: const Text(
'精选歌曲',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 5,),
ListView.builder(
padding: EdgeInsets.zero,
itemCount: songs.length,
itemCount: selectedSongs.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
leading: Image.network(songs[index].pic),
leading: Image.network(selectedSongs[index].pic),
title: Text(
songs[index].title,
selectedSongs[index].title,
style: const TextStyle(fontSize: 18, color: Colors.black),
),
subtitle: Text(
songs[index].artist,
selectedSongs[index].artist,
style: const TextStyle(fontSize: 16, color: Colors.black),
),
trailing: InkWell(
@ -286,14 +375,14 @@ class _HomeViewState extends State<HomeView> {
context,
MaterialPageRoute(
builder: (context) => MusicView(
songList: songs,
songList: selectedSongs,
initialSongIndex: index,
onSongStatusChanged: (index, isCollected, isLiked) {
setState(() {
//
songs[index].collection = isCollected;
songs[index].likes = isLiked;
downloadManager.updateSongInfo(songs[index].id, isCollected, isLiked);
selectedSongs[index].collection = isCollected;
selectedSongs[index].likes = isLiked;
downloadManager.updateSongInfo(selectedSongs[index].id, isCollected, isLiked);
});
},
),
@ -304,14 +393,16 @@ class _HomeViewState extends State<HomeView> {
},
),
const SizedBox(height: 10,),
///
Container(
padding: const EdgeInsets.only(left: 20, right: 20, top: 5),
child: const Text(
'精选歌单',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 5,),
SizedBox(
height: 180,
child: ListView.builder(
@ -399,6 +490,7 @@ class _HomeViewState extends State<HomeView> {
children: [
IconButton(
onPressed: () {
Navigator.pop(context);
Get.to(() => CommentView(
initialSongIndex: index,
));

@ -25,6 +25,7 @@ class _RankViewState extends State<RankView> {
final downloadManager = Get.put(DownloadManager());
@override
void initState() {
super.initState();
_fetchSonglistData();
@ -187,7 +188,7 @@ class _RankViewState extends State<RankView> {
child: RichText(
text: TextSpan(
text: rankNum.toString(),
style: TextStyle(
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xffCE0000),
@ -221,7 +222,7 @@ class _RankViewState extends State<RankView> {
Text(
rankNames[index],
maxLines: 1,
style: TextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight:
@ -230,7 +231,7 @@ class _RankViewState extends State<RankView> {
Text(
rankSingerName[index],
maxLines: 1,
style: TextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 14),
)
@ -298,7 +299,7 @@ class _RankViewState extends State<RankView> {
icon: Image.asset("assets/img/list_add.png"),
iconSize: 60,
),
Text("加入歌单")
const Text("加入歌单")
],
),
Column(
@ -308,7 +309,7 @@ class _RankViewState extends State<RankView> {
icon: Image.asset("assets/img/list_download.png"),
iconSize: 60,
),
Text("下载")
const Text("下载")
],
),
Column(
@ -318,7 +319,7 @@ class _RankViewState extends State<RankView> {
icon: Image.asset("assets/img/list_collection.png"),
iconSize: 60,
),
Text("收藏")
const Text("收藏")
],
),
Column(
@ -328,17 +329,19 @@ class _RankViewState extends State<RankView> {
icon: Image.asset("assets/img/list_good.png"),
iconSize: 60,
),
Text("点赞")
const Text("点赞")
],
),
Column(
children: [
IconButton(
onPressed: (){},
onPressed: (){
Navigator.pop(context);
},
icon: Image.asset("assets/img/list_comment.png"),
iconSize: 60,
),
Text("评论")
const Text("评论")
],
),
],

@ -1,17 +1,11 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:music_player_miao/widget/text_field.dart';
import 'package:image_picker/image_picker.dart';
import '../../api/api_client.dart';
import '../../api/api_client_info.dart';
import '../../common_widget/app_data.dart';
import '../../models/getInfo_bean.dart';
import '../../models/universal_bean.dart';
import '../../view_model/home_view_model.dart';
import 'package:image_picker/image_picker.dart';
class UserInfo extends StatefulWidget {
const UserInfo({super.key});
@ -23,7 +17,7 @@ class UserInfo extends StatefulWidget {
class _UserInfoState extends State<UserInfo> {
final listVM = Get.put(HomeViewModel());
final TextEditingController _controller = TextEditingController();
late File _selectedImage;
File? _selectedImage;
@override
Widget build(BuildContext context) {
@ -42,7 +36,7 @@ class _UserInfoState extends State<UserInfo> {
elevation: 0,
leading: IconButton(
onPressed: () {
Get.back();
Get.back(result: true);
},
icon: Image.asset(
"assets/img/back.png",
@ -60,7 +54,18 @@ class _UserInfoState extends State<UserInfo> {
body: SingleChildScrollView(
child: Column(
children: [
Container(
_buildAvatarRow(),
_buildNicknameRow(),
],
),
),
),
);
}
//
Widget _buildAvatarRow() {
return Container(
height: 80,
color: Colors.white.withOpacity(0.6),
padding: const EdgeInsets.only(left: 48, right: 25),
@ -77,26 +82,24 @@ class _UserInfoState extends State<UserInfo> {
),
Row(
children: [
Image.network(
AppData().currentAvatar,
width: 64,
height: 64,
),
const SizedBox(
width: 20,
),
_buildAvatarImage(),
const SizedBox(width: 20),
Image.asset(
"assets/img/user_next.png",
width: 25,
height: 25,
)
],
)
),
],
),
],
),
),
Container(
);
}
//
Widget _buildNicknameRow() {
return Container(
height: 80,
color: Colors.white.withOpacity(0.6),
padding: const EdgeInsets.only(left: 48, right: 25),
@ -117,27 +120,38 @@ class _UserInfoState extends State<UserInfo> {
AppData().currentUsername,
style: const TextStyle(fontSize: 20),
),
const SizedBox(
width: 15,
),
const SizedBox(width: 15),
Image.asset(
"assets/img/user_next.png",
width: 25,
height: 25,
)
],
)
],
),
),
)
],
),
],
),
),
);
}
// Widget URL
Widget _buildAvatarImage() {
final avatarPath = AppData().currentAvatar;
if (avatarPath.startsWith('http')) {
return Image.network(
avatarPath,
width: 64,
height: 64,
);
} else {
return Image.file(
File(avatarPath),
width: 64,
height: 64,
);
}
}
Future _bottomSheet(BuildContext context) async {
final picker = ImagePicker();
await showModalBottomSheet(
@ -146,7 +160,7 @@ class _UserInfoState extends State<UserInfo> {
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
builder: (context) => Container(
height: 132,
height: 80,
padding: const EdgeInsets.only(top: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -157,18 +171,19 @@ class _UserInfoState extends State<UserInfo> {
final pickedFile =
await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
_selectedImage=File('assets/images/bg.png');
setState(() {
_selectedImage = File(pickedFile.path);
print(_selectedImage);
});
UniversalBean bean = await ChangeApiClient().changeHeader(
setState(() {}); // UI
//
await ChangeApiClient().changeHeader(
Authorization: AppData().currentToken,
avatar: _selectedImage);
GetInfoBean bean1 = await GetInfoApiClient().getInfo(
avatar: _selectedImage!);
//
_updatetouxiang(_selectedImage!.path);
//
await GetInfoApiClient().getInfo(
Authorization: AppData().currentToken);
AppData appData = AppData();
appData.box.write('currentAvatar', AppData().currentAvatar);
}
},
style: ElevatedButton.styleFrom(
@ -182,24 +197,10 @@ class _UserInfoState extends State<UserInfo> {
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xff429482),
padding: const EdgeInsets.symmetric(vertical: 18),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text(
"取消",
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
],
),
));
),
);
}
void _showNicknameDialog() {
@ -212,46 +213,44 @@ class _UserInfoState extends State<UserInfo> {
"修改昵称",
style: TextStyle(fontSize: 20),
)),
content: TextFieldColor(
content: TextField(
controller: _controller,
hintText: '请输入新昵称',
decoration: const InputDecoration(hintText: '请输入新昵称'),
),
actions: <Widget>[
TextButton(
onPressed: () {
onPressed: () async {
_updateNickname();
await ChangeApiClient().changeName(
Authorization: AppData().currentToken,
userName: AppData().currentUsername);
Navigator.of(context).pop();
},
style: TextButton.styleFrom(
backgroundColor: const Color(0xff429482),
minimumSize: const Size(130, 50),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5.0), // Adjust the radius as needed
borderRadius: BorderRadius.circular(5.0),
),
),
child: const Text(
"取消",
"保存",
style: TextStyle(color: Colors.white),
),
),
TextButton(
onPressed: () async {
_updateNickname();
UniversalBean bean = await ChangeApiClient().changeName(
Authorization: AppData().currentToken,
userName: AppData().currentUsername);
onPressed: () {
Navigator.of(context).pop();
},
style: TextButton.styleFrom(
backgroundColor: const Color(0xff429482),
minimumSize: const Size(130, 50),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5.0), // Adjust the radius as needed
borderRadius: BorderRadius.circular(5.0),
),
),
child: const Text(
"保存",
"取消",
style: TextStyle(color: Colors.white),
),
),
@ -267,4 +266,14 @@ class _UserInfoState extends State<UserInfo> {
appData.box.write('currentUsername', _controller.text);
});
}
void _updatetouxiang(String path) {
setState(() {
AppData appData = AppData();
appData.box.write('currentAvatar', path); //
});
}
}

@ -31,6 +31,8 @@ class _UserViewState extends State<UserView> {
List playlistNames = [];
List playlistid = [];
int downloadCount = 0;
String avatar = AppData().currentAvatar;
String username = AppData().currentUsername;
final downloadManager = Get.put(DownloadManager());
@ -85,7 +87,7 @@ class _UserViewState extends State<UserView> {
Row(
children: [
Image.network(
AppData().currentAvatar,
avatar,
width: 64,
height: 64,
),
@ -93,7 +95,7 @@ class _UserViewState extends State<UserView> {
width: 25,
),
Text(
AppData().currentUsername,
username,
style: const TextStyle(fontSize: 20),
)
],
@ -424,8 +426,15 @@ class _UserViewState extends State<UserView> {
Column(
children: [
IconButton(
onPressed: () {
Get.to(const UserInfo());
onPressed: () async {
Navigator.pop(context);
bool result = await Get.to(const UserInfo());
if (result) {
setState(() {
avatar = AppData().currentAvatar;
username = AppData().currentUsername;
});
}
},
icon: Image.asset("assets/img/user_infor.png"),
iconSize: 60,
@ -437,6 +446,7 @@ class _UserViewState extends State<UserView> {
children: [
IconButton(
onPressed: () async {
Navigator.pop(context);
UniversalBean bean = await LogoutApiClient().logout(
Authorization: AppData().currentToken,
);
@ -453,21 +463,6 @@ class _UserViewState extends State<UserView> {
],
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xff429482),
padding: const EdgeInsets.symmetric(vertical: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text(
"取消",
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
],
),
),

@ -30,7 +30,7 @@ environment:
dependencies:
flutter:
sdk: flutter
win32: ^5.1.0
win32: ^5.7.1
flutter_swiper_view: 1.1.8
audioplayers: ^5.2.1
permission_handler: ^11.0.1

Loading…
Cancel
Save