播放页部分调整,完整播放链路层级已打通
This commit is contained in:
+147
-52
@@ -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(
|
runApp(
|
||||||
MaterialApp(
|
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: 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(
|
child: PlayerPage(
|
||||||
onClose: _closePlayerPage,
|
onClose: _closePlayerPage,
|
||||||
|
onOpenPlaylist: _openPlaylist, // ✅ 传入回调
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ④ ✅ 播放列表(最高层级,在播放页之上)
|
||||||
|
if (_showPlaylist)
|
||||||
|
Positioned.fill(
|
||||||
|
child: PlaylistPage(
|
||||||
|
onClose: _closePlaylist,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
+14
-14
@@ -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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user