You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
12 KiB
420 lines
12 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import '../api/api_music_likes.dart';
|
|
import '../api/api_music_list.dart';
|
|
import '../api/api_music_return.dart';
|
|
import '../common/download_manager.dart';
|
|
import '../common_widget/Song_widegt.dart';
|
|
import '../common_widget/app_data.dart';
|
|
import '../models/MusicsListBean.dart';
|
|
import '../models/universal_bean.dart';
|
|
import 'main_tab_view/main_tab_view.dart';
|
|
import 'music_view.dart';
|
|
|
|
class SearchView extends StatefulWidget {
|
|
const SearchView({super.key});
|
|
|
|
@override
|
|
State<SearchView> createState() => _SearchViewState();
|
|
}
|
|
|
|
class _SearchViewState extends State<SearchView> {
|
|
final List<Song> songs = [];
|
|
final TextEditingController _searchController = TextEditingController();
|
|
final FocusNode _searchFocusNode = FocusNode();
|
|
final downloadManager = Get.put(DownloadManager());
|
|
bool isSearching = false;
|
|
String lastSearchQuery = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadRecommendData();
|
|
_searchController.addListener(_onSearchChanged);
|
|
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
if (mounted) {
|
|
_searchFocusNode.requestFocus();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
_searchFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _loadRecommendData() async {
|
|
MusicsListBean bean = await GetMusic()
|
|
.getMusicList(Authorization: AppData().currentToken, num: 10);
|
|
if (bean.code == 200) {
|
|
setState(() {
|
|
songs.clear();
|
|
for (var data in bean.data!) {
|
|
songs.add(Song(
|
|
artistPic: data.coverPath!,
|
|
title: data.name!,
|
|
artist: data.singerName!,
|
|
musicurl: data.musicPath,
|
|
pic: data.coverPath!,
|
|
id: data.id!,
|
|
likes: data.likeOrNot!,
|
|
collection: data.collectOrNot!,
|
|
mid: data.mid,
|
|
));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 搜索内容变化的处理
|
|
void _onSearchChanged() {
|
|
final query = _searchController.text.trim();
|
|
if (query.isEmpty) {
|
|
// 如果搜索框为空,显示推荐列表
|
|
if (lastSearchQuery.isNotEmpty) {
|
|
setState(() {
|
|
isSearching = false;
|
|
lastSearchQuery = '';
|
|
});
|
|
_loadRecommendData();
|
|
}
|
|
} else if (query != lastSearchQuery) {
|
|
// 当搜索内容发生变化且不为空时
|
|
setState(() {
|
|
isSearching = true;
|
|
lastSearchQuery = query;
|
|
});
|
|
_debounceSearch(query);
|
|
}
|
|
}
|
|
|
|
// 防抖搜索
|
|
Future<void> _debounceSearch(String query) async {
|
|
// 等待300ms后执行搜索,避免频繁请求
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
|
if (query == _searchController.text.trim()) {
|
|
_searchSongs(query);
|
|
}
|
|
}
|
|
|
|
// 搜索歌曲的API调用
|
|
Future<void> _searchSongs(String keyword) async {
|
|
try {
|
|
final response = await SearchMusic().search(
|
|
keyword: keyword,
|
|
Authorization: AppData().currentToken,
|
|
);
|
|
|
|
if (response.code == 200 && response.data != null) {
|
|
setState(() {
|
|
songs.clear();
|
|
for (var data in response.data!) {
|
|
songs.add(Song(
|
|
artistPic: data.coverPath ?? 'https://api.aspark.cc/image/1/6759856d288fd.jpg?1',
|
|
title: data.name!,
|
|
artist: data.singerName!,
|
|
musicurl: data.musicPath,
|
|
pic: data.coverPath ?? 'https://api.aspark.cc/image/1/6759856d288fd.jpg?1',
|
|
id: data.id!,
|
|
likes: data.likeOrNot,
|
|
collection: data.collectOrNot,
|
|
mid: data.mid,
|
|
));
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
print('Search error: $error');
|
|
// 可以添加错误提示
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('搜索失败,请稍后重试')),
|
|
);
|
|
}
|
|
}
|
|
|
|
@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,
|
|
body: GestureDetector(
|
|
onTap: () {
|
|
if (_searchFocusNode.hasFocus) {
|
|
_searchFocusNode.unfocus();
|
|
}
|
|
},
|
|
behavior: HitTestBehavior.translucent, // 确保手势能被检测到
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 60),
|
|
_buildSearchBar(),
|
|
const SizedBox(height: 10),
|
|
_buildPlayAllButton(),
|
|
Expanded(
|
|
child: NotificationListener<ScrollNotification>(
|
|
onNotification: (scrollNotification) {
|
|
if (scrollNotification is ScrollStartNotification) {
|
|
if (_searchFocusNode.hasFocus) {
|
|
_searchFocusNode.unfocus();
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
child: _buildSongsList(),
|
|
),
|
|
),
|
|
MiniPlayer(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSearchBar() {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Container(
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(25),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: TextField(
|
|
controller: _searchController,
|
|
focusNode: _searchFocusNode,
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
hintText: '搜索你想找的音乐',
|
|
hintStyle: TextStyle(color: Colors.grey.shade400),
|
|
prefixIcon: Icon(Icons.search, color: Colors.grey.shade400),
|
|
suffixIcon: _searchController.text.isNotEmpty
|
|
? IconButton(
|
|
icon: Icon(Icons.clear, color: Colors.grey.shade400),
|
|
onPressed: () {
|
|
_searchController.clear();
|
|
_searchFocusNode.unfocus();
|
|
},
|
|
)
|
|
: null,
|
|
contentPadding:
|
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
|
),
|
|
onSubmitted: (value) {
|
|
if (value.isNotEmpty) {
|
|
_searchSongs(value);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPlayAllButton() {
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 5,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: ListTile(
|
|
onTap: () {
|
|
if (songs.isNotEmpty) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => MusicView(
|
|
songList: songs,
|
|
initialSongIndex: 0,
|
|
onSongStatusChanged: _updateSongStatus,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
leading: const Icon(Icons.play_circle_fill,
|
|
color: Colors.blueGrey, size: 30),
|
|
title: const Text(
|
|
'播放全部',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
trailing: Text(
|
|
'${songs.length}首',
|
|
style: TextStyle(
|
|
color: Colors.grey.shade600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSongsList() {
|
|
return Container(
|
|
margin: const EdgeInsets.only(top: 10),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
|
|
),
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.only(top: 10),
|
|
itemCount: songs.length,
|
|
itemBuilder: (context, index) => _buildSongItem(index),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSongItem(int index) {
|
|
final song = songs[index];
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 5),
|
|
child: InkWell(
|
|
onTap: () => _onSongTap(index),
|
|
borderRadius: BorderRadius.circular(15),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 5,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Image.network(
|
|
song.pic,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) =>
|
|
const Icon(Icons.music_note, size: 30),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 15),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
song.title,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
song.artist,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: InkWell(
|
|
onTap: () async {
|
|
setState(() {
|
|
songs[index].likes = !songs[index].likes!;
|
|
});
|
|
|
|
UniversalBean response = await LikesApiMusic()
|
|
.likesMusic(
|
|
musicId: song.id,
|
|
Authorization: AppData().currentToken);
|
|
|
|
if (response.code != 200) {
|
|
setState(() {
|
|
songs[index].likes = !songs[index].likes!;
|
|
});
|
|
}
|
|
},
|
|
child: song.likes!
|
|
? Image.asset(
|
|
'assets/img/like.png',
|
|
width: 24,
|
|
height: 24,
|
|
)
|
|
: ColorFiltered(
|
|
colorFilter: ColorFilter.mode(
|
|
Colors.grey[700]!,
|
|
BlendMode.srcIn,
|
|
),
|
|
child: Image.asset(
|
|
'assets/img/unlike.png',
|
|
width: 24,
|
|
height: 24,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _onSongTap(int index) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => MusicView(
|
|
songList: songs,
|
|
initialSongIndex: index,
|
|
onSongStatusChanged: _updateSongStatus,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _updateSongStatus(int index, bool isCollected, bool isLiked) {
|
|
setState(() {
|
|
songs[index].collection = isCollected;
|
|
songs[index].likes = isLiked;
|
|
downloadManager.updateSongInfo(songs[index].id, isCollected, isLiked);
|
|
});
|
|
}
|
|
}
|