diff --git a/lib/main.dart b/lib/main.dart index 68bbfe1..2293d6b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,22 +6,119 @@ 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 'widgets/global_mini_player.dart'; -// ✅ 全局导航键 final GlobalKey navigatorKey = GlobalKey(); void main() { WidgetsFlutterBinding.ensureInitialized(); + bool initSuccess = false; + String? initError; + try { MediaKit.ensureInitialized(); PlaybackService().init(); + initSuccess = true; } catch (e, stack) { - print('❌ 初始化失败: $e'); + initError = '初始化失败: $e'; + print('❌ $initError'); 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 createState() => _QTPlayerAppState(); +} + +class _QTPlayerAppState extends State + with SingleTickerProviderStateMixin { + bool _showPlayerPage = false; + bool _showPlaylist = false; // ✅ 新增:播放列表显示状态 + + late final AnimationController _animationController; + late final Animation _slideAnimation; + + @override + void initState() { + super.initState(); + GlobalMiniPlayer.onOpenPlayerPage = _openPlayerPage; + GlobalMiniPlayer.onOpenPlaylistPage = _openPlaylist; + + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + ); + + _slideAnimation = Tween(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( backgroundColor: const Color(0xFF0E1211), body: Center( @@ -30,70 +127,42 @@ void main() { children: [ Icon(Icons.error_outline, size: 64, color: Colors.red[300]), const SizedBox(height: 16), - Text( + const Text( '应用初始化失败', style: TextStyle(color: Colors.white, fontSize: 20), ), const SizedBox(height: 8), Text( - '$e', + widget.initError ?? '未知错误', 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('重试'), + ), ], ), ), ), - ), - ); - 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 createState() => _QTPlayerAppState(); -} - -class _QTPlayerAppState extends State { - 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( title: '清听', navigatorKey: navigatorKey, @@ -117,6 +186,7 @@ class _QTPlayerAppState extends State { children: [ // ① 主页面 Positioned.fill(child: child!), + // ② MiniPlayer const Positioned( left: 0, @@ -124,11 +194,36 @@ class _QTPlayerAppState extends State { bottom: 0, child: GlobalMiniPlayer(), ), + // ③ 播放页(全屏覆盖) if (_showPlayerPage) Positioned.fill( - child: PlayerPage( - onClose: _closePlayerPage, + 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, ), ), ], diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index 257af3b..9c27a22 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -2,15 +2,17 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; -import '../main.dart'; // ✅ 导入 navigatorKey -import 'playlist_page.dart'; // ✅ 导入 PlaylistPage +import '../main.dart'; +import 'playlist_page.dart'; class PlayerPage extends StatefulWidget { final VoidCallback onClose; + final VoidCallback onOpenPlaylist; const PlayerPage({ super.key, required this.onClose, + required this.onOpenPlaylist, }); @override @@ -81,7 +83,7 @@ class _PlayerPageState extends State { elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), - onPressed: widget.onClose, // ✅ 使用回调关闭 + onPressed: widget.onClose, ), title: const Text( '正在播放', @@ -93,17 +95,6 @@ class _PlayerPageState extends State { ), centerTitle: true, actions: [ - IconButton( - icon: - const Icon(Icons.playlist_play_outlined, color: Colors.white54), - onPressed: () { - navigatorKey.currentState?.push( - MaterialPageRoute( - builder: (_) => const PlaylistPage(), - ), - ); - }, - ), IconButton( icon: const Icon(Icons.more_vert, color: Colors.white54), onPressed: () {}, @@ -309,6 +300,15 @@ class _PlayerPageState extends State { color: service.hasQueue ? Colors.white60 : Colors.grey[600], 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), ], ), diff --git a/lib/pages/playlist_page.dart b/lib/pages/playlist_page.dart index 1754009..1444749 100644 --- a/lib/pages/playlist_page.dart +++ b/lib/pages/playlist_page.dart @@ -4,7 +4,12 @@ import 'package:provider/provider.dart'; import '../services/audio_service.dart'; class PlaylistPage extends StatelessWidget { - const PlaylistPage({super.key}); + final VoidCallback onClose; + + const PlaylistPage({ + super.key, + required this.onClose, + }); @override Widget build(BuildContext context) { @@ -28,7 +33,7 @@ class PlaylistPage extends StatelessWidget { foregroundColor: Colors.white, leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new), - onPressed: () => Navigator.pop(context), + onPressed: onClose, ), actions: [ IconButton( @@ -55,7 +60,7 @@ class PlaylistPage extends StatelessWidget { onPressed: () { service.clearQueue(); Navigator.pop(context); - Navigator.pop(context); + onClose(); }, child: const Text( '清空', @@ -141,7 +146,7 @@ class PlaylistPage extends StatelessWidget { : null, onTap: () { service.setQueue(queue, startIndex: index); - Navigator.pop(context); + onClose(); }, ); }, diff --git a/lib/widgets/global_mini_player.dart b/lib/widgets/global_mini_player.dart index 4191567..c6ce796 100644 --- a/lib/widgets/global_mini_player.dart +++ b/lib/widgets/global_mini_player.dart @@ -2,15 +2,17 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; -import '../pages/playlist_page.dart'; -import '../main.dart'; // ✅ 导入 navigatorKey +import '../main.dart'; class GlobalMiniPlayer extends StatelessWidget { const GlobalMiniPlayer({super.key}); - // ✅ 通过全局回调打开播放页(由 main.dart 注入) + // ✅ 全局回调:打开播放页 static VoidCallback? onOpenPlayerPage; + // ✅ 新增:打开播放列表 + static VoidCallback? onOpenPlaylistPage; + @override Widget build(BuildContext context) { final service = context.watch(); @@ -112,11 +114,10 @@ class GlobalMiniPlayer extends StatelessWidget { size: 24, ), onPressed: () { - navigatorKey.currentState?.push( - MaterialPageRoute( - builder: (_) => const PlaylistPage(), - ), - ); + // ✅ 使用回调打开播放列表(与播放页同一套层级) + if (onOpenPlaylistPage != null) { + onOpenPlaylistPage!(); + } }, ), const SizedBox(width: 4),