fix: 修复登录bug, 添加登录/注册页面点击任意非文本框失焦

master
Spark 1 week ago
parent e908f62d86
commit 04e09c2507

@ -0,0 +1,201 @@
// audio_player_controller.dart
import 'dart:async';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import '../common_widget/Song_widegt.dart';
import '../models/getMusicList_bean.dart';
import '../common/download_manager.dart';
import '../common_widget/app_data.dart';
import '../api/api_music_list.dart';
class AudioPlayerController extends GetxController {
final audioPlayer = AudioPlayer();
final downloadManager = Get.find<DownloadManager>();
final appData = AppData();
// Observable values
final currentSongIndex = 0.obs;
final duration = Duration.zero.obs;
final position = Duration.zero.obs;
final isPlaying = false.obs;
final isLoading = false.obs;
final isRotating = false.obs;
final isDisposed = false.obs;
// Current song info
final artistName = ''.obs;
final musicName = ''.obs;
final likesStatus = false.obs;
final collectionsStatus = false.obs;
// Song lists
final songList = <Song>[].obs;
final ids = <int>[].obs;
final songUrls = <String>[].obs;
final artists = <String>[].obs;
final musicNames = <String>[].obs;
final likes = <bool>[].obs;
final collections = <bool>[].obs;
StreamSubscription? _positionSubscription;
StreamSubscription? _durationSubscription;
StreamSubscription? _playerStateSubscription;
void initWithSongs(List<Song> songs, int initialIndex) {
songList.value = songs;
currentSongIndex.value = initialIndex;
_initializeSongLists();
_initializePlayer();
}
void _initializeSongLists() {
for (int i = 0; i < songList.length; i++) {
ids.add(songList[i].id);
songUrls.add(songList[i].musicurl ?? '');
artists.add(songList[i].artist);
musicNames.add(songList[i].title);
likes.add(songList[i].likes ?? false);
collections.add(songList[i].collection ?? false);
}
_updateCurrentSongInfo();
}
void _initializePlayer() {
// Position updates
_positionSubscription = audioPlayer.positionStream.listen((pos) {
position.value = pos;
});
// Duration updates
_durationSubscription = audioPlayer.durationStream.listen((dur) {
duration.value = dur ?? Duration.zero;
});
// Player state updates
_playerStateSubscription = audioPlayer.playerStateStream.listen((state) {
// isPlaying.value = state.playing;
if (state.processingState == ProcessingState.completed) {
playNext();
}
});
// Initial load
_loadAndPlayCurrentSong();
}
void _updateCurrentSongInfo() {
artistName.value = artists[currentSongIndex.value];
musicName.value = musicNames[currentSongIndex.value];
likesStatus.value = likes[currentSongIndex.value];
collectionsStatus.value = collections[currentSongIndex.value];
}
Future<void> toggleLike() async {
final currentIndex = currentSongIndex.value;
likesStatus.value = !likesStatus.value;
likes[currentIndex] = likesStatus.value;
}
Future<void> toggleCollection() async {
final currentIndex = currentSongIndex.value;
collectionsStatus.value = !collectionsStatus.value;
collections[currentIndex] = collectionsStatus.value;
}
Future<void> _loadAndPlayCurrentSong() async {
isLoading.value = true;
position.value = Duration.zero;
duration.value = Duration.zero;
_updateCurrentSongInfo();
await _checkAndUpdateSongStatus(currentSongIndex.value);
try {
await audioPlayer.stop();
final localSong = downloadManager.getLocalSong(currentSongIndex.value);
final audioSource = localSong != null
? AudioSource.file(localSong.musicurl!)
: AudioSource.uri(Uri.parse(songUrls[currentSongIndex.value]));
await audioPlayer.setAudioSource(audioSource, preload: true);
duration.value = await audioPlayer.duration ?? Duration.zero;
await audioPlayer.play();
} catch (e) {
print('Error loading audio source: $e');
} finally {
isLoading.value = false;
}
}
Future<void> _checkAndUpdateSongStatus(int index) async {
if (songList[index].likes == null || songList[index].collection == null) {
try {
MusicListBean musicListBean = await GetMusic().getMusicById(
id: ids[index],
Authorization: appData.currentToken,
);
if (musicListBean.code == 200) {
likes[index] = musicListBean.likeOrNot!;
collections[index] = musicListBean.collectOrNot!;
if (index == currentSongIndex.value) {
likesStatus.value = musicListBean.likeOrNot!;
collectionsStatus.value = musicListBean.collectOrNot!;
}
}
} catch (e) {
print('Error fetching song status: $e');
}
}
}
void playOrPause() async {
if (audioPlayer.playing) {
isPlaying.value = false;
await audioPlayer.pause();
} else {
await audioPlayer.play();
isPlaying.value = true;
}
}
void playNext() {
if (currentSongIndex.value < songList.length - 1) {
currentSongIndex.value++;
} else {
currentSongIndex.value = 0;
}
_loadAndPlayCurrentSong();
}
void playPrevious() {
if (currentSongIndex.value > 0) {
currentSongIndex.value--;
} else {
currentSongIndex.value = songList.length - 1;
}
_loadAndPlayCurrentSong();
}
void seekTo(Duration position) async {
await audioPlayer.seek(position);
}
void changeSong(int index) {
currentSongIndex.value = index;
_loadAndPlayCurrentSong();
}
@override
void onClose() {
isDisposed.value = true;
_positionSubscription?.cancel();
_durationSubscription?.cancel();
_playerStateSubscription?.cancel();
audioPlayer.dispose();
super.onClose();
}
}

@ -7,5 +7,7 @@ class AppData extends GetxController{
String get currentToken => box.read('currentToken');
String get currentUsername => box.read('currentUsername') ?? '游客';
String get currentAvatar=> box.read('currentAvatar') ?? 'http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg';
set currentToken(String token) => box.write('currentToken', token);
set currentUsername(String username) => box.write('currentUsername', username);
set currentAvatar(String avatar) => box.write('currentAvatar', avatar);
}

@ -17,139 +17,144 @@ class _BeginViewState extends State<BeginView> with TickerProviderStateMixin {
@override
void initState() {
tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this
);
tabController = TabController(initialIndex: 0, length: 2, vsync: this);
tabController.addListener(() {
if (!tabController.indexIsChanging) {
FocusScope.of(context).unfocus();
}
});
super.initState();
}
@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,
resizeToAvoidBottomInset: false,
body: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(
physics: const NeverScrollableScrollPhysics(),
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/img/app_bg.png"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
//
Padding(
padding: const EdgeInsets.only(top: 110, left: 40, right: 40),
child: Row(
children: [
const Column(
child: Scaffold(
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(
physics: const NeverScrollableScrollPhysics(),
),
child: Column(
children: [
//
Padding(
padding:
const EdgeInsets.only(top: 110, left: 40, right: 40),
child: Row(
children: [
Text(
"你好吖喵星来客,",
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w500
),
),
Row(
const Column(
children: [
Text(
"欢迎来到",
"你好吖喵星来客,",
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w500
),
fontWeight: FontWeight.w500),
),
Text(
"喵听",
style: TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w800
),
Row(
children: [
Text(
"欢迎来到",
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w500),
),
Text(
"喵听",
style: TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w800),
),
],
),
],
),
const SizedBox(width: 25),
Image.asset("assets/img/app_logo.png", width: 80),
],
),
const SizedBox(width: 25),
Image.asset("assets/img/app_logo.png", width: 80),
],
),
),
),
const SizedBox(height: 20), //
const SizedBox(height: 20), //
// 使 Expanded
Expanded(
child: Column(
children: [
// TabBar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Material(
color: Colors.white,
elevation: 3,
borderRadius: BorderRadius.circular(10),
child: TabBar(
controller: tabController,
unselectedLabelColor: const Color(0xffCDCDCD),
labelColor: Colors.black,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: MColor.LGreen
),
tabs: [
Container(
padding: const EdgeInsets.all(8.0),
child: const Text(
'登录',
style: TextStyle(
fontSize: 20,
// 使 Expanded
Expanded(
child: Column(
children: [
// TabBar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Material(
color: Colors.white,
elevation: 3,
borderRadius: BorderRadius.circular(10),
child: TabBar(
controller: tabController,
unselectedLabelColor: const Color(0xffCDCDCD),
// onTap: (_) {
// FocusScope.of(context).unfocus();
// },
labelColor: Colors.black,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: MColor.LGreen),
tabs: [
Container(
padding: const EdgeInsets.all(8.0),
child: const Text(
'登录',
style: TextStyle(
fontSize: 20,
),
),
),
),
),
Container(
padding: const EdgeInsets.all(8.0),
child: const Text(
'注册',
style: TextStyle(
fontSize: 20,
Container(
padding: const EdgeInsets.all(8.0),
child: const Text(
'注册',
style: TextStyle(
fontSize: 20,
),
),
),
),
],
),
],
),
),
),
),
// TabBarView
Expanded(
child: TabBarView(
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: const [
LoginV(),
SignUpView(),
],
),
)
],
),
// TabBarView
Expanded(
child: TabBarView(
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: const [
LoginV(),
SignUpView(),
],
),
)
],
),
),
],
),
],
),
),
),
),
);
));
}
}

@ -27,6 +27,16 @@ class _LoginVState extends State<LoginV> {
IconData iconPassword = CupertinoIcons.eye_fill;
bool obscurePassword = true;
final _passwordFocusNode = FocusNode();
final _nameFocusNode = FocusNode();
@override
void dispose() {
_passwordFocusNode.dispose();
_nameFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
@ -39,6 +49,7 @@ class _LoginVState extends State<LoginV> {
child: MyTextField(
controller: nameController,
hintText: '请输入账号',
focusNode: _nameFocusNode,
obscureText: false,
keyboardType: TextInputType.emailAddress,
prefixIcon: Image.asset("assets/img/login_user.png"))),
@ -49,6 +60,7 @@ class _LoginVState extends State<LoginV> {
child: MyTextField(
controller: passwordController,
hintText: '请输入密码',
focusNode: _passwordFocusNode,
obscureText: obscurePassword,
keyboardType: TextInputType.visiblePassword,
prefixIcon: Image.asset("assets/img/login_lock.png"),
@ -78,11 +90,13 @@ class _LoginVState extends State<LoginV> {
child: TextButton(
onPressed: () async {
try {
_nameFocusNode.unfocus();
_passwordFocusNode.unfocus();
Get.dialog(
Center(
child: CircularProgressIndicator(
color: const Color(0xff429482),
backgroundColor: Colors.grey[200],
color: const Color(0xff429482),
backgroundColor: Colors.grey[200],
),
),
barrierDismissible: false, //
@ -99,11 +113,10 @@ class _LoginVState extends State<LoginV> {
SnackBar(
content: const Center(
child: Text(
'登录成功!',
'登录成功',
style: TextStyle(
color: Colors.black,
fontSize: 16.0, //
fontWeight: FontWeight.w500, //
),
),
),
@ -111,7 +124,8 @@ class _LoginVState extends State<LoginV> {
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.white,
elevation: 3,
width: 200, //
width: 200,
//
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
@ -121,7 +135,6 @@ class _LoginVState extends State<LoginV> {
.getInfo(
Authorization: AppData().currentToken);
} else {
Get.back();
throw Exception("账号或密码错误");
}
} catch (error) {
@ -131,11 +144,10 @@ class _LoginVState extends State<LoginV> {
SnackBar(
content: Center(
child: Text(
'${error.toString().replaceAll ('Exception: ', '')} ',
style: TextStyle(
color: Colors.red,
error.toString().replaceAll ('Exception: ', ''),
style: const TextStyle(
color: Colors.black,
fontSize: 16.0, //
fontWeight: FontWeight.w500, //
),
),
),
@ -143,7 +155,11 @@ class _LoginVState extends State<LoginV> {
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.white,
elevation: 3,
width: 200, //
margin: EdgeInsets.only(
bottom: 50, // 50
right: (MediaQuery.of(context).size.width - 200) / 2, //
left: (MediaQuery.of(context).size.width - 200) / 2,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),

@ -30,6 +30,12 @@ class _SignUpViewState extends State<SignUpView> {
bool obscurePassword = true;
bool signUpRequired = false;
final _nameFocusNode = FocusNode();
final _passwordFocusNode = FocusNode();
final _emailFocusNode = FocusNode();
final _confirmPSWFocusNode = FocusNode();
final _confirmFocusNode = FocusNode();
//
Timer? _timer;
int _countDown = 60;
@ -75,6 +81,7 @@ class _SignUpViewState extends State<SignUpView> {
child: MyTextField(
controller: nameController,
hintText: '请输入用户名',
focusNode: _nameFocusNode,
obscureText: false,
keyboardType: TextInputType.emailAddress,
prefixIcon: Image.asset("assets/img/login_user.png"),
@ -94,6 +101,7 @@ class _SignUpViewState extends State<SignUpView> {
controller: emailController,
hintText: '请输入邮箱名',
obscureText: false,
focusNode: _emailFocusNode,
keyboardType: TextInputType.name,
prefixIcon: Image.asset("assets/img/setup_email.png"),
validator: (val) {
@ -110,6 +118,7 @@ class _SignUpViewState extends State<SignUpView> {
controller: passwordController,
hintText: '请输入密码',
obscureText: obscurePassword,
focusNode: _passwordFocusNode,
keyboardType: TextInputType.visiblePassword,
prefixIcon: Image.asset("assets/img/login_lock.png"),
suffixIcon: IconButton(
@ -144,6 +153,7 @@ class _SignUpViewState extends State<SignUpView> {
controller: confirmPSWController,
hintText: '请确认密码',
obscureText: obscurePassword,
focusNode: _confirmPSWFocusNode,
keyboardType: TextInputType.visiblePassword,
prefixIcon: Image.asset("assets/img/login_lock.png"),
suffixIcon: IconButton(
@ -184,6 +194,7 @@ class _SignUpViewState extends State<SignUpView> {
controller: confirmController,
hintText: '请输入验证码',
obscureText: false,
focusNode: _confirmFocusNode,
keyboardType: TextInputType.name,
prefixIcon: Image.asset("assets/img/setup_confirm.png"),
validator: (val) {
@ -205,6 +216,11 @@ class _SignUpViewState extends State<SignUpView> {
),
onPressed: _canSendCode
? () async {
_nameFocusNode.unfocus();
_emailFocusNode.unfocus();
_passwordFocusNode.unfocus();
_confirmPSWFocusNode.unfocus();
_confirmFocusNode.unfocus();
UniversalBean bean = await SetupApiClient().verification(
email: emailController.text,
);
@ -213,9 +229,9 @@ class _SignUpViewState extends State<SignUpView> {
startTimer(); //
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Center(
content: const Center(
child: Text(
'验证码已成功发送!',
'验证码发送成功',
style: TextStyle(color: Colors.black),
),
),
@ -223,7 +239,11 @@ class _SignUpViewState extends State<SignUpView> {
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.white,
elevation: 3,
width: 200,
margin: EdgeInsets.only(
bottom: 50, // 50
right: (MediaQuery.of(context).size.width - 200) / 2, //
left: (MediaQuery.of(context).size.width - 200) / 2,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
@ -232,13 +252,12 @@ class _SignUpViewState extends State<SignUpView> {
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Center(
content: const Center(
child: Text(
'邮箱为空或格式不正确!',
'请检查邮箱',
style: TextStyle(
color: Colors.black,
fontSize: 16.0, //
fontWeight: FontWeight.w500, //
),
),
),
@ -246,7 +265,12 @@ class _SignUpViewState extends State<SignUpView> {
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.white,
elevation: 3,
width: 220,
// width: 220,
margin: EdgeInsets.only(
bottom: 50, // 50
right: (MediaQuery.of(context).size.width - 200) / 2, //
left: (MediaQuery.of(context).size.width - 200) / 2,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
@ -271,6 +295,11 @@ class _SignUpViewState extends State<SignUpView> {
width: MediaQuery.of(context).size.width * 0.85,
child: TextButton(
onPressed: () async {
_nameFocusNode.unfocus();
_emailFocusNode.unfocus();
_passwordFocusNode.unfocus();
_confirmPSWFocusNode.unfocus();
_confirmFocusNode.unfocus();
if (_formKey.currentState?.validate() == false) {
const Text(
'',

@ -5,6 +5,101 @@ import 'package:music_player_miao/view/user/user_view.dart';
import '../home_view.dart';
import '../release_view.dart';
//
class MiniPlayer extends StatelessWidget {
const MiniPlayer({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 50, //
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, -1),
),
],
),
child: Row(
children: [
//
Container(
width: 50,
height: 50,
color: Colors.grey[200],
child: Image.asset(
'assets/img/artist_pic.png',
fit: BoxFit.cover,
),
),
//
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'背对背拥抱',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
'林俊杰',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
//
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
iconSize: 24,
),
const SizedBox(width: 16),
IconButton(
icon: const Icon(Icons.playlist_play),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
iconSize: 24,
),
const SizedBox(width: 8),
],
),
],
),
),
),
],
),
);
}
}
class MainTabView extends StatefulWidget {
const MainTabView({super.key});
@override
@ -38,63 +133,86 @@ class _MainTabViewState extends State<MainTabView> with SingleTickerProviderStat
return Scaffold(
resizeToAvoidBottomInset: false,
key: scaffoldKey,
body: TabBarView(
controller: controller,
children: const [
HomeView(),
RankView(),
ReleaseView(),
UserView()
],
),
bottomNavigationBar: Container(
color: Colors.white,
child: TabBar(
controller: controller,
indicatorColor: Colors.transparent,
labelColor: Colors.black,
labelStyle: const TextStyle(fontSize: 12), //
unselectedLabelColor: const Color(0xffCDCDCD),
unselectedLabelStyle: const TextStyle(fontSize: 12), //
tabs: [
Tab(
height: 60, //
icon: Image.asset(
selectTab == 0 ? "assets/img/home_tab.png" : "assets/img/home_tab_un.png",
width: 32, //
height: 32,
),
text: "首页",
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 1 ? "assets/img/list_tab.png" : "assets/img/list_tab_un.png",
width: 32,
height: 32,
),
text: "排行榜",
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 2 ? "assets/img/music_tab.png" : "assets/img/music_tab_un.png",
width: 32,
height: 32,
body: Stack(
children: [
// TabBarView
Column(
children: [
Expanded(
child: TabBarView(
controller: controller,
children: const [
HomeView(),
RankView(),
ReleaseView(),
UserView()
],
),
),
text: "发布",
],
),
//
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const MiniPlayer(),
Container(
color: Colors.white,
child: TabBar(
controller: controller,
indicatorColor: Colors.transparent,
labelColor: Colors.black,
labelStyle: const TextStyle(fontSize: 12),
unselectedLabelColor: const Color(0xffCDCDCD),
unselectedLabelStyle: const TextStyle(fontSize: 12),
tabs: [
Tab(
height: 60,
icon: Image.asset(
selectTab == 0 ? "assets/img/home_tab.png" : "assets/img/home_tab_un.png",
width: 32,
height: 32,
),
text: "首页",
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 1 ? "assets/img/list_tab.png" : "assets/img/list_tab_un.png",
width: 32,
height: 32,
),
text: "排行榜",
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 2 ? "assets/img/music_tab.png" : "assets/img/music_tab_un.png",
width: 32,
height: 32,
),
text: "发布",
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 3 ? "assets/img/user_tab.png" : "assets/img/user_tab_un.png",
width: 32,
height: 32,
),
text: "我的",
),
],
),
),
],
),
Tab(
height: 60,
icon: Image.asset(
selectTab == 3 ? "assets/img/user_tab.png" : "assets/img/user_tab_un.png",
width: 32,
height: 32,
),
text: "我的",
),
],
),
),
],
),
);
}

@ -0,0 +1,507 @@
// music_view_test.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../common/audio_player_controller.dart';
import '../common/download_manager.dart';
import '../common_widget/Song_widegt.dart';
import '../common_widget/app_data.dart';
import 'comment_view.dart';
class MusicView extends StatefulWidget {
final List<Song> songList;
final int initialSongIndex;
final Function(int index, bool isCollected, bool isLiked)?
onSongStatusChanged;
const MusicView({
super.key,
required this.songList,
required this.initialSongIndex,
this.onSongStatusChanged,
});
@override
State<MusicView> createState() => _MusicViewState();
}
class _MusicViewState extends State<MusicView>
with SingleTickerProviderStateMixin {
// late AnimationController _rotationController;
final AudioPlayerController playerController =
Get.put(AudioPlayerController());
final downloadManager = Get.find<DownloadManager>();
final AppData appData = AppData();
@override
void initState() {
super.initState();
playerController.initWithSongs(widget.songList, widget.initialSongIndex);
}
@override
void dispose() {
super.dispose();
}
String formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "$twoDigitMinutes:$twoDigitSeconds";
}
Widget _buildProgressSlider() {
return Obx(() {
final max = playerController.duration.value.inSeconds.toDouble() == 0
? 0.1
: playerController.duration.value.inSeconds.toDouble();
final current =
playerController.position.value.inSeconds.toDouble().clamp(0, max);
return SliderTheme(
data: const SliderThemeData(
trackHeight: 3.0,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7.0),
overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0),
),
child: Slider(
min: 0,
max: max,
value: current.toDouble(),
onChanged: (value) {
playerController.seekTo(Duration(seconds: value.toInt()));
},
activeColor: const Color(0xff429482),
inactiveColor: const Color(0xffE3F0ED),
),
);
});
}
Widget _buildPlayButton() {
return Obx(() {
return SizedBox(
width: 52,
height: 52,
child: Center(
child: playerController.isLoading.value
? const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xff429482)),
strokeWidth: 3.0,
)
: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 52,
minHeight: 52,
maxWidth: 52,
maxHeight: 52,
),
onPressed: playerController.playOrPause,
icon: !playerController.isPlaying.value
? Image.asset(
"assets/img/music_play.png",
width: 52,
height: 52,
)
: Image.asset(
"assets/img/music_pause.png",
width: 52,
height: 52,
),
),
),
);
});
}
// Widget _buildRotatingAlbumCover() {
// return Obx(() {
// final currentSong =
// widget.songList[playerController.currentSongIndex.value];
// return RotationTransition(
// turns: _rotationController,
// child: Stack(
// alignment: Alignment.center,
// children: [
// Positioned(
// child: ClipRRect(
// child: Image.network(
// currentSong.artistPic,
// width: 225,
// height: 225,
// fit: BoxFit.cover,
// ),
// ),
// ),
// ClipRRect(
// child: Image.asset(
// "assets/img/music_Ellipse.png",
// width: 350,
// height: 350,
// fit: BoxFit.cover,
// ),
// ),
// ],
// ),
// );
// });
// }
Widget _buildRotatingAlbumCover() {
return Obx(() {
final currentSong =
widget.songList[playerController.currentSongIndex.value];
// RotationTransition
return Stack(
alignment: Alignment.center,
children: [
Positioned(
child: ClipRRect(
child: Image.network(
currentSong.artistPic,
width: 225,
height: 225,
fit: BoxFit.cover,
),
),
),
ClipRRect(
child: Image.asset(
"assets/img/music_Ellipse.png",
width: 350,
height: 350,
fit: BoxFit.cover,
),
),
],
);
});
}
void _showPlaylist() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
),
builder: (BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 15),
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.45,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Center(
child: Text(
"播放列表",
style: TextStyle(fontSize: 20),
),
),
const SizedBox(height: 10),
Expanded(
child: Obx(() => ListView.builder(
itemCount: playerController.musicNames.length,
itemBuilder: (BuildContext context, int index) {
final isCurrentlyPlaying =
playerController.currentSongIndex.value == index;
return ListTile(
tileColor: isCurrentlyPlaying
? const Color(0xffE3F0ED)
: null,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(
playerController.musicNames[index],
style: const TextStyle(fontSize: 18),
),
),
Padding(
padding: const EdgeInsets.only(right: 20),
child: Image.asset(
"assets/img/songs_run.png",
width: 25,
),
),
],
),
onTap: () {
playerController.changeSong(index);
Navigator.pop(context);
},
);
},
)),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/img/app_bg.png"),
fit: BoxFit.cover,
),
),
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 45, left: 10, right: 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Get.back();
},
icon: Image.asset(
"assets/img/back.png",
width: 25,
height: 25,
fit: BoxFit.contain,
),
),
Row(
children: [
IconButton(
onPressed: () {},
icon: Image.asset(
"assets/img/music_add.png",
width: 30,
height: 30,
),
),
Obx(() => IconButton(
onPressed: downloadManager.isDownloading(
playerController.ids[playerController
.currentSongIndex.value]) ||
downloadManager.isCompleted(
playerController.ids[playerController
.currentSongIndex.value])
? null
: () async {
await downloadManager.startDownload(
song: widget.songList[playerController
.currentSongIndex.value],
context: context,
);
},
icon: Obx(() {
if (downloadManager.isDownloading(
playerController.ids[playerController
.currentSongIndex.value])) {
return Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 32,
height: 32,
child: CircularProgressIndicator(
value: downloadManager.getProgress(
playerController.ids[
playerController
.currentSongIndex.value]),
backgroundColor: Colors.grey[200],
valueColor:
const AlwaysStoppedAnimation<
Color>(Color(0xff429482)),
strokeWidth: 3.0,
),
),
Text(
'${(downloadManager.getProgress(playerController.ids[playerController.currentSongIndex.value]) * 100).toInt()}',
style: const TextStyle(fontSize: 12),
),
],
);
}
return Image.asset(
downloadManager.isCompleted(
playerController.ids[playerController
.currentSongIndex.value])
? "assets/img/music_download_completed.png"
: "assets/img/music_download.png",
width: 30,
height: 30,
);
}),
)),
],
),
],
),
const SizedBox(height: 80),
Center(child: _buildRotatingAlbumCover()),
const SizedBox(height: 60),
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
playerController.musicName.value,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
playerController.artistName.value,
style: const TextStyle(fontSize: 20),
),
],
),
Row(
children: [
IconButton(
onPressed: () async {
playerController.toggleLike();
final currentIndex =
playerController.currentSongIndex.value;
widget.onSongStatusChanged?.call(
currentIndex,
playerController.collections[currentIndex],
playerController.likes[currentIndex],
);
},
icon: Image.asset(
playerController.likesStatus.value
? "assets/img/music_good.png"
: "assets/img/music_good_un.png",
width: 29,
height: 29,
),
),
IconButton(
onPressed: () async {
playerController.toggleCollection();
final currentIndex =
playerController.currentSongIndex.value;
widget.onSongStatusChanged?.call(
currentIndex,
playerController.collections[currentIndex],
playerController.likes[currentIndex],
);
},
icon: Image.asset(
playerController.collectionsStatus.value
? "assets/img/music_star.png"
: "assets/img/music_star_un.png",
width: 29,
height: 29,
),
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CommentView(
id: playerController.ids[playerController
.currentSongIndex.value],
song: playerController.musicName.value,
singer: playerController.artistName.value,
cover: widget
.songList[playerController
.currentSongIndex.value]
.artistPic,
),
),
);
},
icon: Image.asset(
"assets/img/music_commend_un.png",
width: 29,
height: 29,
),
),
],
),
],
)),
const SizedBox(height: 80),
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
formatDuration(playerController.position.value),
style: const TextStyle(color: Colors.black),
),
Expanded(child: _buildProgressSlider()),
Text(
formatDuration(playerController.duration.value),
style: const TextStyle(color: Colors.black),
),
],
)),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: Image.asset(
"assets/img/music_random.png",
width: 35,
height: 35,
),
),
Row(
children: [
IconButton(
onPressed: playerController.playPrevious,
icon: Image.asset(
"assets/img/music_back.png",
width: 42,
height: 42,
),
),
const SizedBox(width: 10),
_buildPlayButton(),
const SizedBox(width: 10),
IconButton(
onPressed: playerController.playNext,
icon: Image.asset(
"assets/img/music_next.png",
width: 42,
height: 42,
),
),
],
),
IconButton(
onPressed: _showPlaylist,
icon: Image.asset(
"assets/img/music_more.png",
width: 35,
height: 35,
),
),
],
),
],
),
),
),
),
);
}
}

@ -423,6 +423,9 @@ class _UserViewState extends State<UserView> with AutomaticKeepAliveClientMixin
UniversalBean bean = await LogoutApiClient().logout(
Authorization: AppData().currentToken,
);
AppData().currentToken = '';
AppData().currentUsername = '';
AppData().currentAvatar = '';
},
icon: Image.asset("assets/img/user_out.png"),
iconSize: 60,

Loading…
Cancel
Save