播放页部分调整,完整播放链路层级已打通

This commit is contained in:
2026-08-18 23:39:22 +08:00
parent 74a15ca7af
commit 85463c3716
4 changed files with 184 additions and 83 deletions
+152 -57
View File
@@ -6,22 +6,119 @@ import 'services/audio_service.dart';
import 'services/playback_service.dart'; import 'services/playback_service.dart';
import 'pages/home_page.dart'; import 'pages/home_page.dart';
import 'pages/player_page.dart'; import 'pages/player_page.dart';
import 'pages/playlist_page.dart'; // ✅ 导入 PlaylistPage
import 'widgets/global_mini_player.dart'; import 'widgets/global_mini_player.dart';
// ✅ 全局导航键
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
bool initSuccess = false;
String? initError;
try { try {
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
PlaybackService().init(); PlaybackService().init();
initSuccess = true;
} catch (e, stack) { } catch (e, stack) {
print('初始化失败: $e'); initError = '初始化失败: $e';
print('$initError');
print(stack); print(stack);
runApp( }
MaterialApp(
runApp(
ChangeNotifierProvider(
create: (_) => AudioService(),
child: QTPlayerApp(
initSuccess: initSuccess,
initError: initError,
),
),
);
}
class QTPlayerApp extends StatefulWidget {
final bool initSuccess;
final String? initError;
const QTPlayerApp({
super.key,
required this.initSuccess,
required this.initError,
});
@override
State<QTPlayerApp> createState() => _QTPlayerAppState();
}
class _QTPlayerAppState extends State<QTPlayerApp>
with SingleTickerProviderStateMixin {
bool _showPlayerPage = false;
bool _showPlaylist = false; // ✅ 新增:播放列表显示状态
late final AnimationController _animationController;
late final Animation<double> _slideAnimation;
@override
void initState() {
super.initState();
GlobalMiniPlayer.onOpenPlayerPage = _openPlayerPage;
GlobalMiniPlayer.onOpenPlaylistPage = _openPlaylist;
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_slideAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeOutCubic,
),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _openPlayerPage() {
setState(() {
_showPlayerPage = true;
_showPlaylist = false; // 打开播放页时关闭播放列表
});
_animationController.forward(from: 0.0);
}
void _closePlayerPage() {
_animationController.reverse().then((_) {
setState(() {
_showPlayerPage = false;
});
});
}
// ✅ 打开播放列表
void _openPlaylist() {
setState(() {
_showPlaylist = true;
});
}
// ✅ 关闭播放列表
void _closePlaylist() {
setState(() {
_showPlaylist = false;
});
}
@override
Widget build(BuildContext context) {
if (!widget.initSuccess) {
return MaterialApp(
home: Scaffold( home: Scaffold(
backgroundColor: const Color(0xFF0E1211), backgroundColor: const Color(0xFF0E1211),
body: Center( body: Center(
@@ -30,70 +127,42 @@ void main() {
children: [ children: [
Icon(Icons.error_outline, size: 64, color: Colors.red[300]), Icon(Icons.error_outline, size: 64, color: Colors.red[300]),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( const Text(
'应用初始化失败', '应用初始化失败',
style: TextStyle(color: Colors.white, fontSize: 20), style: TextStyle(color: Colors.white, fontSize: 20),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'$e', widget.initError ?? '未知错误',
style: TextStyle(color: Colors.grey[400], fontSize: 14), style: TextStyle(color: Colors.grey[400], fontSize: 14),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
runApp(
ChangeNotifierProvider(
create: (_) => AudioService(),
child: QTPlayerApp(
initSuccess: true,
initError: null,
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFB8D4D0),
foregroundColor: Colors.black87,
),
child: const Text('重试'),
),
], ],
), ),
), ),
), ),
), );
); }
return;
}
FlutterError.onError = (details) {
print('❌ Flutter Error: ${details.exception}');
print('Stack: ${details.stack}');
};
runApp(
ChangeNotifierProvider(
create: (_) => AudioService(),
child: const QTPlayerApp(),
),
);
}
class QTPlayerApp extends StatefulWidget {
const QTPlayerApp({super.key});
@override
State<QTPlayerApp> createState() => _QTPlayerAppState();
}
class _QTPlayerAppState extends State<QTPlayerApp> {
bool _showPlayerPage = false;
// ✅ 播放页回调,暴露给 GlobalMiniPlayer
void _openPlayerPage() {
setState(() {
_showPlayerPage = true;
});
}
void _closePlayerPage() {
setState(() {
_showPlayerPage = false;
});
}
@override
void initState() {
super.initState();
// ✅ 注入回调(GlobalMiniPlayer 是 static 成员)
GlobalMiniPlayer.onOpenPlayerPage = _openPlayerPage;
}
@override
Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: '清听', title: '清听',
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
@@ -117,6 +186,7 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
children: [ children: [
// ① 主页面 // ① 主页面
Positioned.fill(child: child!), Positioned.fill(child: child!),
// ② MiniPlayer // ② MiniPlayer
const Positioned( const Positioned(
left: 0, left: 0,
@@ -124,11 +194,36 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
bottom: 0, bottom: 0,
child: GlobalMiniPlayer(), child: GlobalMiniPlayer(),
), ),
// ③ 播放页(全屏覆盖) // ③ 播放页(全屏覆盖)
if (_showPlayerPage) if (_showPlayerPage)
Positioned.fill( Positioned.fill(
child: PlayerPage( child: AnimatedBuilder(
onClose: _closePlayerPage, animation: _animationController,
builder: (context, child) {
final progress = _slideAnimation.value;
final offsetY =
MediaQuery.of(context).size.height * progress;
return Transform.translate(
offset: Offset(0, offsetY),
child: Opacity(
opacity: 1.0 - progress * 0.3,
child: child,
),
);
},
child: PlayerPage(
onClose: _closePlayerPage,
onOpenPlaylist: _openPlaylist, // ✅ 传入回调
),
),
),
// ④ ✅ 播放列表(最高层级,在播放页之上)
if (_showPlaylist)
Positioned.fill(
child: PlaylistPage(
onClose: _closePlaylist,
), ),
), ),
], ],
+14 -14
View File
@@ -2,15 +2,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../main.dart'; // ✅ 导入 navigatorKey import '../main.dart';
import 'playlist_page.dart'; // ✅ 导入 PlaylistPage import 'playlist_page.dart';
class PlayerPage extends StatefulWidget { class PlayerPage extends StatefulWidget {
final VoidCallback onClose; final VoidCallback onClose;
final VoidCallback onOpenPlaylist;
const PlayerPage({ const PlayerPage({
super.key, super.key,
required this.onClose, required this.onClose,
required this.onOpenPlaylist,
}); });
@override @override
@@ -81,7 +83,7 @@ class _PlayerPageState extends State<PlayerPage> {
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white),
onPressed: widget.onClose, // ✅ 使用回调关闭 onPressed: widget.onClose,
), ),
title: const Text( title: const Text(
'正在播放', '正在播放',
@@ -93,17 +95,6 @@ class _PlayerPageState extends State<PlayerPage> {
), ),
centerTitle: true, centerTitle: true,
actions: [ actions: [
IconButton(
icon:
const Icon(Icons.playlist_play_outlined, color: Colors.white54),
onPressed: () {
navigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) => const PlaylistPage(),
),
);
},
),
IconButton( IconButton(
icon: const Icon(Icons.more_vert, color: Colors.white54), icon: const Icon(Icons.more_vert, color: Colors.white54),
onPressed: () {}, onPressed: () {},
@@ -309,6 +300,15 @@ class _PlayerPageState extends State<PlayerPage> {
color: service.hasQueue ? Colors.white60 : Colors.grey[600], color: service.hasQueue ? Colors.white60 : Colors.grey[600],
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
), ),
IconButton(
onPressed: widget.onOpenPlaylist,
icon: const Icon(
Icons.playlist_play_outlined,
color: Color(0xFFB8D4D0),
size: 26,
),
padding: const EdgeInsets.all(8),
),
const SizedBox(width: 4), const SizedBox(width: 4),
], ],
), ),
+9 -4
View File
@@ -4,7 +4,12 @@ import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
class PlaylistPage extends StatelessWidget { class PlaylistPage extends StatelessWidget {
const PlaylistPage({super.key}); final VoidCallback onClose;
const PlaylistPage({
super.key,
required this.onClose,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,7 +33,7 @@ class PlaylistPage extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new), icon: const Icon(Icons.arrow_back_ios_new),
onPressed: () => Navigator.pop(context), onPressed: onClose,
), ),
actions: [ actions: [
IconButton( IconButton(
@@ -55,7 +60,7 @@ class PlaylistPage extends StatelessWidget {
onPressed: () { onPressed: () {
service.clearQueue(); service.clearQueue();
Navigator.pop(context); Navigator.pop(context);
Navigator.pop(context); onClose();
}, },
child: const Text( child: const Text(
'清空', '清空',
@@ -141,7 +146,7 @@ class PlaylistPage extends StatelessWidget {
: null, : null,
onTap: () { onTap: () {
service.setQueue(queue, startIndex: index); service.setQueue(queue, startIndex: index);
Navigator.pop(context); onClose();
}, },
); );
}, },
+9 -8
View File
@@ -2,15 +2,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../pages/playlist_page.dart'; import '../main.dart';
import '../main.dart'; // ✅ 导入 navigatorKey
class GlobalMiniPlayer extends StatelessWidget { class GlobalMiniPlayer extends StatelessWidget {
const GlobalMiniPlayer({super.key}); const GlobalMiniPlayer({super.key});
// ✅ 通过全局回调打开播放页(由 main.dart 注入) // ✅ 全局回调打开播放页
static VoidCallback? onOpenPlayerPage; static VoidCallback? onOpenPlayerPage;
// ✅ 新增:打开播放列表
static VoidCallback? onOpenPlaylistPage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final service = context.watch<AudioService>(); final service = context.watch<AudioService>();
@@ -112,11 +114,10 @@ class GlobalMiniPlayer extends StatelessWidget {
size: 24, size: 24,
), ),
onPressed: () { onPressed: () {
navigatorKey.currentState?.push( // ✅ 使用回调打开播放列表(与播放页同一套层级)
MaterialPageRoute( if (onOpenPlaylistPage != null) {
builder: (_) => const PlaylistPage(), onOpenPlaylistPage!();
), }
);
}, },
), ),
const SizedBox(width: 4), const SizedBox(width: 4),