From 74a15ca7af02a3a5291beef429fa2ce6a74b880d Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Tue, 18 Aug 2026 23:08:52 +0800 Subject: [PATCH] =?UTF-8?q?=E9=83=A8=E5=88=86=E7=95=8C=E9=9D=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E8=B0=83=E6=95=B4=EF=BC=9A=E6=92=AD=E6=94=BE=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E9=BB=98=E8=AE=A4=E6=9C=80=E9=AB=98=E4=BC=98=E5=85=88?= =?UTF-8?q?=E7=BA=A7=EF=BC=8C=E5=85=B6=E6=AC=A1=E6=98=AF=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E7=9A=84miniplayerbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 41 +++++++++- lib/pages/home_page.dart | 1 - lib/pages/player_page.dart | 94 +++++++++++++--------- lib/widgets/global_mini_player.dart | 14 ++-- lib/widgets/mini_player_bar.dart | 117 ---------------------------- 5 files changed, 103 insertions(+), 164 deletions(-) delete mode 100644 lib/widgets/mini_player_bar.dart diff --git a/lib/main.dart b/lib/main.dart index 1301c0d..68bbfe1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:media_kit/media_kit.dart'; import 'services/audio_service.dart'; import 'services/playback_service.dart'; import 'pages/home_page.dart'; +import 'pages/player_page.dart'; import 'widgets/global_mini_player.dart'; // ✅ 全局导航键 @@ -61,14 +62,41 @@ void main() { ); } -class QTPlayerApp extends StatelessWidget { +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, // ✅ 设置导航键 + navigatorKey: navigatorKey, theme: ThemeData( brightness: Brightness.dark, primaryColor: const Color(0xFF7C9A9E), @@ -87,13 +115,22 @@ class QTPlayerApp extends StatelessWidget { OverlayEntry( builder: (context) => Stack( children: [ + // ① 主页面 Positioned.fill(child: child!), + // ② MiniPlayer const Positioned( left: 0, right: 0, bottom: 0, child: GlobalMiniPlayer(), ), + // ③ 播放页(全屏覆盖) + if (_showPlayerPage) + Positioned.fill( + child: PlayerPage( + onClose: _closePlayerPage, + ), + ), ], ), ), diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 4b45b56..705e6c1 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -4,7 +4,6 @@ import 'package:provider/provider.dart'; import '../services/audio_service.dart'; import '../services/webdav_service.dart'; import '../services/playback_service.dart'; -import '../widgets/mini_player_bar.dart'; import '../widgets/magnetic_scroll_physics.dart'; import 'webdav_setup_page.dart'; import 'webdav_file_list_page.dart'; diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index c3f82bb..257af3b 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -2,16 +2,23 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; +import '../main.dart'; // ✅ 导入 navigatorKey +import 'playlist_page.dart'; // ✅ 导入 PlaylistPage class PlayerPage extends StatefulWidget { - const PlayerPage({super.key}); + final VoidCallback onClose; + + const PlayerPage({ + super.key, + required this.onClose, + }); @override State createState() => _PlayerPageState(); } class _PlayerPageState extends State { - double? _dragProgress; // 拖动时的临时进度 + double? _dragProgress; String _formatDuration(Duration d) { if (d.inMilliseconds < 0) return '00:00'; @@ -28,10 +35,24 @@ class _PlayerPageState extends State { if (song == null) { return Scaffold( backgroundColor: const Color(0xFF0E1211), - body: const Center( - child: Text( - '没有正在播放的歌曲', - style: TextStyle(color: Colors.grey), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const 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('返回'), + ), + ], ), ), ); @@ -51,7 +72,6 @@ class _PlayerPageState extends State { .clamp(0.0, 1.0) : 0.0; - // 拖动时显示拖动的进度,否则显示实际播放进度 final displayProgress = _dragProgress ?? progress; return Scaffold( @@ -61,7 +81,7 @@ class _PlayerPageState extends State { elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), - onPressed: () => Navigator.pop(context), + onPressed: widget.onClose, // ✅ 使用回调关闭 ), title: const Text( '正在播放', @@ -73,6 +93,17 @@ 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: () {}, @@ -84,7 +115,6 @@ class _PlayerPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // ---- 封面占位 ---- Container( width: 240, height: 240, @@ -106,8 +136,6 @@ class _PlayerPageState extends State { ), ), const SizedBox(height: 48), - - // ---- 歌曲信息 ---- Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -136,8 +164,6 @@ class _PlayerPageState extends State { ], ), const SizedBox(height: 40), - -// ---- 双层进度条(视觉与交互分离) ---- Column( children: [ SizedBox( @@ -145,15 +171,14 @@ class _PlayerPageState extends State { child: LayoutBuilder( builder: (context, constraints) { final width = constraints.maxWidth; - final bufferWidth = width * bufferProgress.clamp(0.0, 1.0); - final progressWidth = width * progress.clamp(0.0, 1.0); + final progressWidth = + width * displayProgress.clamp(0.0, 1.0); return Stack( alignment: Alignment.centerLeft, children: [ - // ① 底层:整首歌曲(暗灰色) Container( width: width, height: 4, @@ -162,8 +187,6 @@ class _PlayerPageState extends State { borderRadius: BorderRadius.circular(2), ), ), - - // ② 缓冲层(深青色) Container( width: bufferWidth, height: 4, @@ -172,8 +195,6 @@ class _PlayerPageState extends State { borderRadius: BorderRadius.circular(2), ), ), - - // ③ 播放进度层(亮青色) Container( width: progressWidth, height: 4, @@ -182,26 +203,23 @@ class _PlayerPageState extends State { borderRadius: BorderRadius.circular(2), ), ), - - // ④ 交互层(只负责手势,不显示任何轨道) Positioned.fill( child: SliderTheme( data: SliderTheme.of(context).copyWith( trackHeight: 0, activeTrackColor: Colors.transparent, inactiveTrackColor: Colors.transparent, - thumbColor: Colors.transparent, // ✅ 完全隐藏小球 + thumbColor: Colors.transparent, overlayColor: Colors.transparent, thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 0, ), ), child: Slider( - value: progress.clamp(0.0, 1.0), + value: displayProgress.clamp(0.0, 1.0), min: 0.0, max: 1.0, onChanged: (value) { - // 拖动时只更新本地状态,不触发 seek setState(() { _dragProgress = value; }); @@ -225,7 +243,6 @@ class _PlayerPageState extends State { }, ), ), - // 时间显示 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -248,15 +265,23 @@ class _PlayerPageState extends State { ], ), const SizedBox(height: 32), - - // ---- 播放控制 ---- Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( - onPressed: () {}, + onPressed: service.togglePlayMode, + icon: Icon( + service.playModeIcon, + color: const Color(0xFFB8D4D0), + size: 22, + ), + padding: const EdgeInsets.all(12), + ), + const SizedBox(width: 4), + IconButton( + onPressed: service.hasQueue ? service.previous : null, icon: const Icon(Icons.skip_previous, size: 28), - color: Colors.white60, + color: service.hasQueue ? Colors.white60 : Colors.grey[600], padding: const EdgeInsets.all(12), ), const SizedBox(width: 20), @@ -274,18 +299,17 @@ class _PlayerPageState extends State { size: 28, ), padding: EdgeInsets.zero, - onPressed: () { - service.togglePlay(); - }, + onPressed: service.togglePlay, ), ), const SizedBox(width: 20), IconButton( - onPressed: () {}, + onPressed: service.hasQueue ? service.next : null, icon: const Icon(Icons.skip_next, size: 28), - color: Colors.white60, + color: service.hasQueue ? Colors.white60 : Colors.grey[600], padding: const EdgeInsets.all(12), ), + const SizedBox(width: 4), ], ), const SizedBox(height: 16), diff --git a/lib/widgets/global_mini_player.dart b/lib/widgets/global_mini_player.dart index f633caa..4191567 100644 --- a/lib/widgets/global_mini_player.dart +++ b/lib/widgets/global_mini_player.dart @@ -2,13 +2,15 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; -import '../pages/player_page.dart'; import '../pages/playlist_page.dart'; import '../main.dart'; // ✅ 导入 navigatorKey class GlobalMiniPlayer extends StatelessWidget { const GlobalMiniPlayer({super.key}); + // ✅ 通过全局回调打开播放页(由 main.dart 注入) + static VoidCallback? onOpenPlayerPage; + @override Widget build(BuildContext context) { final service = context.watch(); @@ -18,13 +20,8 @@ class GlobalMiniPlayer extends StatelessWidget { return GestureDetector( onTap: () { - if (song != null) { - // ✅ 使用 navigatorKey 跳转 - navigatorKey.currentState?.push( - MaterialPageRoute( - builder: (_) => const PlayerPage(), - ), - ); + if (song != null && onOpenPlayerPage != null) { + onOpenPlayerPage!(); } }, child: Container( @@ -115,7 +112,6 @@ class GlobalMiniPlayer extends StatelessWidget { size: 24, ), onPressed: () { - // ✅ 使用 navigatorKey 跳转 navigatorKey.currentState?.push( MaterialPageRoute( builder: (_) => const PlaylistPage(), diff --git a/lib/widgets/mini_player_bar.dart b/lib/widgets/mini_player_bar.dart deleted file mode 100644 index be59ce5..0000000 --- a/lib/widgets/mini_player_bar.dart +++ /dev/null @@ -1,117 +0,0 @@ -// lib/widgets/mini_player_bar.dart -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/audio_service.dart'; -import '../pages/player_page.dart'; - -class MiniPlayerBar extends StatelessWidget { - const MiniPlayerBar({super.key}); - - @override - Widget build(BuildContext context) { - final service = context.watch(); - final song = service.currentSong; - - if (song == null) return const SizedBox.shrink(); - - return SafeArea( - top: false, - bottom: true, - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PlayerPage(), - ), - ); - }, - child: Container( - height: 64, - decoration: BoxDecoration( - color: const Color(0xFF1A1F1E), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.4), - blurRadius: 12, - offset: const Offset(0, -4), - ), - ], - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.1), - ], - stops: const [0.0, 1.0], - ), - ), - child: Row( - children: [ - const SizedBox(width: 12), - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: const Color(0xFF2A3332), - borderRadius: BorderRadius.circular(4), - ), - child: const Icon( - Icons.music_note, - color: Colors.white38, - size: 24, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - song.title, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Text( - song.artist, - style: TextStyle( - fontSize: 13, - color: Colors.grey[400], - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - IconButton( - icon: Icon( - service.isPlaying ? Icons.pause : Icons.play_arrow, - color: Colors.white, - ), - onPressed: () { - service.togglePlay(); - }, - ), - IconButton( - icon: const Icon( - Icons.playlist_play_outlined, - color: Colors.white54, - ), - onPressed: () {}, - ), - const SizedBox(width: 4), - ], - ), - ), - ), - ); - } -}