优化部分功能逻辑:元数据信息漂移
优化部分体验问题,包含动画、界面层级等
This commit is contained in:
+101
-75
@@ -1,3 +1,4 @@
|
||||
// lib/main.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
@@ -49,10 +50,10 @@ void main() async {
|
||||
debugPrint('❌ MediaKit 初始化失败: $e');
|
||||
}
|
||||
|
||||
// ⭐ 创建 Handler(AudioService 稍后初始化)
|
||||
// ⭐ 创建 Handler
|
||||
_audioHandler = AudioPlayerHandler();
|
||||
|
||||
// ⭐ 注册切歌回调:当歌曲切换时,立即更新通知
|
||||
// ⭐ 注册切歌回调
|
||||
AudioService().setOnSongChanged((song) {
|
||||
debugPrint(
|
||||
'📢 [main] song.artwork is ${song.artwork != null ? 'not null' : 'null'}');
|
||||
@@ -60,7 +61,7 @@ void main() async {
|
||||
id: song.id,
|
||||
title: song.title,
|
||||
artist: song.artist,
|
||||
artwork: song.artwork, // ⭐ 传递封面图
|
||||
artwork: song.artwork,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -69,7 +70,6 @@ void main() async {
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => AudioService()),
|
||||
ChangeNotifierProvider(create: (_) => AppLifecycleService()),
|
||||
// ⭐ 生命周期管理器
|
||||
ChangeNotifierProvider<AppLifecycleManager>(
|
||||
create: (_) => lifecycleManager,
|
||||
),
|
||||
@@ -108,9 +108,18 @@ class RouteManager extends ChangeNotifier {
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 导航观察者(监听路由变化)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// lib/main.dart
|
||||
|
||||
class MiniPlayerNavigatorObserver extends NavigatorObserver {
|
||||
void _updateRoute(Route? route) {
|
||||
final name = route?.settings.name ?? '/';
|
||||
if (route == null) return;
|
||||
|
||||
// ⭐ 忽略 PopupRoute(菜单、对话框等)
|
||||
if (route is PopupRoute) {
|
||||
return;
|
||||
}
|
||||
|
||||
final name = route.settings.name ?? '/';
|
||||
RouteManager().updateRoute(name);
|
||||
}
|
||||
|
||||
@@ -121,12 +130,21 @@ class MiniPlayerNavigatorObserver extends NavigatorObserver {
|
||||
|
||||
@override
|
||||
void didPop(Route route, Route? previousRoute) {
|
||||
_updateRoute(previousRoute);
|
||||
// ⭐ 返回时,使用上一个路由(previousRoute)
|
||||
if (previousRoute != null && previousRoute is! PopupRoute) {
|
||||
final name = previousRoute.settings.name ?? '/';
|
||||
RouteManager().updateRoute(name);
|
||||
} else if (previousRoute == null) {
|
||||
RouteManager().updateRoute('/');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didReplace({Route? newRoute, Route? oldRoute}) {
|
||||
_updateRoute(newRoute);
|
||||
if (newRoute != null && newRoute is! PopupRoute) {
|
||||
final name = newRoute.settings.name ?? '/';
|
||||
RouteManager().updateRoute(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,49 +159,31 @@ class QTPlayerApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
// ⭐ build 计数器(诊断用)
|
||||
static int _buildCount = 0;
|
||||
|
||||
bool _isInitializing = false;
|
||||
|
||||
// ⭐ 保存生命周期监听器引用
|
||||
void Function(AppLifecycleStatus)? _lifecycleListener;
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
debugPrint('⏱️ T1.5 initState: ${stopwatch.elapsedMilliseconds}ms');
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
debugPrint('⏱️ T2 首帧回调: ${stopwatch.elapsedMilliseconds}ms');
|
||||
|
||||
// ⭐ 1. 初始化生命周期管理器
|
||||
lifecycleManager.init();
|
||||
|
||||
// ⭐ 2. 绑定生命周期到播放器
|
||||
_bindLifecycleToPlayback();
|
||||
|
||||
// ⭐ 3. 后台初始化
|
||||
_startBackgroundInitialization();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// ⭐ 移除生命周期监听器
|
||||
if (_lifecycleListener != null) {
|
||||
lifecycleManager.removeListenerFromManager(_lifecycleListener!);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 生命周期 → 播放器绑定
|
||||
// ============================================================
|
||||
void _bindLifecycleToPlayback() {
|
||||
if (_lifecycleListener != null) {
|
||||
lifecycleManager.removeListenerFromManager(_lifecycleListener!);
|
||||
@@ -195,15 +195,12 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
case AppLifecycleStatus.inactive:
|
||||
debugPrint('🎵 App 进入后台,播放继续');
|
||||
break;
|
||||
|
||||
case AppLifecycleStatus.resumed:
|
||||
debugPrint('🎵 App 回到前台');
|
||||
break;
|
||||
|
||||
case AppLifecycleStatus.backgroundIdle:
|
||||
debugPrint('🎵 长后台唤醒,检查播放状态');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -212,9 +209,6 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
lifecycleManager.addListenerToManager(_lifecycleListener!);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 后台初始化(精细时间切片)
|
||||
// ============================================================
|
||||
Future<void> _startBackgroundInitialization() async {
|
||||
if (_isInitializing) return;
|
||||
_isInitializing = true;
|
||||
@@ -222,7 +216,6 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
final tStart = DateTime.now();
|
||||
debugPrint('⏱️ T3 后台初始化开始');
|
||||
|
||||
// ----- 1. PlaybackService -----
|
||||
final t1 = DateTime.now();
|
||||
try {
|
||||
PlaybackService().init();
|
||||
@@ -231,7 +224,6 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
debugPrint('❌ PlaybackService 失败: $e');
|
||||
}
|
||||
|
||||
// ----- 2. WebDAV(只加载凭证) -----
|
||||
final t2 = DateTime.now();
|
||||
try {
|
||||
final hasCred = await WebDAVService.instance.loadCredentials();
|
||||
@@ -240,10 +232,8 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
debugPrint('❌ WebDAV 凭证加载失败: $e');
|
||||
}
|
||||
|
||||
// ----- 3. ⭐ AudioService(后台播放核心) -----
|
||||
await _initAudioService();
|
||||
|
||||
// ----- 4. 通知权限 -----
|
||||
final t3 = DateTime.now();
|
||||
try {
|
||||
if (await Permission.notification.isDenied) {
|
||||
@@ -260,9 +250,6 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
debugPrint('⏱️ T4 全部初始化完成: ${tEnd.difference(tStart).inMilliseconds}ms');
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// ⭐ AudioService 初始化(后台播放核心)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<void> _initAudioService() async {
|
||||
try {
|
||||
await audio_service.AudioService.init(
|
||||
@@ -280,42 +267,61 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 辅助方法
|
||||
// ════════════════════════════════════════════════════════════
|
||||
String _elapsedMs(DateTime start) {
|
||||
return '${DateTime.now().difference(start).inMilliseconds}';
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Build
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// ⭐ 路由生成(播放页滑入动画,速度 200ms)
|
||||
// lib/main.dart - _onGenerateRoute 方法
|
||||
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
|
||||
if (settings.name == '/player') {
|
||||
return PageRouteBuilder(
|
||||
settings: settings,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return const PlayerPage();
|
||||
},
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
// ⭐ 应用贝塞尔曲线
|
||||
final curvedAnimation = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic, // 平滑减速
|
||||
// curve: Curves.easeOutBack, // 轻微回弹,更灵动
|
||||
// curve: Curves.fastOutSlowIn, // Material 风格
|
||||
);
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
final tween = Tween(begin: begin, end: end);
|
||||
final offsetAnimation = tween.animate(curvedAnimation);
|
||||
return SlideTransition(
|
||||
position: offsetAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
);
|
||||
}
|
||||
|
||||
return MaterialPageRoute(
|
||||
settings: settings,
|
||||
builder: (context) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
return const HomePage();
|
||||
case '/playlist':
|
||||
return const PlaylistPage();
|
||||
default:
|
||||
return const HomePage();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_buildCount++;
|
||||
debugPrint(
|
||||
'🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms');
|
||||
|
||||
// 监听歌曲变化,更新通知栏
|
||||
// main.dart 中 build 方法里的这部分
|
||||
final audioService = context.watch<AudioService>();
|
||||
final handler = context.read<AudioPlayerHandler>();
|
||||
final song = audioService.currentSong;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (song != null) {
|
||||
// ⭐ song 不为空时才调用
|
||||
handler.updateNotification(
|
||||
id: song.id,
|
||||
title: song.title,
|
||||
artist: song.artist,
|
||||
);
|
||||
} else {
|
||||
// 清空通知
|
||||
handler.mediaItem.add(null);
|
||||
}
|
||||
});
|
||||
|
||||
return MaterialApp(
|
||||
title: '清听',
|
||||
theme: ThemeData(
|
||||
@@ -331,11 +337,7 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
),
|
||||
navigatorKey: navigatorKey,
|
||||
navigatorObservers: [MiniPlayerNavigatorObserver()],
|
||||
routes: {
|
||||
'/': (context) => const HomePage(),
|
||||
'/player': (context) => const PlayerPage(),
|
||||
'/playlist': (context) => const PlaylistPage(),
|
||||
},
|
||||
onGenerateRoute: _onGenerateRoute,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
@@ -344,12 +346,36 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||
AnimatedBuilder(
|
||||
animation: RouteManager(),
|
||||
builder: (context, _) {
|
||||
if (!RouteManager().showMiniPlayer) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: GlobalMiniPlayer(),
|
||||
final routeManager = RouteManager();
|
||||
final shouldShow = routeManager.currentRoute != '/player' &&
|
||||
routeManager.currentRoute != '/playlist';
|
||||
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
transitionBuilder: (child, animation) {
|
||||
// ⭐ 淡入淡出 + 轻微上滑
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.1),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: shouldShow
|
||||
? const Align(
|
||||
key: ValueKey('mini_player_visible'),
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: GlobalMiniPlayer(),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('mini_player_hidden'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user