diff --git a/lib/main.dart b/lib/main.dart index 2293d6b..d47a612 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,11 +6,9 @@ import 'services/audio_service.dart'; import 'services/playback_service.dart'; import 'pages/home_page.dart'; import 'pages/player_page.dart'; -import 'pages/playlist_page.dart'; // ✅ 导入 PlaylistPage +import 'pages/playlist_page.dart'; import 'widgets/global_mini_player.dart'; -final GlobalKey navigatorKey = GlobalKey(); - void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -55,7 +53,7 @@ class QTPlayerApp extends StatefulWidget { class _QTPlayerAppState extends State with SingleTickerProviderStateMixin { bool _showPlayerPage = false; - bool _showPlaylist = false; // ✅ 新增:播放列表显示状态 + bool _showPlaylist = false; late final AnimationController _animationController; late final Animation _slideAnimation; @@ -88,33 +86,56 @@ class _QTPlayerAppState extends State void _openPlayerPage() { setState(() { _showPlayerPage = true; - _showPlaylist = false; // 打开播放页时关闭播放列表 + _showPlaylist = false; }); _animationController.forward(from: 0.0); } void _closePlayerPage() { _animationController.reverse().then((_) { - setState(() { - _showPlayerPage = false; - }); + if (mounted) { + setState(() { + _showPlayerPage = false; + }); + } }); } - // ✅ 打开播放列表 void _openPlaylist() { setState(() { _showPlaylist = true; }); } - // ✅ 关闭播放列表 void _closePlaylist() { setState(() { _showPlaylist = false; }); } + // ============================================================ + // ✅ PopScope 的 onPopInvokedWithResult + // ============================================================ + void _onPopInvokedWithResult(bool didPop, Object? result) { + if (didPop) return; + + // 1️⃣ 优先关闭播放列表(最高层级) + if (_showPlaylist) { + _closePlaylist(); + return; + } + + // 2️⃣ 其次关闭播放页 + if (_showPlayerPage) { + _closePlayerPage(); + return; + } + + // 3️⃣ 没有覆盖层,允许退出(由 Navigator 处理) + // 注意:这里不能直接退出,需要让 Navigator 处理 + // 但 canPop 会根据状态返回 true/false + } + @override Widget build(BuildContext context) { if (!widget.initSuccess) { @@ -137,25 +158,6 @@ class _QTPlayerAppState extends State style: TextStyle(color: Colors.grey[400], fontSize: 14), 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('重试'), - ), ], ), ), @@ -165,7 +167,6 @@ class _QTPlayerAppState extends State return MaterialApp( title: '清听', - navigatorKey: navigatorKey, theme: ThemeData( brightness: Brightness.dark, primaryColor: const Color(0xFF7C9A9E), @@ -179,57 +180,70 @@ class _QTPlayerAppState extends State ), home: const HomePage(), builder: (context, child) { - return Overlay( - initialEntries: [ - OverlayEntry( - builder: (context) => Stack( - children: [ - // ① 主页面 - Positioned.fill(child: child!), + // ✅ 暴力测试:canPop 写死为 false + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + debugPrint('🔥🔥🔥 BACK FIRED: didPop=$didPop'); + debugPrint('🔥 playlist=$_showPlaylist, player=$_showPlayerPage'); - // ② MiniPlayer - const Positioned( - left: 0, - right: 0, - bottom: 0, - child: GlobalMiniPlayer(), - ), - - // ③ 播放页(全屏覆盖) - if (_showPlayerPage) - Positioned.fill( - child: AnimatedBuilder( - 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) { + debugPrint('🔥 closing playlist'); + _closePlaylist(); + } else if (_showPlayerPage) { + debugPrint('🔥 closing player'); + _closePlayerPage(); + } else { + debugPrint('🔥 no overlay, allowing exit'); + // 注意:这里 canPop=false,需要手动退出 + // 用 SystemNavigator.pop() 或 Navigator.pop(context) + } + }, + child: Overlay( + initialEntries: [ + OverlayEntry( + builder: (context) => Stack( + children: [ + Positioned.fill(child: child!), + const Positioned( + left: 0, + right: 0, + bottom: 0, + child: GlobalMiniPlayer(), + ), + if (_showPlayerPage) + Positioned.fill( + child: AnimatedBuilder( + 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, + if (_showPlaylist) + Positioned.fill( + child: PlaylistPage( + onClose: _closePlaylist, + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ); }, ); diff --git a/lib/pages/shell_page.dart b/lib/pages/shell_page.dart new file mode 100644 index 0000000..30e8fea --- /dev/null +++ b/lib/pages/shell_page.dart @@ -0,0 +1,114 @@ +// lib/pages/shell_page.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../services/audio_service.dart'; +import '../widgets/global_mini_player.dart'; +import 'home_page.dart'; +import 'player_page.dart'; +import 'playlist_page.dart'; + +class ShellPage extends StatefulWidget { + const ShellPage({super.key}); + + @override + State createState() => _ShellPageState(); +} + +class _ShellPageState extends State { + // ✅ 从 PlayerPage 打开 PlaylistPage + void _openPlaylistFromPlayer() { + Navigator.pushNamed(context, '/playlist'); + } + + // ✅ 关闭 PlayerPage + void _closePlayer() { + Navigator.pop(context); + } + + // ✅ 关闭 PlaylistPage + void _closePlaylist() { + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF0E1211), + body: Stack( + children: [ + // ---- 页面内容(Navigator) ---- + Navigator( + initialRoute: '/', + onGenerateRoute: (settings) { + switch (settings.name) { + case '/': + return _buildPageRoute( + const HomePage(), + settings: settings, + ); + case '/player': + return _buildPageRoute( + PlayerPage( + onClose: _closePlayer, + onOpenPlaylist: _openPlaylistFromPlayer, + ), + settings: settings, + fromBottom: true, + ); + case '/playlist': + return _buildPageRoute( + PlaylistPage( + onClose: _closePlaylist, + ), + settings: settings, + ); + default: + return null; + } + }, + ), + + // ---- MiniPlayer(覆盖在最上层) ---- + const Positioned( + left: 0, + right: 0, + bottom: 0, + child: GlobalMiniPlayer(), + ), + ], + ), + ); + } + + PageRouteBuilder _buildPageRoute( + Widget page, { + required RouteSettings settings, + bool fromBottom = false, + }) { + return PageRouteBuilder( + settings: settings, + pageBuilder: (context, animation, secondaryAnimation) => page, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + if (fromBottom) { + // 播放页:从底部滑入 + final offset = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + )); + return SlideTransition(position: offset, child: child); + } else { + // 其他页面:淡入 + return FadeTransition( + opacity: animation, + child: child, + ); + } + }, + transitionDuration: const Duration(milliseconds: 300), + reverseTransitionDuration: const Duration(milliseconds: 300), + ); + } +} diff --git a/lib/widgets/global_mini_player.dart b/lib/widgets/global_mini_player.dart index c6ce796..f6cc815 100644 --- a/lib/widgets/global_mini_player.dart +++ b/lib/widgets/global_mini_player.dart @@ -2,15 +2,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; -import '../main.dart'; class GlobalMiniPlayer extends StatelessWidget { const GlobalMiniPlayer({super.key}); - // ✅ 全局回调:打开播放页 + // ✅ 保留静态回调 static VoidCallback? onOpenPlayerPage; - - // ✅ 新增:打开播放列表 static VoidCallback? onOpenPlaylistPage; @override @@ -114,7 +111,7 @@ class GlobalMiniPlayer extends StatelessWidget { size: 24, ), onPressed: () { - // ✅ 使用回调打开播放列表(与播放页同一套层级) + // ✅ 使用静态回调,而不是 Navigator.pushNamed if (onOpenPlaylistPage != null) { onOpenPlaylistPage!(); }