diff --git a/lib/main.dart b/lib/main.dart index d47a612..30b1cbe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -// lib/main.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:media_kit/media_kit.dart'; @@ -9,6 +8,8 @@ import 'pages/player_page.dart'; import 'pages/playlist_page.dart'; import 'widgets/global_mini_player.dart'; +final GlobalKey navigatorKey = GlobalKey(); + void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -21,13 +22,15 @@ void main() { initSuccess = true; } catch (e, stack) { initError = '初始化失败: $e'; - print('❌ $initError'); - print(stack); + debugPrint('❌ $initError'); + debugPrint(stack.toString()); } runApp( - ChangeNotifierProvider( - create: (_) => AudioService(), + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AudioService()), + ], child: QTPlayerApp( initSuccess: initSuccess, initError: initError, @@ -36,7 +39,60 @@ void main() { ); } -class QTPlayerApp extends StatefulWidget { +// ═══════════════════════════════════════════════════ +// RouteManager - 路由状态管理 +// ═══════════════════════════════════════════════════ +class RouteManager extends ChangeNotifier { + static final RouteManager _instance = RouteManager._internal(); + factory RouteManager() => _instance; + RouteManager._internal(); + + String _currentRoute = '/'; + + String get currentRoute => _currentRoute; + + bool get showMiniPlayer => + _currentRoute != '/player' && _currentRoute != '/playlist'; + + void updateRoute(String route) { + if (_currentRoute != route) { + _currentRoute = route; + debugPrint('📌 路由更新: $route'); + notifyListeners(); + } + } +} + +// ═══════════════════════════════════════════════════ +// 导航观察者 - 监听路由变化 +// ═══════════════════════════════════════════════════ +class MiniPlayerNavigatorObserver extends NavigatorObserver { + void _updateRoute(Route? route) { + final name = route?.settings.name ?? '/'; + RouteManager().updateRoute(name); + } + + @override + void didPush(Route route, Route? previousRoute) { + _updateRoute(route); + } + + @override + void didPop(Route route, Route? previousRoute) { + // ⭐ 关键:用 previousRoute 而不是 navigatorKey.currentContext + _updateRoute(previousRoute); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + _updateRoute(newRoute); + } +} + +// ═══════════════════════════════════════════════════ +// 主应用 +// ═══════════════════════════════════════════════════ +class QTPlayerApp extends StatelessWidget { final bool initSuccess; final String? initError; @@ -46,99 +102,9 @@ class QTPlayerApp extends StatefulWidget { 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((_) { - 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) { + if (!initSuccess) { return MaterialApp( home: Scaffold( backgroundColor: const Color(0xFF0E1211), @@ -154,7 +120,7 @@ class _QTPlayerAppState extends State ), const SizedBox(height: 8), Text( - widget.initError ?? '未知错误', + initError ?? '未知错误', style: TextStyle(color: Colors.grey[400], fontSize: 14), textAlign: TextAlign.center, ), @@ -178,72 +144,35 @@ class _QTPlayerAppState extends State ), useMaterial3: true, ), - home: const HomePage(), + navigatorKey: navigatorKey, + navigatorObservers: [MiniPlayerNavigatorObserver()], + routes: { + '/': (context) => const HomePage(), + '/player': (context) => const PlayerPage(), + '/playlist': (context) => const PlaylistPage(), + }, + // ⭐ 核心改动:删除手写 Overlay,直接用 Stack builder: (context, child) { - // ✅ 暴力测试:canPop 写死为 false - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) { - debugPrint('🔥🔥🔥 BACK FIRED: didPop=$didPop'); - debugPrint('🔥 playlist=$_showPlaylist, player=$_showPlayerPage'); + return Stack( + fit: StackFit.expand, + children: [ + // 1. Navigator(所有路由页面) + if (child != null) child, - 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, - ), - ), - ], - ), - ), - ], - ), + // 2. MiniPlayer(由路由状态控制显隐) + AnimatedBuilder( + animation: RouteManager(), + builder: (context, _) { + if (!RouteManager().showMiniPlayer) { + return const SizedBox.shrink(); + } + return const Align( + alignment: Alignment.bottomCenter, + child: GlobalMiniPlayer(), + ); + }, + ), + ], ); }, ); diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 705e6c1..7b3cc5f 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,4 +1,3 @@ -// lib/pages/home_page.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; @@ -7,7 +6,6 @@ import '../services/playback_service.dart'; import '../widgets/magnetic_scroll_physics.dart'; import 'webdav_setup_page.dart'; import 'webdav_file_list_page.dart'; -import 'dart:async'; // ================================================== // 歌曲数据模型 @@ -171,7 +169,6 @@ class _HomePageState extends State { return (offset / _mediaLibraryHeight).clamp(0.0, 1.0); } - // 媒体库透明度(快速淡出) double get _mediaOpacity { final p = _progress; if (p <= 0.3) { @@ -181,7 +178,6 @@ class _HomePageState extends State { } } - // 列表展开程度 double get _listReveal { if (_progress <= 0.7) return 0.0; return (_progress - 0.7) / 0.3; @@ -204,7 +200,6 @@ class _HomePageState extends State { super.dispose(); } - // ========== 高度测量(带重试限制) ========== void _measureMediaLibraryHeight() { final renderBox = _mediaLibraryKey.currentContext?.findRenderObject() as RenderBox?; @@ -231,12 +226,10 @@ class _HomePageState extends State { } } - // ========== 滚动监听 ========== void _onScroll() { if (mounted) setState(() {}); } - // ========== 初始化 WebDAV ========== Future _initWebDAV() async { setState(() => _isLoading = true); try { @@ -249,14 +242,10 @@ class _HomePageState extends State { } } - // ============================================================ - // ⭐ 核心:播放方法(UI 优先响应,然后真正播放) - // ============================================================ void _playSong(SongItem song) async { try { final url = WebDAVService.instance.getFileUrl(song.path); - // 1️⃣ 先更新 UI(立即响应) final audioService = context.read(); audioService.playSong(Song( id: song.path, @@ -265,11 +254,9 @@ class _HomePageState extends State { url: url, )); - // 2️⃣ 动态获取认证头 final headers = await WebDAVService.instance.getAuthHeaders(); if (headers.isEmpty) { - print('⚠️ 未获取到认证头,请检查 WebDAV 登录状态'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -281,10 +268,6 @@ class _HomePageState extends State { return; } - print('🎵 [收藏列表] 播放 URL: $url'); - print('📋 [收藏列表] 认证头已设置: ${headers.keys}'); - - // 3️⃣ 播放 await PlaybackService().play(url, headers: headers); } catch (e) { if (mounted) { @@ -298,7 +281,6 @@ class _HomePageState extends State { } } - // ========== 重置磁吸状态 ========== void _resetSnapState() { if (_scrollController.hasClients) { _scrollController.jumpTo(0.0); @@ -306,21 +288,15 @@ class _HomePageState extends State { setState(() {}); } - // ================================================================ - // Build - // ================================================================ @override Widget build(BuildContext context) { final isConnected = WebDAVService.instance.isConnected; final username = WebDAVService.instance.username ?? '点击连接'; - final audioService = context.watch(); - final showMiniBar = audioService.currentSong != null; return Scaffold( backgroundColor: const Color(0xFF0E1211), body: Column( children: [ - // ---- 固定标题 "清听" ---- SafeArea( bottom: false, child: Padding( @@ -344,8 +320,6 @@ class _HomePageState extends State { ), ), ), - - // ---- 滚动区域 ---- Expanded( child: CustomScrollView( controller: _scrollController, @@ -356,7 +330,6 @@ class _HomePageState extends State { ) : const ClampingScrollPhysics(), slivers: [ - // ---- 媒体库 ---- SliverToBoxAdapter( child: Opacity( opacity: _mediaOpacity, @@ -551,8 +524,6 @@ class _HomePageState extends State { ), ), ), - - // ---- "我的收藏" Sticky Header ---- SliverPersistentHeader( pinned: true, delegate: _StickyHeaderDelegate( @@ -578,13 +549,11 @@ class _HomePageState extends State { ), ), ), - - // ---- 收藏列表 ---- SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: 20, right: 20, - bottom: showMiniBar ? 80.0 : 20.0, + bottom: 20, ), sliver: _isLoading ? const SliverFillRemaining( diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index 9c27a22..5e0fa93 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -1,19 +1,10 @@ -// lib/pages/player_page.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; 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, - }); + const PlayerPage({super.key}); @override State createState() => _PlayerPageState(); @@ -37,22 +28,33 @@ class _PlayerPageState extends State { if (song == null) { return Scaffold( backgroundColor: const Color(0xFF0E1211), - body: Center( + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + title: const Text( + '正在播放', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + centerTitle: true, + ), + body: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( + Icon(Icons.music_off, size: 64, color: Colors.grey), + SizedBox(height: 16), + Text( '没有正在播放的歌曲', style: TextStyle(color: Colors.grey), ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: widget.onClose, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFB8D4D0), - foregroundColor: Colors.black87, - ), - child: const Text('返回'), + SizedBox(height: 16), + Text( + '请先在首页点击一首歌曲', + style: TextStyle(color: Colors.grey, fontSize: 12), ), ], ), @@ -83,7 +85,7 @@ class _PlayerPageState extends State { elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), - onPressed: widget.onClose, + onPressed: () => Navigator.pop(context), ), title: const Text( '正在播放', @@ -279,8 +281,8 @@ class _PlayerPageState extends State { Container( width: 56, height: 56, - decoration: BoxDecoration( - color: const Color(0xFFB8D4D0), + decoration: const BoxDecoration( + color: Color(0xFFB8D4D0), shape: BoxShape.circle, ), child: IconButton( @@ -301,7 +303,9 @@ class _PlayerPageState extends State { padding: const EdgeInsets.all(12), ), IconButton( - onPressed: widget.onOpenPlaylist, + onPressed: () { + navigatorKey.currentState?.pushNamed('/playlist'); + }, icon: const Icon( Icons.playlist_play_outlined, color: Color(0xFFB8D4D0), diff --git a/lib/pages/playlist_page.dart b/lib/pages/playlist_page.dart index 1444749..90d1a2b 100644 --- a/lib/pages/playlist_page.dart +++ b/lib/pages/playlist_page.dart @@ -1,15 +1,9 @@ -// lib/pages/playlist_page.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; class PlaylistPage extends StatelessWidget { - final VoidCallback onClose; - - const PlaylistPage({ - super.key, - required this.onClose, - }); + const PlaylistPage({super.key}); @override Widget build(BuildContext context) { @@ -33,7 +27,7 @@ class PlaylistPage extends StatelessWidget { foregroundColor: Colors.white, leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new), - onPressed: onClose, + onPressed: () => Navigator.pop(context), ), actions: [ IconButton( @@ -60,7 +54,7 @@ class PlaylistPage extends StatelessWidget { onPressed: () { service.clearQueue(); Navigator.pop(context); - onClose(); + Navigator.pop(context); }, child: const Text( '清空', @@ -146,7 +140,7 @@ class PlaylistPage extends StatelessWidget { : null, onTap: () { service.setQueue(queue, startIndex: index); - onClose(); + Navigator.pop(context); }, ); }, diff --git a/lib/pages/shell_page.dart b/lib/pages/shell_page.dart deleted file mode 100644 index 30e8fea..0000000 --- a/lib/pages/shell_page.dart +++ /dev/null @@ -1,114 +0,0 @@ -// 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 f6cc815..b6e6d75 100644 --- a/lib/widgets/global_mini_player.dart +++ b/lib/widgets/global_mini_player.dart @@ -1,128 +1,143 @@ -// lib/widgets/global_mini_player.dart 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 Widget build(BuildContext context) { - final service = context.watch(); - final song = service.currentSong; + final audioService = context.watch(); + final song = audioService.currentSong; final bottomPadding = MediaQuery.of(context).padding.bottom; - return GestureDetector( - onTap: () { - if (song != null && onOpenPlayerPage != null) { - onOpenPlayerPage!(); - } - }, - child: Container( - height: 56 + bottomPadding, - decoration: BoxDecoration( - color: const Color(0xFF1A1F1E), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.4), - blurRadius: 12, - offset: const Offset(0, -4), - ), - ], + return Stack( + clipBehavior: Clip.none, + children: [ + // 底层遮罩 + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + height: 56 + bottomPadding, + color: const Color(0xFF1A1F1E), + ), ), - child: SafeArea( - top: false, - bottom: true, - child: Padding( - padding: const EdgeInsets.only(bottom: 0), - child: Row( - children: [ - const SizedBox(width: 12), - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: song != null - ? const Color(0xFF2A3332) - : Colors.grey[800], - borderRadius: BorderRadius.circular(4), - ), - child: Icon( - song != null ? Icons.music_note : Icons.music_off, - color: song != null ? Colors.white38 : Colors.grey[600], - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, + // 上层主体 + Positioned( + left: 0, + right: 0, + bottom: bottomPadding, + child: ClipRRect( + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(16), + ), + child: Container( + height: 56, + color: const Color(0xFF1A1F1E), + // ⭐ 用 Material 包裹 InkWell,获得更好的点击反馈 + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + if (song != null) { + navigatorKey.currentState?.pushNamed('/player'); + } + }, + // ⭐ 让整个区域都响应点击,包括空白部分 + highlightColor: Colors.white.withOpacity(0.05), + splashColor: Colors.white.withOpacity(0.1), + child: Row( children: [ - Text( - song != null ? song.title : '清听', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white, - decoration: TextDecoration.none, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Text( - song != null ? song.artist : '未播放', - style: TextStyle( - fontSize: 12, + const SizedBox(width: 12), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( color: song != null - ? Colors.grey[400] - : Colors.grey[600], - decoration: TextDecoration.none, + ? const Color(0xFF2A3332) + : Colors.grey[800], + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + song != null ? Icons.music_note : Icons.music_off, + color: + song != null ? Colors.white38 : Colors.grey[600], + size: 20, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + song != null ? song.title : '清听', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, + // ⭐ 强制去掉下划线 + decoration: TextDecoration.none, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + song != null ? song.artist : '未播放', + style: TextStyle( + fontSize: 12, + color: song != null + ? Colors.grey[400] + : Colors.grey[600], + // ⭐ 强制去掉下划线 + decoration: TextDecoration.none, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + IconButton( + icon: Icon( + song != null && audioService.isPlaying + ? Icons.pause + : Icons.play_arrow, + color: song != null ? Colors.white : Colors.grey[600], + size: 24, + ), + onPressed: () { + if (song != null) { + audioService.togglePlay(); + } + }, + ), + IconButton( + icon: const Icon( + Icons.playlist_play_outlined, + color: Colors.grey, + size: 24, + ), + onPressed: () { + if (song != null) { + navigatorKey.currentState?.pushNamed('/playlist'); + } + }, + ), + const SizedBox(width: 4), ], ), ), - IconButton( - icon: Icon( - song != null && service.isPlaying - ? Icons.pause - : Icons.play_arrow, - color: song != null ? Colors.white : Colors.grey[600], - size: 24, - ), - onPressed: () { - if (song != null) { - service.togglePlay(); - } - }, - ), - IconButton( - icon: Icon( - Icons.playlist_play_outlined, - color: Colors.grey[500], - size: 24, - ), - onPressed: () { - // ✅ 使用静态回调,而不是 Navigator.pushNamed - if (onOpenPlaylistPage != null) { - onOpenPlaylistPage!(); - } - }, - ), - const SizedBox(width: 4), - ], + ), ), ), ), - ), + ], ); } } diff --git a/web/favicon.png b/web/favicon.png deleted file mode 100644 index 8aaa46a..0000000 Binary files a/web/favicon.png and /dev/null differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png deleted file mode 100644 index b749bfe..0000000 Binary files a/web/icons/Icon-192.png and /dev/null differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png deleted file mode 100644 index 88cfd48..0000000 Binary files a/web/icons/Icon-512.png and /dev/null differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png deleted file mode 100644 index eb9b4d7..0000000 Binary files a/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png deleted file mode 100644 index d69c566..0000000 Binary files a/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/web/index.html b/web/index.html deleted file mode 100644 index ba99a5f..0000000 --- a/web/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - qt_player - - - - - - - diff --git a/web/manifest.json b/web/manifest.json deleted file mode 100644 index a6b6496..0000000 --- a/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "qt_player", - "short_name": "qt_player", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -}